diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..826ae4bd089 --- /dev/null +++ b/LICENSE @@ -0,0 +1,188 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +============================ End of Apache License V 2.0 =================== + +Source and binary distributions of Apache JMeter contain icons from the Open Icon Library +http://openiconlibrary.sourceforge.net/ + +For license details, please see the file: licenses/src/openiconlibrary.txt + + +Binary distributions additionally contain software included under various licenses. + +For details, please see the files under: licenses/bin \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000000..64e77015bb9 --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +Apache JMeter +Copyright 1998-2015 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/README b/README index 8aaa18af021..e2156d976f5 100644 --- a/README +++ b/README @@ -1,78 +1,192 @@ A P A C H E J M E T E R - Version 1.0 beta 1 What is it? ----------- Apache JMeter is a 100% pure Java application designed to test - and measure the performance of URL retrieval and it may be used - as a highly portable server benchmark as well as multiclient - load generator. + and measure performance. It may be used as a highly portable + server benchmark as well as multiclient load generator. + + Apache JMeter features include: + + Ability to load and performance test many different server/protocol types: + o Web - HTTP, HTTPS + o SOAP + o FTP + o Database via JDBC + o LDAP + o Message-oriented middleware (MOM) via JMS + o Mail - SMTP(S), POP3(S) and IMAP(S) + o MongoDB (NoSQL) + o Native commands or shell scripts + o TCP + + Full multithreading framework allows concurrent sampling by many threads and simultaneous sampling of different functions by separate thread groups. + Careful GUI design allows faster Test Plan building and debugging. + Caching and offline analysis/replaying of test results. + Highly Extensible core: + o Pluggable Samplers allow unlimited testing capabilities. + o Several load statistics may be choosen with pluggable timers . + o Data analysis and visualization plugins allow great extensibility as well as personalization. + o Functions can be used to provide dynamic input to a test or provide data manipulation. + o Scriptable Samplers (BeanShell, BSF-compatible languages and JSR223-compatible languages) + The Latest Version ------------------ Details of the latest version can be found on the Java Apache - Project web site (http://java.apache.org). + Project web site (http://jmeter.apache.org/). Requirements ------------ - The following requirements exist for installing Apache JMeter: + The following requirements exist for running Apache JMeter: o Java Interpreter: - A fully compliant Java 1.1 Runtime Environment is required for - Apache JMeter to execute. Since Apache JMeter uses _only_ standard - Java APIs (java.*), please, do not file in a bug report if your - JRE fails to execute Apache JMeter because a class implementation - is missing, or not fully compliant. - - o Supported Java Runtime Evironments are: - - JavaSoft JDK 1.1.x for Solaris/Win32 - - JavaSoft JDK 1.2betax for Solaris/Win32 - - Microsoft 1.1.x compatible JVM for Win32 - - [PENDING: what others?] - - o Currently unsupported platforms are: - - [PENDING: do some tests here] - - o Java JFC 1.1 and above (Swing components): - - These GUI extentions are required for proper functioning. Due to - incompatibility in the package names, the precompiled version works - only with Swing placed under the packaging com.sun.java.swing while - other placing as for JDK 1.2beta3 and JDK 1.2beta4 are not supported - until JavaSoft settles this down. - - If you have JDK 1.2 you may look in the source code and uncomment - those import lines to meet your needs or simply put the JFC 1.1 - swingall.jar file in your classpath. - + A fully compliant Java 6 (or later) Runtime Environment is required + for Apache JMeter to execute. + + o Optional jars: + + Some jars are not included with JMeter. + If required, these should be downloaded and placed in the lib directory + + JDBC - available from database supplier + JMS - available from the JMS provider + o Java Compiler [OPTIONAL]: A Java compiler is not needed since the distribution includes a precompiled java binary archive. Note that a compiler is required if you plan to build plugin classes for Apache JMeter. - Installation Instructions and Documentation - ------------------------------------------- - + Installation Instructions + ------------------------- + + Note that spaces in directory names can cause problems. + + - Release builds + Unpack the binary archive into a suitable directory structure. + + - Nightly builds + Unpack BOTH the _bin and _lib archives into the SAME directory structure + + Running JMeter + -------------- + + Change to the bin directory and run the jmeter (Un*x) or jmeter.bat (Windows) file. + + For Windows (2K, XP etc), there are also some other scripts: + + jmeter-n.cmd - drop a JMX file on this and it will run it as a non-GUI test + jmeter-n-r.cmd - drop a JMX file on this and it will run it as a non-GUI remote (client-server) test + jmeter-t.cmd - drop a JMX file on this and it will open the file for running a GUI test + + Documentation + ------------- The documentation available as of the date of this release is - also included, in HTML format, in the docs/ directory, and it may + also included, in HTML format, in the printable_docs/ directory, and it may be browsed starting from the file called index.html. + Build instructions + ------------------ + - Release builds + Unpack the source archive into a suitable directory structure. + Most of the 3rd party library files can be extracted from the binary archive by unpacking it + into the same directory structure. + You can use Ant to download any missing files: + + ant download_jars + + - Nightly builds + Unpack the _src, _bin and _lib archives into the same directory structure. + + Please note: + To avoid unnecessary duplication, the nightly source archives do not contain + the source files which are needed to run JMeter (for example properties files and scripts). + + Any optional jars (see above) should be placed in lib/opt and/or lib. + + Jars in lib/opt will be used for building JMeter and running the unit test, but won't be used at run-time. + [This is useful for testing what happens if the optional jars are not downloaded + by other JMeter users]. + + JMeter is built using Ant. + + Change to the top-level directory and issue the command: + + ant download_jars ! only needs to be done once; will download any missing 3rd party jars + + ant + + This will compile the application and enable you to run jmeter from the bin + directory. + + ant test [-Djava.awt.headless=true] + + This will compile and run the unit tests. + The optional property definition is required if the system does not have a suitable GUI display. + Licensing and legal issues -------------------------- - For legal and licensing issues, please look in the legal section of - the documentation. + For legal and licensing issues, please look the files: + LICENSE + NOTICE + + This project includes HTMLParser. + For detailed information about HTMLParser, the project is + hosted on Sourceforge at http://htmlparser.sourceforge.net/ + + The developers of Apache JMeter are grateful to the developers + of HTMLParser for re-releasing htmlparser under CPL V1.0 + + HTMLParser was originally created by Somik Raha in 2000. + Derrick Oswald is the current lead developer and was kind + enough to assist JMeter. + + +Cryptographic Software Notice +----------------------------- + +This distribution may include software that has been designed for use +with cryptographic software. The country in which you currently reside +may have restrictions on the import, possession, use, and/or re-export +to another country, of encryption software. BEFORE using any encryption +software, please check your country's laws, regulations and policies +concerning the import, possession, or use, and re-export of encryption +software, to see if this is permitted. See +for more information. + +The U.S. Government Department of Commerce, Bureau of Industry and +Security (BIS), has classified this software as Export Commodity +Control Number (ECCN) 5D002.C.1, which includes information security +software using or performing cryptographic functions with asymmetric +algorithms. The form and manner of this Apache Software Foundation +distribution makes it eligible for export under the License Exception +ENC Technology Software Unrestricted (TSU) exception (see the BIS +Export Administration Regulations, Section 740.13) for both object +code and source code. + +The following provides more details on the included software that +may be subject to export controls on cryptographic software: + + Apache JMeter interfaces with the + Java Secure Socket Extension (JSSE) API to provide + + - HTTPS support + Apache JMeter interfaces (via Apache HttpClient3) with the + Java Cryptography Extension (JCE) API to provide + + - NTLM authentication + + Apache JMeter does not include any implementation of JSSE or JCE. - Thanks for using Apache JMeter. - The Java Apache Project - http://java.apache.org/ \ No newline at end of file + Thank you for using Apache JMeter. \ No newline at end of file diff --git a/STATUS b/STATUS new file mode 100644 index 00000000000..6097d488016 --- /dev/null +++ b/STATUS @@ -0,0 +1,16 @@ +STATUS as at September 2007 +====== + +SVN status: +- trunk is for development of 2.3.1+ +- branches/rel-2-2 is the current development branch for 2.3 final only + +Branch history: +-------------- + +rel-2-1:325546 created from trunk:325542 +rel-2-2:413997 created from rel-2-1:413996 +trunk:574067 moved to branches/java1.5_prototype-was_trunk:574058 +trunk:574063 created from branches/rel-2-2:574062 + +sebb AT apache DOT org \ No newline at end of file diff --git a/bin/Apache-JMeter.jar b/bin/Apache-JMeter.jar deleted file mode 100644 index 87c3d42360a..00000000000 Binary files a/bin/Apache-JMeter.jar and /dev/null differ diff --git a/bin/BeanShellAssertion.bshrc b/bin/BeanShellAssertion.bshrc new file mode 100644 index 00000000000..8a410975af5 --- /dev/null +++ b/bin/BeanShellAssertion.bshrc @@ -0,0 +1,43 @@ +// Sample BeanShell Assertion initialisation file + +/* + * 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. + * + */ + +//print("Initialisation started"); + +import org.apache.jmeter.util.JMeterUtils; + +i = j = k = 0; // for counters + +getprop(p){// get a JMeter property + return JMeterUtils.getPropDefault(p,""); +} + +getprop(p,d){// get a JMeter property with default + return JMeterUtils.getPropDefault(p,d); +} + +setprop(p,v){// set a JMeter property + JMeterUtils.setProperty(p, v); +} + +// Assertions can use the following methods on the Response object: +// SampleResult.setStopThread(true) +// SampleResult.setStopTest(true) + +//print("Initialisation complete"); diff --git a/bin/BeanShellFunction.bshrc b/bin/BeanShellFunction.bshrc new file mode 100644 index 00000000000..765971d6817 --- /dev/null +++ b/bin/BeanShellFunction.bshrc @@ -0,0 +1,64 @@ +// Sample BeanShell Function initialisation file + +/* + * 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. + * + */ + +// N.B. to enable this file to be used, define the JMeter property: +// beanshell.function.init=BeanShellFunction.bshrc + +//print("Initialisation started"); + +import org.apache.jmeter.util.JMeterUtils; + +i = j = k = 0; // for counters + +getprop(p){// get a JMeter property + return JMeterUtils.getPropDefault(p,""); +} + +getprop(p,d){// get a JMeter property with default + return JMeterUtils.getPropDefault(p,d); +} + +setprop(p,v){// set a JMeter property + JMeterUtils.setProperty(p, v); +} + +// Define routines to stop the test or the current thread +stopTest(){// Stop the JMeter test + org.apache.jmeter.engine.StandardJMeterEngine.stopEngine(); +} + +stopThread(){// Stop current JMeter thread + org.apache.jmeter.engine.StandardJMeterEngine.stopThread(Thread.currentThread().getName()); +} + +// Fix ampersands in a string +// e.g. fixAmps("Something containing & ...") +// or fixAmps(vars.get("VARNAME")) - useful if VARNAME may contain " +String fixAmps(s) { + return s.replaceAll("&","&"); +} + +// Fix ampersands in a variable +// e.g. fixAmps("VARNAME") +String fixAmpsInVar(String varname) { + return fixAmps(vars.get(varname)); +} + +//print("Initialisation complete"); diff --git a/bin/BeanShellListeners.bshrc b/bin/BeanShellListeners.bshrc new file mode 100644 index 00000000000..0ebcafaff7f --- /dev/null +++ b/bin/BeanShellListeners.bshrc @@ -0,0 +1,47 @@ +// Example BeanShell Listener definitions + +/* + * 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. + * + */ + +// ThreadListener methods + +threadStarted(){ + print("threadStarted"); +} + +threadFinished(){ + print("threadFinished"); +} + +// TestListener methods + +testStarted(){ + print("testStarted"); +} + +testEnded(){ + print("testEnded"); +} + +testStarted(String s){ + print("testStarted "+s); +} + +testEnded(String s){ + print("testEnded "+s); +} \ No newline at end of file diff --git a/bin/BeanShellSampler.bshrc b/bin/BeanShellSampler.bshrc new file mode 100644 index 00000000000..63683a7b769 --- /dev/null +++ b/bin/BeanShellSampler.bshrc @@ -0,0 +1,69 @@ +// Sample BeanShell Sampler initialisation file +// To enable, define the JMeter property: +// beanshell.sampler.init=BeanShellSampler.bshrc + +/* + * 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. + * + */ + +//print("Initialisation started"); + +import org.apache.jmeter.util.JMeterUtils; + +i = j = k = 0; // for counters + +getprop(p){// get a JMeter property + return JMeterUtils.getPropDefault(p,""); +} + +getprop(p,d){// get a JMeter property with default + return JMeterUtils.getPropDefault(p,d); +} + +setprop(p,v){// set a JMeter property + JMeterUtils.setProperty(p, v); +} + +// Define routines to stop the test or a thread +stopEngine(){// Stop the JMeter test + org.apache.jmeter.engine.StandardJMeterEngine.stopEngine(); +} + +stopThread(t){// Stop a JMeter thread + org.apache.jmeter.engine.StandardJMeterEngine.stopThread(t); +} + +String getVariables(){ // Create a listing of the thread variables + StringBuffer sb = new StringBuffer(100); + Iterator i = vars.getIterator(); + while(i.hasNext()) + { + Map.Entry me = i.next(); + if(String.class.equals(me.getValue().getClass())){ + sb.append(me.toString()).append("\n"); + } + } + return sb.toString(); +} + +// Interruptible interface + +interrupt() { + print("Interrupt detected"); +} + +//print("Initialisation complete"); diff --git a/bin/examples/CSVSample.jmx b/bin/examples/CSVSample.jmx new file mode 100644 index 00000000000..be50cb1a047 --- /dev/null +++ b/bin/examples/CSVSample.jmx @@ -0,0 +1,383 @@ + + + + + Example of using CSV DataSet + false + false + + + + + + + + + false + -1 + + 1 + 1 + 1226457982000 + 1226457982000 + false + continue + + + + + + Top level + , + + CSVSample_user.csv + false + false + All threads + true + USER,PASS + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Login as ${USER} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + Login as ${USER} with password ${PASS} + = + + + ResultData + Login OK for ${USER} + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + ACTION + + + + none + + + false + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Action = ${ACTION} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + ${__jexl("${ACTION}" != "<EOF>")} + + + + CSVSample_actions.csv + + ACTION + , + true + false + false + All threads + + + + "${ACTION}" != "<EOF>" + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Action ${ACTION} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + Perform Action ${ACTION} + = + + + ResultData + Succeeded ${ACTION} + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + Logout ${USER} + = + + + ResultData + Succeeded ${USER} + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Action = ${ACTION} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + ~/CSVSample.jtl + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + diff --git a/bin/examples/CSVSample_actions.csv b/bin/examples/CSVSample_actions.csv new file mode 100644 index 00000000000..27a7ea60561 --- /dev/null +++ b/bin/examples/CSVSample_actions.csv @@ -0,0 +1,4 @@ +a +b +c +d \ No newline at end of file diff --git a/bin/examples/CSVSample_user.csv b/bin/examples/CSVSample_user.csv new file mode 100644 index 00000000000..2269e3c1756 --- /dev/null +++ b/bin/examples/CSVSample_user.csv @@ -0,0 +1,2 @@ +u1,p1 +u2,p2 \ No newline at end of file diff --git a/bin/examples/PerformanceTestPlanMemoryThread.jmx b/bin/examples/PerformanceTestPlanMemoryThread.jmx new file mode 100644 index 00000000000..ad0fb9d51ae --- /dev/null +++ b/bin/examples/PerformanceTestPlanMemoryThread.jmx @@ -0,0 +1,446 @@ + + + + + Requires memory.jsp + false + false + + + + + + + + continue + + false + 1 + + 1 + 30 + 1324216714000 + 1324216714000 + false + 800 + 1 + + + + + + Accept-Language + fr-fr + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + + + User-Agent + Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:8.0.1) Gecko/20100101 Firefox/8.0.1 + + + Referer + http://localhost:8080/examples/servlets/index.html + + + DNT + 1 + + + Accept-Encoding + gzip, deflate + + + Accept-Charset + ISO-8859-1,utf-8;q=0.7,*;q=0.7 + + + + + + + + host + localhost + = + + + + + + + + + ${host} + 8080 + + + + + + HttpClient4 + 4 + + + + + true + default + + + + false + + + + true + 60 + + + + + + + false + 7000 + = + true + size + + + + + + + + http + + /examples/jsp/memory.jsp + GET + true + false + true + false + false + + + + + false + text1 + b>(.+?)</b + $1$ + NV + 0 + + + + false + text2 + li>(.+?)</li + $1$ + NV + 0 + + + + false + text3 + u>(.+?)</u + $1$ + NV + 0 + + + + + 200 + + Assertion.response_code + false + 8 + + + + + abcdefghijklmno + + Assertion.response_data + false + 16 + + + + + false + true + false + + + + + + + false + 5000 + = + true + size + + + + + + + + http + + /examples/jsp/memory.jsp + GET + true + false + true + false + false + + + + + + + Accept-Language + fr-fr + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + + + User-Agent + Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:8.0.1) Gecko/20100101 Firefox/8.0.1 + + + Referer + http://localhost:8080/examples/servlets/index.html + + + DNT + 1 + + + Accept-Encoding + gzip, deflate + + + Accept-Charset + ISO-8859-1,utf-8;q=0.7,*;q=0.7 + + + + + + false + text1 + b>(.+?)</b + $1$ + NV + 0 + + + + false + text2 + li>(.+?)</li + $1$ + NV + 0 + + + + false + text3 + u>(.+?)</u + $1$ + NV + 0 + + + + + 200 + + Assertion.response_code + false + 8 + + + + + abcdefghijklmno + + Assertion.response_data + false + 16 + + + + + 3000 + + + + false + true + false + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + false + true + false + false + false + false + false + false + false + true + false + false + false + false + 0 + true + true + true + true + + + /data/jmeter/BUG_51512/2.6/results.csv + + + + false + + saveConfig + + + true + true + true + + true + false + true + false + false + false + false + false + false + false + true + false + false + false + false + 0 + true + true + true + true + + + + + + + + + diff --git a/bin/examples/jsp/memory.jsp b/bin/examples/jsp/memory.jsp new file mode 100644 index 00000000000..7bf6c6bb18e --- /dev/null +++ b/bin/examples/jsp/memory.jsp @@ -0,0 +1,35 @@ +<%@ page session="false" contentType="text/html" buffer="8kb" %> + + + +textToTExtract + +

zadadadzdadd

+textToTExtract3 +<% +int size = Integer.parseInt(request.getParameter("size")); +for (int i=0;i +

abcdefghijklmno<%=System.currentTimeMillis() %>

+<% +} +%> + + \ No newline at end of file diff --git a/bin/hc.parameters b/bin/hc.parameters new file mode 100644 index 00000000000..fbc7d357a33 --- /dev/null +++ b/bin/hc.parameters @@ -0,0 +1,46 @@ +# 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. + +# Properties file used to define additional default Apache HttpClient parameters +# +# +# This file is enabled by setting the JMeter property: hc.parameters.file +# entries are of the form: +# +# property=value (for strings) +# property$Type=value (for other types) +# +# where Type can be: +# Integer +# Long +# Boolean +# HttpVersion +# +# N.B. Other types are not yet implemented +# + +# Examples: + +#http.protocol.version$HttpVersion=1.0 + +#http.protocol.element-charset=ISO-8859-1 + +#http.socket.timeout$Integer=10000 + +#http.protocol.reject-relative-redirect$Boolean=true + +# Default value since JMeter 2.11, +# also uncomment hc.parameters.file=hc.parameters to enable this check: +#http.connection.stalecheck$Boolean=false \ No newline at end of file diff --git a/bin/heapdump.cmd b/bin/heapdump.cmd new file mode 100644 index 00000000000..5e504e02324 --- /dev/null +++ b/bin/heapdump.cmd @@ -0,0 +1,23 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem Ask the JMeter client to perform a HeapDump + +rem P1 = command port for JMeter instance (defaults to 4445) + +java -cp %~dp0ApacheJMeter.jar org.apache.jmeter.util.ShutdownClient HeapDump %* +pause \ No newline at end of file diff --git a/bin/heapdump.sh b/bin/heapdump.sh new file mode 100755 index 00000000000..941c9c4201a --- /dev/null +++ b/bin/heapdump.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +## 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. + +# Ask the JMeter client to perform a HeapDump + +# P1 = command port for JMeter instance (defaults to 4445) + +DIRNAME=`dirname $0` + +java -cp ${DIRNAME}/ApacheJMeter.jar org.apache.jmeter.util.ShutdownClient HeapDump "$@" diff --git a/bin/httpclient.parameters b/bin/httpclient.parameters new file mode 100644 index 00000000000..e269b9fca62 --- /dev/null +++ b/bin/httpclient.parameters @@ -0,0 +1,45 @@ +# 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. + +# Properties file used to define additional default Commons HttpClient parameters +# +# See: http://hc.apache.org/httpclient-3.x/preference-api.html +# +# This file is enabled by setting the JMeter property: httpclient.parameters.file +# entries are of the form: +# +# property=value (for strings) +# property$Type=value (for other types) +# +# where Type can be: +# Integer +# Long +# Boolean +# HttpVersion +# +# N.B. Other types are not yet implemented, so not all parameters are supported +# + +# Examples: + +#http.protocol.version$HttpVersion=1.0 + +#http.protocol.element-charset=ISO-8859-1 + +#http.socket.timeout$Integer=10000 + +#http.protocol.reject-relative-redirect$Boolean=true + +#http.authentication.preemptive$Boolean=true \ No newline at end of file diff --git a/bin/jaas.conf b/bin/jaas.conf new file mode 100644 index 00000000000..11bc75e3654 --- /dev/null +++ b/bin/jaas.conf @@ -0,0 +1,30 @@ +/** + * 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. + */ + +/** + * Sample file, you need to edit for your configuration + * see http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/lab/part6.html#Kerberos_5_Configuration + * see http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/tutorials/KerberosReq.html + * see http://docs.oracle.com/javase/7/docs/jre/api/security/jaas/spec/com/sun/security/auth/module/Krb5LoginModule.html + */ + +JMeter { + com.sun.security.auth.module.Krb5LoginModule required + doNotPrompt=false + useKeyTab=false + storeKey=false; +}; \ No newline at end of file diff --git a/bin/jmeter b/bin/jmeter index a5539866349..8a2dd343de1 100755 --- a/bin/jmeter +++ b/bin/jmeter @@ -1,3 +1,136 @@ -#!/bin/sh +#! /bin/sh -javaw -classpath $CLASSPATH;Apache-JMeter.jar org.apache.jmeter.JMeter \ No newline at end of file +## 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. + +## ============================================== +## Environment variables: +## JVM_ARGS - optional java args, e.g. -Dprop=val +## +## e.g. +## JVM_ARGS="-Xms512m -Xmx512m" jmeter etc. +## +## ============================================== + +# Minimal version to run JMeter +MINIMAL_VERSION=1.6.0 + +# Check if Java is present and the minimal version requierement +_java=`type java | awk '{ print $ NF }'` +CURRENT_VERSION=`"$_java" -version 2>&1 | awk -F'"' '/version/ {print $2}'` +minimal_version=`echo $MINIMAL_VERSION | awk -F'.' '{ print $2 }'` +current_version=`echo $CURRENT_VERSION | awk -F'.' '{ print $2 }'` +if [ $current_version ]; then + if [ $current_version -lt $minimal_version ]; then + echo "Error: Java version is too low to run JMeter. Needs at least Java >= ${MINIMAL_VERSION}." + exit 1 + fi + else + echo "Not able to find Java executable or version. Please check your Java installation." + exit 1 +fi + +JMETER_OPTS="" +case `uname` in + Darwin*) + # Add Mac-specific property - should be ignored elsewhere (Bug 47064) + JMETER_OPTS="-Xdock:name=JMeter -Xdock:icon="`dirname $0`/../docs/images/logo.jpg" -Dapple.laf.useScreenMenuBar=true -Dapple.eawt.quitStrategy=CLOSE_ALL_WINDOWS" + ;; +esac + + +# resolve links - $0 may be a softlink (code as used by Tomcat) +# N.B. readlink would be a lot simpler but is not supported on Solaris +PRG="$0" + +while [ -h "$PRG" ]; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`/"$link" + fi +done + +PRGDIR=`dirname "$PRG"` + +# The following should be reasonably good values for most tests running +# on Sun JVMs. Following is the analysis on which it is based. If it's total +# gibberish to you, please study my article at +# http://www.atg.com/portal/myatg/developer?paf_dm=full&paf_gear_id=1100010&detailArticle=true&id=9606 +# +# JMeter objects can generally be grouped into three life-length groups: +# +# - Per-sample objects (results, DOMs,...). An awful lot of those. +# Life length of milliseconds to a few seconds. +# +# - Per-run objects (threads, listener data structures,...). Not that many +# of those unless we use the table or tree listeners on heavy runs. +# Life length of minutes to several hours, from creation to start of next run. +# +# - Per-work-session objects (test plans, GUIs,...). +# Life length: for the life of the JVM. + +# This is the base heap size -- you may increase or decrease it to fit your +# system's memory availablity: +HEAP="-Xms512m -Xmx512m" + +# There's an awful lot of per-sample objects allocated during test run, so we +# need a large eden to avoid too frequent scavenges -- you'll need to tune this +# down proportionally if you modify the HEAP values above, below example is fine for 512m of Heap: +# NEW="-XX:NewSize=128m -XX:MaxNewSize=128m" + +# This ratio and target have been proven OK in tests with a specially high +# amount of per-sample objects (the HtmlParserHTMLParser tests): +# SURVIVOR="-XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=50" + +# Think about it: trying to keep per-run objects in tenuring definitely +# represents a cost, but where's the benefit? They won't disappear before +# the test is over, and at that point we will no longer care about performance. +# +# So we will have JMeter do an explicit Full GC before starting a test run, +# but then we won't make any effort (or spend any CPU) to keep objects +# in tenuring longer than the life of per-sample objects -- which is hopefully +# shorter than the period between two scavenges): +# +TENURING="-XX:MaxTenuringThreshold=2" + +# This evacuation ratio is OK (see the comments for SURVIVOR) during test +# runs -- not so sure about operations that bring a lot of long-lived information into +# memory in a short period of time, such as loading tests or listener data files. +# Increase it if you experience OutOfMemory problems during those operations +# without having gone through a lot of Full GC-ing just before the OOM: +# EVACUATION="-XX:MaxLiveObjectEvacuationRatio=20%" + +# Java 8 remove Permanent generation, don't settings the PermSize +if [ $current_version -lt "8" ]; then + # Increase MaxPermSize if you use a lot of Javascript in your Test Plan : + PERM="-XX:PermSize=64m -XX:MaxPermSize=128m" +fi + +CLASS_UNLOAD="-XX:+CMSClassUnloadingEnabled" + +# Finally, some tracing to help in case things go astray: +#DEBUG="-verbose:gc -XX:+PrintTenuringDistribution" + +# Always dump on OOM (does not cost anything unless triggered) +DUMP="-XX:+HeapDumpOnOutOfMemoryError" + +SERVER="-server" + +ARGS="$SERVER $DUMP $HEAP $NEW $SURVIVOR $TENURING $EVACUATION $PERM $CLASS_UNLOAD" + +java $ARGS $JVM_ARGS $JMETER_OPTS -jar "$PRGDIR/ApacheJMeter.jar" "$@" diff --git a/bin/jmeter-n-r.cmd b/bin/jmeter-n-r.cmd new file mode 100644 index 00000000000..2d41c927062 --- /dev/null +++ b/bin/jmeter-n-r.cmd @@ -0,0 +1,59 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem ============================================ +rem Non-GUI version of JMETER.BAT (WinNT/2K only) +rem +rem Drop a JMX file on this batch script, and it +rem will run it in non-GUI mode, with a log file +rem formed from the input file name but with the +rem extension .jtl +rem +rem Only the first parameter is used. +rem Only works for Win2k. +rem +rem ============================================ + +if "%OS%"=="Windows_NT" goto WinNT +echo "Sorry, this command file requires Windows NT/ 2000 / XP" +pause +goto END +:WinNT + +rem Check file is supplied +if a == a%1 goto winNT2 + +rem Allow special name LAST +if LAST == %1 goto winNT3 + +rem Check it has extension .jmx +if "%~x1" == ".jmx" goto winNT3 +:winNT2 +echo Please supply a script name with the extension .jmx +pause +goto END +:winNT3 + +rem Change to script directory +pushd %~dp1 + +rem use same directory to find jmeter script +call "%~dp0"jmeter -n -t "%~nx1" -j "%~n1.log" -l "%~n1.jtl" -r %2 %3 %4 %5 %6 %7 %8 %9 + +popd + +:END \ No newline at end of file diff --git a/bin/jmeter-n.cmd b/bin/jmeter-n.cmd new file mode 100644 index 00000000000..86c1e88018e --- /dev/null +++ b/bin/jmeter-n.cmd @@ -0,0 +1,59 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem ============================================ +rem Non-GUI version of JMETER.BAT (WinNT/2K only) +rem +rem Drop a JMX file on this batch script, and it +rem will run it in non-GUI mode, with a log file +rem formed from the input file name but with the +rem extension .jtl +rem +rem Only the first parameter is used. +rem Only works for Win2k. +rem +rem ============================================ + +if "%OS%"=="Windows_NT" goto WinNT +echo "Sorry, this command file requires Windows NT/ 2000 / XP" +pause +goto END +:WinNT + +rem Check file is supplied +if a == a%1 goto winNT2 + +rem Allow special name LAST +if LAST == %1 goto winNT3 + +rem Check it has extension .jmx +if "%~x1" == ".jmx" goto winNT3 +:winNT2 +echo Please supply a script name with the extension .jmx +pause +goto END +:winNT3 + +rem Change to script directory +pushd %~dp1 + +rem use same directory to find jmeter script +call "%~dp0"jmeter -n -t "%~nx1" -j "%~n1.log" -l "%~n1.jtl" %2 %3 %4 %5 %6 %7 %8 %9 + +popd + +:END \ No newline at end of file diff --git a/bin/jmeter-report b/bin/jmeter-report new file mode 100755 index 00000000000..e3ac32525b8 --- /dev/null +++ b/bin/jmeter-report @@ -0,0 +1,76 @@ +#! /bin/sh + +## 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. + +# The following should be reasonably good values for most tests running +# on Sun JVMs. Following is the analysis on which it is based. If it's total +# gibberish to you, please study my article at +# http://www.atg.com/portal/myatg/developer?paf_dm=full&paf_gear_id=1100010&detailArticle=true&id=9606 +# +# JMeter objects can generally be grouped into three life-length groups: +# +# - Per-sample objects (results, DOMs,...). An awful lot of those. +# Life length of milliseconds to a few seconds. +# +# - Per-run objects (threads, listener data structures,...). Not that many +# of those unless we use the table or tree listeners on heavy runs. +# Life length of minutes to several hours, from creation to start of next run. +# +# - Per-work-session objects (test plans, GUIs,...). +# Life length: for the life of the JVM. + +# This is the base heap size -- you may increase or decrease it to fit your +# system's memory availablity: +HEAP="-Xms256m -Xmx256m" + +# There's an awful lot of per-sample objects allocated during test run, so we +# need a large eden to avoid too frequent scavenges -- you'll need to tune this +# down proportionally if you reduce the HEAP values above: +NEW="-XX:NewSize=128m -XX:MaxNewSize=128m" + +# This ratio and target have been proven OK in tests with a specially high +# amount of per-sample objects (the HtmlParserHTMLParser tests): +# SURVIVOR="-XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=50%" + +# Think about it: trying to keep per-run objects in tenuring definitely +# represents a cost, but where's the benefit? They won't disappear before +# the test is over, and at that point we will no longer care about performance. +# +# So we will have JMeter do an explicit Full GC before starting a test run, +# but then we won't make any effort (or spend any CPU) to keep objects +# in tenuring longer than the life of per-sample objects -- which is hopefully +# shorter than the period between two scavenges): +# +TENURING="-XX:MaxTenuringThreshold=2" + +# This evacuation ratio is OK (see the comments for SURVIVOR) during test +# runs -- no so sure about operations that bring a lot of long-lived information into +# memory in a short period of time, such as loading tests or listener data files. +# Increase it if you experience OutOfMemory problems during those operations +# without having gone through a lot of Full GC-ing just before the OOM: +# EVACUATION="-XX:MaxLiveObjectEvacuationRatio=20%" + +# PermSize is a scam. Leave it like this: +PERM="-XX:PermSize=64m -XX:MaxPermSize=64m" + +# Finally, some tracing to help in case things go astray: +DEBUG="-verbose:gc -XX:+PrintTenuringDistribution" + +SERVER="-server" + +ARGS="$SERVER $HEAP $NEW $SURVIVOR $TENURING $EVACUATION $PERM $DEBUG" + +java -server -jar `dirname $0`/ApacheJMeter.jar report "$@" diff --git a/bin/jmeter-report.bat b/bin/jmeter-report.bat new file mode 100644 index 00000000000..0a49c5dbf1a --- /dev/null +++ b/bin/jmeter-report.bat @@ -0,0 +1,36 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem set JAVA_HOME=c:/jdk1.5.0 + +setlocal + +rem On NT/2K grab all arguments at once +set JMETER_CMD_LINE_ARGS=%* + +rem See the unix startup file for the rationale of the following parameters, +rem including some tuning recommendations +set HEAP=-Xms256m -Xmx256m +set NEW=-XX:NewSize=128m -XX:MaxNewSize=128m +set SURVIVOR=-XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=50% +set TENURING=-XX:MaxTenuringThreshold=2 +set PERM=-XX:PermSize=64m -XX:MaxPermSize=64m +set DEBUG=-verbose:gc -XX:+PrintTenuringDistribution +rem set ARGS=%HEAP% %NEW% %SURVIVOR% %TENURING% %PERM% %DEBUG% +set ARGS=-server -Xms128m -Xmx512m + +%JAVA_HOME%/bin/java %JVM_ARGS% %ARGS% -jar ApacheJMeter.jar report %JMETER_CMD_LINE_ARGS% diff --git a/bin/jmeter-server b/bin/jmeter-server new file mode 100755 index 00000000000..41588fe2148 --- /dev/null +++ b/bin/jmeter-server @@ -0,0 +1,32 @@ +#!/bin/sh + +## 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. + +## To change the RMI/Server port: +## +## SERVER_PORT=1234 jmeter-server +## + +DIRNAME=`dirname $0` + +# If the client fails with: +# ERROR - jmeter.engine.ClientJMeterEngine: java.rmi.ConnectException: Connection refused to host: 127.0.0.1 +# then it may be due to the server host returning 127.0.0.1 as its address + +# One way to fix this is to define RMI_HOST_DEF below +#RMI_HOST_DEF=-Djava.rmi.server.hostname=xxx.xxx.xxx.xxx + +${DIRNAME}/jmeter ${RMI_HOST_DEF} -Dserver_port=${SERVER_PORT:-1099} -s -j jmeter-server.log "$@" diff --git a/bin/jmeter-server.bat b/bin/jmeter-server.bat new file mode 100644 index 00000000000..a4e3379a4b6 --- /dev/null +++ b/bin/jmeter-server.bat @@ -0,0 +1,73 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem =============================================================== +rem Enviroment variables +rem SERVER_PORT (optional) - define the rmiregistry and server port +rem +rem JVM_ARGS - Java flags - these are handled by jmeter.bat +rem +rem =============================================================== + + +REM Protect environment against changes +setlocal + +if exist jmeter-server.bat goto winNT1 +echo Changing to JMeter home directory +cd /D %~dp0 +:winNT1 + +if exist %JMETER_HOME%\lib\ext\ApacheJMeter_core.jar goto setCP +echo Could not find ApacheJmeter_core.jar ... +REM Try to work out JMETER_HOME +echo ... Trying JMETER_HOME=.. +set JMETER_HOME=.. +if exist %JMETER_HOME%\lib\ext\ApacheJMeter_core.jar goto setCP +echo ... trying JMETER_HOME=. +set JMETER_HOME=. +if exist %JMETER_HOME%\lib\ext\ApacheJMeter_core.jar goto setCP +echo Cannot determine JMETER_HOME ! +goto exit + +:setCP +echo Found ApacheJMeter_core.jar + +REM No longer need to create the rmiregistry as it is done by the server +REM set CLASSPATH=%JMETER_HOME%\lib\ext\ApacheJMeter_core.jar;%JMETER_HOME%\lib\jorphan.jar;%JMETER_HOME%\lib\logkit-1.2.jar + +REM START rmiregistry %SERVER_PORT% +REM + +rem On NT/2K grab all arguments at once +set JMETER_CMD_LINE_ARGS=%* + +if not "%SERVER_PORT%" == "" goto port + +call jmeter -s -j jmeter-server.log %JMETER_CMD_LINE_ARGS% +goto end + + +:port +call jmeter -Dserver_port=%SERVER_PORT% -s -j jmeter-server.log %JMETER_CMD_LINE_ARGS% + +:end + +rem No longer needed, as server is started in-process +rem taskkill /F /IM rmiregistry.exe + +:exit \ No newline at end of file diff --git a/bin/jmeter-t.cmd b/bin/jmeter-t.cmd new file mode 100644 index 00000000000..3c0c8cbc914 --- /dev/null +++ b/bin/jmeter-t.cmd @@ -0,0 +1,58 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem ============================================ +rem +rem Drop a JMX file on this batch script, and it +rem will load it in the GUI. +rem +rem Only the first parameter is used. +rem Only works for Win2k. +rem +rem ============================================ + + +if "%OS%"=="Windows_NT" goto WinNT +echo "Sorry, this command file requires Windows NT/ 2000 / XP" +pause +goto END +:WinNT + +rem Check file is supplied +if a == a%1 goto winNT2 + +rem Allow special name LAST +if LAST == %1 goto winNT3 + +rem Check it has extension .jmx +if "%~x1" == ".jmx" goto winNT3 +:winNT2 +echo Please supply a script name with the extension .jmx +pause +goto END +:winNT3 + +rem Start in directory with JMX file +pushd %~dp1 + +rem Prepend the directory in which this script resides in case not on path + +call "%~dp0"jmeter -j "%~n1.log" -t "%~nx1" %2 %3 %4 %5 %6 %7 %8 %9 + +popd + +:END \ No newline at end of file diff --git a/bin/jmeter.bat b/bin/jmeter.bat old mode 100755 new mode 100644 index bdc3c408f18..673c24afdc0 --- a/bin/jmeter.bat +++ b/bin/jmeter.bat @@ -1,3 +1,127 @@ -@echo off - -javaw -classpath %classpath%;Apache-JMeter.jar org.apache.jmeter.JMeter \ No newline at end of file +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem ===================================================== +rem Environment variables that can be defined externally: +rem +rem JMETER_BIN - JMeter bin directory (must end in \) +rem JM_LAUNCH - java.exe (default) or javaw.exe +rem JVM_ARGS - additional java options, e.g. -Dprop=val +rem JM_START - set this to "start" to launch JMeter in a separate window +rem this is used by the jmeterw.cmd script. +rem +rem ===================================================== + +setlocal + +rem Minimal version to run JMeter +set MINIMAL_VERSION=1.6.0 + +for /f "tokens=3" %%g in ('java -version 2^>^&1 ^| findstr /i "version"') do ( + rem @echo Debug Output: %%g + set JAVAVER=%%g +) +if not defined JAVAVER ( + @echo Not able to find Java executable or version. Please check your Java installation. + set ERRORLEVEL=2 + goto pause +) +set JAVAVER=%JAVAVER:"=% +for /f "delims=. tokens=1-3" %%v in ("%JAVAVER%") do ( + set current_minor=%%w +) + +for /f "delims=. tokens=1-3" %%v in ("%MINIMAL_VERSION%") do ( + set minimal_minor=%%w +) + +if not defined current_minor ( + @echo Not able to find Java executable or version. Please check your Java installation. + set ERRORLEVEL=2 + goto pause +) +rem @echo Debug: CURRENT=%current_minor% - MINIMAL=%minimal_minor% +if %current_minor% LSS %minimal_minor% ( + @echo Error: Java version -- %JAVAVER% -- is too low to run JMeter. Needs a Java version greater than or equal to %MINIMAL_VERSION% + set ERRORLEVEL=3 + goto pause +) + +if .%JM_LAUNCH% == . set JM_LAUNCH=java.exe + +if exist jmeter.bat goto winNT1 +if .%JMETER_BIN% == . set JMETER_BIN=%~dp0 + +:winNT1 +rem On NT/2K grab all arguments at once +set JMETER_CMD_LINE_ARGS=%* + +rem The following link describes the -XX options: +rem http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html +rem http://java.sun.com/developer/TechTips/2000/tt1222.html has some more descriptions +rem Unfortunately TechTips no longer seem to be available + +rem See the unix startup file for the rationale of the following parameters, +rem including some tuning recommendations +set HEAP=-Xms512m -Xmx512m +set NEW=-XX:NewSize=128m -XX:MaxNewSize=128m +set SURVIVOR=-XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=50% +set TENURING=-XX:MaxTenuringThreshold=2 +rem Java 8 remove Permanent generation, don't settings the PermSize +if %current_minor% LEQ "8" ( + rem Increase MaxPermSize if you use a lot of Javascript in your Test Plan : + set PERM=-XX:PermSize=64m -XX:MaxPermSize=128m +) + +set CLASS_UNLOAD=-XX:+CMSClassUnloadingEnabled +rem set DEBUG=-verbose:gc -XX:+PrintTenuringDistribution + +rem Always dump on OOM (does not cost anything unless triggered) +set DUMP=-XX:+HeapDumpOnOutOfMemoryError + +rem Additional settings that might help improve GUI performance on some platforms +rem See: http://java.sun.com/products/java-media/2D/perf_graphics.html + +set DDRAW= +rem Setting this flag to true turns off DirectDraw usage, which sometimes helps to get rid of a lot of rendering problems on Win32. +rem set DDRAW=%DDRAW% -Dsun.java2d.noddraw=true + +rem Setting this flag to false turns off DirectDraw offscreen surfaces acceleration by forcing all createVolatileImage calls to become createImage calls, and disables hidden acceleration performed on surfaces created with createImage . +rem set DDRAW=%DDRAW% -Dsun.java2d.ddoffscreen=false + +rem Setting this flag to true enables hardware-accelerated scaling. +rem set DDRAW=%DDRAW% -Dsun.java2d.ddscale=true + +rem Server mode +rem Collect the settings defined above +set ARGS=%DUMP% %HEAP% %NEW% %SURVIVOR% %TENURING% %PERM% %CLASS_UNLOAD% %DDRAW% + +%JM_START% %JM_LAUNCH% %ARGS% %JVM_ARGS% -jar "%JMETER_BIN%ApacheJMeter.jar" %JMETER_CMD_LINE_ARGS% + +rem If the errorlevel is not zero, then display it and pause + +if NOT errorlevel 0 goto pause +if errorlevel 1 goto pause + +goto end + +:pause +echo errorlevel=%ERRORLEVEL% +pause + +:end + diff --git a/bin/jmeter.properties b/bin/jmeter.properties new file mode 100644 index 00000000000..33fcc3a97fa --- /dev/null +++ b/bin/jmeter.properties @@ -0,0 +1,1141 @@ +################################################################################ +# Apache JMeter Property file +################################################################################ + +## 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. + +################################################################################ +# +# THIS FILE SHOULD NOT BE MODIFIED +# +# This avoids having to re-apply the modifications when upgrading JMeter +# Instead only user.properties should be modified: +# 1/ copy the property you want to modify to user.properties from jmeter.properties +# 2/ Change its value there +# +################################################################################ + +#Preferred GUI language. Comment out to use the JVM default locale's language. +#language=en + +# Additional locale(s) to add to the displayed list. +# The current default list is: en, fr, de, no, es, tr, ja, zh_CN, zh_TW, pl, pt_BR +# [see JMeterMenuBar#makeLanguageMenu()] +# The entries are a comma-separated list of language names +#locales.add=zu + +# Netscape HTTP Cookie file +cookies=cookies + +#--------------------------------------------------------------------------- +# File format configuration for JMX and JTL files +#--------------------------------------------------------------------------- + +# Properties: +# file_format - affects both JMX and JTL files +# file_format.testplan - affects JMX files only +# file_format.testlog - affects JTL files only +# +# Possible values are: +# 2.1 - initial format using XStream +# 2.2 - updated format using XStream, with shorter names + +# N.B. format 2.0 (Avalon) is no longer supported + +#--------------------------------------------------------------------------- +# XML Parser +#--------------------------------------------------------------------------- + +# XML Reader(Parser) - Must implement SAX 2 specs +xml.parser=org.apache.xerces.parsers.SAXParser + +# Path to a Properties file containing Namespace mapping in the form +# prefix=Namespace +# Example: +# ns=http://biz.aol.com/schema/2006-12-18 +#xpath.namespace.config= + +#--------------------------------------------------------------------------- +# SSL configuration +#--------------------------------------------------------------------------- + +## SSL System properties are now in system.properties + +# JMeter no longer converts javax.xxx property entries in this file into System properties. +# These must now be defined in the system.properties file or on the command-line. +# The system.properties file gives more flexibility. + +# By default, SSL session contexts are now created per-thread, rather than being shared. +# The original behaviour can be enabled by setting the JMeter property: +#https.sessioncontext.shared=true + +# Default HTTPS protocol level: +#https.default.protocol=TLS +# This may need to be changed here (or in user.properties) to: +#https.default.protocol=SSLv3 + +# List of protocols to enable. You may have to select only a subset if you find issues with target server. +# This is needed when server does not support Socket version negotiation, this can lead to: +# javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated +# java.net.SocketException: Connection reset +# see https://bz.apache.org/bugzilla/show_bug.cgi?id=54759 +#https.socket.protocols=SSLv2Hello SSLv3 TLSv1 + +# Control if we allow reuse of cached SSL context between iterations +# set the value to 'false' to reset the SSL context each iteration +#https.use.cached.ssl.context=true + +# Start and end index to be used with keystores with many entries +# The default is to use entry 0, i.e. the first +#https.keyStoreStartIndex=0 +#https.keyStoreEndIndex=0 + +#--------------------------------------------------------------------------- +# Look and Feel configuration +#--------------------------------------------------------------------------- + +#Classname of the Swing default UI +# +# The LAF classnames that are available are now displayed as ToolTip text +# when hovering over the Options/Look and Feel selection list. +# +# You can either use a full class name, as shown above, +# or one of the strings "System" or "CrossPlatform" which means +# JMeter will use the corresponding string returned by UIManager.getLookAndFeelClassName() + +# LAF can be overridden by os.name (lowercased, spaces replaced by '_') +# Sample os.name LAF: +#jmeter.laf.windows_xp=javax.swing.plaf.metal.MetalLookAndFeel + +# Failing that, the OS family = os.name, but only up to first space: +# Sample OS family LAF: +#jmeter.laf.windows=com.sun.java.swing.plaf.windows.WindowsLookAndFeel + +# Mac apparently looks better with the System LAF +jmeter.laf.mac=System + +# Failing that, the JMeter default laf can be defined: +#jmeter.laf=System + +# If none of the above jmeter.laf properties are defined, JMeter uses the CrossPlatform LAF. +# This is because the CrossPlatform LAF generally looks better than the System LAF. +# See https://bz.apache.org/bugzilla/show_bug.cgi?id=52026 for details +# N.B. the laf can be defined in user.properties. + +# LoggerPanel display +# default to false +#jmeter.loggerpanel.display=false + +# Enable LogViewer Panel to receive log event even if closed +# Enabled since 2.12 +# Note this has some impact on performances, but as GUI mode must +# not be used for Load Test it is acceptable +#jmeter.loggerpanel.enable_when_closed=true + +# Error/Fatal Log count display +# defaults to true +#jmeter.errorscounter.display=true + +# Max characters kept in LoggerPanel, default to 80000 chars +# O means no limit +#jmeter.loggerpanel.maxlength=80000 + +# Toolbar display +# default: +#jmeter.toolbar.display=true +# Toolbar icon definitions +#jmeter.toolbar.icons=org/apache/jmeter/images/toolbar/icons-toolbar.properties +# Toolbar list +#jmeter.toolbar=new,open,close,save,save_as_testplan,|,cut,copy,paste,|,expand,collapse,toggle,|,test_start,test_stop,test_shutdown,|,test_start_remote_all,test_stop_remote_all,test_shutdown_remote_all,|,test_clear,test_clear_all,|,search,search_reset,|,function_helper,help +# Toolbar icons default size: 22x22. Available sizes are: 22x22, 32x32, 48x48 +#jmeter.toolbar.icons.size=22x22 + +# Icon definitions +# default: +#jmeter.icons=org/apache/jmeter/images/icon.properties +# alternate: +#jmeter.icons=org/apache/jmeter/images/icon_1.properties + +#Components to not display in JMeter GUI (GUI class name or static label) +# These elements are deprecated: HTML Parameter Mask,HTTP User Parameter Modifier, Webservice (SOAP) Request +not_in_menu=org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui, HTTP User Parameter Modifier, org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui + +# Number of items in undo history +# Feature is disabled by default (0) +# Set it to a number > 0 (25 can be a good default) +# The bigger it is, the more it consumes memory +#undo.history.size=0 + +#--------------------------------------------------------------------------- +# Remote hosts and RMI configuration +#--------------------------------------------------------------------------- + +# Remote Hosts - comma delimited +remote_hosts=127.0.0.1 +#remote_hosts=localhost:1099,localhost:2010 + +# RMI port to be used by the server (must start rmiregistry with same port) +#server_port=1099 + +# To change the port to (say) 1234: +# On the server(s) +# - set server_port=1234 +# - start rmiregistry with port 1234 +# On Windows this can be done by: +# SET SERVER_PORT=1234 +# JMETER-SERVER +# +# On Unix: +# SERVER_PORT=1234 jmeter-server +# +# On the client: +# - set remote_hosts=server:1234 + +# Parameter that controls the RMI port used by the RemoteSampleListenerImpl (The Controler) +# Default value is 0 which means port is randomly assigned +# You may need to open Firewall port on the Controller machine +#client.rmi.localport=0 + +# When distributed test is starting, there may be several attempts to initialize +# remote engines. By default, only single try is made. Increase following property +# to make it retry for additional times +#client.tries=1 + +# If there is initialization retries, following property sets delay between attempts +#client.retries_delay=5000 + +# When all initialization tries was made, test will fail if some remote engines are failed +# Set following property to true to ignore failed nodes and proceed with test +#client.continue_on_fail=false + +# To change the default port (1099) used to access the server: +#server.rmi.port=1234 + +# To use a specific port for the JMeter server engine, define +# the following property before starting the server: +#server.rmi.localport=4000 + +# From JMeter 2.3.1, the jmeter server creates the RMI registry as part of the server process. +# To stop the server creating the RMI registry: +#server.rmi.create=false + +# From JMeter 2.3.1, define the following property to cause JMeter to exit after the first test +#server.exitaftertest=true + +# Prefix used by IncludeController when building file name +#includecontroller.prefix= + +#--------------------------------------------------------------------------- +# Logging Configuration +#--------------------------------------------------------------------------- + +# Note: JMeter uses Avalon (Excalibur) LogKit + +# Logging Format +# see http://excalibur.apache.org/apidocs/org/apache/log/format/PatternFormatter.html + +# +# Default format: +#log_format=%{time:yyyy/MM/dd HH:mm:ss} %5.5{priority} - %{category}: %{message} %{throwable} +# \n is automatically added to the end of the string +# +# Predefined formats in the JMeter LoggingManager: +#log_format_type=default +#log_format_type=thread_prefix +#log_format_type=thread_suffix +# default is as above +# thread_prefix adds the thread name as a prefix to the category +# thread_suffix adds the thread name as a suffix to the category +# Note that thread name is not included by default, as it requires extra processing. +# +# To change the logging format, define either log_format_type or log_format +# If both are defined, the type takes precedence +# Note that these properties cannot be defined using the -J or -D JMeter +# command-line flags, as the format will have already been determined by then +# However, they can be defined as JVM properties + +#Logging levels for the logging categories in JMeter. Correct values are FATAL_ERROR, ERROR, WARN, INFO, and DEBUG +# To set the log level for a package or individual class, use: +# log_level.[package_name].[classname]=[PRIORITY_LEVEL] +# But omit "org.apache" from the package name. The classname is optional. Further examples below. + +log_level.jmeter=INFO +log_level.jmeter.junit=DEBUG +#log_level.jmeter.control=DEBUG +#log_level.jmeter.testbeans=DEBUG +#log_level.jmeter.engine=DEBUG +#log_level.jmeter.threads=DEBUG +#log_level.jmeter.gui=WARN +#log_level.jmeter.testelement=DEBUG +#log_level.jmeter.util=WARN +#log_level.jmeter.protocol.http=DEBUG +# For CookieManager, AuthManager etc: +#log_level.jmeter.protocol.http.control=DEBUG +#log_level.jmeter.protocol.ftp=WARN +#log_level.jmeter.protocol.jdbc=DEBUG +#log_level.jmeter.protocol.java=WARN +#log_level.jmeter.testelements.property=DEBUG +log_level.jorphan=INFO + + +#Log file for log messages. +# You can specify a different log file for different categories via: +# log_file.[category]=[filename] +# category is equivalent to the package/class names described above + +# Combined log file (for jmeter and jorphan) +#log_file=jmeter.log +# To redirect logging to standard output, try the following: +# (it will probably report an error, but output will be to stdout) +#log_file= + +# Or define separate logs if required: +#log_file.jorphan=jorphan.log +#log_file.jmeter=jmeter.log + +# If the filename contains paired single-quotes, then the name is processed +# as a SimpleDateFormat format applied to the current date, for example: +#log_file='jmeter_'yyyyMMddHHmmss'.tmp' + +# N.B. When JMeter starts, it sets the system property: +# org.apache.commons.logging.Log +# to +# org.apache.commons.logging.impl.LogKitLogger +# if not already set. This causes Apache and Commons HttpClient to use the same logging as JMeter + +# Further logging configuration +# Excalibur logging provides the facility to configure logging using +# configuration files written in XML. This allows for such features as +# log file rotation which are not supported directly by JMeter. +# +# If such a file specified, it will be applied to the current logging +# hierarchy when that has been created. +# +#log_config=logkit.xml + +#--------------------------------------------------------------------------- +# HTTP Java configuration +#--------------------------------------------------------------------------- + +# Number of connection retries performed by HTTP Java sampler before giving up +#http.java.sampler.retries=10 +# 0 now means don't retry connection (in 2.3 and before it meant no tries at all!) + +#--------------------------------------------------------------------------- +# Commons HTTPClient configuration +#--------------------------------------------------------------------------- + +# define a properties file for overriding Commons HttpClient parameters +# See: http://hc.apache.org/httpclient-3.x/preference-api.html +# Uncomment this line if you put anything in httpclient.parameters file +#httpclient.parameters.file=httpclient.parameters + + +# define a properties file for overriding Apache HttpClient parameters +# See: TBA +# Uncomment this line if you put anything in hc.parameters file +#hc.parameters.file=hc.parameters + +# Following properties apply to both Commons and Apache HttpClient + +# set the socket timeout (or use the parameter http.socket.timeout) +# for AJP Sampler and HttpClient3 implementation. +# Note for HttpClient3 implementation it is better to use GUI to set timeout +# or use http.socket.timeout in httpclient.parameters +# Value is in milliseconds +#httpclient.timeout=0 +# 0 == no timeout + +# Set the http version (defaults to 1.1) +#httpclient.version=1.0 (or use the parameter http.protocol.version) + +# Define characters per second > 0 to emulate slow connections +#httpclient.socket.http.cps=0 +#httpclient.socket.https.cps=0 + +#Enable loopback protocol +#httpclient.loopback=true + +# Define the local host address to be used for multi-homed hosts +#httpclient.localaddress=1.2.3.4 + +# AuthManager Kerberos configuration +# Name of application module used in jaas.conf +#kerberos_jaas_application=JMeter + +# Should ports be stripped from urls before constructing SPNs +# for spnego authentication +#kerberos.spnego.strip_port=true + +# Sample logging levels for Commons HttpClient +# +# Commons HttpClient Logging information can be found at: +# http://hc.apache.org/httpclient-3.x/logging.html + +# Note that full category names are used, i.e. must include the org.apache. +# Info level produces no output: +#log_level.org.apache.commons.httpclient=debug +# Might be useful: +#log_level.org.apache.commons.httpclient.Authenticator=trace + +# Show headers only +#log_level.httpclient.wire.header=debug + +# Full wire debug produces a lot of output; consider using separate file: +#log_level.httpclient.wire=debug +#log_file.httpclient=httpclient.log + + +# Apache Commons HttpClient logging examples +# +# Enable header wire + context logging - Best for Debugging +#log_level.org.apache.http=DEBUG +#log_level.org.apache.http.wire=ERROR + +# Enable full wire + context logging +#log_level.org.apache.http=DEBUG + +# Enable context logging for connection management +#log_level.org.apache.http.impl.conn=DEBUG + +# Enable context logging for connection management / request execution +#log_level.org.apache.http.impl.conn=DEBUG +#log_level.org.apache.http.impl.client=DEBUG +#log_level.org.apache.http.client=DEBUG + +#--------------------------------------------------------------------------- +# Apache HttpComponents HTTPClient configuration (HTTPClient4) +#--------------------------------------------------------------------------- + +# Number of retries to attempt (default 0) +#httpclient4.retrycount=0 + +# Idle connection timeout (ms) to apply if the server does not send Keep-Alive headers +#httpclient4.idletimeout=0 +# Note: this is currently an experimental fix + +#--------------------------------------------------------------------------- +# Apache HttpComponents HTTPClient configuration (HTTPClient 3.1) +#--------------------------------------------------------------------------- + +# Number of retries to attempt (default 0) +#httpclient3.retrycount=0 + +#--------------------------------------------------------------------------- +# HTTP Cache Manager configuration +#--------------------------------------------------------------------------- +# +# Space or comma separated list of methods that can be cached +#cacheable_methods=GET +# N.B. This property is currently a temporary solution for Bug 56162 + +# Since 2.12, JMeter does not create anymore a Sample Result with 204 response +# code for a resource found in cache which is inline with what browser do. +#cache_manager.cached_resource_mode=RETURN_NO_SAMPLE + +# You can choose between 3 modes: +# RETURN_NO_SAMPLE (default) +# RETURN_200_CACHE +# RETURN_CUSTOM_STATUS + +# Those mode have the following behaviours: +# RETURN_NO_SAMPLE : this mode returns no Sample Result, it has no additional configuration +# RETURN_200_CACHE : this mode will return Sample Result with response code to 200 and response message to "(ex cache)", you can modify response message by setting +# RETURN_200_CACHE.message=(ex cache) +# RETURN_CUSTOM_STATUS : This mode lets you select what response code and message you want to return, if you use this mode you need to set those properties +# RETURN_CUSTOM_STATUS.code= +# RETURN_CUSTOM_STATUS.message= + +#--------------------------------------------------------------------------- +# Results file configuration +#--------------------------------------------------------------------------- + +# This section helps determine how result data will be saved. +# The commented out values are the defaults. + +# legitimate values: xml, csv, db. Only xml and csv are currently supported. +#jmeter.save.saveservice.output_format=csv + + +# true when field should be saved; false otherwise + +# assertion_results_failure_message only affects CSV output +#jmeter.save.saveservice.assertion_results_failure_message=false +# +# legitimate values: none, first, all +#jmeter.save.saveservice.assertion_results=none +# +#jmeter.save.saveservice.data_type=true +#jmeter.save.saveservice.label=true +#jmeter.save.saveservice.response_code=true +# response_data is not currently supported for CSV output +#jmeter.save.saveservice.response_data=false +# Save ResponseData for failed samples +#jmeter.save.saveservice.response_data.on_error=false +#jmeter.save.saveservice.response_message=true +#jmeter.save.saveservice.successful=true +#jmeter.save.saveservice.thread_name=true +#jmeter.save.saveservice.time=true +#jmeter.save.saveservice.subresults=true +#jmeter.save.saveservice.assertions=true +#jmeter.save.saveservice.latency=true +#jmeter.save.saveservice.connect_time=false +#jmeter.save.saveservice.samplerData=false +#jmeter.save.saveservice.responseHeaders=false +#jmeter.save.saveservice.requestHeaders=false +#jmeter.save.saveservice.encoding=false +#jmeter.save.saveservice.bytes=true +#jmeter.save.saveservice.url=false +#jmeter.save.saveservice.filename=false +#jmeter.save.saveservice.hostname=false +#jmeter.save.saveservice.thread_counts=true +#jmeter.save.saveservice.sample_count=false +#jmeter.save.saveservice.idle_time=false + +# Timestamp format - this only affects CSV output files +# legitimate values: none, ms, or a format suitable for SimpleDateFormat +#jmeter.save.saveservice.timestamp_format=ms +#jmeter.save.saveservice.timestamp_format=yyyy/MM/dd HH:mm:ss.SSS + +# For use with Comma-separated value (CSV) files or other formats +# where the fields' values are separated by specified delimiters. +# Default: +#jmeter.save.saveservice.default_delimiter=, +# For TAB, since JMeter 2.3 one can use: +#jmeter.save.saveservice.default_delimiter=\t + +# Only applies to CSV format files: +#jmeter.save.saveservice.print_field_names=false + +# Optional list of JMeter variable names whose values are to be saved in the result data files. +# Use commas to separate the names. For example: +#sample_variables=SESSION_ID,REFERENCE +# N.B. The current implementation saves the values in XML as attributes, +# so the names must be valid XML names. +# Versions of JMeter after 2.3.2 send the variable to all servers +# to ensure that the correct data is available at the client. + +# Optional xml processing instruction for line 2 of the file: +#jmeter.save.saveservice.xml_pi= + +# Prefix used to identify filenames that are relative to the current base +#jmeter.save.saveservice.base_prefix=~/ + +# AutoFlush on each line written in XML or CSV output +# Setting this to true will result in less test results data loss in case of Crash +# but with impact on performances, particularly for intensive tests (low or no pauses) +# Since JMeter 2.10, this is false by default +#jmeter.save.saveservice.autoflush=false + +#--------------------------------------------------------------------------- +# Settings that affect SampleResults +#--------------------------------------------------------------------------- + +# Save the start time stamp instead of the end +# This also affects the timestamp stored in result files +sampleresult.timestamp.start=true + +# Whether to use System.nanoTime() - otherwise only use System.currentTimeMillis() +#sampleresult.useNanoTime=true + +# Use a background thread to calculate the nanoTime offset +# Set this to <= 0 to disable the background thread +#sampleresult.nanoThreadSleep=5000 + +#--------------------------------------------------------------------------- +# Upgrade property +#--------------------------------------------------------------------------- + +# File that holds a record of name changes for backward compatibility issues +upgrade_properties=/bin/upgrade.properties + +#--------------------------------------------------------------------------- +# JMeter Test Script recorder configuration +# +# N.B. The element was originally called the Proxy recorder, which is why the +# properties have the prefix "proxy". +#--------------------------------------------------------------------------- + +# If the recorder detects a gap of at least 5s (default) between HTTP requests, +# it assumes that the user has clicked a new URL +#proxy.pause=5000 + +# Add numeric prefix to Sampler names (default true) +#proxy.number.requests=true + +# List of URL patterns that will be added to URL Patterns to exclude +# Separate multiple lines with ; +#proxy.excludes.suggested=.*\\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff) + +# Change the default HTTP Sampler (currently HttpClient4) +# Java: +#jmeter.httpsampler=HTTPSampler +#or +#jmeter.httpsampler=Java +# +# Apache HTTPClient: +#jmeter.httpsampler=HTTPSampler2 +#or +#jmeter.httpsampler=HttpClient3.1 +# +# HttpClient4.x +#jmeter.httpsampler=HttpClient4 + +# By default JMeter tries to be more lenient with RFC2616 redirects and allows +# relative paths. +# If you want to test strict conformance, set this value to true +# When the property is true, JMeter follows http://tools.ietf.org/html/rfc3986#section-5.2 +#jmeter.httpclient.strict_rfc2616=false + +# Default content-type include filter to use +#proxy.content_type_include=text/html|text/plain|text/xml +# Default content-type exclude filter to use +#proxy.content_type_exclude=image/.*|text/css|application/.* + +# Default headers to remove from Header Manager elements +# (Cookie and Authorization are always removed) +#proxy.headers.remove=If-Modified-Since,If-None-Match,Host + +# Binary content-type handling +# These content-types will be handled by saving the request in a file: +#proxy.binary.types=application/x-amf,application/x-java-serialized-object +# The files will be saved in this directory: +#proxy.binary.directory=user.dir +# The files will be created with this file filesuffix: +#proxy.binary.filesuffix=.binary + +#--------------------------------------------------------------------------- +# Test Script Recorder certificate configuration +#--------------------------------------------------------------------------- + +#proxy.cert.directory= +#proxy.cert.file=proxyserver.jks +#proxy.cert.type=JKS +#proxy.cert.keystorepass=password +#proxy.cert.keypassword=password +#proxy.cert.factory=SunX509 +# define this property if you wish to use your own keystore +#proxy.cert.alias= +# The default validity for certificates created by JMeter +#proxy.cert.validity=7 +# Use dynamic key generation (if supported by JMeter/JVM) +# If false, will revert to using a single key with no certificate +#proxy.cert.dynamic_keys=true + +#--------------------------------------------------------------------------- +# Test Script Recorder miscellaneous configuration +#--------------------------------------------------------------------------- + +# Whether to attempt disabling of samples that resulted from redirects +# where the generated samples use auto-redirection +#proxy.redirect.disabling=true + +# SSL configuration +#proxy.ssl.protocol=TLS + +#--------------------------------------------------------------------------- +# JMeter Proxy configuration +#--------------------------------------------------------------------------- +# use command-line flags for user-name and password +#http.proxyDomain=NTLM domain, if required by HTTPClient sampler + +#--------------------------------------------------------------------------- +# HTTPSampleResponse Parser configuration +#--------------------------------------------------------------------------- + +# Space-separated list of parser groups +HTTPResponse.parsers=htmlParser wmlParser +# for each parser, there should be a parser.types and a parser.className property + +#--------------------------------------------------------------------------- +# HTML Parser configuration +#--------------------------------------------------------------------------- + +# Define the HTML parser to be used. +# Default parser: +# This new parser (since 2.10) should perform better than all others +# see https://bz.apache.org/bugzilla/show_bug.cgi?id=55632 +#htmlParser.className=org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser + +# Other parsers: +# Default parser before 2.10 +#htmlParser.className=org.apache.jmeter.protocol.http.parser.HtmlParserHTMLParser +#htmlParser.className=org.apache.jmeter.protocol.http.parser.JTidyHTMLParser +# Note that Regexp extractor may detect references that have been commented out. +# In many cases it will work OK, but you should be aware that it may generate +# additional references. +#htmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser +# This parser is based on JSoup, it should be the most accurate but less performant +# than LagartoBasedHtmlParser +#htmlParser.className=org.apache.jmeter.protocol.http.parser.JsoupBasedHtmlParser + +#Used by HTTPSamplerBase to associate htmlParser with content types below +htmlParser.types=text/html application/xhtml+xml application/xml text/xml + +#--------------------------------------------------------------------------- +# WML Parser configuration +#--------------------------------------------------------------------------- + +wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser + +#Used by HTTPSamplerBase to associate wmlParser with content types below +wmlParser.types=text/vnd.wap.wml + +#--------------------------------------------------------------------------- +# Remote batching configuration +#--------------------------------------------------------------------------- +# How is Sample sender implementations configured: +# - true (default) means client configuration will be used +# - false means server configuration will be used +#sample_sender_client_configured=true + +# Remote batching support +# Since JMeter 2.9, default is MODE_STRIPPED_BATCH, which returns samples in +# batch mode (every 100 samples or every minute by default) +# Note also that MODE_STRIPPED_BATCH strips response data from SampleResult, so if you need it change to +# another mode +# Hold retains samples until end of test (may need lots of memory) +# Batch returns samples in batches +# Statistical returns sample summary statistics +# hold_samples was originally defined as a separate property, +# but can now also be defined using mode=Hold +# mode can also be the class name of an implementation of org.apache.jmeter.samplers.SampleSender +#mode=Standard +#mode=Batch +#mode=Hold +#mode=Statistical +#Set to true to key statistical samples on threadName rather than threadGroup +#key_on_threadname=false +#mode=Stripped +#mode=StrippedBatch +#mode=org.example.load.MySampleSender +# +#num_sample_threshold=100 +# Value is in milliseconds +#time_threshold=60000 +# +# Asynchronous sender; uses a queue and background worker process to return the samples +#mode=Asynch +# default queue size +#asynch.batch.queue.size=100 +# Same as Asynch but strips response data from SampleResult +#mode=StrippedAsynch +# +# DiskStore: as for Hold mode, but serialises the samples to disk, rather than saving in memory +#mode=DiskStore +# Same as DiskStore but strips response data from SampleResult +#mode=StrippedDiskStore +# Note: the mode is currently resolved on the client; +# other properties (e.g. time_threshold) are resolved on the server. + +# To set the Monitor Health Visualiser buffer size, enter the desired value +# monitor.buffer.size=800 + +#--------------------------------------------------------------------------- +# JDBC Request configuration +#--------------------------------------------------------------------------- + +# Max number of PreparedStatements per Connection for PreparedStatement cache +#jdbcsampler.maxopenpreparedstatements=100 + +# String used to indicate a null value +#jdbcsampler.nullmarker=]NULL[ + +#--------------------------------------------------------------------------- +# OS Process Sampler configuration +#--------------------------------------------------------------------------- +# Polling to see if process has finished its work, used when a timeout is configured on sampler +#os_sampler.poll_for_timeout=100 + +#--------------------------------------------------------------------------- +# TCP Sampler configuration +#--------------------------------------------------------------------------- + +# The default handler class +#tcp.handler=TCPClientImpl +# +# eolByte = byte value for end of line +# set this to a value outside the range -128 to +127 to skip eol checking +#tcp.eolByte=1000 +# +# TCP Charset, used by org.apache.jmeter.protocol.tcp.sampler.TCPClientImpl +# default to Platform defaults charset as returned by Charset.defaultCharset().name() +#tcp.charset= +# +# status.prefix and suffix = strings that enclose the status response code +#tcp.status.prefix=Status= +#tcp.status.suffix=. +# +# status.properties = property file to convert codes to messages +#tcp.status.properties=mytestfiles/tcpstatus.properties + +# The length prefix used by LengthPrefixedBinaryTCPClientImpl implementation +# defaults to 2 bytes. +#tcp.binarylength.prefix.length=2 + +#--------------------------------------------------------------------------- +# Summariser - Generate Summary Results - configuration (mainly applies to non-GUI mode) +#--------------------------------------------------------------------------- +# +# Define the following property to automatically start a summariser with that name +# (applies to non-GUI mode only) +#summariser.name=summary +# +# interval between summaries (in seconds) default 30 seconds +#summariser.interval=30 +# +# Write messages to log file +#summariser.log=true +# +# Write messages to System.out +#summariser.out=true + + +#--------------------------------------------------------------------------- +# Aggregate Report and Aggregate Graph - configuration +#--------------------------------------------------------------------------- +# +# Percentiles to display in reports +# Can be float value between 0 and 100 +# First percentile to display, defaults to 90% +#aggregate_rpt_pct1=90 +# Second percentile to display, defaults to 95% +#aggregate_rpt_pct2=95 +# Second percentile to display, defaults to 99% +#aggregate_rpt_pct3=99 + +#--------------------------------------------------------------------------- +# Aggregate Report and Aggregate Graph - configuration +#--------------------------------------------------------------------------- +# +# Backend metrics sliding window size for Percentiles, Min, Max +#backend_metrics_window=100 + +#--------------------------------------------------------------------------- +# BeanShell configuration +#--------------------------------------------------------------------------- + +# BeanShell Server properties +# +# Define the port number as non-zero to start the http server on that port +#beanshell.server.port=9000 +# The telnet server will be started on the next port + +# +# Define the server initialisation file +beanshell.server.file=../extras/startup.bsh + +# +# Define a file to be processed at startup +# This is processed using its own interpreter. +#beanshell.init.file= + +# +# Define the intialisation files for BeanShell Sampler, Function and other BeanShell elements +# N.B. Beanshell test elements do not share interpreters. +# Each element in each thread has its own interpreter. +# This is retained between samples. +#beanshell.sampler.init=BeanShellSampler.bshrc +#beanshell.function.init=BeanShellFunction.bshrc +#beanshell.assertion.init=BeanShellAssertion.bshrc +#beanshell.listener.init=etc +#beanshell.postprocessor.init=etc +#beanshell.preprocessor.init=etc +#beanshell.timer.init=etc + +# The file BeanShellListeners.bshrc contains sample definitions +# of Test and Thread Listeners. + +#--------------------------------------------------------------------------- +# MailerModel configuration +#--------------------------------------------------------------------------- + +# Number of successful samples before a message is sent +#mailer.successlimit=2 +# +# Number of failed samples before a message is sent +#mailer.failurelimit=2 + +#--------------------------------------------------------------------------- +# CSVRead configuration +#--------------------------------------------------------------------------- + +# CSVRead delimiter setting (default ",") +# Make sure that there are no trailing spaces or tabs after the delimiter +# characters, or these will be included in the list of valid delimiters +#csvread.delimiter=, +#csvread.delimiter=; +#csvread.delimiter=! +#csvread.delimiter=~ +# The following line has a tab after the = +#csvread.delimiter= + +#--------------------------------------------------------------------------- +# __time() function configuration +# +# The properties below can be used to redefine the default formats +#--------------------------------------------------------------------------- +#time.YMD=yyyyMMdd +#time.HMS=HHmmss +#time.YMDHMS=yyyyMMdd-HHmmss +#time.USER1= +#time.USER2= + +#--------------------------------------------------------------------------- +# CSV DataSet configuration +#--------------------------------------------------------------------------- + +# String to return at EOF (if recycle not used) +#csvdataset.eofstring= + +#--------------------------------------------------------------------------- +# LDAP Sampler configuration +#--------------------------------------------------------------------------- +# Maximum number of search results returned by a search that will be sorted +# to guarantee a stable ordering (if more results then this limit are retruned +# then no sorting is done). Set to 0 to turn off all sorting, in which case +# "Equals" response assertions will be very likely to fail against search results. +# +#ldapsampler.max_sorted_results=1000 + +# Number of characters to log for each of three sections (starting matching section, diff section, +# ending matching section where not all sections will appear for all diffs) diff display when an Equals +# assertion fails. So a value of 100 means a maximum of 300 characters of diff text will be displayed +# (+ a number of extra characters like "..." and "[[["/"]]]" which are used to decorate it). +#assertion.equals_section_diff_len=100 +# test written out to log to signify start/end of diff delta +#assertion.equals_diff_delta_start=[[[ +#assertion.equals_diff_delta_end=]]] + +#--------------------------------------------------------------------------- +# Miscellaneous configuration +#--------------------------------------------------------------------------- + +# If defined, then start the mirror server on the port +#mirror.server.port=8081 + +# ORO PatternCacheLRU size +#oro.patterncache.size=1000 + +#TestBeanGui +# +#propertyEditorSearchPath=null + +# Turn expert mode on/off: expert mode will show expert-mode beans and properties +#jmeter.expertMode=true + +# Maximum redirects to follow in a single sequence (default 5) +#httpsampler.max_redirects=5 +# Maximum frame/iframe nesting depth (default 5) +#httpsampler.max_frame_depth=5 +# Maximum await termination timeout (secs) when concurrent download embedded resources (default 60) +#httpsampler.await_termination_timeout=60 +# Revert to BUG 51939 behaviour (no separate container for embedded resources) by setting the following false: +#httpsampler.separate.container=true + +# If embedded resources download fails due to missing resources or other reasons, if this property is true +# Parent sample will not be marked as failed +#httpsampler.ignore_failed_embedded_resources=false + +# The encoding to be used if none is provided (default ISO-8859-1) +#sampleresult.default.encoding=ISO-8859-1 + +# Network response size calculation method +# Use real size: number of bytes for response body return by webserver +# (i.e. the network bytes received for response) +# if set to false, the (uncompressed) response data size will used (default before 2.5) +# Include headers: add the headers size in real size +#sampleresult.getbytes.body_real_size=true +#sampleresult.getbytes.headers_size=true + +# CookieManager behaviour - should cookies with null/empty values be deleted? +# Default is true. Use false to revert to original behaviour +#CookieManager.delete_null_cookies=true + +# CookieManager behaviour - should variable cookies be allowed? +# Default is true. Use false to revert to original behaviour +#CookieManager.allow_variable_cookies=true + +# CookieManager behaviour - should Cookies be stored as variables? +# Default is false +#CookieManager.save.cookies=false + +# CookieManager behaviour - prefix to add to cookie name before storing it as a variable +# Default is COOKIE_; to remove the prefix, define it as one or more spaces +#CookieManager.name.prefix= + +# CookieManager behaviour - check received cookies are valid before storing them? +# Default is true. Use false to revert to previous behaviour +#CookieManager.check.cookies=true + +# (2.0.3) JMeterThread behaviour has been changed to set the started flag before +# the controllers are initialised. This is so controllers can access variables earlier. +# In case this causes problems, the previous behaviour can be restored by uncommenting +# the following line. +#jmeterthread.startearlier=false + +# (2.2.1) JMeterThread behaviour has changed so that PostProcessors are run in forward order +# (as they appear in the test plan) rather than reverse order as previously. +# Uncomment the following line to revert to the original behaviour +#jmeterthread.reversePostProcessors=true + +# (2.2) StandardJMeterEngine behaviour has been changed to notify the listeners after +# the running version is enabled. This is so they can access variables. +# In case this causes problems, the previous behaviour can be restored by uncommenting +# the following line. +#jmeterengine.startlistenerslater=false + +# Number of milliseconds to wait for a thread to stop +#jmeterengine.threadstop.wait=5000 + +#Whether to invoke System.exit(0) in server exit code after stopping RMI +#jmeterengine.remote.system.exit=false + +# Whether to call System.exit(1) on failure to stop threads in non-GUI mode. +# This only takes effect if the test was explictly requested to stop. +# If this is disabled, it may be necessary to kill the JVM externally +#jmeterengine.stopfail.system.exit=true + +# Whether to force call System.exit(0) at end of test in non-GUI mode, even if +# there were no failures and the test was not explicitly asked to stop. +# Without this, the JVM may never exit if there are other threads spawned by +# the test which never exit. +#jmeterengine.force.system.exit=false + +# How long to pause (in ms) in the daemon thread before reporting that the JVM has failed to exit. +# If the value is <= 0, the JMeter does not start the daemon thread +#jmeter.exit.check.pause=2000 + +# If running non-GUI, then JMeter listens on the following port for a shutdown message. +# To disable, set the port to 1000 or less. +#jmeterengine.nongui.port=4445 +# +# If the initial port is busy, keep trying until this port is reached +# (to disable searching, set the value less than or equal to the .port property) +#jmeterengine.nongui.maxport=4455 + +# How often to check for shutdown during ramp-up (milliseconds) +#jmeterthread.rampup.granularity=1000 + +#Should JMeter expand the tree when loading a test plan? +# default value is false since JMeter 2.7 +#onload.expandtree=false + +#JSyntaxTextArea configuration +#jsyntaxtextarea.wrapstyleword=true +#jsyntaxtextarea.linewrap=true +#jsyntaxtextarea.codefolding=true +# Set 0 to disable undo feature in JSyntaxTextArea +#jsyntaxtextarea.maxundos=50 + +# Set this to false to disable the use of JSyntaxTextArea for the Console Logger panel +#loggerpanel.usejsyntaxtext=true + +# Maximum size of HTML page that can be displayed; default=200 * 1024 +# Set to 0 to disable the size check and display the whole response +#view.results.tree.max_size=204800 + +# Order of Renderers in View Results Tree +# Note full class names should be used for non jmeter core renderers +# For JMeter core renderers, class names start with . and are automatically +# prefixed with org.apache.jmeter.visualizers +view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsCssJQuery,.RenderAsXPath,.RenderAsHTML,.RenderAsHTMLWithEmbedded,.RenderAsDocument,.RenderAsJSON,.RenderAsXML + +# Maximum size of Document that can be parsed by Tika engine; defaut=10 * 1024 * 1024 (10MB) +# Set to 0 to disable the size check +#document.max_size=0 + +#JMS options +# Enable the following property to stop JMS Point-to-Point Sampler from using +# the properties java.naming.security.[principal|credentials] when creating the queue connection +#JMSSampler.useSecurity.properties=false + +# Set the following value to true in order to skip the delete confirmation dialogue +#confirm.delete.skip=false + +# Used by Webservice Sampler (SOAP) +# Size of Document Cache +#soap.document_cache=50 + +# Used by JSR223 elements +# Size of compiled scripts cache +#jsr223.compiled_scripts_cache_size=100 + +#--------------------------------------------------------------------------- +# Classpath configuration +#--------------------------------------------------------------------------- + +# List of paths (separated by ;) to search for additional JMeter plugin classes, +# for example new GUI elements and samplers. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib/ext directory. +# Do not use this for utility or plugin dependency jars. +#search_paths=/app1/lib;/app2/lib + +# List of paths that JMeter will search for utility and plugin dependency classes. +# Use your platform path separator to separate multiple paths. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory. +# All entries will be added to the class path of the system class loader +# and also to the path of the JMeter internal loader. +# Paths with spaces may cause problems for the JVM +#user.classpath=../classes;../lib;../app1/jar1.jar;../app2/jar2.jar + +# List of paths (separated by ;) that JMeter will search for utility +# and plugin dependency classes. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory +# or given by the user.classpath property. +# All entries will be added to the path of the JMeter internal loader only. +# For plugin dependencies using plugin_dependency_paths should be preferred over +# user.classpath. +#plugin_dependency_paths=../dependencies/lib;../app1/jar1.jar;../app2/jar2.jar + +# Classpath finder +# ================ +# The classpath finder currently needs to load every single JMeter class to find +# the classes it needs. +# For non-GUI mode, it's only necessary to scan for Function classes, but all classes +# are still loaded. +# All current Function classes include ".function." in their name, +# and none include ".gui." in the name, so the number of unwanted classes loaded can be +# reduced by checking for these. However, if a valid function class name does not match +# these restrictions, it will not be loaded. If problems are encountered, then comment +# or change the following properties: +classfinder.functions.contain=.functions. +classfinder.functions.notContain=.gui. + +#--------------------------------------------------------------------------- +# Additional property files to load +#--------------------------------------------------------------------------- + +# Should JMeter automatically load additional JMeter properties? +# File name to look for (comment to disable) +user.properties=user.properties + +# Should JMeter automatically load additional system properties? +# File name to look for (comment to disable) +system.properties=system.properties + +# Comma separated list of files that contain reference to templates and their description +# Path must be relative to jmeter root folder +#template.files=/bin/templates/templates.xml diff --git a/bin/jmeter.sh b/bin/jmeter.sh new file mode 100755 index 00000000000..c60b1bdbd22 --- /dev/null +++ b/bin/jmeter.sh @@ -0,0 +1,73 @@ +#! /bin/sh + +## 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. + +## Basic JMeter startup script for Un*x systems +## See the "jmeter" script for details of options that can be used for Sun JVMs + +## ============================================== +## Environment variables: +## JVM_ARGS - optional java args, e.g. -Dprop=val +## +## e.g. +## JVM_ARGS="-Xms512m -Xmx512m" jmeter.sh etc. +## +## ============================================== + +# Minimal version to run JMeter +MINIMAL_VERSION=1.6.0 + +# Check if Java is present and the minimal version requierement +_java=`type java | awk '{ print $ NF }'` +CURRENT_VERSION=`"$_java" -version 2>&1 | awk -F'"' '/version/ {print $2}'` +minimal_version=`echo $MINIMAL_VERSION | awk -F'.' '{ print $2 }'` +current_version=`echo $CURRENT_VERSION | awk -F'.' '{ print $2 }'` +if [ $current_version ]; then + if [ $current_version -lt $minimal_version ]; then + echo "Error: Java version is too low to run JMeter. Needs at least Java >= ${MINIMAL_VERSION}." + exit 1 + fi + else + echo "Not able to find Java executable or version. Please check your Java installation." + exit 1 +fi + +JMETER_OPTS="" +case `uname` in + Darwin*) + # Add Mac-specific property - should be ignored elsewhere (Bug 47064) + JMETER_OPTS="-Xdock:name=JMeter -Xdock:icon="`dirname $0`/../docs/images/logo.jpg" -Dapple.laf.useScreenMenuBar=true -Dapple.eawt.quitStrategy=CLOSE_ALL_WINDOWS" + ;; +esac + + +# resolve links - $0 may be a softlink (code as used by Tomcat) +# N.B. readlink would be a lot simpler but is not supported on Solaris +PRG="$0" + +while [ -h "$PRG" ]; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`/"$link" + fi +done + +PRGDIR=`dirname "$PRG"` + +java $JVM_ARGS $JMETER_OPTS -jar "$PRGDIR/ApacheJMeter.jar" "$@" diff --git a/bin/jmeterw.cmd b/bin/jmeterw.cmd new file mode 100644 index 00000000000..d9fae5614cf --- /dev/null +++ b/bin/jmeterw.cmd @@ -0,0 +1,26 @@ +@echo off +rem Run JMeter using javaw + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +set JM_START=start +set JM_LAUNCH=javaw.exe + +rem Only works in Win2K +call jmeter %* + +set JM_START= +set JM_LAUNCH= \ No newline at end of file diff --git a/bin/krb5.conf b/bin/krb5.conf new file mode 100644 index 00000000000..59f85907d99 --- /dev/null +++ b/bin/krb5.conf @@ -0,0 +1,34 @@ +; 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. + +; Sample file, you need to edit for your configuration +; see http://www.fnal.gov/docs/strongauth/krb5conf.html +; see http://web.mit.edu/kerberos/krb5-1.3/krb5-1.3.1/doc/krb5-admin.html +; see http://linux.die.net/man/5/krb5.conf + +[libdefaults] +default_realm = EXAMPLE.COM +default_tkt_enctypes = aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96 +default_tgs_enctypes = aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96 +forwardable=true + +[realms] +EXAMPLE.COM = { + kdc = kerberos.example.com:60088 +} + +[domain_realm] +example.com= EXAMPLE.COM +.example.com= EXAMPLE.COM diff --git a/bin/log4j.conf b/bin/log4j.conf new file mode 100644 index 00000000000..8dac9515db8 --- /dev/null +++ b/bin/log4j.conf @@ -0,0 +1,44 @@ + +## 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. + +############################### IMPORTANT NOTE ############################## +# JMeter does not use log4j as logging system +# This configuration will only be used by libraries that do use log4j +# or your custom code if it uses it +# For logging configuration of JMeter, it needs to be done in user.properties + +# Set appender specific options + +log4j.appender.Root_Appender=org.apache.log4j.RollingFileAppender +log4j.appender.Root_Appender.File=root.log +log4j.appender.Root_Appender.Append=true +log4j.appender.Root_Appender.MaxBackupIndex=0 +log4j.appender.Root_Appender.layout=org.apache.log4j.PatternLayout +log4j.appender.Root_Appender.layout.ConversionPattern=%-5p %d{MM/dd, hh:mm:ss} %-20.30c %m%n + +log4j.appender.File_Appender=org.apache.log4j.RollingFileAppender +log4j.appender.File_Appender.File=extra.log +log4j.appender.File_Appender.Append=false +log4j.appender.File_Appender.layout=org.apache.log4j.PatternLayout +log4j.appender.File_Appender.layout.ConversionPattern=%r %d{MM/dd, hh:mm:ss} %-5p %-50.50c %m%n + +log4j.appender.SystemOut_Appender=org.apache.log4j.ConsoleAppender +log4j.appender.SystemOut_Appender.layout=org.apache.log4j.SimpleLayout + + +# Set the appenders for the categories +log4j.rootCategory=INFO,Root_Appender +#log4j.configDebug \ No newline at end of file diff --git a/bin/logkit.xml b/bin/logkit.xml new file mode 100644 index 00000000000..16f35ffed4f --- /dev/null +++ b/bin/logkit.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + false + format.log + +AT: %{time:yyyy/MM/dd HH:mm:ss} PRI: %5.5{priority} CAT: %{category} TEXT: %{message} EX: %{throwable}\n + + + + + + false + prefix + + 1000000 + + + + + + + false + my_log + + 1000000 + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/mirror-server b/bin/mirror-server new file mode 100755 index 00000000000..1668267808c --- /dev/null +++ b/bin/mirror-server @@ -0,0 +1,19 @@ +#!/bin/sh + +exec $0.sh "$@" + +## 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. + diff --git a/bin/mirror-server.cmd b/bin/mirror-server.cmd new file mode 100644 index 00000000000..0fae3277b1a --- /dev/null +++ b/bin/mirror-server.cmd @@ -0,0 +1,30 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem Run the JMeter mirror server in non-GUI mode +rem P1 = port to use (default 8080) + +setlocal + +cd /D %~dp0 + +set CP=..\lib\ext\ApacheJMeter_http.jar;..\lib\ext\ApacheJMeter_core.jar;..\lib\jorphan.jar +set CP=%CP%;..\lib\logkit-2.0.jar;..\lib\avalon-framework-4.1.4.jar;..\lib\oro-2.0.8.jar + +java -cp %CP% org.apache.jmeter.protocol.http.control.HttpMirrorServer %1 + +pause diff --git a/bin/mirror-server.sh b/bin/mirror-server.sh new file mode 100755 index 00000000000..d8b61f5c832 --- /dev/null +++ b/bin/mirror-server.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +## 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. + +# Run the JMeter mirror server in non-GUI mode +# P1 = port to use (default 8080) + +cd `dirname $0` + +CP=../lib/ext/ApacheJMeter_http.jar:../lib/ext/ApacheJMeter_core.jar:../lib/jorphan.jar +CP=${CP}:../lib/logkit-2.0.jar:../lib/avalon-framework-4.1.4.jar:../lib/oro-2.0.8.jar + +java -cp $CP org.apache.jmeter.protocol.http.control.HttpMirrorServer $1 diff --git a/bin/saveservice.properties b/bin/saveservice.properties new file mode 100644 index 00000000000..08da47e35ec --- /dev/null +++ b/bin/saveservice.properties @@ -0,0 +1,381 @@ +#--------------------------------------------------------- +# SAVESERVICE PROPERTIES - JMETER INTERNAL USE ONLY +#--------------------------------------------------------- + +## 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. + +# This file is used to define how XStream (de-)serializes classnames +# in JMX test plan files. + +# FOR JMETER INTERNAL USE ONLY + +#--------------------------------------------------------- + +# N.B. To ensure backward compatibility, please do NOT change or delete any entries + +# New entries can be added as necessary. +# +# Note that keys starting with an underscore are special, +# and are not used as aliases. +# +# Please keep the entries in alphabetical order within the sections +# to reduce the likelihood of duplicates +# +# version number of this file (automatically generated by SVN) +_file_version=$Revision$ +# +# Conversion version (for JMX output files) +# Must be updated if the file has been changed since the previous release +# Format is: +# Save service version=JMeter version at which change occured +# 1.7 = 2.1.1 +# 1.8 = 2.1.2 +# (Some version updates were missed here...) +# 2.0 = 2.3.1 +# 2.1 = 2.3.2 +# (Some version updates were missed here...) +# 2.2 = 2.6 +# 2.3 = 2.7 +# 2.4 = 2.9 +# 2.5 = 2.10 +# 2.6 = 2.11 +# 2.7 = 2.12 +# 2.8 = 2.13 +_version=2.8 +# +# +# Character set encoding used to read and write JMeter XML files and CSV results +# +_file_encoding=UTF-8 +# +#--------------------------------------------------------- +# +# The following properties are used to create aliases +# [Must all start with capital letter] +# +AccessLogSampler=org.apache.jmeter.protocol.http.sampler.AccessLogSampler +AjpSampler=org.apache.jmeter.protocol.http.sampler.AjpSampler +AjpSamplerGui=org.apache.jmeter.protocol.http.control.gui.AjpSamplerGui +AnchorModifier=org.apache.jmeter.protocol.http.modifier.AnchorModifier +AnchorModifierGui=org.apache.jmeter.protocol.http.modifier.gui.AnchorModifierGui +Argument=org.apache.jmeter.config.Argument +Arguments=org.apache.jmeter.config.Arguments +ArgumentsPanel=org.apache.jmeter.config.gui.ArgumentsPanel +AssertionGui=org.apache.jmeter.assertions.gui.AssertionGui +AssertionVisualizer=org.apache.jmeter.visualizers.AssertionVisualizer +AuthManager=org.apache.jmeter.protocol.http.control.AuthManager +Authorization=org.apache.jmeter.protocol.http.control.Authorization +AuthPanel=org.apache.jmeter.protocol.http.gui.AuthPanel +BackendListener=org.apache.jmeter.visualizers.backend.BackendListener +BackendListenerGui=org.apache.jmeter.visualizers.backend.BackendListenerGui +BeanShellAssertion=org.apache.jmeter.assertions.BeanShellAssertion +BeanShellAssertionGui=org.apache.jmeter.assertions.gui.BeanShellAssertionGui +BeanShellListener=org.apache.jmeter.visualizers.BeanShellListener +BeanShellPostProcessor=org.apache.jmeter.extractor.BeanShellPostProcessor +BeanShellPreProcessor=org.apache.jmeter.modifiers.BeanShellPreProcessor +BeanShellSampler=org.apache.jmeter.protocol.java.sampler.BeanShellSampler +BeanShellSamplerGui=org.apache.jmeter.protocol.java.control.gui.BeanShellSamplerGui +BeanShellTimer=org.apache.jmeter.timers.BeanShellTimer +BSFAssertion=org.apache.jmeter.assertions.BSFAssertion +BSFListener=org.apache.jmeter.visualizers.BSFListener +BSFPreProcessor=org.apache.jmeter.modifiers.BSFPreProcessor +BSFPostProcessor=org.apache.jmeter.extractor.BSFPostProcessor +BSFSampler=org.apache.jmeter.protocol.java.sampler.BSFSampler +BSFSamplerGui=org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui +BSFTimer=org.apache.jmeter.timers.BSFTimer +CacheManager=org.apache.jmeter.protocol.http.control.CacheManager +CacheManagerGui=org.apache.jmeter.protocol.http.gui.CacheManagerGui +CompareAssertion=org.apache.jmeter.assertions.CompareAssertion +ComparisonVisualizer=org.apache.jmeter.visualizers.ComparisonVisualizer +ConfigTestElement=org.apache.jmeter.config.ConfigTestElement +ConstantThroughputTimer=org.apache.jmeter.timers.ConstantThroughputTimer +ConstantTimer=org.apache.jmeter.timers.ConstantTimer +ConstantTimerGui=org.apache.jmeter.timers.gui.ConstantTimerGui +Cookie=org.apache.jmeter.protocol.http.control.Cookie +CookieManager=org.apache.jmeter.protocol.http.control.CookieManager +CookiePanel=org.apache.jmeter.protocol.http.gui.CookiePanel +CounterConfig=org.apache.jmeter.modifiers.CounterConfig +CriticalSectionController=org.apache.jmeter.control.CriticalSectionController +CriticalSectionControllerGui=org.apache.jmeter.control.gui.CriticalSectionControllerGui +CounterConfigGui=org.apache.jmeter.modifiers.gui.CounterConfigGui +CSVDataSet=org.apache.jmeter.config.CSVDataSet +DebugPostProcessor=org.apache.jmeter.extractor.DebugPostProcessor +DebugSampler=org.apache.jmeter.sampler.DebugSampler +DistributionGraphVisualizer=org.apache.jmeter.visualizers.DistributionGraphVisualizer +DNSCacheManager=org.apache.jmeter.protocol.http.control.DNSCacheManager +DNSCachePanel=org.apache.jmeter.protocol.http.gui.DNSCachePanel +DurationAssertion=org.apache.jmeter.assertions.DurationAssertion +DurationAssertionGui=org.apache.jmeter.assertions.gui.DurationAssertionGui +# Should really have been defined as floatProp to agree with other properties +# No point changing this now +FloatProperty=org.apache.jmeter.testelement.property.FloatProperty +ForeachController=org.apache.jmeter.control.ForeachController +ForeachControlPanel=org.apache.jmeter.control.gui.ForeachControlPanel +FtpConfigGui=org.apache.jmeter.protocol.ftp.config.gui.FtpConfigGui +FTPSampler=org.apache.jmeter.protocol.ftp.sampler.FTPSampler +FtpTestSamplerGui=org.apache.jmeter.protocol.ftp.control.gui.FtpTestSamplerGui +GaussianRandomTimer=org.apache.jmeter.timers.GaussianRandomTimer +GaussianRandomTimerGui=org.apache.jmeter.timers.gui.GaussianRandomTimerGui +GenericController=org.apache.jmeter.control.GenericController +GraphAccumVisualizer=org.apache.jmeter.visualizers.GraphAccumVisualizer +GraphVisualizer=org.apache.jmeter.visualizers.GraphVisualizer +Header=org.apache.jmeter.protocol.http.control.Header +HeaderManager=org.apache.jmeter.protocol.http.control.HeaderManager +HeaderPanel=org.apache.jmeter.protocol.http.gui.HeaderPanel +HTMLAssertion=org.apache.jmeter.assertions.HTMLAssertion +HTMLAssertionGui=org.apache.jmeter.assertions.gui.HTMLAssertionGui +HTTPArgument=org.apache.jmeter.protocol.http.util.HTTPArgument +HTTPArgumentsPanel=org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel +HTTPFileArg=org.apache.jmeter.protocol.http.util.HTTPFileArg +HTTPFileArgs=org.apache.jmeter.protocol.http.util.HTTPFileArgs +HttpDefaultsGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui +HtmlExtractor=org.apache.jmeter.extractor.HtmlExtractor +HtmlExtractorGui=org.apache.jmeter.extractor.gui.HtmlExtractorGui +# removed in r1039684, probably not released. Not present in r322831 or since. +#HttpGenericSampler=org.apache.jmeter.protocol.http.sampler.HttpGenericSampler +# removed in r1039684, probably not released. Not present in r322831 or since. +#HttpGenericSamplerGui=org.apache.jmeter.protocol.http.control.gui.HttpGenericSamplerGui +HttpMirrorControl=org.apache.jmeter.protocol.http.control.HttpMirrorControl +HttpMirrorControlGui=org.apache.jmeter.protocol.http.control.gui.HttpMirrorControlGui +# r397955 - removed test class. Keep as commented entry for info only. +#HTTPNullSampler=org.apache.jmeter.protocol.http.sampler.HTTPNullSampler +# Merge previous 2 HTTP samplers into one +HTTPSampler_=org.apache.jmeter.protocol.http.sampler.HTTPSampler +HTTPSampler2_=org.apache.jmeter.protocol.http.sampler.HTTPSampler2 +HTTPSamplerProxy,HTTPSampler,HTTPSampler2=org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy +# Merge GUIs +HttpTestSampleGui,HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui +#HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui2 +IfController=org.apache.jmeter.control.IfController +IfControllerPanel=org.apache.jmeter.control.gui.IfControllerPanel +IncludeController=org.apache.jmeter.control.IncludeController +IncludeControllerGui=org.apache.jmeter.control.gui.IncludeControllerGui +InterleaveControl=org.apache.jmeter.control.InterleaveControl +InterleaveControlGui=org.apache.jmeter.control.gui.InterleaveControlGui +JavaConfig=org.apache.jmeter.protocol.java.config.JavaConfig +JavaConfigGui=org.apache.jmeter.protocol.java.config.gui.JavaConfigGui +JavaSampler=org.apache.jmeter.protocol.java.sampler.JavaSampler +JavaTest=org.apache.jmeter.protocol.java.test.JavaTest +JavaTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JavaTestSamplerGui +JDBCDataSource=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +JDBCPostProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPostProcessor +JDBCPreProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPreProcessor +JDBCSampler=org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler +# Renamed to JMSSamplerGui; keep original entry for backwards compatibility +JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui +JMSProperties=org.apache.jmeter.protocol.jms.sampler.JMSProperties +JMSProperty=org.apache.jmeter.protocol.jms.sampler.JMSProperty +JMSPublisherGui=org.apache.jmeter.protocol.jms.control.gui.JMSPublisherGui +JMSSampler=org.apache.jmeter.protocol.jms.sampler.JMSSampler +JMSSamplerGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui +JMSSubscriberGui=org.apache.jmeter.protocol.jms.control.gui.JMSSubscriberGui +# Removed in r545311 as Jndi no longer present; keep for compat. +JndiDefaultsGui=org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui +JSR223Assertion=org.apache.jmeter.assertions.JSR223Assertion +JSR223Listener=org.apache.jmeter.visualizers.JSR223Listener +JSR223PostProcessor=org.apache.jmeter.extractor.JSR223PostProcessor +JSR223PreProcessor=org.apache.jmeter.modifiers.JSR223PreProcessor +JSR223Sampler=org.apache.jmeter.protocol.java.sampler.JSR223Sampler +JSR223Timer=org.apache.jmeter.timers.JSR223Timer +JUnitSampler=org.apache.jmeter.protocol.java.sampler.JUnitSampler +JUnitTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JUnitTestSamplerGui +KeystoreConfig=org.apache.jmeter.config.KeystoreConfig +LDAPArgument=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgument +LDAPArguments=org.apache.jmeter.protocol.ldap.config.gui.LDAPArguments +LDAPArgumentsPanel=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgumentsPanel +LdapConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui +LdapExtConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapExtConfigGui +LDAPExtSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPExtSampler +LdapExtTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapExtTestSamplerGui +LDAPSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPSampler +LdapTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapTestSamplerGui +LogicControllerGui=org.apache.jmeter.control.gui.LogicControllerGui +LoginConfig=org.apache.jmeter.config.LoginConfig +LoginConfigGui=org.apache.jmeter.config.gui.LoginConfigGui +LoopController=org.apache.jmeter.control.LoopController +LoopControlPanel=org.apache.jmeter.control.gui.LoopControlPanel +MailerModel=org.apache.jmeter.reporters.MailerModel +MailerResultCollector=org.apache.jmeter.reporters.MailerResultCollector +MailerVisualizer=org.apache.jmeter.visualizers.MailerVisualizer +MailReaderSampler=org.apache.jmeter.protocol.mail.sampler.MailReaderSampler +MailReaderSamplerGui=org.apache.jmeter.protocol.mail.sampler.gui.MailReaderSamplerGui +MD5HexAssertion=org.apache.jmeter.assertions.MD5HexAssertion +MD5HexAssertionGUI=org.apache.jmeter.assertions.gui.MD5HexAssertionGUI +ModuleController=org.apache.jmeter.control.ModuleController +ModuleControllerGui=org.apache.jmeter.control.gui.ModuleControllerGui +MongoScriptSampler=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler +MongoSourceElement=org.apache.jmeter.protocol.mongodb.config.MongoSourceElement +MonitorHealthVisualizer=org.apache.jmeter.visualizers.MonitorHealthVisualizer +NamePanel=org.apache.jmeter.gui.NamePanel +ObsoleteGui=org.apache.jmeter.config.gui.ObsoleteGui +OnceOnlyController=org.apache.jmeter.control.OnceOnlyController +OnceOnlyControllerGui=org.apache.jmeter.control.gui.OnceOnlyControllerGui +ParamMask=org.apache.jmeter.protocol.http.modifier.ParamMask +ParamModifier=org.apache.jmeter.protocol.http.modifier.ParamModifier +ParamModifierGui=org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui +PoissonRandomTimer=org.apache.jmeter.timers.PoissonRandomTimer +PoissonRandomTimerGui=org.apache.jmeter.timers.gui.PoissonRandomTimerGui +PropertyControlGui=org.apache.jmeter.visualizers.PropertyControlGui +ProxyControl=org.apache.jmeter.protocol.http.proxy.ProxyControl +ProxyControlGui=org.apache.jmeter.protocol.http.proxy.gui.ProxyControlGui +PublisherSampler=org.apache.jmeter.protocol.jms.sampler.PublisherSampler +RandomControlGui=org.apache.jmeter.control.gui.RandomControlGui +RandomController=org.apache.jmeter.control.RandomController +RandomOrderController=org.apache.jmeter.control.RandomOrderController +RandomOrderControllerGui=org.apache.jmeter.control.gui.RandomOrderControllerGui +RandomVariableConfig=org.apache.jmeter.config.RandomVariableConfig +RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController +RecordingController=org.apache.jmeter.protocol.http.control.RecordingController +# removed in r1039684, class was deleted in r580452 +ReflectionThreadGroup=org.apache.jmeter.threads.ReflectionThreadGroup +RegexExtractor=org.apache.jmeter.extractor.RegexExtractor +RegexExtractorGui=org.apache.jmeter.extractor.gui.RegexExtractorGui +RegExUserParameters=org.apache.jmeter.protocol.http.modifier.RegExUserParameters +RegExUserParametersGui=org.apache.jmeter.protocol.http.modifier.gui.RegExUserParametersGui +RemoteListenerWrapper=org.apache.jmeter.samplers.RemoteListenerWrapper +RemoteSampleListenerWrapper=org.apache.jmeter.samplers.RemoteSampleListenerWrapper +RemoteTestListenerWrapper=org.apache.jmeter.samplers.RemoteTestListenerWrapper +RemoteThreadsListenerWrapper=org.apache.jmeter.threads.RemoteThreadsListenerWrapper +ResponseAssertion=org.apache.jmeter.assertions.ResponseAssertion +RespTimeGraphVisualizer=org.apache.jmeter.visualizers.RespTimeGraphVisualizer +ResultAction=org.apache.jmeter.reporters.ResultAction +ResultActionGui=org.apache.jmeter.reporters.gui.ResultActionGui +ResultCollector=org.apache.jmeter.reporters.ResultCollector +ResultSaver=org.apache.jmeter.reporters.ResultSaver +ResultSaverGui=org.apache.jmeter.reporters.gui.ResultSaverGui +RunTime=org.apache.jmeter.control.RunTime +RunTimeGui=org.apache.jmeter.control.gui.RunTimeGui +SampleSaveConfiguration=org.apache.jmeter.samplers.SampleSaveConfiguration +SimpleConfigGui=org.apache.jmeter.config.gui.SimpleConfigGui +SimpleDataWriter=org.apache.jmeter.visualizers.SimpleDataWriter +SizeAssertion=org.apache.jmeter.assertions.SizeAssertion +SizeAssertionGui=org.apache.jmeter.assertions.gui.SizeAssertionGui +SMIMEAssertion=org.apache.jmeter.assertions.SMIMEAssertionTestElement +SMIMEAssertionGui=org.apache.jmeter.assertions.gui.SMIMEAssertionGui +SmtpSampler=org.apache.jmeter.protocol.smtp.sampler.SmtpSampler +SmtpSamplerGui=org.apache.jmeter.protocol.smtp.sampler.gui.SmtpSamplerGui +SoapSampler=org.apache.jmeter.protocol.http.sampler.SoapSampler +SoapSamplerGui=org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui +SplineVisualizer=org.apache.jmeter.visualizers.SplineVisualizer +# Originally deleted in r397955 as class is obsolete; needed for compat. +SqlConfigGui=org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui +StatGraphVisualizer=org.apache.jmeter.visualizers.StatGraphVisualizer +StatVisualizer=org.apache.jmeter.visualizers.StatVisualizer +SubscriberSampler=org.apache.jmeter.protocol.jms.sampler.SubscriberSampler +SubstitutionElement=org.apache.jmeter.assertions.SubstitutionElement +Summariser=org.apache.jmeter.reporters.Summariser +SummariserGui=org.apache.jmeter.reporters.gui.SummariserGui +SummaryReport=org.apache.jmeter.visualizers.SummaryReport +SwitchController=org.apache.jmeter.control.SwitchController +SwitchControllerGui=org.apache.jmeter.control.gui.SwitchControllerGui +SyncTimer=org.apache.jmeter.timers.SyncTimer +SystemSampler=org.apache.jmeter.protocol.system.SystemSampler +SystemSamplerGui=org.apache.jmeter.protocol.system.gui.SystemSamplerGui +TableVisualizer=org.apache.jmeter.visualizers.TableVisualizer +TCPConfigGui=org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui +TCPSampler=org.apache.jmeter.protocol.tcp.sampler.TCPSampler +TCPSamplerGui=org.apache.jmeter.protocol.tcp.control.gui.TCPSamplerGui +TestAction=org.apache.jmeter.sampler.TestAction +TestActionGui=org.apache.jmeter.sampler.gui.TestActionGui +TestBeanGUI=org.apache.jmeter.testbeans.gui.TestBeanGUI +TestFragmentController=org.apache.jmeter.control.TestFragmentController +TestFragmentControllerGui=org.apache.jmeter.control.gui.TestFragmentControllerGui +TestPlan=org.apache.jmeter.testelement.TestPlan +TestPlanGui=org.apache.jmeter.control.gui.TestPlanGui +ThreadGroup=org.apache.jmeter.threads.ThreadGroup +ThreadGroupGui=org.apache.jmeter.threads.gui.ThreadGroupGui +PostThreadGroup=org.apache.jmeter.threads.PostThreadGroup +PostThreadGroupGui=org.apache.jmeter.threads.gui.PostThreadGroupGui +SetupThreadGroup=org.apache.jmeter.threads.SetupThreadGroup +SetupThreadGroupGui=org.apache.jmeter.threads.gui.SetupThreadGroupGui +ThroughputController=org.apache.jmeter.control.ThroughputController +ThroughputControllerGui=org.apache.jmeter.control.gui.ThroughputControllerGui +TransactionController=org.apache.jmeter.control.TransactionController +TransactionControllerGui=org.apache.jmeter.control.gui.TransactionControllerGui +TransactionSampler=org.apache.jmeter.control.TransactionSampler +UniformRandomTimer=org.apache.jmeter.timers.UniformRandomTimer +UniformRandomTimerGui=org.apache.jmeter.timers.gui.UniformRandomTimerGui +URLRewritingModifier=org.apache.jmeter.protocol.http.modifier.URLRewritingModifier +URLRewritingModifierGui=org.apache.jmeter.protocol.http.modifier.gui.URLRewritingModifierGui +UserParameterModifier=org.apache.jmeter.protocol.http.modifier.UserParameterModifier +UserParameterModifierGui=org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui +UserParameters=org.apache.jmeter.modifiers.UserParameters +UserParametersGui=org.apache.jmeter.modifiers.gui.UserParametersGui +ViewResultsFullVisualizer=org.apache.jmeter.visualizers.ViewResultsFullVisualizer +WebServiceSampler=org.apache.jmeter.protocol.http.sampler.WebServiceSampler +WebServiceSamplerGui=org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui +WhileController=org.apache.jmeter.control.WhileController +WhileControllerGui=org.apache.jmeter.control.gui.WhileControllerGui +WorkBench=org.apache.jmeter.testelement.WorkBench +WorkBenchGui=org.apache.jmeter.control.gui.WorkBenchGui +XMLAssertion=org.apache.jmeter.assertions.XMLAssertion +XMLAssertionGui=org.apache.jmeter.assertions.gui.XMLAssertionGui +XMLSchemaAssertion=org.apache.jmeter.assertions.XMLSchemaAssertion +XMLSchemaAssertionGUI=org.apache.jmeter.assertions.gui.XMLSchemaAssertionGUI +XPathAssertion=org.apache.jmeter.assertions.XPathAssertion +XPathAssertionGui=org.apache.jmeter.assertions.gui.XPathAssertionGui +XPathExtractor=org.apache.jmeter.extractor.XPathExtractor +XPathExtractorGui=org.apache.jmeter.extractor.gui.XPathExtractorGui +# +# Properties - all start with lower case letter and end with Prop +# +boolProp=org.apache.jmeter.testelement.property.BooleanProperty +collectionProp=org.apache.jmeter.testelement.property.CollectionProperty +doubleProp=org.apache.jmeter.testelement.property.DoubleProperty +elementProp=org.apache.jmeter.testelement.property.TestElementProperty +# see above - already defined as FloatProperty +#floatProp=org.apache.jmeter.testelement.property.FloatProperty +intProp=org.apache.jmeter.testelement.property.IntegerProperty +longProp=org.apache.jmeter.testelement.property.LongProperty +mapProp=org.apache.jmeter.testelement.property.MapProperty +objProp=org.apache.jmeter.testelement.property.ObjectProperty +stringProp=org.apache.jmeter.testelement.property.StringProperty +# +# Other - must start with a lower case letter (and not end with Prop) +# (otherwise they could clash with the initial set of aliases) +# +hashTree=org.apache.jorphan.collections.ListedHashTree +jmeterTestPlan=org.apache.jmeter.save.ScriptWrapper +sample=org.apache.jmeter.samplers.SampleResult +httpSample=org.apache.jmeter.protocol.http.sampler.HTTPSampleResult +statSample=org.apache.jmeter.samplers.StatisticalSampleResult +testResults=org.apache.jmeter.save.TestResultWrapper +assertionResult=org.apache.jmeter.assertions.AssertionResult +monitorStats=org.apache.jmeter.visualizers.MonitorStats +sampleEvent=org.apache.jmeter.samplers.SampleEvent +# +# Converters to register. Must start line with '_' +# If the converter is a collection of subitems, set equal to "collection" +# If the converter needs to know the class mappings but is not a collection of +# subitems, set it equal to "mapping" +_org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseConverter=collection +_org.apache.jmeter.protocol.http.util.HTTPResultConverter=collection +_org.apache.jmeter.save.converters.BooleanPropertyConverter= +_org.apache.jmeter.save.converters.IntegerPropertyConverter= +_org.apache.jmeter.save.converters.LongPropertyConverter= +_org.apache.jmeter.save.converters.MultiPropertyConverter=collection +_org.apache.jmeter.save.converters.SampleEventConverter= +_org.apache.jmeter.save.converters.SampleResultConverter=collection +_org.apache.jmeter.save.converters.SampleSaveConfigurationConverter=collection +_org.apache.jmeter.save.converters.StringPropertyConverter= +_org.apache.jmeter.save.converters.HashTreeConverter=collection +_org.apache.jmeter.save.converters.TestElementConverter=collection +_org.apache.jmeter.save.converters.TestElementPropertyConverter=collection +_org.apache.jmeter.save.converters.TestResultWrapperConverter=collection +_org.apache.jmeter.save.ScriptWrapperConverter=mapping +# +# Remember to update the _version entry +# \ No newline at end of file diff --git a/bin/shutdown.cmd b/bin/shutdown.cmd new file mode 100644 index 00000000000..4cb874646a6 --- /dev/null +++ b/bin/shutdown.cmd @@ -0,0 +1,23 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem Run the Shutdown client to stop a non-GUI instance gracefully + +rem P1 = command port for JMeter instance (defaults to 4445) + +java -cp %~dp0ApacheJMeter.jar org.apache.jmeter.util.ShutdownClient Shutdown %* +pause \ No newline at end of file diff --git a/bin/shutdown.sh b/bin/shutdown.sh new file mode 100755 index 00000000000..7ef54ae2b30 --- /dev/null +++ b/bin/shutdown.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +## 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. + +# Run the Shutdown client to stop a non-GUI instance gracefully + +# P1 = command port for JMeter instance (defaults to 4445) + +DIRNAME=`dirname $0` + +java -cp ${DIRNAME}/ApacheJMeter.jar org.apache.jmeter.util.ShutdownClient Shutdown "$@" diff --git a/bin/stoptest.cmd b/bin/stoptest.cmd new file mode 100644 index 00000000000..6e06ab88290 --- /dev/null +++ b/bin/stoptest.cmd @@ -0,0 +1,23 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem Run the Shutdown client to stop a non-GUI instance abruptly + +rem P1 = command port for JMeter instance (defaults to 4445) + +java -cp %~dp0ApacheJMeter.jar org.apache.jmeter.util.ShutdownClient StopTestNow %* +pause \ No newline at end of file diff --git a/bin/stoptest.sh b/bin/stoptest.sh new file mode 100755 index 00000000000..a96c798a13b --- /dev/null +++ b/bin/stoptest.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +## 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. + +# Run the Shutdown client to stop a non-GUI instance abruptly + +# P1 = command port for JMeter instance (defaults to 4445) + +DIRNAME=`dirname $0` + +java -cp ${DIRNAME}/ApacheJMeter.jar org.apache.jmeter.util.ShutdownClient StopTestNow "$@" diff --git a/bin/system.properties b/bin/system.properties new file mode 100644 index 00000000000..da87b3451b4 --- /dev/null +++ b/bin/system.properties @@ -0,0 +1,113 @@ +# Sample system.properties file +# +## 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. + +# Commons Logging properties +# Used by HttpComponents 4.x, see: +# http://hc.apache.org/httpcomponents-client-4.3.x/logging.html +# +# By default, Commons Logging is configured by JMeter to use the same logging system +# as the main JMeter code; to configure it please see jmeter.properties. +# +# Uncomment to enable debugging of Commons Logging setup; may be useful if +# implementation cannot be instantiated: +#org.apache.commons.logging.diagnostics.dest=STDERR +# +# Uncomment to enable Commons Logging to use standard output +#org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog +#org.apache.commons.logging.simplelog.showdatetime=true +# +# Uncomment the following two lines to generate basic debug logging for HC4.x +#org.apache.commons.logging.simplelog.log.org.apache.http=DEBUG +#org.apache.commons.logging.simplelog.log.org.apache.http.wire=ERROR + +# Java networking-related properties +# +# For details of Oracle Java network properties, see for example: +# http://download.oracle.com/javase/1.5.0/docs/guide/net/properties.html +# +#java.net.preferIPv4Stack=false +#java.net.preferIPv6Addresses=false +#networkaddress.cache.ttl=-1 +#networkaddress.cache.negative.ttl=10 + +# +# +# SSL properties (moved from jmeter.properties) +# +# See http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#Customization +# for information on the javax.ssl system properties + +# Truststore properties (trusted certificates) +#javax.net.ssl.trustStore=/path/to/[jsse]cacerts +#javax.net.ssl.trustStorePassword +#javax.net.ssl.trustStoreProvider +#javax.net.ssl.trustStoreType [default = KeyStore.getDefaultType()] + +# Keystore properties (client certificates) +# Location +#javax.net.ssl.keyStore=.keystore +# +#The password to your keystore +#javax.net.ssl.keyStorePassword=changeit +# +#javax.net.ssl.keyStoreProvider +#javax.net.ssl.keyStoreType [default = KeyStore.getDefaultType()] + +# SSL debugging: +# See http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#Debug +# +# javax.net.debug=help - generates the list below: +#all turn on all debugging +#ssl turn on ssl debugging +# +#The following can be used with ssl: +# record enable per-record tracing +# handshake print each handshake message +# keygen print key generation data +# session print session activity +# defaultctx print default SSL initialization +# sslctx print SSLContext tracing +# sessioncache print session cache tracing +# keymanager print key manager tracing +# trustmanager print trust manager tracing +# +# handshake debugging can be widened with: +# data hex dump of each handshake message +# verbose verbose handshake message printing +# +# record debugging can be widened with: +# plaintext hex dump of record plaintext +# +# Examples: +#javax.net.debug=ssl +#javax.net.debug=sslctx,session,sessioncache +# +# +# We enable the following property to allow headers such as "Host" to be passed through. +# See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6996110 +sun.net.http.allowRestrictedHeaders=true + +#Uncomment for Kerberos authentication and edit the 2 config files to match your domains +#With the following configuration krb5.conf and jaas.conf must be located in bin folder +#You can modify these file paths to use absolute location +#java.security.krb5.conf=krb5.conf +#java.security.auth.login.config=jaas.conf + +# Location of keytool application +# This property can be defined if JMeter cannot find the application automatically +# It should not be necessary in most cases. +#keytool.directory=/bin diff --git a/bin/templates/BeanShellSampler.jmx b/bin/templates/BeanShellSampler.jmx new file mode 100644 index 00000000000..ad5a61a2a08 --- /dev/null +++ b/bin/templates/BeanShellSampler.jmx @@ -0,0 +1,27 @@ + + + + + // A simple script +log.info("Example"); + +type = bsh.args[0]; + +log.info(type); + +if ("1".equals(type)) { + ResponseCode = 2 * 100; + ResponseMessage = bsh.args[1]; +} else { + ResponseCode = 500; + ResponseMessage = "Invalid Type: " + type; + IsSuccess = false; +} +return Parameters; + + 2 OK + false + + + + diff --git a/bin/templates/build-adv-web-test-plan.jmx b/bin/templates/build-adv-web-test-plan.jmx new file mode 100644 index 00000000000..096e4c0d41f --- /dev/null +++ b/bin/templates/build-adv-web-test-plan.jmx @@ -0,0 +1,412 @@ + + + + + + false + false + + + + + + + + + + resources_folder + ${__P(resources_folder, CHANGE_FOLDER)} + Change this value to folder containing your CSV files + = + + + host + jmeter.apache.org + = + + + bugzilla_host + bz.apache.org + = + + + + + + + true + org.apache.jmeter.protocol.http.control.HC4CookieHandler + + + + + + User-Agent + Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:22.0) Gecko/20100101 Firefox/22.0 + + + Connection + keep-alive + + + Accept-Language + fr,en;q=0.8,fr-fr;q=0.5,en-us;q=0.3 + + + Accept-Encoding + gzip, deflate + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + + + + + + CSV file that must contains rows with 2 columns separated by comma, first column will be mapped to login, second one to password + , + UTF-8 + ${resources_folder}/login.csv + false + true + All threads + false + login,password + + + + continue + + false + 2 + + 1 + 5 + 1373789594000 + 1373789594000 + false + + + + + + + + + ${host} + + + + + + + Shows how to set defaults on page + 4 + + + + + 200 + + Check Response code for all children samplers of JMeter Users Thread Group + Assertion.response_code + false + 8 + + + + + + + + + + + + + / + GET + true + false + true + false + false + + + + + + <title>Apache JMeter - Apache JMeter&trade;</title> + + We check page contains some text + Assertion.response_data + false + 16 + + + + + + + + + + + + + + /changes.html + GET + true + false + true + false + false + + + + + 5000 + 500.0 + + + + + <title>Apache JMeter - Changes</title> + + We check page contains some text + Assertion.response_data + false + 16 + + + + Shows how to extract bug id from a link in response + false + bugId + https://bz.apache.org/bugzilla/show_bug.cgi\?id=([^"]+?)" + $1$ + nv_bugId + 0 + + + + + + + + ${bugzilla_host} + + + + https + + /bugzilla/show_bug.cgi?id=${bugId} + GET + true + false + true + false + false + + We use bugId variable extracted in Changes by randomBugIdExtractor + + + + 5000 + 500.0 + + + + + This is <b>ASF Bugzilla</b>: the Apache Software Foundation bug system + + We check page contains some text + Assertion.response_data + false + 16 + + + + + + + + true + ${login} + = + true + username + + + true + ${password} + = + true + password + + + + www.example.com + + + + + + /loginform.html + POST + true + false + true + false + false + + We use here data from loginData CSV DataSet + + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + Use only during debug of script + + + + + + diff --git a/bin/templates/build-ftp-test-plan.jmx b/bin/templates/build-ftp-test-plan.jmx new file mode 100644 index 00000000000..9050b75cae2 --- /dev/null +++ b/bin/templates/build-ftp-test-plan.jmx @@ -0,0 +1,103 @@ + + + + + + false + false + + + + + + + + continue + + false + 2 + + 4 + 1 + 1373791853000 + 1373791853000 + false + + + + + + ftp.domain.com + + + + + false + false + false + + + + + + /directory/file1.txt + + + false + false + false + anonymous + anonymous@test.com + + + + + + /directory/file2.txt + + + false + false + false + anonymous + anonymous@test.com + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + true + + + + + + + + + diff --git a/bin/templates/build-ldap-ext-test-plan.jmx b/bin/templates/build-ldap-ext-test-plan.jmx new file mode 100644 index 00000000000..2603177fe56 --- /dev/null +++ b/bin/templates/build-ldap-ext-test-plan.jmx @@ -0,0 +1,373 @@ + + + + + + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1375035819000 + 1375035819000 + false + + + + + + + + + 2 + + + + false + false + + false + false + + + + + + + unbind + + + + true + 1 + + + + ldap.test.com + 636 + dc=test,dc=com + 2 + + + + false + false + 5000 + false + true + cn=adminldap,dc=test,dc=com + password + + + + + bind + + + + + + + 2 + 0 + 0 + cn;dn;objectClass + false + false + + false + false + + + + + + + search + ou=Users + (sn=Doe) + + + + + + + 2 + + + + false + false + + false + false + + + cn=jdoe,ou=Users + mail=jdoe@test.com + + + compare + + + + ldap.test.com + 636 + dc=test,dc=com + 2 + + + + false + false + 5000 + false + true + cn=jdoe,ou=Users,dc=test,dc=com + password + + + + + sbind + + + + + + + 2 + + + + false + false + + false + false + + + + + + + add + cn=Little John Doe,ou=Users + + + + sn + Doe + = + + + cn + Little John Doe + = + + + objectclass + top + = + + + objectclass + person + = + + + objectclass + organizationalPerson + = + + + objectclass + inetOrgPerson + = + + + userpassword + password + = + + + displayname + Little John + = + + + givenname + Doe + = + + + description + Test + = + + + + + + + + + + 2 + + + + false + false + + false + false + + + + + + + modify + cn=Little John Doe,ou=Users + + + + mail + littlejohndoe@test.com + replace + = + + + telephoneNumber + +155669988 + add + = + + + description + Test + delete + = + + + + + + + + + + 2 + + + + false + false + + false + false + + + + + cn=Little John Doe,ou=Users + cn=John Junior Doe,ou=Users + rename + + + + + + + 2 + + + + false + false + + false + false + + + + + + + delete + cn=John Junior Doe,ou=Users + + + + + + + 2 + + + + false + false + + false + false + + + + + + + unbind + + + + + false + + saveConfig + + + true + true + true + + true + false + true + false + false + true + true + false + false + false + true + false + false + false + false + 0 + true + true + true + + + + + + + + + diff --git a/bin/templates/build-ldap-test-plan.jmx b/bin/templates/build-ldap-test-plan.jmx new file mode 100644 index 00000000000..5ef9dcf91e0 --- /dev/null +++ b/bin/templates/build-ldap-test-plan.jmx @@ -0,0 +1,141 @@ + + + + + + false + false + + + + + + + + continue + + false + 4 + + 4 + 1 + 1373790870000 + 1373790870000 + false + + + + + + cn=LDAP User,dc=test,dc=com + password + + + + ldap.test.com + 389 + dc=test,dc=com + false + add + + + + + + + + + + + false + add + + + + + + + + + + + + + false + search + + + + + + + + + + + false + modify + + + + + + + + + + + + + false + delete + + + + + + + + successful + + Assertion.response_data + false + 16 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/bin/templates/build-web-test-plan.jmx b/bin/templates/build-web-test-plan.jmx new file mode 100644 index 00000000000..da7bf7f3401 --- /dev/null +++ b/bin/templates/build-web-test-plan.jmx @@ -0,0 +1,153 @@ + + + + + + false + false + + + + + + + + continue + + false + 2 + + 5 + 5 + 1373789594000 + 1373789594000 + false + + + + + + + + + jmeter.apache.org + + + + + + + 4 + + + + + + + + + + + + + / + GET + true + false + true + false + false + + + + + + + + + + + + + + /changes.html + GET + true + false + true + false + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + + + + + + + + + + true + johndoe + = + true + username + + + true + secret + = + true + password + + + + www.example.com + + + + + + /loginform.html + POST + true + false + true + false + false + + + + + + + diff --git a/bin/templates/build-webservice-test-plan.jmx b/bin/templates/build-webservice-test-plan.jmx new file mode 100644 index 00000000000..b78dc077479 --- /dev/null +++ b/bin/templates/build-webservice-test-plan.jmx @@ -0,0 +1,184 @@ + + + + + + false + false + + + + + + + + + + host + wsf.cdyne.com + = + Host of Webservice + + + + + + + + + ${host} + + + + + + + 4 + + + + continue + + false + 2 + + 5 + 5 + 1375525852000 + 1375525852000 + false + + + + + + true + + + + false + <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Body> + <GetCityForecastByZIP xmlns="http://ws.cdyne.com/WeatherWS/"> + <ZIP>60601</ZIP> + </GetCityForecastByZIP> + </soap:Body> +</soap:Envelope> + = + + + + + + + + + + /WeatherWS/Weather.asmx + POST + true + false + true + false + false + + + + + + + Content-Type + text/xml; charset=utf-8 + + + SOAPAction + "http://ws.cdyne.com/WeatherWS/GetCityForecastByZIP" + + + + + + + </GetCityForecastByZIPResult> + + Verify content in response + Assertion.response_data + false + 16 + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + + + + + + diff --git a/bin/templates/jdbc.jmx b/bin/templates/jdbc.jmx new file mode 100644 index 00000000000..0ca8a700b58 --- /dev/null +++ b/bin/templates/jdbc.jmx @@ -0,0 +1,93 @@ + + + + + + false + false + + + + + + + + true + Select 1 + 5000 + jdbcConfig + jdbc:postgresql://hostname:port/dbname + org.postgresql.Driver + true + password + 10 + 10000 + DEFAULT + 60000 + username + + + + continue + + false + 1 + + 1 + 1 + 1370729701000 + 1370729701000 + false + + + + + + jdbcConfig + select column1 from table + + + Select Statement + + col1 + + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + + + + + + diff --git a/bin/templates/mongodb.jmx b/bin/templates/mongodb.jmx new file mode 100644 index 00000000000..bc52858c1d8 --- /dev/null +++ b/bin/templates/mongodb.jmx @@ -0,0 +1,194 @@ + + + + + Shows how to setup a MongoDB Test + false + false + + + + + + + + 127.0.0.1 + db + false + 50 + 0 + 0 + 120000 + 0 + false + 5 + false + false + false + 0 + 0 + false + Configures connection to MongoDB + + + + continue + + false + -1 + + 1 + 30 + 1367357168000 + 1367357168000 + false + 300 + 10 + + + + groovy + + + insert1 + import com.mongodb.DB; +import org.apache.jmeter.protocol.mongodb.config.MongoDBHolder; +import com.mongodb.WriteResult; +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.WriteConcern; +import com.mongodb.WriteResult; + +// Get DB +com.mongodb.DB db = org.apache.jmeter.protocol.mongodb.config.MongoDBHolder.getDBFromSource("db", "test"); + +// Get collection to insert +DBCollection coll = db.getCollection("testCollection"); +BasicDBObject doc = new BasicDBObject("name", "MongoDB"). + append("type", "database"). + append("count", 1). + append("info", new BasicDBObject("x", 203).append("y", 102)); + +// Insert object +WriteResult wr = coll.insert(doc, WriteConcern.ACKNOWLEDGED); + +// Set response data +SampleResult.setResponseData(""+wr.toString(),"UTF-8"); + + + + + "err" : null + + Assertion.response_data + false + 2 + + + + + groovy + + + count1 + import com.mongodb.DB; +import org.apache.jmeter.protocol.mongodb.config.MongoDBHolder; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.DBCollection; + +DB db = MongoDBHolder.getDBFromSource("db", "test"); + +DBCollection coll = db.getCollection("testCollection"); +int size = coll.count(); +SampleResult.setResponseData(""+size,"UTF-8"); + + + + + \d+ + + Assertion.response_data + false + 1 + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + + Remove for Load Test + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + + Remove for Load Test + + + + + + + diff --git a/bin/templates/recording.jmx b/bin/templates/recording.jmx new file mode 100644 index 00000000000..6bb61bbdfc7 --- /dev/null +++ b/bin/templates/recording.jmx @@ -0,0 +1,154 @@ + + + + + + false + false + + + + + + + + + + + + + + + + + + + + + + 4 + + + + + true + + + + continue + + false + 1 + + 1 + 1 + 1370726934000 + 1370726934000 + false + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + + + + + + true + + + + 8888 + + (?i).*\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff)[\?;].* + (?i).*\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff) + + + true + 4 + false + + false + true + true + false + true + + + true + + + + false + + saveConfig + + + true + true + true + + true + true + true + false + false + true + false + false + false + false + true + false + false + true + true + 0 + true + true + true + true + + + + + + + + + diff --git a/bin/templates/templates.dtd b/bin/templates/templates.dtd new file mode 100644 index 00000000000..221593377b4 --- /dev/null +++ b/bin/templates/templates.dtd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/bin/templates/templates.xml b/bin/templates/templates.xml new file mode 100644 index 00000000000..c1b4db6fc6e --- /dev/null +++ b/bin/templates/templates.xml @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/AssertionTestPlan.jmx b/bin/testfiles/AssertionTestPlan.jmx new file mode 100644 index 00000000000..1fe871fa8b0 --- /dev/null +++ b/bin/testfiles/AssertionTestPlan.jmx @@ -0,0 +1,121 @@ + + + + + + + + + false + false + + + + + 0 + + + 1 + false + + false + 1 + + 0 + continue + 0 + + + + + + + / + GET + false + http + false + false + + jakarta.apache.org + false + false + Java + + + + + </html> + + Assertion.response_data + false + 2 + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + assertion.dat + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + + + diff --git a/bin/testfiles/AuthManagerTestPlan.jmx b/bin/testfiles/AuthManagerTestPlan.jmx new file mode 100644 index 00000000000..c2c874a8261 --- /dev/null +++ b/bin/testfiles/AuthManagerTestPlan.jmx @@ -0,0 +1,136 @@ + + + + + + + + + false + false + + + + + 0 + + + 1 + false + + false + 1 + + 0 + continue + 0 + + + + + + http://localhost/secret + kevin + spot + + + + + + + + / + localhost + + + + + + + + + + + + /secret/index.html + GET + false + http + false + false + + + false + false + Java + + + + + + + /secret/index2.html + GET + false + http + false + false + + + false + false + Java + + + + + + + /index.html + GET + false + http + false + false + + + false + false + Java + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + auth-manager.dat + + + + + + diff --git a/bin/testfiles/BatchTestLocal.csv b/bin/testfiles/BatchTestLocal.csv new file mode 100644 index 00000000000..fd67c9a4aa7 --- /dev/null +++ b/bin/testfiles/BatchTestLocal.csv @@ -0,0 +1,134 @@ +label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,grpThreads,allThreads,URL,Filename,SampleCount,ErrorCount +Setup 1,200,OK,Setup Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Loop5 C1=1 C2=1 C3=1,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=2 C3=2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +If Test C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=4 C3=4,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=5 C3=5,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Loop5 C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +If Test C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=2 C3=7,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=3 C3=8,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +If Test C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=5 C3=10,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Loop5 C1=1 C2=1 C3=11,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +If Test C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=3 C3=13,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=4 C3=14,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +If Test C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Loop5 C1=2 C2=1 C3=16,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=2 C3=17,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +If Test C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=4 C3=19,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=5 C3=20,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +"HTTP ""Request,",200,OK,Thread Group 2-1,text,true,,132845,1,1,file:testfiles/BatchTestLocal.jmx,,1,0 +1 1,200,OK,Thread Group 2-1,,true,,0,1,1,null,,1,0 +2 2,200,OK,Thread Group 2-1,,true,,0,1,1,null,,1,0 +3 3,200,OK,Thread Group 2-1,,true,,0,1,1,null,,1,0 +0 4,200,OK,Thread Group 2-1,,true,,0,1,1,null,,1,0 +NAME USER1,200,OK,Thread Group 3-1,,true,,0,1,1,null,,1,0 +TG1 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG OO = 1,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG Loop =3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG2 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG1 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG Loop =3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG2 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG1 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG Loop =3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG2 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +CSV_VAR=2,200,OK,CSV Test 6-1,,true,,0,1,1,null,,1,0 +CSV_VAR=3,200,OK,CSV Test 6-1,,true,,0,1,1,null,,1,0 +CSV_VAR=1,200,OK,CSV Test 6-1,,true,,0,1,1,null,,1,0 +CSV_VAR=2,200,OK,CSV Test 6-1,,true,,0,1,1,null,,1,0 +BSH Counter: 1,200,OK,Bug 48943 7-1,text,true,,0,1,1,null,,1,0 +BSH Counter: 2,200,OK,Bug 48943 7-1,text,true,,0,1,1,null,,1,0 +BSH Counter: 3,200,OK,Bug 48943 7-1,text,true,,0,1,1,null,,1,0 +BSF Sampler,200,OK,BSF/JSR 8-1,text,true,,3,1,1,null,,1,0 +JSR223 Sampler,200,OK,BSF/JSR 8-1,text,true,,3,1,1,null,,1,0 +Post 1,200,OK,Post Thread Group 1-1,,true,,0,1,1,null,,1,0 diff --git a/bin/testfiles/BatchTestLocal.jmx b/bin/testfiles/BatchTestLocal.jmx new file mode 100644 index 00000000000..ec786aa2a58 --- /dev/null +++ b/bin/testfiles/BatchTestLocal.jmx @@ -0,0 +1,2329 @@ + + + + + + + + + true + false + Batch Test using only local resources. +**N.B. If this file is updated, then the expected test data files need to be updated with the new length of this file** + + + + 1172922900000 + + + 2 + false + + false + 2 + + 1172922900000 + continue + 40 + N.B. The ramp-up period is set so that the first thread will finish before the second. +This is to ensure the test output is predictable, whilst still allowing testing of unshared counter etc + + + + 1 + + 1 + C1 + + true + Should increment for each TG loop + + + + true + 3 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Java 1 C1=${C1} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + SamplerData + = + + + ResultData + ResultData + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + true + 5 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Loop5 C1=${C1} C2=${C2} C3=${C3} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + ${__StringFromFile(testfiles/BatchTestLocal.txt)} > 2 + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + If Test C1=${C1} C2=${C2} C3=${C3} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + true + 3 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Loop3 C1=${C1} C2=${C2} C3=${C3} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + WorkBench + Test Plan + Thread Group + Simple Controller for Module Controller + + + + + + + 1 + 5 + 1 + C2 + + true + + + + 1 + + 1 + C3 + + false + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + true + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + true + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocal.xml + + + + false + + saveConfig + + + false + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + true + 2 + + + + ${JMeterThread.last_sample_ok} + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + BAD + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + ${JMeterThread.last_sample_ok} + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + BAD + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + for Module Controller + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + ${__P(module)} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + true + Test zero loops + 0 + + + + Just in case the loop accidentally runs + 2 + 2 + + + + + + + Runs after first thread group. + + false + 1 + + 1 + 1 + 1194880755000 + 1194880755000 + false + continue + + + + + + + + + + + + + file + + testfiles/BatchTestLocal.jmx + GET + false + true + true + false + Java + false + + N.B. The file that is loaded must have a fixed size, so HTML won't do as it will be different on Unix and Windows. +We use this file, which has eol=LF - but of course any changes need to be reflected in the expected test data. + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + true + true + true + false + true + true + false + true + true + false + true + 0 + true + true + true + true + + + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocal.xml + + + + true + 4 + + + + ${__counter(TRUE,COUNT)} + + + + 0 + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 0 ${COUNT} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 1 ${COUNT} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2 ${COUNT} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 3 ${COUNT} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + false + 1 + + 1 + 1 + 1226668173000 + 1226668173000 + false + continue + + + + + + + NAME + + + + USER1 + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + NAME ${NAME} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + true + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + + + + + + + + Once Only Controller tests + + false + 3 + + 1 + 1 + 1242238972000 + 1242238972000 + false + continue + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + true + 3 + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + Does not work currently - should only run once, as SC should be ignored + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocal.xml + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + 0 + + 1 + 1 + 1242238814000 + 1242238814000 + false + continue + + + + + + Just in case Thread Group runs + 2 + 2 + + + + + + continue + + false + 1 + + 1 + 1 + 1296159999000 + 1296159999000 + false + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocal.xml + + + + + continue + + false + 1 + + 1 + 1 + 1296160031000 + 1296160031000 + false + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocal.xml + + + + + , + + BatchTestLocal.txt + false + true + All threads + false + CSV_VAR + + + + continue + + false + 4 + + 1 + 1 + 1296160584000 + 1296160584000 + false + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocal.xml + + + + + continue + + false + 3 + + 1 + 1 + 1316002322000 + 1316002322000 + false + + + + + + + + SampleResult.setSampleLabel("BSH Counter: ${__counter(FALSE)}"); + + + false + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocal.xml + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + continue + + false + 1 + + 1 + 1 + 1352333174000 + 1352333174000 + false + + + + + + + + 12*12 + beanshell + + + + + + + 11*11 + Jexl2 + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocal.xml + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + + + diff --git a/bin/testfiles/BatchTestLocal.txt b/bin/testfiles/BatchTestLocal.txt new file mode 100644 index 00000000000..d0aa7a89b9a --- /dev/null +++ b/bin/testfiles/BatchTestLocal.txt @@ -0,0 +1,30 @@ +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 \ No newline at end of file diff --git a/bin/testfiles/BatchTestLocal.xml b/bin/testfiles/BatchTestLocal.xml new file mode 100644 index 00000000000..83cdf3e116d --- /dev/null +++ b/bin/testfiles/BatchTestLocal.xml @@ -0,0 +1,791 @@ + + + + + + + + + + + ResultData + + SamplerData + + + + + ResultData + + SamplerData + + + + + ResultData + + SamplerData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResultData + + SamplerData + + + + + ResultData + + SamplerData + + + + + ResultData + + SamplerData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResultData + + SamplerData + + + + + ResultData + + SamplerData + + + + + ResultData + + SamplerData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResultData + + SamplerData + + + + + ResultData + + SamplerData + + + + + ResultData + + SamplerData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GET + + file:testfiles/BatchTestLocal.jmx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SampleResult.setSampleLabel("BSH Counter: 1"); + + + + + + SampleResult.setSampleLabel("BSH Counter: 2"); + + + + + + SampleResult.setSampleLabel("BSH Counter: 3"); + + + + + + 12*12 + + + + + + 11*11 + + + + + + + + diff --git a/bin/testfiles/BatchTestLocalRemote.csv b/bin/testfiles/BatchTestLocalRemote.csv new file mode 100644 index 00000000000..58275fbd2ca --- /dev/null +++ b/bin/testfiles/BatchTestLocalRemote.csv @@ -0,0 +1,134 @@ +label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,grpThreads,allThreads,URL,Filename,SampleCount,ErrorCount +Setup 1,200,OK,Setup Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Loop5 C1=1 C2=1 C3=1,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=2 C3=2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +If Test C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=3 C3=3,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=4 C3=4,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=5 C3=5,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-1,text,true,,10,1,1,null,,1,0 +Loop5 C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +If Test C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=1 C3=6,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=2 C3=7,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=3 C3=8,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +If Test C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=4 C3=9,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=5 C3=10,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-1,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-1,,true,,0,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Java 1 C1=1,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Loop5 C1=1 C2=1 C3=11,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +If Test C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=2 C3=12,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=3 C3=13,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=4 C3=14,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +If Test C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=1 C2=5 C3=15,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Java 1 C1=2,200,OK,Thread Group 1-2,text,true,,10,1,1,null,,1,0 +Loop5 C1=2 C2=1 C3=16,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=2 C3=17,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +If Test C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop3 C1=2 C2=3 C3=18,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Module,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=4 C3=19,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Loop5 C1=2 C2=5 C3=20,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If once 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java If once 2,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +Java If all 1,,,Thread Group 1-2,,false,,0,1,1,null,,1,1 +Java OK,200,OK,Thread Group 1-2,,true,,0,1,1,null,,1,0 +"HTTP ""Request,",200,OK,Thread Group 2-1,text,true,,132945,1,1,file:testfiles/BatchTestLocalRemote.jmx,,1,0 +1 1,200,OK,Thread Group 2-1,,true,,0,1,1,null,,1,0 +2 2,200,OK,Thread Group 2-1,,true,,0,1,1,null,,1,0 +3 3,200,OK,Thread Group 2-1,,true,,0,1,1,null,,1,0 +0 4,200,OK,Thread Group 2-1,,true,,0,1,1,null,,1,0 +NAME USER1,200,OK,Thread Group 3-1,,true,,0,1,1,null,,1,0 +TG1 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG OO = 1,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG Loop =3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG2 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG1 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG Loop =3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG2 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG1 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG Loop =3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +TG2 = 3,200,OK,Thread Group 4-1,,true,,0,1,1,null,,1,0 +CSV_VAR=2,200,OK,CSV Test 6-1,,true,,0,1,1,null,,1,0 +CSV_VAR=3,200,OK,CSV Test 6-1,,true,,0,1,1,null,,1,0 +CSV_VAR=1,200,OK,CSV Test 6-1,,true,,0,1,1,null,,1,0 +CSV_VAR=2,200,OK,CSV Test 6-1,,true,,0,1,1,null,,1,0 +BSH Counter: 1,200,OK,Bug 48943 7-1,text,true,,0,1,1,null,,1,0 +BSH Counter: 2,200,OK,Bug 48943 7-1,text,true,,0,1,1,null,,1,0 +BSH Counter: 3,200,OK,Bug 48943 7-1,text,true,,0,1,1,null,,1,0 +BSF Sampler,200,OK,BSF/JSR 8-1,text,true,,3,1,1,null,,1,0 +JSR223 Sampler,200,OK,BSF/JSR 8-1,text,true,,3,1,1,null,,1,0 +Post 1,200,OK,Post Thread Group 1-1,,true,,0,1,1,null,,1,0 diff --git a/bin/testfiles/BatchTestLocalRemote.jmx b/bin/testfiles/BatchTestLocalRemote.jmx new file mode 100644 index 00000000000..828dc6264ff --- /dev/null +++ b/bin/testfiles/BatchTestLocalRemote.jmx @@ -0,0 +1,2329 @@ + + + + + + + + + true + false + Batch Test using only local resources. +**N.B. If this file is updated, then the expected test data files need to be updated with the new length of this file** + + + + 1172922900000 + + + 2 + false + + false + 2 + + 1172922900000 + continue + 40 + N.B. The ramp-up period is set so that the first thread will finish before the second. +This is to ensure the test output is predictable, whilst still allowing testing of unshared counter etc + + + + 1 + + 1 + C1 + + true + Should increment for each TG loop + + + + true + 3 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Java 1 C1=${C1} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + SamplerData + = + + + ResultData + ResultData + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + true + 5 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Loop5 C1=${C1} C2=${C2} C3=${C3} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + ${__StringFromFile(testfiles/BatchTestLocalRemote.txt)} > 2 + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + If Test C1=${C1} C2=${C2} C3=${C3} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + true + 3 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Loop3 C1=${C1} C2=${C2} C3=${C3} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + WorkBench + Test Plan + Thread Group + Simple Controller for Module Controller + + + + + + + 1 + 5 + 1 + C2 + + true + + + + 1 + + 1 + C3 + + false + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocalRemote.xml + + + + false + + saveConfig + + + false + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + true + 2 + + + + ${JMeterThread.last_sample_ok} + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + BAD + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + ${JMeterThread.last_sample_ok} + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + BAD + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + for Module Controller + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + ${__P(module)} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + true + Test zero loops + 0 + + + + Just in case the loop accidentally runs + 2 + 2 + + + + + + + Runs after first thread group. + + false + 1 + + 1 + 1 + 1194880755000 + 1194880755000 + false + continue + + + + + + + + + + + + + file + + testfiles/BatchTestLocalRemote.jmx + GET + false + true + true + false + Java + false + + N.B. The file that is loaded must have a fixed size, so HTML won't do as it will be different on Unix and Windows. +We use this file, which has eol=LF - but of course any changes need to be reflected in the expected test data. + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + true + true + true + false + true + true + false + true + true + false + true + 0 + true + true + true + true + + + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocalRemote.xml + + + + true + 4 + + + + ${__counter(TRUE,COUNT)} + + + + 0 + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 0 ${COUNT} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 1 ${COUNT} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2 ${COUNT} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 3 ${COUNT} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + false + 1 + + 1 + 1 + 1226668173000 + 1226668173000 + false + continue + + + + + + + NAME + + + + USER1 + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + NAME ${NAME} + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + + + + + + + + Once Only Controller tests + + false + 3 + + 1 + 1 + 1242238972000 + 1242238972000 + false + continue + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + true + 3 + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + Does not work currently - should only run once, as SC should be ignored + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocalRemote.xml + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + 0 + + 1 + 1 + 1242238814000 + 1242238814000 + false + continue + + + + + + Just in case Thread Group runs + 2 + 2 + + + + + + continue + + false + 1 + + 1 + 1 + 1296159999000 + 1296159999000 + false + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocalRemote.xml + + + + + continue + + false + 1 + + 1 + 1 + 1296160031000 + 1296160031000 + false + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocalRemote.xml + + + + + , + + BatchTestLocalRemote.txt + false + true + All threads + false + CSV_VAR + + + + continue + + false + 4 + + 1 + 1 + 1296160584000 + 1296160584000 + false + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocalRemote.xml + + + + + continue + + false + 3 + + 1 + 1 + 1316002322000 + 1316002322000 + false + + + + + + + + SampleResult.setSampleLabel("BSH Counter: ${__counter(FALSE)}"); + + + false + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocalRemote.xml + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + continue + + false + 1 + + 1 + 1 + 1352333174000 + 1352333174000 + false + + + + + + + + 12*12 + beanshell + + + + + + + 11*11 + Jexl2 + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + true + true + true + true + false + true + 0 + true + true + true + true + true + + + BatchTestLocalRemote.xml + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + true + false + true + true + true + false + true + 0 + true + true + true + true + true + + + ${__P(CSVFILE)} + + + + + + diff --git a/bin/testfiles/BatchTestLocalRemote.txt b/bin/testfiles/BatchTestLocalRemote.txt new file mode 100644 index 00000000000..d0aa7a89b9a --- /dev/null +++ b/bin/testfiles/BatchTestLocalRemote.txt @@ -0,0 +1,30 @@ +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 +1 +2 +3 \ No newline at end of file diff --git a/bin/testfiles/BatchTestLocalRemote.xml b/bin/testfiles/BatchTestLocalRemote.xml new file mode 100644 index 00000000000..934b07bf0eb --- /dev/null +++ b/bin/testfiles/BatchTestLocalRemote.xml @@ -0,0 +1,685 @@ + + + + + + + + + + + + SamplerData + + + + + + SamplerData + + + + + + SamplerData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SamplerData + + + + + + SamplerData + + + + + + SamplerData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SamplerData + + + + + + SamplerData + + + + + + SamplerData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SamplerData + + + + + + SamplerData + + + + + + SamplerData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GET + + file:testfiles/BatchTestLocalRemote.jmx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SampleResult.setSampleLabel("BSH Counter: 1"); + + + + + + SampleResult.setSampleLabel("BSH Counter: 2"); + + + + + + SampleResult.setSampleLabel("BSH Counter: 3"); + + + + + + 12*12 + + + + + + 11*11 + + + + + + + + diff --git a/bin/testfiles/BeanShellTest.bsh b/bin/testfiles/BeanShellTest.bsh new file mode 100644 index 00000000000..6e380877f25 --- /dev/null +++ b/bin/testfiles/BeanShellTest.bsh @@ -0,0 +1,20 @@ +// BeanShell Test initialisation file +// Used in unit tests + +i=j=0; + +import org.apache.jmeter.util.JMeterUtils; + +getprop(p){// get a JMeter property + return JMeterUtils.getPropDefault(p,""); +} + +getprop(p,d){// get a JMeter property with default + return JMeterUtils.getPropDefault(p,d); +} + +setprop(p,v){// set a JMeter property + JMeterUtils.getJMeterProperties().setProperty(p, v); +} + +return 9876; // used by source test \ No newline at end of file diff --git a/bin/testfiles/Bug47165.csv b/bin/testfiles/Bug47165.csv new file mode 100644 index 00000000000..ef72374a56f --- /dev/null +++ b/bin/testfiles/Bug47165.csv @@ -0,0 +1 @@ +label,responseCode,responseMessage,threadName,dataType,success,bytes,Latency diff --git a/bin/testfiles/Bug47165.jmx b/bin/testfiles/Bug47165.jmx new file mode 100644 index 00000000000..283d3b413e6 --- /dev/null +++ b/bin/testfiles/Bug47165.jmx @@ -0,0 +1,181 @@ + + + + + Causes NPE when run in non-GUI mode on 2.3.2 and earler + false + false + + + + + + + + + false + 1 + + 1 + 1 + 1375864955000 + 1375864955000 + false + continue + + + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + false + 1 + + 1 + 1 + 1375865019000 + 1375865019000 + false + continue + + + + + + + WorkBench + Test Plan + Thread Group + Simple Controller + + + + + + WorkBench + Test Plan + Thread Group + Simple Controller + + + + + + false + + saveConfig + + + true + false + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + false + 0 + true + + + Bug47165.csv + + + + false + + saveConfig + + + true + false + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + Bug47165.xml + + + + + diff --git a/bin/testfiles/Bug47165.xml b/bin/testfiles/Bug47165.xml new file mode 100644 index 00000000000..32bb536a8d3 --- /dev/null +++ b/bin/testfiles/Bug47165.xml @@ -0,0 +1,4 @@ + + + + diff --git a/bin/testfiles/Bug50898.csv b/bin/testfiles/Bug50898.csv new file mode 100644 index 00000000000..4685911df66 --- /dev/null +++ b/bin/testfiles/Bug50898.csv @@ -0,0 +1,3 @@ +label,responseCode,responseMessage,threadName,dataType,success,bytes,Latency +Java Request,,,Thread Group 1-1,,true,0,0 +Java Request,,,Thread Group 1-1,,true,0,0 diff --git a/bin/testfiles/Bug50898.jmx b/bin/testfiles/Bug50898.jmx new file mode 100644 index 00000000000..5ab918f31ae --- /dev/null +++ b/bin/testfiles/Bug50898.jmx @@ -0,0 +1,104 @@ + + + + + Duplicate IncludeController names cause NPE in non-GUI mode + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1316699696000 + 1316699696000 + false + + + + + + Bug50898_inc.jmx + + + + Bug50898_inc.jmx + + + + + false + + saveConfig + + + true + false + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + false + 0 + true + + + Bug50898.csv + + + + false + + saveConfig + + + true + false + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + Bug50898.xml + + + + + diff --git a/bin/testfiles/Bug50898.xml b/bin/testfiles/Bug50898.xml new file mode 100644 index 00000000000..8d6617c4b8a --- /dev/null +++ b/bin/testfiles/Bug50898.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/bin/testfiles/Bug50898_inc.jmx b/bin/testfiles/Bug50898_inc.jmx new file mode 100644 index 00000000000..99a7b0c4a9d --- /dev/null +++ b/bin/testfiles/Bug50898_inc.jmx @@ -0,0 +1,67 @@ + + + + + + false + false + + + + + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + diff --git a/bin/testfiles/Bug52310.csv b/bin/testfiles/Bug52310.csv new file mode 100644 index 00000000000..7ed4b840cf9 --- /dev/null +++ b/bin/testfiles/Bug52310.csv @@ -0,0 +1,3 @@ +ComputeIPAddr,200,OK,TG2 1-1,text,true +HTTP-Request-HC31,200,OK,TG2 1-1,text,true +HTTP-Request-HC4,200,OK,TG2 1-1,text,true diff --git a/bin/testfiles/Bug52310.jmx b/bin/testfiles/Bug52310.jmx new file mode 100644 index 00000000000..0a73d48e74a --- /dev/null +++ b/bin/testfiles/Bug52310.jmx @@ -0,0 +1,178 @@ + + + + + + false + false + + + + + + = + + + + + + + + + + + + false + 1 + + + 1187292555000 + continue + 1 + + false + 1 + + 1187292555000 + + + + + + + jmeter.apache.org + + + + + + + 4 + + + + vars.putObject("IP_ADDR", InetAddress.getLocalHost().getHostAddress()); + + + false + + + + + + + + + + + + + + GET + true + false + true + false + HttpClient3.1 + true + true + false + .*jmeter.apache.org.* + ${IP_ADDR} + + + + + + + + + + + + + + GET + true + false + true + false + HttpClient4 + true + true + false + .*jmeter.apache.org.* + ${IP_ADDR} + + + + 100 + 50 + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + + + Bug52310.csv + + + + false + + saveConfig + + + false + false + true + + true + true + false + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + + Bug52310.xml + + + + + + diff --git a/bin/testfiles/Bug52310.xml b/bin/testfiles/Bug52310.xml new file mode 100644 index 00000000000..22211004592 --- /dev/null +++ b/bin/testfiles/Bug52310.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/Bug52968.csv b/bin/testfiles/Bug52968.csv new file mode 100644 index 00000000000..bd87d94543d --- /dev/null +++ b/bin/testfiles/Bug52968.csv @@ -0,0 +1,60 @@ +TNP1-JR0-0,200,,TG0-SNL 1-1,,true,1,1,1,0 +TC-NOPARENT-1,200,"Number of samples in transaction : 1, number of failing samples : 0",TG0-SNL 1-1,,true,1,1,1,0 +JR-MUSTEXECUTE,,,TG0-SNL 1-1,,true,1,1,1,0 +TNP1-JR0-1,200,,TG0-SNL 1-1,,true,1,1,1,0 +TC-NOPARENT-1,200,"Number of samples in transaction : 1, number of failing samples : 0",TG0-SNL 1-1,,true,1,1,1,0 +JR-MUSTEXECUTE,,,TG0-SNL 1-1,,true,1,1,1,0 +TNP1-JR0-2,200,,TG0-SNL 1-1,,true,1,1,1,0 +TC-NOPARENT-1,200,"Number of samples in transaction : 1, number of failing samples : 0",TG0-SNL 1-1,,true,1,1,1,0 +JR-MUSTEXECUTE,,,TG0-SNL 1-1,,true,1,1,1,0 +TNPF-JR0-0,500,,TG1-SNL 2-1,,false,1,1,1,1 +TC-NOPARENT-FAILING0,,"Number of samples in transaction : 1, number of failing samples : 1",TG1-SNL 2-1,,false,1,1,1,1 +TNPF-JR0-1,500,,TG1-SNL 2-1,,false,1,1,1,1 +TC-NOPARENT-FAILING0,,"Number of samples in transaction : 1, number of failing samples : 1",TG1-SNL 2-1,,false,1,1,1,1 +TNPF-JR0-2,500,,TG1-SNL 2-1,,false,1,1,1,1 +TC-NOPARENT-FAILING0,,"Number of samples in transaction : 1, number of failing samples : 1",TG1-SNL 2-1,,false,1,1,1,1 +TNPF-JR0-0,200,,TG2-SNL 3-1,,true,1,1,1,0 +TNPF-JR1-0,500,,TG2-SNL 3-1,,false,1,1,1,1 +TC-NOPARENT-FAILING1,,"Number of samples in transaction : 2, number of failing samples : 1",TG2-SNL 3-1,,false,1,1,1,1 +TNPF-JR0-1,200,,TG2-SNL 3-1,,true,1,1,1,0 +TNPF-JR1-1,500,,TG2-SNL 3-1,,false,1,1,1,1 +TC-NOPARENT-FAILING1,,"Number of samples in transaction : 2, number of failing samples : 1",TG2-SNL 3-1,,false,1,1,1,1 +TNPF-JR0-2,200,,TG2-SNL 3-1,,true,1,1,1,0 +TNPF-JR1-2,500,,TG2-SNL 3-1,,false,1,1,1,1 +TC-NOPARENT-FAILING1,,"Number of samples in transaction : 2, number of failing samples : 1",TG2-SNL 3-1,,false,1,1,1,1 +TNPF2-JR1-0,200,,TG3-SNL 4-1,,true,1,1,1,0 +TNPF2-JR2-0,500,,TG3-SNL 4-1,,false,1,1,1,1 +TC-NOPARENT-FAILING2,,"Number of samples in transaction : 2, number of failing samples : 1",TG3-SNL 4-1,,false,1,1,1,1 +TNPF2-JR1-1,200,,TG3-SNL 4-1,,true,1,1,1,0 +TNPF2-JR2-1,500,,TG3-SNL 4-1,,false,1,1,1,1 +TC-NOPARENT-FAILING2,,"Number of samples in transaction : 2, number of failing samples : 1",TG3-SNL 4-1,,false,1,1,1,1 +TNPF2-JR1-2,200,,TG3-SNL 4-1,,true,1,1,1,0 +TNPF2-JR2-2,500,,TG3-SNL 4-1,,false,1,1,1,1 +TC-NOPARENT-FAILING2,,"Number of samples in transaction : 2, number of failing samples : 1",TG3-SNL 4-1,,false,1,1,1,1 +TNPTA-JR1-0,200,,TG4-SNL 5-1,,true,1,1,1,0 +TNPTA-JR2-0,200,,TG4-SNL 5-1,,true,1,1,1,0 +TC-NOPARENT-TestAction,,"Number of samples in transaction : 3, number of failing samples : 0",TG4-SNL 5-1,,true,1,1,1,0 +TNPTA-JR1-1,200,,TG4-SNL 5-1,,true,1,1,1,0 +TNPTA-JR2-1,200,,TG4-SNL 5-1,,true,1,1,1,0 +TC-NOPARENT-TestAction,,"Number of samples in transaction : 3, number of failing samples : 0",TG4-SNL 5-1,,true,1,1,1,0 +TNPTA-JR1-2,200,,TG4-SNL 5-1,,true,1,1,1,0 +TNPTA-JR2-2,200,,TG4-SNL 5-1,,true,1,1,1,0 +TC-NOPARENT-TestAction,,"Number of samples in transaction : 3, number of failing samples : 0",TG4-SNL 5-1,,true,1,1,1,0 +TC-PARENT,200,"Number of samples in transaction : 1, number of failing samples : 0",TG5-SNL 6-1,,true,1,1,1,0 +JR-MUSTEXECUTE,,,TG5-SNL 6-1,,true,1,1,1,0 +TC-PARENT,200,"Number of samples in transaction : 1, number of failing samples : 0",TG5-SNL 6-1,,true,1,1,1,0 +JR-MUSTEXECUTE,,,TG5-SNL 6-1,,true,1,1,1,0 +TC-PARENT,200,"Number of samples in transaction : 1, number of failing samples : 0",TG5-SNL 6-1,,true,1,1,1,0 +JR-MUSTEXECUTE,,,TG5-SNL 6-1,,true,1,1,1,0 +TC-PARENT-FAILING1,500,"Number of samples in transaction : 1, number of failing samples : 1",TG6-SNL 7-1,,false,1,1,1,1 +TC-PARENT-FAILING1,500,"Number of samples in transaction : 1, number of failing samples : 1",TG6-SNL 7-1,,false,1,1,1,1 +TC-PARENT-FAILING1,500,"Number of samples in transaction : 1, number of failing samples : 1",TG6-SNL 7-1,,false,1,1,1,1 +TC-PARENT-FAILING1,500,"Number of samples in transaction : 2, number of failing samples : 1",TG7-SNL 8-1,,false,1,1,1,1 +TC-PARENT-FAILING1,500,"Number of samples in transaction : 2, number of failing samples : 1",TG7-SNL 8-1,,false,1,1,1,1 +TC-PARENT-FAILING1,500,"Number of samples in transaction : 2, number of failing samples : 1",TG7-SNL 8-1,,false,1,1,1,1 +TC-PARENT-FAILING2,500,"Number of samples in transaction : 2, number of failing samples : 1",TG8-SNL 9-1,,false,1,1,1,1 +TC-PARENT-FAILING2,500,"Number of samples in transaction : 2, number of failing samples : 1",TG8-SNL 9-1,,false,1,1,1,1 +TC-PARENT-FAILING2,500,"Number of samples in transaction : 2, number of failing samples : 1",TG8-SNL 9-1,,false,1,1,1,1 +TC-PARENT-TestAction,200,"Number of samples in transaction : 2, number of failing samples : 0",TG9-SNL 10-1,,true,1,1,1,0 +TC-PARENT-TestAction,200,"Number of samples in transaction : 2, number of failing samples : 0",TG9-SNL 10-1,,true,1,1,1,0 +TC-PARENT-TestAction,200,"Number of samples in transaction : 2, number of failing samples : 0",TG9-SNL 10-1,,true,1,1,1,0 diff --git a/bin/testfiles/Bug52968.jmx b/bin/testfiles/Bug52968.jmx new file mode 100644 index 00000000000..5dd3a1d8507 --- /dev/null +++ b/bin/testfiles/Bug52968.jmx @@ -0,0 +1,1753 @@ + + + + + + false + true + + + + + + + + 0 + 100 + 1 + counter + + true + + + + startnextloop + + false + 3 + + 1 + 1 + 1332605004000 + 1332605004000 + false + + + + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNP1-JR0-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-MUSTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + startnextloop + + false + 3 + + 1 + 1 + 1332707267000 + 1332707267000 + false + + + + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNPF-JR0-${counter} + = + + + ResponseCode + 500 + = + + + ResponseMessage + + = + + + Status + KO + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-SHOULDNOTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + startnextloop + + false + 3 + + 1 + 1 + 1332707428000 + 1332707428000 + false + + + + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNPF-JR0-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNPF-JR1-${counter} + = + + + ResponseCode + 500 + = + + + ResponseMessage + + = + + + Status + KO + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-SHOULDNOTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + startnextloop + + false + 3 + + 1 + 1 + 1332707466000 + 1332707466000 + false + + + + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNPF2-JR1-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNPF2-JR2-${counter} + = + + + ResponseCode + 500 + = + + + ResponseMessage + + = + + + Status + KO + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNPF2-JR3-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-SHOULDNOTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + startnextloop + + false + 3 + + 1 + 1 + 1332707571000 + 1332707571000 + false + + + + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNPTA-JR1-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNPTA-JR2-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + 3 + 0 + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TNPTA-JR3-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-SHOULDNOTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + startnextloop + + false + 3 + + 1 + 1 + 1332707571000 + 1332707571000 + false + + + + + + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TP-JR0-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-MUSTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + startnextloop + + false + 3 + + 1 + 1 + 1332707571000 + 1332707571000 + false + + + + + + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TPF-JR0-${counter} + = + + + ResponseCode + 500 + = + + + ResponseMessage + + = + + + Status + KO + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-SHOULDNOTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + startnextloop + + false + 3 + + 1 + 1 + 1332707571000 + 1332707571000 + false + + + + + + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TP-JR0-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TP-JR1-${counter} + = + + + ResponseCode + 500 + = + + + ResponseMessage + + = + + + Status + KO + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-SHOULDNOTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + startnextloop + + false + 3 + + 1 + 1 + 1332707571000 + 1332707571000 + false + + + + + + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TPF-JR1-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TPF-JR2-${counter} + = + + + ResponseCode + 500 + = + + + ResponseMessage + + = + + + Status + KO + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TPF-JR3-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-SHOULDNOTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + startnextloop + + false + 3 + + 1 + 1 + 1332707571000 + 1332707571000 + false + + + + + + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TPTA-JR1-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TPTA-JR2-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + 3 + 0 + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + TPTA-JR3-${counter} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JR-SHOULDNOTEXECUTE + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + true + false + 0 + true + true + + + Bug52968.csv + + + + false + + saveConfig + + + false + false + true + + true + true + false + true + false + true + true + false + false + true + false + false + false + true + false + 0 + true + true + + + Bug52968.xml + + + + + diff --git a/bin/testfiles/Bug52968.xml b/bin/testfiles/Bug52968.xml new file mode 100644 index 00000000000..6c38fe8d9f4 --- /dev/null +++ b/bin/testfiles/Bug52968.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/Bug54685.csv b/bin/testfiles/Bug54685.csv new file mode 100644 index 00000000000..582e0f8b7a7 --- /dev/null +++ b/bin/testfiles/Bug54685.csv @@ -0,0 +1,2 @@ +label,responseCode,responseMessage,threadName,dataType,success,bytes,"REFERENCE","JSESSIONID" +"sample_variables=REFERENCE,JSESSIONID REFERENCE=reference JSESSIONID=jsessionId",,,Thread Group 1-1,,true,0,reference,jsessionId diff --git a/bin/testfiles/Bug54685.jmx b/bin/testfiles/Bug54685.jmx new file mode 100644 index 00000000000..d33fb557496 --- /dev/null +++ b/bin/testfiles/Bug54685.jmx @@ -0,0 +1,155 @@ + + + + + + false + false + + + + REFERENCE + reference + = + + + JSESSIONID + jsessionId + = + + + + + + + + continue + + false + 1 + + 1 + 1 + 1364309240000 + 1364309240000 + false + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + sample_variables=${__P(sample_variables,'undef')} REFERENCE=${REFERENCE} JSESSIONID=${JSESSIONID} + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + false + 0 + true + + + Bug54685.csv + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + Bug54685.xml + + + + + diff --git a/bin/testfiles/Bug54685.xml b/bin/testfiles/Bug54685.xml new file mode 100644 index 00000000000..f72b4a36317 --- /dev/null +++ b/bin/testfiles/Bug54685.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/bin/testfiles/Bug55375.csv b/bin/testfiles/Bug55375.csv new file mode 100644 index 00000000000..041bf70b94e --- /dev/null +++ b/bin/testfiles/Bug55375.csv @@ -0,0 +1,2 @@ +label,responseCode,responseMessage,threadName,dataType,success,bytes,SampleCount,ErrorCount +reload,,,threadgroup 1-1,,true,0,1,0 diff --git a/bin/testfiles/Bug55375.jmx b/bin/testfiles/Bug55375.jmx new file mode 100644 index 00000000000..63e52ec1475 --- /dev/null +++ b/bin/testfiles/Bug55375.jmx @@ -0,0 +1,158 @@ + + + + + + false + true + + + + + + + + continue + + false + 1 + + 1 + 0 + 1344428409000 + 1344428409000 + false + + + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + WorkBench + Bug55375 + threadgroup + reload + + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + false + 0 + true + true + + + Bug55375.csv + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + false + true + true + false + false + false + false + 0 + true + true + + + Bug55375.xml + + + + + diff --git a/bin/testfiles/Bug55375.xml b/bin/testfiles/Bug55375.xml new file mode 100644 index 00000000000..bc195d9a783 --- /dev/null +++ b/bin/testfiles/Bug55375.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/bin/testfiles/Bug56243.csv b/bin/testfiles/Bug56243.csv new file mode 100644 index 00000000000..7fffb2ea843 --- /dev/null +++ b/bin/testfiles/Bug56243.csv @@ -0,0 +1,9 @@ +Starting iteration,200,OK,TG2 1-1,text,true,1,1,1,0 +Debug RE,200,OK,TG2 1-1,text,true,1,1,1,0 +DS-123,200,OK,TG2 1-1,text,true,1,1,1,0 +Starting iteration,200,OK,TG2 1-1,text,true,1,1,1,0 +Debug RE,200,OK,TG2 1-1,text,true,1,1,1,0 +DS-123,200,OK,TG2 1-1,text,true,1,1,1,0 +Starting iteration,200,OK,TG2 1-1,text,true,1,1,1,0 +Debug RE,200,OK,TG2 1-1,text,true,1,1,1,0 +DS-123,200,OK,TG2 1-1,text,true,1,1,1,0 diff --git a/bin/testfiles/Bug56243.jmx b/bin/testfiles/Bug56243.jmx new file mode 100644 index 00000000000..67728391141 --- /dev/null +++ b/bin/testfiles/Bug56243.jmx @@ -0,0 +1,147 @@ + + + + + + false + false + + + + + + = + + + + + + + + + + + + false + 1 + + + 1187292555000 + continue + 1 + + false + 3 + + 1187292555000 + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + Bug56243.csv + + + + false + + saveConfig + + + false + false + true + + true + true + false + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + + + Bug56243.xml + + + + vars.put("my_var", "-----123--456--------------------789---"); + + + false + + + + false + number + \d+ + $0$ + + -1 + variable + my_var + + + + false + false + false + + + + number + current_number + true + 0 + 1 + + + + false + false + false + + + + + + + diff --git a/bin/testfiles/Bug56243.xml b/bin/testfiles/Bug56243.xml new file mode 100644 index 00000000000..7ac890e4066 --- /dev/null +++ b/bin/testfiles/Bug56243.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/bin/testfiles/Bug56811.csv b/bin/testfiles/Bug56811.csv new file mode 100644 index 00000000000..0988e147e7e --- /dev/null +++ b/bin/testfiles/Bug56811.csv @@ -0,0 +1,31 @@ +label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,grpThreads,allThreads,URL,SampleCount,ErrorCount +01 Transaction Controller,200,"Number of samples in transaction : 1, number of failing samples : 1",TG-UsingTCwithGenerateParentSample 1-1,,false,,5,1,1,null,1,1 +01 Transaction Controller2,200,"Number of samples in transaction : 1, number of failing samples : 0",TG-UsingTCwithGenerateParentSample 1-1,,true,,0,1,1,null,1,0 +02 Transaction Controller,200,"Number of samples in transaction : 1, number of failing samples : 1",TG-UsingTCwithGenerateParentSample 1-1,,false,,5,1,1,null,1,1 +02 Transaction Controller2,200,"Number of samples in transaction : 1, number of failing samples : 0",TG-UsingTCwithGenerateParentSample 1-1,,true,,0,1,1,null,1,0 +03 Transaction Controller,200,"Number of samples in transaction : 1, number of failing samples : 1",TG-UsingTCwithGenerateParentSample 1-1,,false,,5,1,1,null,1,1 +03 Transaction Controller2,200,"Number of samples in transaction : 1, number of failing samples : 0",TG-UsingTCwithGenerateParentSample 1-1,,true,,0,1,1,null,1,0 +01 Transaction Controller,200,"Number of samples in transaction : 1, number of failing samples : 1",TG-UsingTCwithGenerateParentSampleAndResultStatusActionHandlerWithStartNextThreadLoop 2-1,,false,,5,1,1,null,1,1 +02 Transaction Controller,200,"Number of samples in transaction : 1, number of failing samples : 1",TG-UsingTCwithGenerateParentSampleAndResultStatusActionHandlerWithStartNextThreadLoop 2-1,,false,,5,1,1,null,1,1 +03 Transaction Controller,200,"Number of samples in transaction : 1, number of failing samples : 1",TG-UsingTCwithGenerateParentSampleAndResultStatusActionHandlerWithStartNextThreadLoop 2-1,,false,,5,1,1,null,1,1 +01 Transaction Controller,200,"Number of samples in transaction : 1, number of failing samples : 1",TG-UsingTCwithGenerateParentSampleAndStartNextThreadLoop 3-1,,false,,5,1,1,null,1,1 +02 Transaction Controller,200,"Number of samples in transaction : 1, number of failing samples : 1",TG-UsingTCwithGenerateParentSampleAndStartNextThreadLoop 3-1,,false,,5,1,1,null,1,1 +03 Transaction Controller,200,"Number of samples in transaction : 1, number of failing samples : 1",TG-UsingTCwithGenerateParentSampleAndStartNextThreadLoop 3-1,,false,,5,1,1,null,1,1 +Samp 1,200,Alright,TG-UsingTCAndStartNextThreadLoop 4-1,,true,,0,1,1,null,1,0 +Samp 2,200,Alright,TG-UsingTCAndStartNextThreadLoop 4-1,,true,,0,1,1,null,1,0 +BeanShell Sampler,200,OK,TG-UsingTCAndStartNextThreadLoop 4-1,text,false,,5,1,1,null,1,1 +Transaction Controller2,,"Number of samples in transaction : 3, number of failing samples : 1",TG-UsingTCAndStartNextThreadLoop 4-1,,false,,5,1,1,null,1,1 +Transaction Controller1,,"Number of samples in transaction : 3, number of failing samples : 1",TG-UsingTCAndStartNextThreadLoop 4-1,,false,,5,1,1,null,1,1 +01 Transaction Controller,,"Number of samples in transaction : 3, number of failing samples : 1",TG-UsingTCAndStartNextThreadLoop 4-1,,false,,5,1,1,null,1,1 +Samp 1,200,Alright,TG-UsingTCAndStartNextThreadLoop 4-1,,true,,0,1,1,null,1,0 +Samp 2,200,Alright,TG-UsingTCAndStartNextThreadLoop 4-1,,true,,0,1,1,null,1,0 +BeanShell Sampler,200,OK,TG-UsingTCAndStartNextThreadLoop 4-1,text,false,,5,1,1,null,1,1 +Transaction Controller2,,"Number of samples in transaction : 3, number of failing samples : 1",TG-UsingTCAndStartNextThreadLoop 4-1,,false,,5,1,1,null,1,1 +Transaction Controller1,,"Number of samples in transaction : 3, number of failing samples : 1",TG-UsingTCAndStartNextThreadLoop 4-1,,false,,5,1,1,null,1,1 +02 Transaction Controller,,"Number of samples in transaction : 3, number of failing samples : 1",TG-UsingTCAndStartNextThreadLoop 4-1,,false,,5,1,1,null,1,1 +Samp 1,200,Alright,TG-UsingTCAndStartNextThreadLoop 4-1,,true,,0,1,1,null,1,0 +Samp 2,200,Alright,TG-UsingTCAndStartNextThreadLoop 4-1,,true,,0,1,1,null,1,0 +BeanShell Sampler,200,OK,TG-UsingTCAndStartNextThreadLoop 4-1,text,false,,5,1,1,null,1,1 +Transaction Controller2,,"Number of samples in transaction : 3, number of failing samples : 1",TG-UsingTCAndStartNextThreadLoop 4-1,,false,,5,1,1,null,1,1 +Transaction Controller1,,"Number of samples in transaction : 3, number of failing samples : 1",TG-UsingTCAndStartNextThreadLoop 4-1,,false,,5,1,1,null,1,1 +03 Transaction Controller,,"Number of samples in transaction : 3, number of failing samples : 1",TG-UsingTCAndStartNextThreadLoop 4-1,,false,,5,1,1,null,1,1 diff --git a/bin/testfiles/Bug56811.jmx b/bin/testfiles/Bug56811.jmx new file mode 100644 index 00000000000..bec8741f1ed --- /dev/null +++ b/bin/testfiles/Bug56811.jmx @@ -0,0 +1,1706 @@ + + + + + + false + true + + + + + + + + continue + + false + 3 + + 1 + 1 + 1407159429000 + 1407159429000 + false + + + + + + 1 + + 1 + iter + 00 + false + + + + true + 1 + + + + false + true + + + + false + true + + + + false + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + IsSuccess = false; + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 4 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + false + true + + + + false + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 3 + = + + + ResponseCode + 666 + = + + + ResponseMessage + Error + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 4 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + continue + + false + 3 + + 1 + 1 + 1407159429000 + 1407159429000 + false + + + + + + 1 + + 1 + iter + 00 + false + + + + true + 1 + + + + false + true + + + + false + true + + + + false + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + IsSuccess = false; + + + false + + + + 4 + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 4 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + false + true + + + + false + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 3 + = + + + ResponseCode + 666 + = + + + ResponseMessage + Error + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 4 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + startnextloop + + false + 3 + + 1 + 1 + 1407159429000 + 1407159429000 + false + + + + + + 1 + + 1 + iter + 00 + false + + + + true + 1 + + + + false + true + + + + false + true + + + + false + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + IsSuccess = false; + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 4 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + false + true + + + + false + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 3 + = + + + ResponseCode + 666 + = + + + ResponseMessage + Error + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 4 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + startnextloop + + false + 3 + + 1 + 1 + 1407159429000 + 1407159429000 + false + + + + + + 1 + + 1 + iter + 00 + false + + + + true + 1 + + + + false + false + + + + false + false + + + + false + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + IsSuccess = false; + + + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Samp 4 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + false + false + + + + false + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 3 + = + + + ResponseCode + 666 + = + + + ResponseMessage + Error + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + 2Samp 4 + = + + + ResponseCode + 200 + = + + + ResponseMessage + Alright + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + false + false + false + false + false + true + false + false + false + true + 0 + true + true + true + true + + + Bug56811.csv + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + true + + + Bug56811.xml + + + + + diff --git a/bin/testfiles/Bug56811.xml b/bin/testfiles/Bug56811.xml new file mode 100644 index 00000000000..d5ec9f5d596 --- /dev/null +++ b/bin/testfiles/Bug56811.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/GenTest210.jmx b/bin/testfiles/GenTest210.jmx new file mode 100644 index 00000000000..ad40b9d418f --- /dev/null +++ b/bin/testfiles/GenTest210.jmx @@ -0,0 +1,1505 @@ + + + + + Test Plan including all test elements in JMeter 2.19 + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337352661000 + 1337352661000 + false + + + + + + continue + + false + 1 + + 1 + 1 + 1337300621000 + 1337300621000 + false + + + + + + + + + + true + + + + + false + + + + + + + + 1 + + + + true + 1 + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + false + + + + + + + + + + + + + + + + false + + + + , + + + false + true + shareMode.all + false + + + + + + + + + + false + false + false + + + + + + + + false + false + + + + + false + + + + + + + + + + + + + + + + + + 4 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + true + Select 1 + 5000 + + + + true + + 10 + 10000 + DEFAULT + 60000 + + + + + + True + + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + + + + false + + 10 + false + false + 0 + 120000 + false + false + 0 + + 5 + false + 0 + 0 + + + + + 1 + + false + + + + + + + + + true + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + 0 + + throughput + 0.0 + 0.0 + + + + + 300 + + + + 300 + 100.0 + + + + + + + + + + + + 300 + 100 + + + + 0 + + + + 0 + 100.0 + + + + + + + + + false + + + + + + + + + + + + + + + false + false + false + false + + + + + + + + Select Statement + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + false + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + false + + + + + + + + + + + false + true + false + + + + + + + + + false + false + false + + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Select Statement + + + + + + + + + + true + false + false + + + + + + + + + + + + + + false + + + + + + + + + + jms_use_text + jms_text_message + 1 + false + + + + + + + false + + + + + + + false + 1 + true + jms_subscriber_receive + + + + + + + + + + + + test.RerunTest + + testRerun + + Test successful + 1000 + Test failed + 0001 + An unexpected error occured + 9999 + false + false + false + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + pop3 + INBOX + + + + -1 + false + false + false + false + false + false + + + + + + + + + + + + + false + 0 + + + + + + + + + + + + + + + + + + + + false + false + + false + + false + false + false + false + false + + false + + false + + + false + false + + + + + + + + + + + + true + false + + + + + true + + false + + + + + + + + 1 + 0 + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + false + true + true + false + + + + + + + + Select Statement + + + + + + + + + + + + + + + false + + + + + + + + + 0 + + + + + + + false + false + false + + + + + + + + + + false + + + + + + + + + + + true + -1 + + + + + + + + + 0 + 0 + omit + false + 0 + + + + + + + + + + + + + + + + + + Assertion.response_data + false + 2 + + + + SizeAssertion.response_network_size + + 1 + + + + false + false + + + + + + false + false + false + + + + + + + + + + + false + / + false + false + false + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + 2 + 2 + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + + + + + + + + false + false + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337352666000 + 1337352666000 + false + + + + + + + + + true + + + + 8081 + 0 + 25 + + + + 8080 + + + true + 0 + false + + false + true + true + false + false + + + + + + + + + diff --git a/bin/testfiles/GenTest22.jmx b/bin/testfiles/GenTest22.jmx new file mode 100644 index 00000000000..cce6f58a9a6 --- /dev/null +++ b/bin/testfiles/GenTest22.jmx @@ -0,0 +1,1129 @@ + + + + false + + + false + + + + + + + false + 1 + + + 1337359410000 + continue + 1 + + 1 + false + + 1337359410000 + + + + + + true + + + + + + + + + + + + + + 1 + + + + + + 1 + true + + + + + + + + 1 + + + + + + 1 + + + + + 1 + + + + + 100.0 + 0.0 + ThroughputController.percentThroughput + + true + 0 + 1 + + + + + + + + + + + + + + + , + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + 10 + true + 5000 + + + + 60000 + + true + Select 1 + 10000 + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + this thread only + + 0.0 + 0.0 + throughput + + + + + 0 + + + + 300 + + + + 300 + 100.0 + + + + 0 + 100.0 + + + + + + + + + + + + + + false + + + + + + + + + + true + + + + + + + + 0 + + 1 + + 10 + + + + + true + false + false + false + + + + + users.xml + + + + + + + + + + + + + + + + + GET + + + + + + false + true + + true + false + + + + + + + + GET + + + + + + false + true + + true + false + + + + + + + + GET + + + + + + false + true + + true + false + + + + + + true + + + + + + + + + + + + POST + false + + + + + + true + + + + + + false + http + + + + + + + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + false + + + + + + + + + + + + + + + + + + + + testMethodPass + false + Test failed + 1000 + + false + 0001 + false + + woolfel.DummyTestCase + Test successful + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + Select Statement + + + + + + + true + + + + + + + + + + + 2000 + + + + + + false + + + + + + + + Text Message + + Not Required + Textarea + + + + + + Use TopicSubscriber.receive() + false + + true + + + + + Not Required + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + INBOX + + + -1 + false + + pop3 + + + + + + + false + + + + + + + 1 + 0 + 0 + + + + + + + + + + + + + + + false + + + + + false + + + + + + + 0 + + + + + false + + + + + + + + + false + 2 + + Assertion.response_data + + + + + + + + + + + + + + false + 0 + 0 + 0 + + omit + + + + + + + + 1 + 0 + + + + + + + + + + false + false + false + false + / + false + + + + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + 2 + + + + + 2 + + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + saveConfig + + + false + + + + + + + diff --git a/bin/testfiles/GenTest231.jmx b/bin/testfiles/GenTest231.jmx new file mode 100644 index 00000000000..1bdddd65364 --- /dev/null +++ b/bin/testfiles/GenTest231.jmx @@ -0,0 +1,1188 @@ + + + + + + false + false + + + + + + + + + false + 1 + + 1 + 1 + 1337360144000 + 1337360144000 + false + continue + + + + + + + + + + true + + + + + false + + + + + + + + 1 + + + + + + true + 1 + + + + + + + + 1 + + + + + + 1 + + + + 1 + + + + + 0 + true + 1 + + 100.0 + 0.0 + ThroughputController.percentThroughput + + + + + false + + + + + + + + + + + + + , + + + true + false + + + + + + + + + + + + + + + + + + + false + false + false + + + + + + + + + + + + + + + + + + + + false + rfc2109 + + + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + true + Select 1 + 5000 + + + + true + + 10 + 10000 + 60000 + + + + + + + + false + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + true + + false + + + + + + + + + + + + + + + this thread only + + 0.0 + 0.0 + throughput + + + + + 0 + + + + 300 + + + + 300 + 100.0 + + + + 0 + 100.0 + + + + + + + + + + + + + + + + + + false + + + + + + + + false + + + + + + + + + 0 + 10 + 1 + + + + + + + false + false + false + false + + + + users.xml + + + + + + + + + + false + false + false + + + + + + + + + + + + + + GET + false + true + true + false + + + + false + + + + + + + + + + + + + GET + false + true + true + false + + + + false + + + + + + + + + + + + + GET + false + true + true + false + + + + false + + + + + + + + + + + + true + false + + + + + + + + + http + + + POST + + + + + + true + false + false + + + + + + + + + + false + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + + beanbasic + + + + + + + + + + + + woolfel.DummyTestCase + + + Test successful + 1000 + Test failed + 0001 + false + false + false + testMethodPass + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Select Statement + + + + + + + true + false + 2000 + + + + + + + + + + + + + false + + + + + + + + + + Textarea + Text Message + + Not Required + + + + false + + + + + + + Not Required + + true + Use TopicSubscriber.receive() + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + + + + + + pop3 + INBOX + + + + -1 + false + + + + + true + + false + + + + + + + + false + true + false + + + + 1 + 0 + + + + + + + + + + + + + + + + false + + + + + + + + + + + + false + true + + + + 0 + + + + + false + + + + + + + + + + Assertion.response_data + false + 2 + + + + + + + + + + + + + + 0 + 0 + omit + false + 0 + + + + + + + + + 0 + 1 + + + + + + + + + + false + false + false + false + false + / + + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + 2 + 2 + + + + + + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + false + + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + saveConfig + + + + + + + + + diff --git a/bin/testfiles/GenTest24.jmx b/bin/testfiles/GenTest24.jmx new file mode 100644 index 00000000000..97cded67a45 --- /dev/null +++ b/bin/testfiles/GenTest24.jmx @@ -0,0 +1,1371 @@ + + + + + + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337360263000 + 1337360263000 + false + + + + + + + + + + true + + + + + false + + + + + + + + 1 + + + + true + 1 + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + false + + + + + + + + + + + , + + + false + true + All threads + false + + + + + + + + + + false + + + + + + + + + false + false + false + + + + + + + + false + false + + + + + false + rfc2109 + + + + + + + + + + + + + + + + + + + + + true + Select 1 + 5000 + + + + true + + 10 + 10000 + 60000 + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + + + + + 1 + + false + + + + + + + + + true + + false + + + + + + + + + + + + + + + + + + + + + + false + + + + + this thread only + + throughput + 0.0 + 0.0 + + + + + 300 + + + + 300 + 100.0 + + + + + + + + + + + 0 + + + + 0 + 100.0 + + + + + + + + + + + + + + + + false + + + + + + + + false + false + false + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + + false + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + + beanbasic + + + + + + + + + false + + + + false + true + false + + + + + + + + + false + false + false + + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + + Select Statement + + + + + + + + true + false + false + + + + + + + + + + + + + + false + + + + + + + + + + jms_use_text + jms_text_message + 1 + false + + + + false + + + + + + + false + 1 + true + jms_subscriber_receive + + + + + + + + + + + test.RerunTest + + testRerun + + Test successful + 1000 + Test failed + 0001 + An unexpected error occured + 9999 + false + false + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + pop3 + INBOX + + + + -1 + false + + + + + + + + + + + false + + + false + false + false + false + false + + false + + false + + + false + false + + + + + + + + + + + + true + false + + + + + true + + false + + + + + + + + 1 + 0 + + + + + + + + + + http + + + POST + + + + + + true + false + false + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + false + + + + + + + + + 0 + + + + + + + false + false + false + + + + + + + + + + + + + + + + + false + + + + true + -1 + + + + + + + + + 0 + 0 + omit + false + 0 + + + + + + + + + + + + + + + + + Assertion.response_data + false + 2 + + + + false + false + + + + + + false + false + false + + + + + 0 + 1 + + + + + + + + + + false + / + false + false + false + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + 2 + 2 + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + false + false + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + diff --git a/bin/testfiles/GenTest25.jmx b/bin/testfiles/GenTest25.jmx new file mode 100644 index 00000000000..670733eda62 --- /dev/null +++ b/bin/testfiles/GenTest25.jmx @@ -0,0 +1,1396 @@ + + + + + + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337360672000 + 1337360672000 + false + + + + + + continue + + false + 1 + + 1 + 1 + 1337360420000 + 1337360420000 + false + + + + + + + + + + true + + + + + false + + + + + + + + 1 + + + + true + 1 + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + false + + + + + + + + + + + , + + + false + true + All threads + false + + + + + + + + + + false + + + + + + + + + false + false + false + + + + + + + + false + false + + + + + false + + + + + + + + + + + + + + + + + + 4 + + + + true + Select 1 + 5000 + + + + true + + 10 + 10000 + 60000 + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + + + + + 1 + + false + + + + + + + + + true + + false + + + + + + + + + + + + + + + + + + + + + + false + + + + + this thread only + + throughput + 0.0 + 0.0 + + + + + 300 + + + + 300 + 100.0 + + + + + + + + + + + 0 + + + + 0 + 100.0 + + + + + + + + + + + + + + + + false + + + + + + + + false + false + false + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + GET + true + false + true + false + 4 + false + + + + + + + + + false + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + + beanbasic + + + + + + + + + false + + + + false + true + false + + + + + + + + + false + false + false + + + + + + + + + + + + + + + + GET + true + false + true + false + 4 + false + + + + + + + + + Select Statement + + + + + + + + + true + false + false + + + + + + + + + + + + + + false + + + + + + + + + + jms_use_text + jms_text_message + 1 + false + + + + false + + + + + + + false + 1 + true + jms_subscriber_receive + + + + + + + + + + + test.RerunTest + + testRerun + + Test successful + 1000 + Test failed + 0001 + An unexpected error occured + 9999 + false + false + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + pop3 + INBOX + + + + -1 + false + false + false + false + false + false + + + + + + + + + + + + + false + false + + false + + false + false + false + false + false + + false + + false + + + false + false + + + + + + + + + + + + true + false + + + + + true + + false + + + + + + + + 1 + 0 + + + + + + + + + + http + + + POST + + + + + + true + false + false + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + false + + + + + + + + + 0 + + + + + + + false + false + false + + + + + + + + + + + + + + + + + false + + + + true + -1 + + + + + + + + + 0 + 0 + omit + false + 0 + + + + + + + + + + + + + + + + + Assertion.response_data + false + 2 + + + + false + false + + + + + + false + false + false + + + + + SizeAssertion.response_network_size + + 1 + + + + + + + + + + false + / + false + false + false + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + 2 + 2 + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + false + false + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337360665000 + 1337360665000 + false + + + + + + + + + diff --git a/bin/testfiles/GenTest251.jmx b/bin/testfiles/GenTest251.jmx new file mode 100644 index 00000000000..0402d741d9e --- /dev/null +++ b/bin/testfiles/GenTest251.jmx @@ -0,0 +1,1394 @@ + + + + + + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337360547000 + 1337360547000 + false + + + + + + continue + + false + 1 + + 1 + 1 + 1337360522000 + 1337360522000 + false + + + + + + + + + + true + + + + + false + + + + + + + + 1 + + + + true + 1 + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + false + + + + + + + + + + + , + + + false + true + All threads + false + + + + + + + + + + false + + + + + + + + + false + false + false + + + + + + + + false + false + + + + + false + + + + + + + + + + + + + + + + + + 4 + + + + true + Select 1 + 5000 + + + + true + + 10 + 10000 + 60000 + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + + + + + 1 + + false + + + + + + + + + true + + false + + + + + + + + + + + + + + + + + + + + + + false + + + + + this thread only + + throughput + 0.0 + 0.0 + + + + + 300 + + + + 300 + 100.0 + + + + + + + + + + + 0 + + + + 0 + 100.0 + + + + + + + + + + + + + + + + false + + + + + + + + false + false + false + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + + false + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + + beanbasic + + + + + + + + + false + + + + false + true + false + + + + + + + + + false + false + false + + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + + Select Statement + + + + + + + + + true + false + false + + + + + + + + + + + + + + false + + + + + + + + + + jms_use_text + jms_text_message + 1 + false + + + + false + + + + + + + false + 1 + true + jms_subscriber_receive + + + + + + + + + + + test.RerunTest + + testRerun + + Test successful + 1000 + Test failed + 0001 + An unexpected error occured + 9999 + false + false + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + pop3 + INBOX + + + + -1 + false + false + false + false + false + false + + + + + + + + + + + + + false + false + + false + + false + false + false + false + false + + false + + false + + + false + false + + + + + + + + + + + + true + false + + + + + true + + false + + + + + + + + 1 + 0 + + + + + + + + + + http + + + POST + + + + + + true + false + false + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + false + + + + + + + + + 0 + + + + + + + false + false + false + + + + + + + + + + + + + + + + + false + + + + true + -1 + + + + + + + + + 0 + 0 + omit + false + 0 + + + + + + + + + + + + + + + + + Assertion.response_data + false + 2 + + + + false + false + + + + + + false + false + false + + + + + SizeAssertion.response_network_size + + 1 + + + + + + + + + + false + / + false + false + false + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + 2 + 2 + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + false + false + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337360555000 + 1337360555000 + false + + + + + + + + + diff --git a/bin/testfiles/GenTest26.jmx b/bin/testfiles/GenTest26.jmx new file mode 100644 index 00000000000..7c516565aa8 --- /dev/null +++ b/bin/testfiles/GenTest26.jmx @@ -0,0 +1,1431 @@ + + + + + + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337361781000 + 1337361781000 + false + + + + + + continue + + false + 1 + + 1 + 1 + 1337361769000 + 1337361769000 + false + + + + + + + + + + true + + + + + false + + + + + + + + 1 + + + + true + 1 + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + false + + + + + + + + + + + + + + + + false + + + + , + + + false + true + All threads + false + + + + + + + + + + false + false + false + + + + + + + + false + false + + + + + false + + + + + + + + + + + + + + + + + + 4 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + true + Select 1 + 5000 + + + + true + + 10 + 10000 + DEFAULT + 60000 + + + + + + True + + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + + + + + 1 + + false + + + + + + + + + true + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + this thread only + + throughput + 0.0 + 0.0 + + + + + 300 + + + + 300 + 100.0 + + + + + + + + + + + 300 + 100 + + + + 0 + + + + 0 + 100.0 + + + + + + + + + false + + + + + + + + + + + + + + + false + false + false + false + + + + + + + + Select Statement + + + + + + + + + + + + + + + + + false + + + + + + + + + + + false + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + false + + + + + + + + + + + false + true + false + + + + + + + + + false + false + false + + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Select Statement + + + + + + + + + true + false + false + + + + + + + + + + + + + + false + + + + + + + + + + jms_use_text + jms_text_message + 1 + false + + + + false + + + + + + + false + 1 + true + jms_subscriber_receive + + + + + + + + + + + test.RerunTest + + testRerun + + Test successful + 1000 + Test failed + 0001 + An unexpected error occured + 9999 + false + false + false + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + pop3 + INBOX + + + + -1 + false + false + false + false + false + false + + + + + + + + + + + + + false + false + + false + + false + false + false + false + false + + false + + false + + + false + false + + + + + + + + + + + + true + false + + + + + true + + false + + + + + + + + 1 + 0 + + + + + + + + + + http + + + POST + + + + + + true + false + false + + + + + + + + + + + false + + + + + + + + + + + + false + true + true + false + + + + + + + + Select Statement + + + + + + + + + + + + + false + + + + + + + + + 0 + + + + + + + false + false + false + + + + + + + + + + false + + + + + + + + + + + true + -1 + + + + + + + + + 0 + 0 + omit + false + 0 + + + + + + + + + + + + + + + + + Assertion.response_data + false + 2 + + + + SizeAssertion.response_network_size + + 1 + + + + false + false + + + + + + false + false + false + + + + + + + + + + + false + / + false + false + false + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + 2 + 2 + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + false + false + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337361784000 + 1337361784000 + false + + + + + + + + + diff --git a/bin/testfiles/GenTest27.jmx b/bin/testfiles/GenTest27.jmx new file mode 100644 index 00000000000..4f1138859ed --- /dev/null +++ b/bin/testfiles/GenTest27.jmx @@ -0,0 +1,1415 @@ + + + + + Test Plan including all test elements in JMeter 2.7 + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337352661000 + 1337352661000 + false + + + + + + continue + + false + 1 + + 1 + 1 + 1337300621000 + 1337300621000 + false + + + + + + + + + + true + + + + + false + + + + + + + + 1 + + + + true + 1 + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + false + + + + + + + + + + + + + + + + false + + + + , + + + false + true + shareMode.all + false + + + + + + + + + + false + false + false + + + + + + + + false + false + + + + + false + + + + + + + + + + + + + + + + + + 4 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + true + Select 1 + 5000 + + + + true + + 10 + 10000 + DEFAULT + 60000 + + + + + + True + + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + + + + + 1 + + false + + + + + + + + + true + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + 0 + + throughput + 0.0 + 0.0 + + + + + 300 + + + + 300 + 100.0 + + + + + + + + + + + 300 + 100 + + + + 0 + + + + 0 + 100.0 + + + + + + + + + false + + + + + + + + + + + + + + + false + false + false + false + + + + + + + + Select Statement + + + + + + + + + + + + + + + + + false + + + + + + + + + + + false + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + false + + + + + + + + + + + false + true + false + + + + + + + + + false + false + false + + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Select Statement + + + + + + + + + true + false + false + + + + + + + + + + + + + + false + + + + + + + + + + jms_use_text + jms_text_message + 1 + false + + + + + + + false + + + + + + + false + 1 + true + jms_subscriber_receive + + + + + + + + + + + test.RerunTest + + testRerun + + Test successful + 1000 + Test failed + 0001 + An unexpected error occured + 9999 + false + false + false + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + pop3 + INBOX + + + + -1 + false + false + false + false + false + false + + + + + false + 0 + + + + + + + + + + + + + + + + + + + + false + false + + false + + false + false + false + false + false + + false + + false + + + false + false + + + + + + + + + + + + true + false + + + + + true + + false + + + + + + + + 1 + 0 + + + + + + + + + + http + + + POST + + + + + + true + false + false + + + + + + + + + + + false + + + + + + + + + + + + false + true + true + false + + + + + + + + Select Statement + + + + + + + + + + + + + false + + + + + + + + + 0 + + + + + + + false + false + false + + + + + + + + + + false + + + + + + + + + + + true + -1 + + + + + + + + + 0 + 0 + omit + false + 0 + + + + + + + + + + + + + + + + + Assertion.response_data + false + 2 + + + + SizeAssertion.response_network_size + + 1 + + + + false + false + + + + + + false + false + false + + + + + + + + + + + false + / + false + false + false + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + 2 + 2 + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + false + false + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337352666000 + 1337352666000 + false + + + + + + + + + diff --git a/bin/testfiles/GenTest27_original.jmx b/bin/testfiles/GenTest27_original.jmx new file mode 100644 index 00000000000..67d4688be89 --- /dev/null +++ b/bin/testfiles/GenTest27_original.jmx @@ -0,0 +1,1415 @@ + + + + + Test Plan including all test elements in JMeter 2.7 + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337352661000 + 1337352661000 + false + + + + + + continue + + false + 1 + + 1 + 1 + 1337300621000 + 1337300621000 + false + + + + + + + + + + true + + + + + false + + + + + + + + 1 + + + + true + 1 + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + false + + + + + + + + + + + + + + + + false + + + + , + + + false + true + All threads + false + + + + + + + + + + false + false + false + + + + + + + + false + false + + + + + false + + + + + + + + + + + + + + + + + + 4 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + true + Select 1 + 5000 + + + + true + + 10 + 10000 + DEFAULT + 60000 + + + + + + True + + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + + + + + 1 + + false + + + + + + + + + true + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + this thread only + + throughput + 0.0 + 0.0 + + + + + 300 + + + + 300 + 100.0 + + + + + + + + + + + 300 + 100 + + + + 0 + + + + 0 + 100.0 + + + + + + + + + false + + + + + + + + + + + + + + + false + false + false + false + + + + + + + + Select Statement + + + + + + + + + + + + + + + + + false + + + + + + + + + + + false + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + false + + + + + + + + + + + false + true + false + + + + + + + + + false + false + false + + + + + + + + + + + + + + + + GET + true + false + true + false + false + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Select Statement + + + + + + + + + true + false + false + + + + + + + + + + + + + + false + + + + + + + + + + jms_use_text + jms_text_message + 1 + false + + + + + + + false + + + + + + + false + 1 + true + jms_subscriber_receive + + + + + + + + + + + test.RerunTest + + testRerun + + Test successful + 1000 + Test failed + 0001 + An unexpected error occured + 9999 + false + false + false + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + + + + false + add + + + + + + + + + + pop3 + INBOX + + + + -1 + false + false + false + false + false + false + + + + + false + 0 + + + + + + + + + + + + + + + + + + + + false + false + + false + + false + false + false + false + false + + false + + false + + + false + false + + + + + + + + + + + + true + false + + + + + true + + false + + + + + + + + 1 + 0 + + + + + + + + + + http + + + POST + + + + + + true + false + false + + + + + + + + + + + false + + + + + + + + + + + + false + true + true + false + + + + + + + + Select Statement + + + + + + + + + + + + + false + + + + + + + + + 0 + + + + + + + false + false + false + + + + + + + + + + false + + + + + + + + + + + true + -1 + + + + + + + + + 0 + 0 + omit + false + 0 + + + + + + + + + + + + + + + + + Assertion.response_data + false + 2 + + + + SizeAssertion.response_network_size + + 1 + + + + false + false + + + + + + false + false + false + + + + + + + + + + + false + / + false + false + false + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + 2 + 2 + + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + false + false + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + continue + + false + 1 + + 1 + 1 + 1337352666000 + 1337352666000 + false + + + + + + + + + diff --git a/bin/testfiles/GuiTest.jmx b/bin/testfiles/GuiTest.jmx new file mode 100644 index 00000000000..1210dda835a --- /dev/null +++ b/bin/testfiles/GuiTest.jmx @@ -0,0 +1,827 @@ + + + + + + + + + = + jakarta.apache.org + server + + + = + jakarta.apache.org + server + + + + false + false + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + Assertion.response_data + false + 2 + + + + 0 + + + + 1 + 0 + + + + + + + + + 1 + + + + + WorkBench + Test Plan + Components + Assertions + Assertions + + + + + + + 1 + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + ${__threadNum()} == 3 + + + + + + + + + false + + + + + + + + + + 0 + 0 + + false + 0 + + + + + username + password + category + color + + + + user1 + pass1 + cat1 + red + + + user2 + pass2 + cat2 + green + + + user3 + pass3 + cat3 + + + + true + + + + + + + 60 + false + + + + 300 + + + + 300 + 100.0 + + + + 300 + 100.0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + + + + + + + + true + 1 + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + 0 + 10 + 1 + + + + + + false + false + + false + + + + + + + + + + + POST + true + + false + true + + false + + false + Java + + + + + + + / + / + POST + http + -1 + + + + + + + + + + / + / + POST + + http + + 80 + false + + + false + + + false + + + + + + + + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + org.apache.jmeter.protocol.http.util.accesslog.StandardGenerator + false + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + = + 1000 + SleepTime + + + = + 0x3FF + SleepMask + + + + org.apache.jmeter.protocol.java.test.SleepTest + + + + + + + + + + = + 1000 + SleepTime + + + = + 0x3FF + SleepMask + + + + org.apache.jmeter.protocol.java.test.SleepTest + + + + + + + = + 100 + Sleep_Time + + + = + 0xFF + Sleep_Mask + + + = + JavaTest + Label + + + = + 200 (or any other number) + ResponseCode + + + = + OK (or any other text) + ResponseMessage + + + = + OK + Status + + + = + SamplerData goes here + SamplerData + + + = + ResultData goes here + ResultData + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + ResponseCode=12; +ResponseMessage="Buckle my shoe"; +IsSuccess=false; +Label="Sticky"; +// FileName is the Script file name +// bsh.args[1] == "quick" +return "This will go into the Response Data field"; + the quick brown fox + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + Select 1 + 10000 + 60000 + true + 10 + + 5000 + + + true + + + + + + + + + 1 + + 50 + + + + org.apache.jmeter.protocol.jdbc.util.JMeter19ConnectionPool + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + false + + + + + + + + + + + + false + + + + + + + diff --git a/bin/testfiles/GuiTest231.jmx b/bin/testfiles/GuiTest231.jmx new file mode 100644 index 00000000000..f64dd194df7 --- /dev/null +++ b/bin/testfiles/GuiTest231.jmx @@ -0,0 +1,1186 @@ + + + + + All GUI test elements in JMeter 2.3.1 + false + false + + + + + + + + + false + 1 + + 1 + 1 + 1210207165000 + 1210207165000 + false + continue + + + + + + + + Input + Output + true + + + + If condition + false + + + + + + + + 1 + + + + + + true + 123 + + + + + WorkBench + GUI231 + Thread Group + Controllers + Simple Controller + + + + + + + 1 + + + + + + 321 + + + + 1 + 7 + + + + 0 + true + 111 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + false + + + + Condition + + + + + + + + + , + encoding + filename + true + false + a,b,c,d + + + + + + NAME + VALUE + = + + + + + + USER + PASS + + + + + + server + remote + local + true + true + true + + + + + + + false + Value + = + true + Name + + + + localhost + 8080 + http + utf8 + /path + + + + + + base + user + pass + domain + realm + + + + + + + + value + domain + path + false + 0 + true + true + + + false + + + + + + Browser + JMeter + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + true + Select 1 + 5000 + variable + url + driver + true + pass + 10 + 10000 + 60000 + user + + + + server + port + DN + false + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + server + true + port + false + 1234 + text + + + + + + + file + p1 + script + + + + 0 + + throughput + 11.0 + 0.0 + + + + + 12 + + + + 3001 + + + + 3001 + 100.01 + + + + 01 + 100.01 + + + + + + + file + p1 + script + + + + 11 + 33 + 22 + REF + ## + false + + + + + Name + + + + user1 + + + user2 + + + false + + + + + + + name + prefix + 11 + 100 + 12 + suffix + + + + + JSESSION + false + true + false + true + + + + + JMS Sub and JMS P-P not available (JavaMail) + + + + server + remote + local + true + true + false + user + pass + + + + + + + false + Value + = + true + Name + + + + server + port + http + encoding + /path + GET + false + true + true + false + false + match + + + + path + mime + name + + + + + + + + + + server + port + https + utf8 + /delete + DELETE + false + true + true + false + false + + Java + + + + + + + localhost + xxx + + + + GET + false + true + true + false + false + + HttpClient3.1 + + + + + + + URL + Data + filename + Action + true + false + + + + + + + server + port + http + path + wsdl + POST + action + Data + File + Folder + timeout + true + false + true + Host + 9999 + + + + + + + server + false + log + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + port + org.apache.jmeter.protocol.http.util.accesslog.LogFilter + + + + scriptfile + javascript + params + script + + + + script + file + a,b,c + + + + woolfel.DummyTestCase + + + Test successful + 1000 + Test failed + 0001 + false + false + false + testMethodPass + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + POOL + query + v + t + Select Statement + + + + false + factory + url + conn + topic + user + pass + message + + + Textarea + Object Message + count + Not Required + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + server + port + dn + false + user + pass + + + + imap + Trash + -1 + false + server + user + pass + + + + server + true + port + false + 1000 + text + user + pass + + + + false + true + false + + + + 1 + 0 + 1234 + + + + + + + + must match + + Assertion.response_data + false + 1 + + + + script + file + a,b,c + + + + 111 + + + + 0 + 0 + omit + false + 0 + sadaasd + + + + abcd + + + + 44 + 4 + + + + + + schema + + + + false + false + false + false + false + /xpath + + + + + + + file + p1 + script + + + + + + false + ref + exp + $1$ + DEF + 12 + + + + def + ref + /abc + false + true + + + + 1 + + + + prefix + false + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + b + a + c + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + 2 + 2 + failed + from + server + ok + to + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + diff --git a/bin/testfiles/GuiTest231_original.jmx b/bin/testfiles/GuiTest231_original.jmx new file mode 100644 index 00000000000..d61f6eeaa10 --- /dev/null +++ b/bin/testfiles/GuiTest231_original.jmx @@ -0,0 +1,1222 @@ + + + + + All GUI test elements in JMeter 2.3.1 + false + false + + + + + + + + + false + 1 + + 1 + 1 + 1210207165000 + 1210207165000 + false + continue + + + + + + + + Input + Output + true + + + + If condition + false + + + + + + + + 1 + + + + + + true + 123 + + + + + WorkBench + GUI231 + Thread Group + Controllers + Simple Controller + + + + + + + 1 + + + + + + 321 + + + + 1 + 7 + + + + 0 + true + 111 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + false + + + + Condition + + + + + + + + + , + encoding + filename + true + false + a,b,c,d + + + + + + NAME + VALUE + = + + + + + + USER + PASS + + + + + + server + remote + local + true + true + true + + + + + + + false + Value + = + true + Name + + + + localhost + 8080 + http + utf8 + /path + + + + + + base + user + pass + domain + realm + + + + + + + + value + domain + path + false + 0 + true + true + + + false + + + + + + Browser + JMeter + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + true + Select 1 + 5000 + variable + url + driver + true + pass + 10 + 10000 + 60000 + user + + + + server + port + DN + false + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + server + true + port + false + 1234 + text + + + + + + + file + p1 + script + + + + this thread only + + throughput + 11.0 + 0.0 + + + + + 12 + + + + 3001 + + + + 3001 + 100.01 + + + + 01 + 100.01 + + + + + + + file + p1 + script + + + + 11 + 33 + 22 + REF + ## + false + + + + + Name + + + + user1 + + + user2 + + + false + + + + + + + name + prefix + 11 + 100 + 12 + suffix + + + + + JSESSION + false + true + false + true + + + + users.xml + + + + + JMS Sub and JMS P-P not available (JavaMail) + + + + server + remote + local + true + true + false + user + pass + + + + + + + false + Value + = + true + Name + + + + server + port + http + encoding + /path + GET + false + true + true + false + false + match + + + + path + mime + name + + + + + + + + + + server + port + https + utf8 + /delete + DELETE + false + true + true + false + false + + Java + + + + + + + localhost + xxx + + + + GET + false + true + true + false + false + + HttpClient3.1 + + + + + + + URL + Data + filename + Action + true + false + + + + + + + server + port + http + path + wsdl + POST + action + Data + File + Folder + timeout + true + false + true + Host + 9999 + + + + + + + server + false + log + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + port + org.apache.jmeter.protocol.http.util.accesslog.LogFilter + + + + scriptfile + javascript + params + script + + + + script + file + a,b,c + + + + woolfel.DummyTestCase + + + Test successful + 1000 + Test failed + 0001 + false + false + false + testMethodPass + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + POOL + query + v + t + Select Statement + + + + false + factory + url + conn + topic + user + pass + message + + + Textarea + Object Message + count + Not Required + + + + + + + 2 + + + + false + false + + false + false + + + + + + + + + + server + port + dn + false + user + pass + + + + imap + Trash + -1 + false + server + user + pass + + + + server + true + port + false + 1000 + text + user + pass + + + + false + true + false + + + + 1 + 0 + 1234 + + + + + + + + must match + + Assertion.response_data + false + 1 + + + + script + file + a,b,c + + + + 111 + + + + 0 + 0 + omit + false + 0 + sadaasd + + + + abcd + + + + 44 + 4 + + + + + + schema + + + + false + false + false + false + false + /xpath + + + + + + + file + p1 + script + + + + + + false + ref + exp + $1$ + DEF + 12 + + + + def + ref + /abc + false + true + + + + 1 + + + + prefix + false + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + b + a + c + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + 2 + 2 + failed + from + server + ok + to + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + + diff --git a/bin/testfiles/GuiTest_original.jmx b/bin/testfiles/GuiTest_original.jmx new file mode 100644 index 00000000000..4c5a34fd96e --- /dev/null +++ b/bin/testfiles/GuiTest_original.jmx @@ -0,0 +1,867 @@ + + + + + + + + + = + jakarta.apache.org + server + + + = + jakarta.apache.org + server + + + + false + false + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + Assertion.response_data + false + 2 + + + + 0 + + + + 1 + 0 + + + + + + + + + 1 + + + + + WorkBench + Test Plan + Components + Assertions + Assertions + + + + + + + 1 + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + ${__threadNum()} == 3 + + + + + + + + + false + + + + + + + + + + 0 + 0 + + false + 0 + + + + + username + password + category + color + + + + user1 + pass1 + cat1 + red + + + user2 + pass2 + cat2 + green + + + user3 + pass3 + cat3 + + + + true + + + + + + + 60 + false + + + + 300 + + + + 300 + 100.0 + + + + 300 + 100.0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + + + + + + + + true + 1 + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + 0 + 10 + 1 + + + + + + false + false + + false + + + + users.xml + + + + + + + + + + + POST + true + + false + true + + false + + false + Java + + + + + + + / + / + POST + http + -1 + + + + + + + + + + / + / + POST + + http + + 80 + false + + + false + + + false + + + + + + + + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + false + 80 + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + org.apache.jmeter.protocol.http.util.accesslog.StandardGenerator + false + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + = + 1000 + SleepTime + + + = + 0x3FF + SleepMask + + + + org.apache.jmeter.protocol.java.test.SleepTest + + + + + + + + + + = + 1000 + SleepTime + + + = + 0x3FF + SleepMask + + + + org.apache.jmeter.protocol.java.test.SleepTest + + + + + + + = + 100 + Sleep_Time + + + = + 0xFF + Sleep_Mask + + + = + JavaTest + Label + + + = + 200 (or any other number) + ResponseCode + + + = + OK (or any other text) + ResponseMessage + + + = + OK + Status + + + = + SamplerData goes here + SamplerData + + + = + ResultData goes here + ResultData + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + ResponseCode=12; +ResponseMessage="Buckle my shoe"; +IsSuccess=false; +Label="Sticky"; +// FileName is the Script file name +// bsh.args[1] == "quick" +return "This will go into the Response Data field"; + the quick brown fox + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + Select 1 + 10000 + 60000 + true + 10 + + 5000 + + + true + + + + + + + + + 1 + + 50 + + + + org.apache.jmeter.protocol.jdbc.util.JMeter19ConnectionPool + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + false + + + + + + + + + + + + false + + + + + + + diff --git a/bin/testfiles/HTMLParserTestCase.all b/bin/testfiles/HTMLParserTestCase.all new file mode 100644 index 00000000000..05b9c8d1330 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCase.all @@ -0,0 +1,18 @@ +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif diff --git a/bin/testfiles/HTMLParserTestCase.html b/bin/testfiles/HTMLParserTestCase.html new file mode 100644 index 00000000000..aae76531404 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCase.html @@ -0,0 +1,45 @@ + + + + + + + + +
+ + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/HTMLParserTestCase.set b/bin/testfiles/HTMLParserTestCase.set new file mode 100644 index 00000000000..41a7f21bce0 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCase.set @@ -0,0 +1,16 @@ +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/tr.gif diff --git a/bin/testfiles/HTMLParserTestCase2.html b/bin/testfiles/HTMLParserTestCase2.html new file mode 100644 index 00000000000..c5f7068d28a --- /dev/null +++ b/bin/testfiles/HTMLParserTestCase2.html @@ -0,0 +1,7 @@ + + + Empty HTML Test + + + + diff --git a/bin/testfiles/HTMLParserTestCase3.html b/bin/testfiles/HTMLParserTestCase3.html new file mode 100644 index 00000000000..41219289e55 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCase3.html @@ -0,0 +1,7 @@ + + + + Empty HTML Test with mixed up tags</tattle> +</body> + <body> +</html> diff --git a/bin/testfiles/HTMLParserTestCaseBase.all b/bin/testfiles/HTMLParserTestCaseBase.all new file mode 100644 index 00000000000..e3490d285f1 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseBase.all @@ -0,0 +1,14 @@ +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif diff --git a/bin/testfiles/HTMLParserTestCaseBase.set b/bin/testfiles/HTMLParserTestCaseBase.set new file mode 100644 index 00000000000..71b70c5c92e --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseBase.set @@ -0,0 +1,12 @@ +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseFrames.all b/bin/testfiles/HTMLParserTestCaseFrames.all new file mode 100644 index 00000000000..27aa0bb0cec --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseFrames.all @@ -0,0 +1,4 @@ +http://localhost/audio/clap.wav +http://localhost/banner.html +http://localhost/home_nav.html +http://localhost/main.html diff --git a/bin/testfiles/HTMLParserTestCaseFrames.html b/bin/testfiles/HTMLParserTestCaseFrames.html new file mode 100644 index 00000000000..563332a3a81 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseFrames.html @@ -0,0 +1,20 @@ +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<title>Frame test case + + + + + + + + + + <body> + <p>This page uses frames, but your browser doesn't support them.</p> + </body> + + diff --git a/bin/testfiles/HTMLParserTestCaseFrames.set b/bin/testfiles/HTMLParserTestCaseFrames.set new file mode 100644 index 00000000000..27aa0bb0cec --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseFrames.set @@ -0,0 +1,4 @@ +http://localhost/audio/clap.wav +http://localhost/banner.html +http://localhost/home_nav.html +http://localhost/main.html diff --git a/bin/testfiles/HTMLParserTestCaseWithBaseHRef.html b/bin/testfiles/HTMLParserTestCaseWithBaseHRef.html new file mode 100644 index 00000000000..3f520e8dd39 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithBaseHRef.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/HTMLParserTestCaseWithBaseHRef2.html b/bin/testfiles/HTMLParserTestCaseWithBaseHRef2.html new file mode 100644 index 00000000000..071c17e2892 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithBaseHRef2.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/HTMLParserTestCaseWithComments.html b/bin/testfiles/HTMLParserTestCaseWithComments.html new file mode 100644 index 00000000000..f87c90a91fd --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithComments.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional1.html b/bin/testfiles/HTMLParserTestCaseWithConditional1.html new file mode 100644 index 00000000000..332f93777df --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional1.html @@ -0,0 +1,51 @@ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional1_FF.all b/bin/testfiles/HTMLParserTestCaseWithConditional1_FF.all new file mode 100644 index 00000000000..1b311628088 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional1_FF.all @@ -0,0 +1,18 @@ +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional1_IE6.all b/bin/testfiles/HTMLParserTestCaseWithConditional1_IE6.all new file mode 100644 index 00000000000..1b311628088 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional1_IE6.all @@ -0,0 +1,18 @@ +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional1_IE7.all b/bin/testfiles/HTMLParserTestCaseWithConditional1_IE7.all new file mode 100644 index 00000000000..2205426306e --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional1_IE7.all @@ -0,0 +1,19 @@ +http://localhost/mydir/fileForIE7.css +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional1_IE8.all b/bin/testfiles/HTMLParserTestCaseWithConditional1_IE8.all new file mode 100644 index 00000000000..cdb9e3a9231 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional1_IE8.all @@ -0,0 +1,19 @@ +http://localhost/mydir/fileForIE8.css +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional2.html b/bin/testfiles/HTMLParserTestCaseWithConditional2.html new file mode 100644 index 00000000000..fbe27eac5f0 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional2.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional2_FF.all b/bin/testfiles/HTMLParserTestCaseWithConditional2_FF.all new file mode 100644 index 00000000000..5cd48dba92a --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional2_FF.all @@ -0,0 +1,20 @@ +http://localhost/mydir/fileForIE7or8.css +http://localhost/mydir/fileForIE7or8or9.css +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional2_IE7.all b/bin/testfiles/HTMLParserTestCaseWithConditional2_IE7.all new file mode 100644 index 00000000000..0b9e4650fa1 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional2_IE7.all @@ -0,0 +1,21 @@ +http://localhost/mydir/fileForIE7.css +http://localhost/mydir/fileForIE7or8.css +http://localhost/mydir/fileForIE7or8or9.css +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional2_IE8.all b/bin/testfiles/HTMLParserTestCaseWithConditional2_IE8.all new file mode 100644 index 00000000000..5cd48dba92a --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional2_IE8.all @@ -0,0 +1,20 @@ +http://localhost/mydir/fileForIE7or8.css +http://localhost/mydir/fileForIE7or8or9.css +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional2_IE9.all b/bin/testfiles/HTMLParserTestCaseWithConditional2_IE9.all new file mode 100644 index 00000000000..f03ccce609e --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional2_IE9.all @@ -0,0 +1,19 @@ +http://localhost/mydir/fileForIE7or8or9.css +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional3.html b/bin/testfiles/HTMLParserTestCaseWithConditional3.html new file mode 100644 index 00000000000..ec0aec1b88f --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional3.html @@ -0,0 +1,50 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional3_FF.all b/bin/testfiles/HTMLParserTestCaseWithConditional3_FF.all new file mode 100644 index 00000000000..1b311628088 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional3_FF.all @@ -0,0 +1,18 @@ +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional3_IE10.all b/bin/testfiles/HTMLParserTestCaseWithConditional3_IE10.all new file mode 100644 index 00000000000..1b311628088 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional3_IE10.all @@ -0,0 +1,18 @@ +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional3_IE55.all b/bin/testfiles/HTMLParserTestCaseWithConditional3_IE55.all new file mode 100644 index 00000000000..b969c091841 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional3_IE55.all @@ -0,0 +1,19 @@ +http://localhost/mydir/fileForIE55.css +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithConditional3_IE6.all b/bin/testfiles/HTMLParserTestCaseWithConditional3_IE6.all new file mode 100644 index 00000000000..1b311628088 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithConditional3_IE6.all @@ -0,0 +1,18 @@ +http://localhost/mydir/images/body&soul.gif +http://localhost/mydir/images/table.gif +http://localhost/mydir/images/tr.gif +http://localhost/mydir/images/td.gif +http://localhost/mydir/images/image-a.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-b.gif +http://localhost/mydir/images/image-c.gif +http://localhost/mydir/images/image-d.gif +http://localhost/mydir/images/image-e.gif +http://localhost/mydir/images/sub/image-f.gif +http://localhost/mydir/images/image-a2.gif +http://localhost/mydir/images/image-b2.gif +http://localhost/mydir/images/sub/image-c2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-d2.gif +http://localhost/mydir/images/image-e2.gif +http://localhost/mydir/images/image-f2.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestCaseWithMissingBaseHRef.html b/bin/testfiles/HTMLParserTestCaseWithMissingBaseHRef.html new file mode 100644 index 00000000000..2f0f7ab9d54 --- /dev/null +++ b/bin/testfiles/HTMLParserTestCaseWithMissingBaseHRef.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/HTMLParserTestFile_2.all b/bin/testfiles/HTMLParserTestFile_2.all new file mode 100644 index 00000000000..10294de6da0 --- /dev/null +++ b/bin/testfiles/HTMLParserTestFile_2.all @@ -0,0 +1,8 @@ +file:HTMLParserTestFile_2_files/style.css +file:HTMLParserTestFile_2_files/halfbanner.htm +file:HTMLParserTestFile_2_files/jakarta-logo.gif +file:HTMLParserTestFile_2_files/logo.jpg +file:HTMLParserTestFile_2_files/http-config-example.png +file:HTMLParserTestFile_2_files/scoping1.png +file:HTMLParserTestFile_2_files/scoping2.png +file:HTMLParserTestFile_2_files/scoping3.png diff --git a/bin/testfiles/HTMLParserTestFile_2.csv b/bin/testfiles/HTMLParserTestFile_2.csv new file mode 100644 index 00000000000..16442ed7b65 --- /dev/null +++ b/bin/testfiles/HTMLParserTestFile_2.csv @@ -0,0 +1,2 @@ +label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,grpThreads,allThreads,URL,Filename,Latency,Encoding,SampleCount,ErrorCount +Download embedded,200,OK,Thread Group 1-1,text,true,,90110,1,1,file:testfiles/HTMLParserTestFile_2.html,,0,UTF-8,1,0 diff --git a/bin/testfiles/HTMLParserTestFile_2.html b/bin/testfiles/HTMLParserTestFile_2.html new file mode 100644 index 00000000000..7111bc46c05 --- /dev/null +++ b/bin/testfiles/HTMLParserTestFile_2.html @@ -0,0 +1,1277 @@ + + + + + + + + + + + + + +JMeter - User's Manual: Elements of a Test Plan + + + + + + + + + + +
+ + + +Jakarta + +JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +4. Elements of a Test Plan +
+
+

+The Test Plan object has a checkbox called "Functional Testing". If selected, it +will cause JMeter to record the data returned from the server for each sample. If you have +selected a file in your test listeners, this data will be written to file. This can be useful if +you are doing a small run to ensure that JMeter is configured correctly, and that your server +is returning the expected results. The consequence is that the file will grow huge quickly, and +JMeter's performance will suffer. This option should be off if you are doing stress-testing (it +is off by default). +

+

+If you are not recording the data to file, this option makes no difference. +

+

+You can also use the Configuration button on a listener to decide what fields to save. +

+ + + + +
+ +4.1 ThreadGroup + +
+
+

+Thread group elements are the beginning points of any test plan. +All controllers and samplers must be under a thread group. +Other elements, e.g. Listeners, may be placed directly under the test plan, +in which case they will apply to all the thread groups. +As the name implies, the thread group +element controls the number of threads JMeter will use to execute your test. The +controls for a thread group allow you to: + +

    +
  • +Set the number of threads +
  • + + +
  • +Set the ramp-up period +
  • + + +
  • +Set the number of times to execute the test +
  • + + +
+

+

+Each thread will execute the test plan in its entirety and completely independently +of other test threads. Multiple threads are used to simulate concurrent connections +to your server application. +

+

+The ramp-up period tells JMeter how long to take to "ramp-up" to the full number of +threads chosen. If 10 threads are used, and the ramp-up period is 100 seconds, then +JMeter will take 100 seconds to get all 10 threads up and running. Each thread will +start 10 (100/10) seconds after the previous thread was begun. If there are 30 threads +and a ramp-up period of 120 seconds, then each successive thread will be delayed by 4 seconds. +

+

+Ramp-up needs to be long enough to avoid too large a work-load at the start +of a test, and short enough that the last threads start running before +the first ones finish (unless one wants that to happen). + +

+

+ +Start with Ramp-up = number of threads and adjust up or down as needed. + +

+

+By default, the thread group is configured to loop once through its elements. +

+

+Version 1.9 introduces a test run + +scheduler + +. + Click the checkbox at the bottom of the Thread Group panel to reveal extra fields + in which you can enter the start and end times of the run. + When the test is started, JMeter will wait if necessary until the start-time has been reached. + At the end of each cycle, JMeter checks if the end-time has been reached, and if so, the run is stopped, + otherwise the test is allowed to continue until the iteration limit is reached. +

+

+Alternatively, one can use the relative delay and duration fields. + Note that delay overrides start-time, and duration over-rides end-time. +

+
+

+ + + + +
+ +4.2 Controllers + +
+
+

+ +JMeter has two types of Controllers: Samplers and Logical Controllers. +These drive the processing of a test. + +

+

+Samplers tell JMeter to send requests to a server. For +example, add an HTTP Request Sampler if you want JMeter +to send an HTTP request. You can also customize a request by adding one +or more Configuration Elements to a Sampler. For more +information, see + + +Samplers + +. +

+

+Logical Controllers let you customize the logic that JMeter uses to +decide when to send requests. For example, you can add an Interleave +Logic Controller to alternate between two HTTP Request Samplers. +For more information, see + +Logical Controllers + +. +

+
+

+ + + + +
+ +4.2.1 Samplers + +
+
+

+ +Samplers tell JMeter to send requests to a server and wait for a response. +They are processed in the order they appear in the tree. +Controllers can be used to modify the number of repetitions of a sampler. + +

+

+ +JMeter samplers include: + +

    + + +
  • +FTP Request +
  • + + +
  • +HTTP Request +
  • + + +
  • +JDBC Request +
  • + + +
  • +Java object request +
  • + + +
  • +LDAP Request +
  • + + +
  • +SOAP/XML-RPC Request +
  • + + +
  • +WebService (SOAP) Request +
  • + + +
+ +Each sampler has several properties you can set. +You can further customize a sampler by adding one or more Configuration Elements to the Test Plan. + +

+

+If you are going to send multiple requests of the same type (for example, +HTTP Request) to the same server, consider using a Defaults Configuration +Element. Each controller has one or more Defaults elements (see below). +

+

+Remember to add a Listener to your test plan to view and/or store the +results of your requests to disk. +

+

+If you are interested in having JMeter perform basic validation on +the response of your request, add an + +Assertion + + to +the sampler. For example, in stress testing a web application, the server +may return a successful "HTTP Response" code, but the page may have errors on it or +may be missing sections. You could add assertions to check for certain HTML tags, +common error strings, and so on. JMeter lets you create these assertions using regular +expressions. +

+

+ +JMeter's built-in samplers + +

+
+

+ + + + +
+ +4.2.2 Logic Controllers + +
+
+

+Logic Controllers let you customize the logic that JMeter uses to +decide when to send requests. +Logic Controllers can change the order of requests coming from their +child elements. They can modify the requests themselves, cause JMeter to repeat +requests, etc. + +

+

+To understand the effect of Logic Controllers on a test plan, consider the +following test tree: +

+

+ + +

    + + +
  • +Test Plan +
  • + + +
      + + +
    • +Thread Group +
    • + + +
        + + +
      • +Once Only Controller +
      • + + + + + +
      • +Load Search Page (HTTP Sampler) +
      • + + +
      • +Interleave Controller +
      • + + +
          + + +
        • +Search "A" (HTTP Sampler) +
        • + + +
        • +Search "B" (HTTP Sampler) +
        • + + +
        • +HTTP default request (Configuration Element) +
        • + + +
        + + +
      • +HTTP default request (Configuration Element) +
      • + + +
      • +Cookie Manager (Configuration Element) +
      • + + +
      + + +
    + + +
+ + +

+

+The first thing about this test is that the login request will be executed only +the first time through. Subsequent iterations will skip it. This is due to the +effects of the +Once Only Controller +. +

+

+After the login, the next Sampler loads the search page (imagine a +web application where the user logs in, and then goes to a search page to do a search). This +is just a simple request, not filtered through any Logic Controller. +

+

+After loading the search page, we want to do a search. Actually, we want to do +two different searches. However, we want to re-load the search page itself between +each search. We could do this by having 4 simple HTTP request elements (load search, +search "A", load search, search "B"). Instead, we use the +Interleave Controller + which passes on one child request each time through the test. It keeps the +ordering (ie - it doesn't pass one on at random, but "remembers" its place) of its +child elements. Interleaving 2 child requests may be overkill, but there could easily have +been 8, or 20 child requests. +

+

+Note the +HTTP Request Defaults + that +belongs to the Interleave Controller. Imagine that "Search A" and "Search B" share +the same PATH info (an HTTP request specification includes domain, port, method, protocol, +path, and arguments, plus other optional items). This makes sense - both are search requests, + hitting the same back-end search engine (a servlet or cgi-script, let's say). Rather than + configure both HTTP Samplers with the same information in their PATH field, we + can abstract that information out to a single Configuration Element. When the Interleave + Controller "passes on" requests from "Search A" or "Search B", it will fill in the blanks with + values from the HTTP default request Configuration Element. So, we leave the PATH field + blank for those requests, and put that information into the Configuration Element. In this +case, this is a minor benefit at best, but it demonstrates the feature. +

+

+The next element in the tree is another HTTP default request, this time added to the +Thread Group itself. The Thread Group has a built-in Logic Controller, and thus, it uses +this Configuration Element exactly as described above. It fills in the blanks of any +Request that passes through. It is extremely useful in web testing to leave the DOMAIN +field blank in all your HTTP Sampler elements, and instead, put that information +into an HTTP default request element, added to the Thread Group. By doing so, you can +test your application on a different server simply by changing one field in your Test Plan. +Otherwise, you'd have to edit each and every Sampler. +

+

+The last element is a +HTTP Cookie Manager +. A Cookie Manager should be added to all web tests - otherwise JMeter will +ignore cookies. By adding it at the Thread Group level, we ensure that all HTTP requests +will share the same cookies. +

+

+Logic Controllers can be combined to achieve various results. See the list of + +built-in +Logic Controllers + +. +

+
+

+ + + + +
+ +4.2.3 Test Fragments + +
+
+

+The Test Fragment element is a special type of + +controller + + that +exists on the Test Plan tree at the same level as the Thread Group element. It is distinguished +from a Thread Group in that it is not executed unless it is +referenced by either a +Module Controller + or an +Include_Controller +. + +

+

+This element is purely for code re-use within Test Plans and was introduced in Version 2.5 +

+
+

+ + + + +
+ +4.3 Listeners + +
+
+

+Listeners provide access to the information JMeter gathers about the test cases while +JMeter runs. The +Graph Results + listener plots the response times on a graph. +The "View Results Tree" Listener shows details of sampler requests and +responses, and can display basic HTML and XML representations of the +response. +Other listeners provide summary or aggregation information. + +

+

+ +Additionally, listeners can direct the data to a file for later use. +Every listener in JMeter provides a field to indicate the file to store data to. +There is also a Configuration button which can be used to choose which fields to save, and whether to use CSV or XML format. + + +Note that all Listeners save the same data; the only difference is in the way the data is presented on the screen. + + + +

+

+ +Listeners can be added anywhere in the test, including directly under the test plan. +They will collect data only from elements at or below their level. + +

+

+There are several + +listeners + + +that come with JMeter. +

+
+

+ + + + +
+ +4.4 Timers + +
+
+

+By default, a JMeter thread sends requests without pausing between each request. +We recommend that you specify a delay by adding one of the available timers to +your Thread Group. If you do not add a delay, JMeter could overwhelm your server by +making too many requests in a very short amount of time. +

+

+The timer will cause JMeter to delay a certain amount of time + +before + + each +sampler which is in its + +scope + +. +

+

+ +If you choose to add more than one timer to a Thread Group, JMeter takes the sum of +the timers and pauses for that amount of time before executing the samplers to which the timers apply. +Timers can be added as children of samplers or controllers in order to restrict the samplers to which they are applied. + +

+

+ +To provide a pause at a single place in a test plan, one can use the +Test Action + Sampler. + +

+
+

+ + + + +
+ +4.5 Assertions + +
+
+

+Assertions allow you to assert facts about responses received from the +server being tested. Using an assertion, you can essentially "test" that your +application is returning the results you expect it to. +

+

+For instance, you can assert that the response to a query will contain some +particular text. The text you specify can be a Perl-style regular expression, and +you can indicate that the response is to contain the text, or that it should match +the whole response. +

+

+You can add an assertion to any Sampler. For example, you can +add an assertion to a HTTP Request that checks for the text, "</HTML>". JMeter +will then check that the text is present in the HTTP response. If JMeter cannot find the +text, then it will mark this as a failed request. +

+

+ +Note that assertions apply to all samplers which are in its + +scope + +. +To restrict the assertion to a single sampler, add the assertion as a child of the sampler. + +

+

+To view the assertion results, add an Assertion Listener to the Thread Group. +Failed Assertions will also show up in the Tree View and Table Listeners, +and will count towards the error %age for example in the Aggregate and Summary reports. + +

+
+

+ + + + +
+ +4.6 Configuration Elements + +
+
+

+A configuration element works closely with a Sampler. Although it does not send requests +(except for +HTTP Proxy Server +), it can add to or modify requests. +

+

+A configuration element is accessible from only inside the tree branch where you place the element. +For example, if you place an HTTP Cookie Manager inside a Simple Logic Controller, the Cookie Manager will +only be accessible to HTTP Request Controllers you place inside the Simple Logic Controller (see figure 1). +The Cookie Manager is accessible to the HTTP requests "Web Page 1" and "Web Page 2", but not "Web Page 3". +

+

+Also, a configuration element inside a tree branch has higher precedence than the same element in a "parent" +branch. For example, we defined two HTTP Request Defaults elements, "Web Defaults 1" and "Web Defaults 2". +Since we placed "Web Defaults 1" inside a Loop Controller, only "Web Page 2" can access it. The other HTTP +requests will use "Web Defaults 2", since we placed it in the Thread Group (the "parent" of all other branches). +

+


+Figure 1 - + Test Plan Showing Accessability of Configuration Elements +

+

+

+ +
+The +User Defined Variables + Configuration element is different. +It is processed at the start of a test, no matter where it is placed. +For simplicity, it is suggested that the element is placed only at the start of a Thread Group. + +
+

+
+

+ + + + +
+ +4.7 Pre-Processor Elements + +
+
+

+A Pre-Processor executes some action prior to a Sampler Request being +made. +If a Pre-Processor is attached to a Sampler element, then it will +execute just prior to that sampler element running. +A Pre-Processor is most often used to modify the settings of a Sample +Request just before it runs, or to update variables that aren't +extracted from response text. +See the + + +scoping rules + + + for more details on when Pre-Processors are executed. +

+
+

+ + + + +
+ +4.8 Post-Processor Elements + +
+
+

+A Post-Processor executes some action after a Sampler Request has been made. +If a Post-Processor is attached to a Sampler element, then it will execute just after that sampler element runs. +A Post-Processor is most often used to process the response data, often to extract values from it. +See the + + +scoping rules + + + for more details on when Post-Processors are executed. +

+
+

+ + + + +
+ +4.9 Execution order + +
+
+
    + + +
  1. +Configuration elements +
  2. + + +
  3. +Pre-Processors +
  4. + + +
  5. +Timers +
  6. + + +
  7. +Sampler +
  8. + + +
  9. +Post-Processors (unless SampleResult is null) +
  10. + + +
  11. +Assertions (unless SampleResult is null) +
  12. + + +
  13. +Listeners (unless SampleResult is null) +
  14. + + +
+

+

+ +
+Please note that Timers, Assertions, Pre- and Post-Processors are only processed if there is a sampler to which they apply. +Logic Controllers and Samplers are processed in the order in which they appear in the tree. +Other test elements are processed according to the scope in which they are found, and the type of test element. +[Within a type, elements are processed in the order in which they appear in the tree]. + +
+

+

+ +For example, in the following test plan: + +

    + + +
  • +Controller +
  • + + +
      + + +
    • +Post-Processor 1 +
    • + + +
    • +Sampler 1 +
    • + + +
    • +Sampler 2 +
    • + + +
    • +Timer 1 +
    • + + +
    • +Assertion 1 +
    • + + +
    • +Pre-Processor 1 +
    • + + +
    • +Timer 2 +
    • + + +
    • +Post-Processor 2 +
    • + + +
    + + +
+ +The order of execution would be: + +
+Pre-Processor 1
+Timer 1
+Timer 2
+Sampler 1
+Post-Processor 1
+Post-Processor 2
+Assertion 1
+
+Pre-Processor 1
+Timer 1
+Timer 2
+Sampler 2
+Post-Processor 1
+Post-Processor 2
+Assertion 1
+
+
+ + +

+
+

+ + + + +
+ +4.10 Scoping Rules + +
+
+

+ +The JMeter test tree contains elements that are both hierarchical and +ordered. Some elements in the test trees are strictly hierarchical +(Listeners, Config Elements, Post-Procesors, Pre-Processors, Assertions, + Timers), and some are primarily ordered (controllers, samplers). When +you create your test plan, you will create an ordered list of sample +request (via Samplers) that represent a set of steps to be executed. +These requests are often organized within controllers that are also +ordered. Given the following test tree: +

+


+Example test tree +

+

+The order of requests will be, One, Two, Three, Four. +

+

+Some controllers affect the order of their subelements, and you can read about these specific controllers in + +the component reference + +. +

+

+Other elements are hierarchical. An Assertion, for instance, is hierarchical in the test tree. +If its parent is a request, then it is applied to that request. If its +parent is a Controller, then it affects all requests that are descendants of +that Controller. In the following test tree: +

+


+Hierarchy example +

+

+Assertion #1 is applied only to Request One, while Assertion #2 is applied to Requests Two and Three. +

+

+Another example, this time using Timers: +

+


+complex example +

+

+In this example, the requests are named to reflect the order in which +they will be executed. Timer #1 will apply to Requests Two, Three, and +Four (notice how order is irrelevant for hierarchical elements). +Assertion #1 will apply only to Request Three. Timer #2 will affect all + the requests. +

+

+Hopefully these examples make it clear how configuration (hierarchical) +elements are applied. If you imagine each Request being passed up the +tree branches, to its parent, then to its parent's parent, etc, and each + time collecting all the configuration elements of that parent, then you + will see how it works. +

+ + +The Configuration elements Header Manager, Cookie Manager and Authorization manager are +treated differently from the Configuration Default elements. +The settings from the Configuration Default elements are merged into a set of values that the Sampler has access to. +However, the settings from the Managers are not merged. +If more than one Manager is in the scope of a Sampler, +only one Manager is used, but there is currently no way to specify + +which + + is used. + + +
+

+ + + + +
+ +4.11 Properties and Variables + +
+
+

+ +JMeter + +properties + + are defined in jmeter.properties (see + +Gettting Started - Configuring JMeter + + for more details). + +
+
+ +Properties are global to jmeter, and are mostly used to define some of the defaults JMeter uses. +For example the property remote_hosts defines the servers that JMeter will try to run remotely. +Properties can be referenced in test plans +- see + +Functions - read a property + + - +but cannot be used for thread-specific values. + +

+

+ +JMeter + +variables + + are local to each thread. The values may be the same for each thread, or they may be different. + +
+
+ +If a variable is updated by a thread, only the thread copy of the variable is changed. +For example the +Regular Expression Extractor + Post-Processor +will set its variables according to the sample that its thread has read, and these can be used later +by the same thread. +For details of how to reference variables and functions, see + +Functions and Variables + + + +

+

+ +Note that the values defined by the +Test Plan + and the +User Defined Variables + configuration element +are made available to the whole test plan at startup. +If the same variable is defined by multiple UDV elements, then the last one takes effect. +Once a thread has started, the initial set of variables is copied to each thread. +Other elements such as the + +User Parameters + Pre-Processor or +Regular Expression Extractor + Post-Processor +may be used to redefine the same variables (or create new ones). These redefinitions only apply to the current thread. + +

+

+ +The + +setProperty + + function can be used to define a JMeter property. +These are global to the test plan, so can be used to pass information between threads - should that be needed. + +

+

+

+ +
Both variables and properties are case-sensitive. +
+

+
+

+ + + + +
+ +4.12 Using Variables to parameterise tests + +
+
+

+ +Variables don't have to vary - they can be defined once, and if left alone, will not change value. +So you can use them as short-hand for expressions that appear frequently in a test plan. +Or for items which are constant during a run, but which may vary between runs. +For example, the name of a host, or the number of threads in a thread group. + +

+

+ +When deciding how to structure a Test Plan, +make a note of which items are constant for the run, but which may change between runs. +Decide on some variable names for these - +perhaps use a naming convention such as prefixing them with C_ or K_ or using uppercase only +to distinguish them from variables that need to change during the test. +Also consider which items need to be local to a thread - +for example counters or values extracted with the Regular Expression Post-Processor. +You may wish to use a different naming convention for these. + +

+

+ +For example, you might define the following on the Test Plan: + +

+HOST             www.example.com
+THREADS          10
+LOOPS            20
+
+
+ +You can refer to these in the test plan as ${HOST} ${THREADS} etc. +If you later want to change the host, just change the value of the HOST variable. +This works fine for small numbers of tests, but becomes tedious when testing lots of different combinations. +One solution is to use a property to define the value of the variables, for example: + +
+HOST             ${__P(host,www.example.com)}
+THREADS          ${__P(threads,10)}
+LOOPS            ${__P(loops,20)}
+
+
+ +You can then change some or all of the values on the command-line as follows: + +
+jmeter ... -Jhost=www3.example.org -Jloops=13
+
+
+ + +

+
+

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2011, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestFile_2.jmx b/bin/testfiles/HTMLParserTestFile_2.jmx new file mode 100644 index 00000000000..29a3d0d7fbf --- /dev/null +++ b/bin/testfiles/HTMLParserTestFile_2.jmx @@ -0,0 +1,125 @@ + + + + + + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1317685259000 + 1317685259000 + false + + + + + + + + + + + + + file + + testfiles/HTMLParserTestFile_2.html + GET + false + false + false + false + true + false + + + + + + false + + saveConfig + + + false + false + true + + true + true + true + true + true + true + true + false + true + true + false + true + true + false + true + 0 + true + true + true + true + true + + + HTMLParserTestFile_2.xml + + + + false + + saveConfig + + + true + false + true + + true + true + true + true + true + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + HTMLParserTestFile_2.csv + + + + + diff --git a/bin/testfiles/HTMLParserTestFile_2.set b/bin/testfiles/HTMLParserTestFile_2.set new file mode 100644 index 00000000000..2fb2e512ef9 --- /dev/null +++ b/bin/testfiles/HTMLParserTestFile_2.set @@ -0,0 +1,8 @@ +file:HTMLParserTestFile_2_files/halfbanner.htm +file:HTMLParserTestFile_2_files/jakarta-logo.gif +file:HTMLParserTestFile_2_files/logo.jpg +file:HTMLParserTestFile_2_files/style.css +file:HTMLParserTestFile_2_files/http-config-example.png +file:HTMLParserTestFile_2_files/scoping1.png +file:HTMLParserTestFile_2_files/scoping2.png +file:HTMLParserTestFile_2_files/scoping3.png diff --git a/bin/testfiles/HTMLParserTestFile_2.xml b/bin/testfiles/HTMLParserTestFile_2.xml new file mode 100644 index 00000000000..4c03007b3a5 --- /dev/null +++ b/bin/testfiles/HTMLParserTestFile_2.xml @@ -0,0 +1,112 @@ + + + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2.html + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/style.css + + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/halfbanner.htm + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/halfbanner_data/2011-na-234x60.png + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/halfbanner.htm + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/jakarta-logo.gif + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/logo.jpg + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/http-config-example.png + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/scoping1.png + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/scoping2.png + + + + + + + GET + + file:testfiles/HTMLParserTestFile_2_files/scoping3.png + + + + + + GET + + file:testfiles/HTMLParserTestFile_2.html + + + diff --git a/bin/testfiles/HTMLParserTestFile_2_files/halfbanner.htm b/bin/testfiles/HTMLParserTestFile_2_files/halfbanner.htm new file mode 100644 index 00000000000..602f0753411 --- /dev/null +++ b/bin/testfiles/HTMLParserTestFile_2_files/halfbanner.htm @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestFile_2_files/halfbanner_data/2011-na-234x60.png b/bin/testfiles/HTMLParserTestFile_2_files/halfbanner_data/2011-na-234x60.png new file mode 100644 index 00000000000..08026c19e36 Binary files /dev/null and b/bin/testfiles/HTMLParserTestFile_2_files/halfbanner_data/2011-na-234x60.png differ diff --git a/bin/testfiles/HTMLParserTestFile_2_files/http-config-example.png b/bin/testfiles/HTMLParserTestFile_2_files/http-config-example.png new file mode 100644 index 00000000000..bd292e8242c Binary files /dev/null and b/bin/testfiles/HTMLParserTestFile_2_files/http-config-example.png differ diff --git a/bin/testfiles/HTMLParserTestFile_2_files/jakarta-logo.gif b/bin/testfiles/HTMLParserTestFile_2_files/jakarta-logo.gif new file mode 100644 index 00000000000..049cf822952 Binary files /dev/null and b/bin/testfiles/HTMLParserTestFile_2_files/jakarta-logo.gif differ diff --git a/bin/testfiles/HTMLParserTestFile_2_files/logo.jpg b/bin/testfiles/HTMLParserTestFile_2_files/logo.jpg new file mode 100644 index 00000000000..304363abfc4 Binary files /dev/null and b/bin/testfiles/HTMLParserTestFile_2_files/logo.jpg differ diff --git a/bin/testfiles/HTMLParserTestFile_2_files/scoping1.png b/bin/testfiles/HTMLParserTestFile_2_files/scoping1.png new file mode 100644 index 00000000000..0a58fdc5a17 Binary files /dev/null and b/bin/testfiles/HTMLParserTestFile_2_files/scoping1.png differ diff --git a/bin/testfiles/HTMLParserTestFile_2_files/scoping2.png b/bin/testfiles/HTMLParserTestFile_2_files/scoping2.png new file mode 100644 index 00000000000..4c5a78a3fd0 Binary files /dev/null and b/bin/testfiles/HTMLParserTestFile_2_files/scoping2.png differ diff --git a/bin/testfiles/HTMLParserTestFile_2_files/scoping3.png b/bin/testfiles/HTMLParserTestFile_2_files/scoping3.png new file mode 100644 index 00000000000..4ada01e061a Binary files /dev/null and b/bin/testfiles/HTMLParserTestFile_2_files/scoping3.png differ diff --git a/bin/testfiles/HTMLParserTestFile_2_files/style.css b/bin/testfiles/HTMLParserTestFile_2_files/style.css new file mode 100644 index 00000000000..34dd386e8c7 --- /dev/null +++ b/bin/testfiles/HTMLParserTestFile_2_files/style.css @@ -0,0 +1,39 @@ +/* + 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. +*/ + +/*Shows the value of the name attribute when hovered*/ +/* Disabled +a[name]:hover:after{ + content: " #" attr(name); + font-size: 90%; + text-decoration: none; +} +*/ + +/* + * Hide class="sectionlink", except when an enclosing heading + * has the :hover property. + * Used to hide the ¶ marker for generating internal links + */ +.sectionlink { + display: none; +} +:hover > .sectionlink { + display: inline; + /* Green so shows up on section headings too */ + color: rgb(0,255,0); +} diff --git a/bin/testfiles/HTMLParserTestFrames.all b/bin/testfiles/HTMLParserTestFrames.all new file mode 100644 index 00000000000..574415bab0b --- /dev/null +++ b/bin/testfiles/HTMLParserTestFrames.all @@ -0,0 +1,3 @@ +http://localhost/banner.html +http://localhost/home_nav1.html +http://localhost/main.html \ No newline at end of file diff --git a/bin/testfiles/HTMLParserTestFrames.html b/bin/testfiles/HTMLParserTestFrames.html new file mode 100644 index 00000000000..cd50ee85a39 --- /dev/null +++ b/bin/testfiles/HTMLParserTestFrames.html @@ -0,0 +1,23 @@ + + + + +Main Page + + + + + + + + + + + + <body> + <p>This page uses frames, but your browser doesn't support them.</p> + </body> + + diff --git a/bin/testfiles/HTMLScript.all b/bin/testfiles/HTMLScript.all new file mode 100644 index 00000000000..931cbf501f1 --- /dev/null +++ b/bin/testfiles/HTMLScript.all @@ -0,0 +1,4 @@ +http://localhost/css/wcm_style.css +http://localhost/scripts/navigation/hm_loader.js +http://localhost/shared/images/spacer.gif +http://localhost/shared/images/spacer.gif \ No newline at end of file diff --git a/bin/testfiles/HTMLScript.html b/bin/testfiles/HTMLScript.html new file mode 100644 index 00000000000..c8e7621f61c --- /dev/null +++ b/bin/testfiles/HTMLScript.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + +

+ + + + + +
+
+ + + diff --git a/bin/testfiles/HTMLScript.set b/bin/testfiles/HTMLScript.set new file mode 100644 index 00000000000..1d206f10208 --- /dev/null +++ b/bin/testfiles/HTMLScript.set @@ -0,0 +1,3 @@ +http://localhost/css/wcm_style.css +http://localhost/scripts/navigation/hm_loader.js +http://localhost/shared/images/spacer.gif \ No newline at end of file diff --git a/bin/testfiles/HeaderManagerTestPlan.jmx b/bin/testfiles/HeaderManagerTestPlan.jmx new file mode 100644 index 00000000000..219e104a9fd --- /dev/null +++ b/bin/testfiles/HeaderManagerTestPlan.jmx @@ -0,0 +1,89 @@ + + + + + + + + + false + false + + + + + 0 + + + 1 + false + + false + 1 + + 0 + continue + 0 + + + + + + User-Agent + Mozilla/4.0 (compatible; MSIE 5.5; Windows 98) + + + + + + + + + / + GET + false + http + false + false + + jakarta.apache.org + false + false + Java + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + header-manager.dat + + + + + + diff --git a/bin/testfiles/IfTest.jmx b/bin/testfiles/IfTest.jmx new file mode 100644 index 00000000000..dcfe4935b99 --- /dev/null +++ b/bin/testfiles/IfTest.jmx @@ -0,0 +1,175 @@ + + + + + false + + + + + false + + + + + 1 + false + continue + 1096150006000 + + false + 3 + + 1 + + + 1096150006000 + + + + false + COUNT + 1 + 100 + 1 + + + + + 2!=${COUNT} + false + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + If ${COUNT} + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Always ${COUNT} + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/bin/testfiles/InterleaveTestPlan.jmx b/bin/testfiles/InterleaveTestPlan.jmx new file mode 100644 index 00000000000..fbd33bce620 --- /dev/null +++ b/bin/testfiles/InterleaveTestPlan.jmx @@ -0,0 +1,129 @@ + + + + + + + + + false + false + + + + + 0 + + + 2 + false + + false + 5 + + 0 + continue + 0 + + + + 0 + + + + + + + + + + + + + + + + + /site/news.html + GET + false + http + false + false + + false + + false + Java + + + + + + + /site/faqs.html + GET + false + http + false + false + + false + + false + Java + + + + + + + /gump + GET + false + http + false + false + + false + + false + Java + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + interleave-test.dat + + + + + + diff --git a/bin/testfiles/InterleaveTestPlan2.jmx b/bin/testfiles/InterleaveTestPlan2.jmx new file mode 100644 index 00000000000..c5ae2f93e09 --- /dev/null +++ b/bin/testfiles/InterleaveTestPlan2.jmx @@ -0,0 +1,156 @@ + + + + + + + + + false + false + + + + + 0 + + + 1 + false + + false + 8 + + 0 + continue + 0 + + + + 0 + + + + / + jakarta.apache.org + + + + + + + + + 0 + + + + + + + / + GET + false + http + false + false + + false + + false + Java + + + + + + + /site/cvsindex.html + GET + false + http + false + false + + false + + false + Java + + + + + 0 + + + + + + + /site/bugs.html + GET + false + http + false + false + + false + + false + Java + + + + + + + /site/faqs.html + GET + false + http + false + false + + false + + false + Java + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + interleave-test2.dat + + + + + + diff --git a/bin/testfiles/Load_JMeter_Page.jmx b/bin/testfiles/Load_JMeter_Page.jmx new file mode 100644 index 00000000000..bb007a18b35 --- /dev/null +++ b/bin/testfiles/Load_JMeter_Page.jmx @@ -0,0 +1,26 @@ + + + + + + + + jakarta.apache.org + + + + /jmeter/index.html + GET + true + false + true + false + + + + false + + + + + diff --git a/bin/testfiles/LoopTestPlan.jmx b/bin/testfiles/LoopTestPlan.jmx new file mode 100644 index 00000000000..b2664dcee97 --- /dev/null +++ b/bin/testfiles/LoopTestPlan.jmx @@ -0,0 +1,113 @@ + + + + + + + + + false + false + + + + + 0 + + + 1 + false + + false + 1 + + 0 + continue + 0 + + + + / + jakarta.apache.org + + + + + + + + + + + + / + GET + false + http + false + false + + false + + false + Java + + + + true + 5 + + + + + + + /site/news.html + GET + false + http + false + false + + false + + false + Java + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + loop-test.dat + + + + + + diff --git a/bin/testfiles/Modification Manager.jmx b/bin/testfiles/Modification Manager.jmx new file mode 100644 index 00000000000..9228ab5ed6e --- /dev/null +++ b/bin/testfiles/Modification Manager.jmx @@ -0,0 +1,68 @@ + + + + + + + + + false + false + + + + + + + + + + + + = + .* + true + request.job_id + true + request.job_id + .* + + + = + opjobadmin.action.inspect + true + request.action1 + true + request.action1 + opjobadmin.action.inspect + + + + /xdx/admin/inspect_job.jsp + GET + false + http + false + false + 80 + false + + false + Java + + + + + 13.231.197.158 + + 80 + + + + + + + + + + diff --git a/bin/testfiles/OnceOnlyTestPlan.jmx b/bin/testfiles/OnceOnlyTestPlan.jmx new file mode 100644 index 00000000000..00401879e66 --- /dev/null +++ b/bin/testfiles/OnceOnlyTestPlan.jmx @@ -0,0 +1,110 @@ + + + + + + + + + false + false + + + + + 0 + + + 2 + false + + false + 3 + + 0 + continue + 0 + + + + / + jakarta.apache.org + + + + + + + + + + + + + + / + GET + false + http + false + false + + false + + false + Java + + + + + + + + /site/bugs.html + GET + false + http + false + false + + false + + false + Java + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + once-only-test.dat + + + + + + diff --git a/bin/testfiles/ProxyServerTestPlan.jmx b/bin/testfiles/ProxyServerTestPlan.jmx new file mode 100644 index 00000000000..de16f16c957 --- /dev/null +++ b/bin/testfiles/ProxyServerTestPlan.jmx @@ -0,0 +1,44 @@ + + + + + + + + + false + false + + + + + 0 + + + 1 + false + + 1 + false + + 0 + continue + 0 + + + + 0 + true + true + + <html + + + 8080 + false + + + + + + diff --git a/bin/testfiles/RenderTreeTest.jmx b/bin/testfiles/RenderTreeTest.jmx new file mode 100644 index 00000000000..841e710997c --- /dev/null +++ b/bin/testfiles/RenderTreeTest.jmx @@ -0,0 +1,159 @@ + + + + + + + + false + false + + + + + + 1114154647000 + + + 1 + false + + false + 1 + + 1114154647000 + continue + 1 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Recipe Fine XML Test + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + <?xml version="1.0"?> <recipe name="Black Pepper Shrimp" description="Black Pepper shrimp dishes is very tasty."> <!--COMMENT TEST--><ingredients> <ingredient index="1" amount="3 pounds" description="fresh shrimp, unpeeled" /> <ingredient index="2" amount="8 tablespoons" description="butter" /> <ingredient index="3" amount="4 tablespoons" description="freshly ground pepper" /> </ingredients> <steps> <step index="1" description="Wash and drain shrimp, and place in a shallow baking pan." /> <step index="1" description="In a saucepan, melt the butter, add the garlic, and saute for 3 to 4 minutes." /> <step index="1" description="Pour the butter mixture over the shrimp and toss to coat." />Content Value of steps <step index="1" description="Bake until pink, approximately 5 minutes, turn, bake a few minutes longer, and pepper again." /> </steps> </recipe> + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Recipe Error XML Test + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + <?xml version="1.0"?> <recipe name=Black Pepper Shrimp description=Black Pepper shrimp dishes is very tasty."> <ingredients> <ingredient index="1" amount="3 pounds" description="fresh shrimp, unpeeled" /> <ingredient index="2" amount="8 tablespoons" description="butter" /> <ingredient index="3" amount="4 tablespoons" description="freshly ground pepper" /> </ingredients> <steps> <step index="1" description="Wash and drain shrimp, and place in a shallow baking pan." /> <step index="1" description="In a saucepan, melt the butter, add the garlic, and saute for 3 to 4 minutes." /> <step index="1" description="Pour the butter mixture over the shrimp and toss to coat." /> <step index="1" description="Bake until pink, approximately 5 minutes, turn, bake a few minutes longer, and pepper again." /> </steps> </recipe> + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + true + true + true + false + true + true + false + true + true + false + false + 0 + + + + + + + + + diff --git a/bin/testfiles/SFFTest1.txt b/bin/testfiles/SFFTest1.txt new file mode 100644 index 00000000000..d92e49a12e0 --- /dev/null +++ b/bin/testfiles/SFFTest1.txt @@ -0,0 +1,5 @@ +uno +dos +tres +cuatro +cinco \ No newline at end of file diff --git a/bin/testfiles/SFFTest2.txt b/bin/testfiles/SFFTest2.txt new file mode 100644 index 00000000000..da9dfd0f0ca --- /dev/null +++ b/bin/testfiles/SFFTest2.txt @@ -0,0 +1,5 @@ +one +two +three +four +five \ No newline at end of file diff --git a/bin/testfiles/SFFTest3.txt b/bin/testfiles/SFFTest3.txt new file mode 100644 index 00000000000..2dab0db280f --- /dev/null +++ b/bin/testfiles/SFFTest3.txt @@ -0,0 +1,5 @@ +eins +zwei +drei +fier +fuenf \ No newline at end of file diff --git a/bin/testfiles/SimpleTestPlan.jmx b/bin/testfiles/SimpleTestPlan.jmx new file mode 100644 index 00000000000..81da8599981 --- /dev/null +++ b/bin/testfiles/SimpleTestPlan.jmx @@ -0,0 +1,147 @@ + + + + + + + + + false + false + + + + + 0 + + + 1 + false + + false + 1 + + 0 + continue + 0 + + + + / + jakarta.apache.org + + + + + + + + + + + + + + /ant/index.html + GET + false + http + false + false + + false + + false + Java + + + + + + + /ant/antnews.html + GET + false + http + false + false + + false + + false + Java + + + + + + + + + + /log4j/index.html + GET + false + http + false + false + + false + + false + Java + + + + + + + /log4j/docs/history.html + GET + false + http + false + false + + false + + false + Java + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + simple-test.dat + + + + + + diff --git a/bin/testfiles/Test Plan_out.jmx b/bin/testfiles/Test Plan_out.jmx new file mode 100644 index 00000000000..6f62dc317c5 --- /dev/null +++ b/bin/testfiles/Test Plan_out.jmx @@ -0,0 +1,874 @@ + + + + + + + + + = + jakarta.apache.org + server + + + + false + false + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + Assertion.response_data + 2 + false + + + + 0 + + + + 1 + 0 + + + + + + + + + 1 + + + + + WorkBench + Test Plan + Components + Assertions + Assertions + + + + + + + 1 + + + + 0 + true + 1 + + ThroughputController.percentThroughput + 100.0 + 0.0 + + + + + ${__threadNum()} == 3 + false + + + + + + + + + false + + + + + + + + + + 0 + 0 + + false + 0 + + + + + + username + password + category + color + + + + user1 + pass1 + cat1 + red + + + user2 + pass2 + cat2 + green + + + user3 + pass3 + cat3 + + + + true + + + + + + + + throughput + 60.0 + 0.0 + + false + this thread only + + + + 300 + + + + 100.0 + 300 + + + + 100.0 + 300 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + false + true + true + false + false + false + false + true + true + false + false + false + false + 0 + + + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + + + true + 1 + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + false + false + false + + + + + + + + + + + false + false + false + + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + 0 + 10 + 1 + + + + + + false + false + false + + true + + + + users.xml + + + + + + + + + + + + + + + POST + true + false + true + false + + + + false + + + + + + + + / + / + POST + http + -1 + + + + + + false + false + + + + + + + / + / + POST + + http + + 80 + false + + + + + false + false + + + + + + + + + + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + false + 80 + org.apache.jmeter.protocol.http.util.accesslog.TCLogParser + + + + + org.apache.jmeter.protocol.http.util.accesslog.StandardGenerator + + false + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + SleepTime + 1000 + = + + + SleepMask + 0x3FF + = + + + + org.apache.jmeter.protocol.java.test.SleepTest + + + + + + + + + + SleepTime + 1000 + = + + + SleepMask + 0x3FF + = + + + + org.apache.jmeter.protocol.java.test.SleepTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + JavaTest + = + + + ResponseCode + 200 (or any other number) + = + + + ResponseMessage + OK (or any other text) + = + + + Status + OK + = + + + SamplerData + SamplerData goes here + = + + + ResultData + ResultData goes here + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + ResponseCode=12; +ResponseMessage="Buckle my shoe"; +IsSuccess=false; +Label="Sticky"; +// FileName is the Script file name +// bsh.args[1] == "quick" +return "This will go into the Response Data field"; + + the quick brown fox + false + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + 1 + + 50 + + + + org.apache.jmeter.protocol.jdbc.util.JMeter19ConnectionPool + + Select Statement + + + + + + + + 0 + + + 1 + false + + false + -1 + + 0 + continue + 1 + + + + + + + + + false + + + + + + + + + + false + + + + + + + + + diff --git a/bin/testfiles/TestAuth.txt b/bin/testfiles/TestAuth.txt new file mode 100644 index 00000000000..2ed7757ff9a --- /dev/null +++ b/bin/testfiles/TestAuth.txt @@ -0,0 +1,13 @@ +# Test file for AuthManager +# Format: +# URL\tuser\tpass + +http://a.b.c/1 login1 password1 +http://a.b.c/2 login2 password2 +http://a.b.c/1/1 login11 password11 +http://a.b.c/22 login22 password22 +http://a.b.c/ login password +http://d.e.f:80/ user pass domain realm +https://j.k.l/ jkl pass +https://l.m.n:443/ lmn443 pass +https://l.m.n:8443/ lmn8443 pass diff --git a/bin/testfiles/TestDisable.jmx b/bin/testfiles/TestDisable.jmx new file mode 100644 index 00000000000..728a784906e --- /dev/null +++ b/bin/testfiles/TestDisable.jmx @@ -0,0 +1,359 @@ + + + + + + + + + false + false + + + + + + 0 + + false + 5 + + 0 + + continue + 3 + false + 0 + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Java1 + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Java2 - disabled + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Java3 + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Java4 - disabled + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Java5 + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Java5 - disabled + = + + + ResponseCode + + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + diff --git a/bin/testfiles/XMLSchema-fail.xsd b/bin/testfiles/XMLSchema-fail.xsd new file mode 100644 index 00000000000..a8d8ca4c8a2 --- /dev/null +++ b/bin/testfiles/XMLSchema-fail.xsd @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/XMLSchema-pass.xsd b/bin/testfiles/XMLSchema-pass.xsd new file mode 100644 index 00000000000..cc6c7edb3ae --- /dev/null +++ b/bin/testfiles/XMLSchema-pass.xsd @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/XMLSchematest.xml b/bin/testfiles/XMLSchematest.xml new file mode 100644 index 00000000000..684679845ff --- /dev/null +++ b/bin/testfiles/XMLSchematest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/bin/testfiles/XPathAssertionTest.xml b/bin/testfiles/XPathAssertionTest.xml new file mode 100644 index 00000000000..7a293fd1616 --- /dev/null +++ b/bin/testfiles/XPathAssertionTest.xml @@ -0,0 +1,5 @@ + + 1 + Print Error + Network Error + diff --git a/bin/testfiles/XPathTest.xml b/bin/testfiles/XPathTest.xml new file mode 100644 index 00000000000..11839a177dd --- /dev/null +++ b/bin/testfiles/XPathTest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/bin/testfiles/cookies.txt b/bin/testfiles/cookies.txt new file mode 100644 index 00000000000..6c74a8a9c4e --- /dev/null +++ b/bin/testfiles/cookies.txt @@ -0,0 +1,4 @@ +# JMeter generated Cookie file +domain TRUE path TRUE 0 name value + TRUE FALSE 0 name2 value2 +c TRUE d TRUE 9223372036854775807 a b \ No newline at end of file diff --git a/bin/testfiles/empty.csv b/bin/testfiles/empty.csv new file mode 100644 index 00000000000..e69de29bb2d diff --git a/bin/testfiles/jmeter-batch.properties b/bin/testfiles/jmeter-batch.properties new file mode 100644 index 00000000000..0785967f8fe --- /dev/null +++ b/bin/testfiles/jmeter-batch.properties @@ -0,0 +1,26 @@ +################################################################################ +# Apache JMeter Property file for batch runs +################################################################################ + +## 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. + +# Ensure log is empty by default +log_level.jmeter=WARN +# Revert to original default mode +mode=Standard + +# Since JMeter 2.12, defaults for this property is true +jmeter.save.saveservice.thread_counts=false \ No newline at end of file diff --git a/bin/testfiles/jmeter_home_page.html b/bin/testfiles/jmeter_home_page.html new file mode 100644 index 00000000000..8d0897b9670 --- /dev/null +++ b/bin/testfiles/jmeter_home_page.html @@ -0,0 +1,277 @@ +HTTP/1.1 200 OK +Date: Thu, 24 Apr 2003 21:15:26 GMT +Server: Apache/2.0.45-dev (Unix) +Last-Modified: Thu, 24 Apr 2003 13:26:51 GMT +ETag: "31a620-1b1e-6ee17cc0" +Accept-Ranges: bytes +Content-Length: 6942 +Keep-Alive: timeout=5, max=100 +Connection: Keep-Alive +Content-Type: text/html + + + + + + + + + + + + + + + + + + JMeter - Apache JMeter + + + + + + + + +
+ +
+ + + + + + + + + + + + +
+
+
+

About

+ +

Documentation

+ +

JMeter Resources

+ +

Community

+ +
+ + + + +
+ + Apache JMeter + +
+
+

+ + + + Apache JMeter + + is a 100% pure Java desktop application designed + to load test functional behavior and measure performance. It was + originally designed for testing Web Applications but has + since expanded to other test functions. + +

+

+ What can I do with it? +

+

+ + Apache JMeter may be used to test performance both on static and dynamic + resources (files, Servlets, Perl scripts, Java Objects, Data Bases and + Queries, FTP Servers and more). It can be used to simulate a heavy +load on a server, network or object to test its strength or to analyze +overall performance under different load types. You can use it to make a +graphical analysis of performance or to test your server/script/object +behavior under heavy concurrent load. + +

+

+ + Using JMeter + +

+

+ What does it do? +

+

+ Apache JMeter features include: +

+
    + + +
  • + Can load and performance test HTTP and FTP servers as well as + arbitrary database queries (via JDBC) +
  • + + +
  • + Complete portability and + + 100% Java purity + + . +
  • + + +
  • + Full + + Swing + + and lightweight component support (precompiled JAR uses packages + + + javax.swing.* + + ). +
  • + + +
  • + Full + + multithreading + + framework allows concurrent sampling by many threads and + simultaneous sampling of different functions by seperate thread groups. +
  • + + +
  • + Careful + + GUI + + design allows faster operation and more precise timings. +
  • + + +
  • + Caching and offline analysis/replaying of test results. +
  • + + +
  • + Highly Extensible: + +
      + + +
    • + Pluggable Samplers allow unlimited testing capabilities. +
    • + + +
    • + Several load statistics may be choosen with + + pluggable timers + + . +
    • + + +
    • + Data analysis and + + visualization plugins + + allow great extendibility + as well as personalization. +
    • + + +
    + + +
  • + + +
+
+

+

+
+
+
+
+ Copyright © 1999-2001, Apache Software Foundation +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/jmeter_home_page_with_base_href.html b/bin/testfiles/jmeter_home_page_with_base_href.html new file mode 100644 index 00000000000..0757009cd12 --- /dev/null +++ b/bin/testfiles/jmeter_home_page_with_base_href.html @@ -0,0 +1,277 @@ +HTTP/1.1 200 OK +Date: Thu, 24 Apr 2003 21:15:26 GMT +Server: Apache/2.0.45-dev (Unix) +Last-Modified: Thu, 24 Apr 2003 13:26:51 GMT +ETag: "31a620-1b1e-6ee17cc0" +Accept-Ranges: bytes +Content-Length: 6942 +Keep-Alive: timeout=5, max=100 +Connection: Keep-Alive +Content-Type: text/html + + + + + + + + + + + + + + + + + + JMeter - Apache JMeter + + + + + + + + +
+ +
+ + + + + + + + + + + + +
+
+
+

About

+ +

Documentation

+ +

JMeter Resources

+ +

Community

+ +
+ + + + +
+ + Apache JMeter + +
+
+

+ + + + Apache JMeter + + is a 100% pure Java desktop application designed + to load test functional behavior and measure performance. It was + originally designed for testing Web Applications but has + since expanded to other test functions. + +

+

+ What can I do with it? +

+

+ + Apache JMeter may be used to test performance both on static and dynamic + resources (files, Servlets, Perl scripts, Java Objects, Data Bases and + Queries, FTP Servers and more). It can be used to simulate a heavy +load on a server, network or object to test its strength or to analyze +overall performance under different load types. You can use it to make a +graphical analysis of performance or to test your server/script/object +behavior under heavy concurrent load. + +

+

+ + Using JMeter + +

+

+ What does it do? +

+

+ Apache JMeter features include: +

+
    + + +
  • + Can load and performance test HTTP and FTP servers as well as + arbitrary database queries (via JDBC) +
  • + + +
  • + Complete portability and + + 100% Java purity + + . +
  • + + +
  • + Full + + Swing + + and lightweight component support (precompiled JAR uses packages + + + javax.swing.* + + ). +
  • + + +
  • + Full + + multithreading + + framework allows concurrent sampling by many threads and + simultaneous sampling of different functions by seperate thread groups. +
  • + + +
  • + Careful + + GUI + + design allows faster operation and more precise timings. +
  • + + +
  • + Caching and offline analysis/replaying of test results. +
  • + + +
  • + Highly Extensible: + +
      + + +
    • + Pluggable Samplers allow unlimited testing capabilities. +
    • + + +
    • + Several load statistics may be choosen with + + pluggable timers + + . +
    • + + +
    • + Data analysis and + + visualization plugins + + allow great extendibility + as well as personalization. +
    • + + +
    + + +
  • + + +
+
+

+

+
+
+
+
+ Copyright © 1999-2001, Apache Software Foundation +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/jmeter_home_page_with_relative_links.html b/bin/testfiles/jmeter_home_page_with_relative_links.html new file mode 100644 index 00000000000..8f9d5aa4431 --- /dev/null +++ b/bin/testfiles/jmeter_home_page_with_relative_links.html @@ -0,0 +1,277 @@ +HTTP/1.1 200 OK +Date: Thu, 24 Apr 2003 21:15:26 GMT +Server: Apache/2.0.45-dev (Unix) +Last-Modified: Thu, 24 Apr 2003 13:26:51 GMT +ETag: "31a620-1b1e-6ee17cc0" +Accept-Ranges: bytes +Content-Length: 6942 +Keep-Alive: timeout=5, max=100 +Connection: Keep-Alive +Content-Type: text/html + + + + + + + + + + + + + + + + + + JMeter - Apache JMeter + + + + + + + + +
+ +
+ + + + + + + + + + + + +
+
+
+

About

+ +

Documentation

+ +

JMeter Resources

+ +

Community

+ +
+ + + + +
+ + Apache JMeter + +
+
+

+ + + + Apache JMeter + + is a 100% pure Java desktop application designed + to load test functional behavior and measure performance. It was + originally designed for testing Web Applications but has + since expanded to other test functions. + +

+

+ What can I do with it? +

+

+ + Apache JMeter may be used to test performance both on static and dynamic + resources (files, Servlets, Perl scripts, Java Objects, Data Bases and + Queries, FTP Servers and more). It can be used to simulate a heavy +load on a server, network or object to test its strength or to analyze +overall performance under different load types. You can use it to make a +graphical analysis of performance or to test your server/script/object +behavior under heavy concurrent load. + +

+

+ + Using JMeter + +

+

+ What does it do? +

+

+ Apache JMeter features include: +

+
    + + +
  • + Can load and performance test HTTP and FTP servers as well as + arbitrary database queries (via JDBC) +
  • + + +
  • + Complete portability and + + 100% Java purity + + . +
  • + + +
  • + Full + + Swing + + and lightweight component support (precompiled JAR uses packages + + + javax.swing.* + + ). +
  • + + +
  • + Full + + multithreading + + framework allows concurrent sampling by many threads and + simultaneous sampling of different functions by seperate thread groups. +
  • + + +
  • + Careful + + GUI + + design allows faster operation and more precise timings. +
  • + + +
  • + Caching and offline analysis/replaying of test results. +
  • + + +
  • + Highly Extensible: + +
      + + +
    • + Pluggable Samplers allow unlimited testing capabilities. +
    • + + +
    • + Several load statistics may be choosen with + + pluggable timers + + . +
    • + + +
    • + Data analysis and + + visualization plugins + + allow great extendibility + as well as personalization. +
    • + + +
    + + +
  • + + +
+
+

+

+
+
+
+
+ Copyright © 1999-2001, Apache Software Foundation +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/jmetertest.properties b/bin/testfiles/jmetertest.properties new file mode 100644 index 00000000000..afb424c5491 --- /dev/null +++ b/bin/testfiles/jmetertest.properties @@ -0,0 +1,175 @@ +################################################################################ +# Apache JMeter Property file - used for unit tests only +################################################################################ + +## 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. + +#Preferred GUI language. Comment out to use the JVM default locale's language. +#language=de + +#Paths to search for classes (";" must be the separator) +#search_paths=null + +# Netscape HTTP Cookie file +cookies=cookies + +#File format for saved test files. +# JMeter 2.1+ uses a new format for JMX and JTL files - using XStream. +# JMeter 2.1.2+ has a new shorter format for JMX files. +# Set value to 2.0 or 2.1 to save to old formats +# +# Save test plans and test logs in 2.0 format +#file_format=2.0 +# Just test plans (jmx) +#file_format.testplan=2.1 +# Just test logs (jtl) +#file_format.testlog=2.0 + +# Authorization +authorization=authorization + +#Working directory +user.dir=. + +# XML Reader(Parser) - Must implement SAX 2 specs +xml.parser=org.apache.xerces.parsers.SAXParser + +#Classname of the ssl provider to be used (to enable testing of https urls) +#And the package name where Stream Handlers can be found +#These provided defaults can be uncommented, and they will work if you are using +#Sun's JSSE implementation. + +#ssl.provider=com.sun.net.ssl.internal.ssl.Provider +#ssl.pkgs=com.sun.net.ssl.internal.www.protocol + +#The location of the truststore (trusted certificates) and keystore ( if other than the default. +#you can uncomment this and change the path to the correct location. +#javax.net.ssl.trustStore=/path/to/cacerts +#javax.net.ssl.keyStore=/path/to/keystore + +#The password to your keystore +#javax.net.ssl.keyStorePassword=password + + +#Flag for whether to output debug messages to System.err +#To enable it, set the value to "all" Note, for it to work with +#JSSE, it needs to be done from the Java command (i.e. -Djavax.net.debug=all) +#javax.net.debug=all + +#Classname of the Swing default UI +#Installed Look and Feel classes on Windows are: +# Metal = javax.swing.plaf.metal.MetalLookAndFeel +# Motif = com.sun.java.swing.plaf.motif.MotifLookAndFeel +# Windows = com.sun.java.swing.plaf.windows.WindowsLookAndFeel +## Let LAF be picked up from default +## (otherwise can cause problems for Eclipse JUnit GUI mode) +#jmeter.laf=javax.swing.plaf.metal.MetalLookAndFeel +#jmeter.laf=com.sun.java.swing.plaf.motif.MotifLookAndFeel + +#icons -> moved to program code +#timer.tree.icon=timer.gif +#listener.tree.icon=ear.gif +#bench.tree.icon=clipboard.gif +#thread.tree.icon=thread.gif +#control.tree.icon=knob.gif +#plan.tree.icon=beaker.gif +#config.tree.icon=leafnode.gif + +# Remote Hosts - comma delimited +remote_hosts=127.0.0.1 + +#Components to not display in JMeter GUI +not_in_menu=Remote Method Configuration,JNDI Configuration,JNDI Lookup Configuration,JNDI Request,Default Controller,org.apache.jmeter.control.DynamicController, org.apache.jmeter.protocol.http.control.Cookie,org.apache.jmeter.protocol.http.control.Authorization,org.apache.jmeter.config.LoginConfig,Header,org.apache.jmeter.protocol.http.config.MultipartUrlConfig + +#Logging levels for the logging categories in JMeter. Correct values are FATAL_ERROR, ERROR, WARN, INFO, and DEBUG +# To set the log level for a package or individual class, use: +# log_level.[package_name].[classname]=[PRIORITY_LEVEL] +# But omit "org.apache" from the package name. The classname is optional. Further examples below. + +log_level.jmeter=INFO +#log_level.jmeter.junit=DEBUG +#log_level.jmeter.engine=WARN +#log_level.jmeter.gui=WARN +#log_level.jmeter.testelement=DEBUG +#log_level.jmeter.util=WARN +#log_level.jmeter.util.classfinder=WARN +#log_level.jmeter.test=DEBUG +#log_level.jmeter.protocol.http=DEBUG +#log_level.jmeter.protocol.ftp=WARN +#log_level.jmeter.protocol.jdbc=WARN +#log_level.jmeter.protocol.java=WARN +#log_level.jmeter.testelements.property=DEBUG +log_level.jorphan=INFO +#log_level.jorphan.reflect.ClassFinder=DEBUG + +#Log file for log messages. +# You can specify a different log file for different categories via: +# log_file.[category]=[filename] +# category is equivalent to the package/class names described above + +# Combined log file (for jmeter and jorphan) +log_file=jmeter-test.log + +# Or define separate logs if required: +#log_file.jorphan=jorphan.log +#log_file.jmeter=jmeter.log + + +#--------------------------------------------------------------------------- +# Results file configuration +#--------------------------------------------------------------------------- + +# For testing, output is changed to CSV and variable fields +# (timestamp and elased) are suppressed + +# This section helps determine how result data will be saved. +# The commented out values are the defaults. + +# legitimate values: xml, csv, db. Only xml and csv are currently supported. +jmeter.save.saveservice.output_format=csv + +# Define true to save the output files in TestSaveService.java +#testsaveservice.saveout=true + +# true when field should be saved; false otherwise + +# assertion_results_failure_message only affects CSV output +#jmeter.save.saveservice.assertion_results_failure_message=true +#jmeter.save.saveservice.data_type=true +#jmeter.save.saveservice.label=true +#jmeter.save.saveservice.response_code=true +#jmeter.save.saveservice.response_data=false +#jmeter.save.saveservice.response_message=true +#jmeter.save.saveservice.successful=true +#jmeter.save.saveservice.thread_name=true +jmeter.save.saveservice.time=false +# Since JMeter 2.12, defaults for this property is true +jmeter.save.saveservice.thread_counts=false + +# legitimate values: none, ms, or a format suitable for SimpleDateFormat +#jmeter.save.saveservice.timestamp_format=none +#jmeter.save.saveservice.timestamp_format=MM/dd/yy HH:mm:ss + +# legitimate values: none, first, all +#jmeter.save.saveservice.assertion_results=none + +# For use with Comma-separated value (CSV) files or other formats +# where the fields' values are separated by specified delimiters. +#jmeter.save.saveservice.default_delimiter=, +#jmeter.save.saveservice.print_field_names=true + +# File that holds a record of name changes for backward compatibility issues +upgrade_properties=/bin/upgrade.properties diff --git a/bin/testfiles/load_bug_list.jmx b/bin/testfiles/load_bug_list.jmx new file mode 100644 index 00000000000..e0dc7dd55c6 --- /dev/null +++ b/bin/testfiles/load_bug_list.jmx @@ -0,0 +1,231 @@ + + + + + + + + = + .* + true + bug_status + false + + + = + .* + true + bug_status + false + + + = + .* + true + bug_status + false + + + = + + true + email1 + false + + + = + substring + true + emailtype1 + false + + + = + 1 + true + emailassigned_to1 + false + + + = + + true + email2 + false + + + = + substring + true + emailtype2 + false + + + = + 1 + true + emailreporter2 + false + + + = + include + true + bugidtype + false + + + = + + true + bug_id + false + + + = + + true + changedin + false + + + = + + true + votes + false + + + = + + true + chfieldfrom + false + + + = + Now + true + chfieldto + false + + + = + + true + chfieldvalue + false + + + = + JMeter + true + product + false + + + = + + true + short_desc + false + + + = + substring + true + short_desc_type + false + + + = + + true + long_desc + false + + + = + substring + true + long_desc_type + false + + + = + + true + bug_file_loc + false + + + = + substring + true + bug_file_loc_type + false + + + = + + true + keywords + false + + + = + anywords + keywords_type + false + + + = + noop + field0-0-0 + false + + + = + noop + type0-0-0 + false + + + = + + value0-0-0 + false + + + = + doit + cmdtype + false + + + = + Reuse same sort as last time + order + true + + + + bz.apache.org + 80 + http + + /bugzilla/buglist.cgi + GET + false + false + true + false + + + + false + + + + + diff --git a/bin/testfiles/monitorStatus.xml b/bin/testfiles/monitorStatus.xml new file mode 100644 index 00000000000..c5f93b9b605 --- /dev/null +++ b/bin/testfiles/monitorStatus.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/proxy.jmx b/bin/testfiles/proxy.jmx new file mode 100644 index 00000000000..89798645a49 --- /dev/null +++ b/bin/testfiles/proxy.jmx @@ -0,0 +1,45 @@ + + + + + + + + + false + false + + + + + 0 + + + 1 + false + + -1 + false + + 0 + continue + 0 + + + + + + + jakarta.apache.org + + + + + + + + + + + + diff --git a/bin/testfiles/sample_log1.jtl b/bin/testfiles/sample_log1.jtl new file mode 100644 index 00000000000..64a7383a806 --- /dev/null +++ b/bin/testfiles/sample_log1.jtl @@ -0,0 +1,10004 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/sample_log1b.jtl b/bin/testfiles/sample_log1b.jtl new file mode 100644 index 00000000000..4f6dc5e321e --- /dev/null +++ b/bin/testfiles/sample_log1b.jtl @@ -0,0 +1,10004 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/sample_log1c.jtl b/bin/testfiles/sample_log1c.jtl new file mode 100644 index 00000000000..5ab2177c004 --- /dev/null +++ b/bin/testfiles/sample_log1c.jtl @@ -0,0 +1,2004 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/sample_log2.jtl b/bin/testfiles/sample_log2.jtl new file mode 100644 index 00000000000..086e2a13a5b --- /dev/null +++ b/bin/testfiles/sample_log2.jtl @@ -0,0 +1,10004 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/testfiles/test.csv b/bin/testfiles/test.csv new file mode 100644 index 00000000000..18ca22e8d55 --- /dev/null +++ b/bin/testfiles/test.csv @@ -0,0 +1,4 @@ +a1,b1,c1,d1 +a2,b2,c2,d2 +a3,b3,c3,d3 +a4,b4,c4,d4 \ No newline at end of file diff --git a/bin/testfiles/test.tsv b/bin/testfiles/test.tsv new file mode 100644 index 00000000000..736872ed009 --- /dev/null +++ b/bin/testfiles/test.tsv @@ -0,0 +1,4 @@ +a1 b1 c1 d1 +a2 b2 c2 d2 +a3 b3 c3 d3 +a4 b4 c4 d4 \ No newline at end of file diff --git a/bin/testfiles/test_config.xml b/bin/testfiles/test_config.xml new file mode 100644 index 00000000000..66a5dd1f4f3 --- /dev/null +++ b/bin/testfiles/test_config.xml @@ -0,0 +1,233 @@ +JMeter { + config.dir=@webapp_path@/WEB-INF + loginText=@LOGIN_TEXT@ + urlBase=@URL_BASE@ + altUrlBase=@ALT_URL_BASE@ + altLoginText=@ALT_LOGIN_TEXT@ + + services { + + org.apache.service.logging.LoggingManager { + class=org.apache.service.logging.DefaultLoggingManager + log4j.configuration=@webapp_path@/WEB-INF/log4j.properties + } + + org.apache.service.classfinder.ClassFinderService { + class=org.apache.service.classfinder.DefaultClassFinderService + paths { + @webapp_path@/WEB-INF/classes + @webapp_path@/WEB-INF/lib/giblex.jar + } + } + + org.apache.service.webaction.WebRequestHandler { + class=org.apache.service.webaction.WebRequestHandlerContainer + default_call_page=index.jsp + betaweek=@beta@ + requiresLogin { + add_todo + update_time_lock_pref + update_manager_notification_pref + add_site + add_commit + update_site + update_full_timesheet + Send Timesheet + get_time_report + } + loginProof=user + initActions { + set_context + } + autoActions { + set_request + clean_session + } + profileInterval=500 + } + + org.apache.service.webaction.error.Redirector { + class=org.apache.service.webaction.error.DefaultRedirector + exceptions { + InvalidLoginException { + page=/@appname@/index.jsp + msg=Incorrect username/password. + } + NotLoggedInException { + page=/@appname@/NoPermission.jsp + msg=You must be logged in to use that resource. + } + NoPermissionError { + page=/@appname@/NoPermission.jsp + msg=You have no permission to use that resource. + } + InvalidCredentialsException { + page=/@appname@/index.jsp + msg=Incorrect username/password. + } + NoSuchObjectException { + msg=[[! +The system could not find the resource you requested. Please check your request and try again. +If the problem persists, report it to the system administrators. + !]] + } + InvalidInputException + InvalidActionError { + msg=The system requested an unknown task. + } + DEFAULT { + msg=An unexpected error occurred. Please check your request and try again. If the problem persists, report it to the system administrators. + } + InvalidContextException { + msg=You've confused the poor server with your use of the back button. Try again from the top. + } + NullRequesterException { + page=/@appname@/EditRequest.jsp + msg=You must select or enter the name of the request's requester. + } + TimesheetNotLockedException { + msg=Your timesheet needs to be finalized and approved by your manager before it can be sent. + } + } + } + + org.apache.service.authentication.AuthenticationService { + class=org.apache.service.authentication.ldap.LdapAuthenticator + ldap-hosts { + @LDAP1@ + @LDAP2@ + @LDAP3@ + } + users { + mike=mstover { password=**** } + peter=plin { password=**** } + } + } + + org.apache.service.template.TemplateService { + class=org.apache.service.template.velocity.VelocityService + runtime.log=@webapp_path@/WEB-INF/velocity.log + file.resource.loader.path=@webapp_path@/WEB-INF/templates + file.resource.loader.cache=true + file.resource.loader.modificationCheckInterval=60 + velocimacro.library=menu.vm,jsp.vm,sql.vm,news.vm,timesheet.vm + components { + pmService=org.apache.service.JMeterBusinessLogicService + userService=org.apache.service.UserGroupService + } + } + + groovyService { + class=org.apache.service.template.groovy.GroovyService + path=@webapp_path@/WEB-INF/groovyTemplates + components { + pmService=org.apache.service.JMeterBusinessLogicService + userService=org.apache.service.UserGroupService + repo=org.apache.service.sql.ObjectMappingService + } + } + + org.apache.avalon.excalibur.datasource.DataSourceComponent { + class=org.apache.avalon.excalibur.datasource.J2eeDataSource + dbname=@db_name@ + } + + org.apache.service.sql.ObjectMappingService { + class=org.apache.service.sql.DefaultMappingService + mappingDirectory=@webapp_path@/WEB-INF/mappings + packages { org.apache.service.dbObjects } + profileInterval=500 + } + + org.apache.service.JMeterBusinessLogicService { + class=org.apache.service.searching.DefaultJMeterService + } + + org.apache.service.PreferenceService { + class=org.apache.service.impl.DefaultPreferenceService + preferenceSql { + Buddy List=Contact { sql=getBuddyList.sql } + Group List=Contact { sql=getPreferredGroups.sql } + Note Type=Group { sql=getPreferredNoteTypes.sql } + Project Type=Group { sql=getPreferredProjectTypes.sql } + Milestones On Timesheet=Group + All Sites=Contact + Manager Notification=Contact + Timesheet Lock Style=Group + Commit Scripts=Group + Projects By Contact=Contact + Projects By Group=Contact + Projects=Contact + Programs By Contact=Contact + Programs By Group=Contact + Programs=Contact + Applications By Contact=Contact + Applications By Group=Contact + Applications=Contact + Tasks By Contact=Contact + Tasks By Group=Contact + Tasks=Contact + Sites By Contact=Contact + Sites By Group=Contact + Sites=Contact + Todos By Contact=Contact + Todos By Group=Contact + Todos=Contact + } + } + + org.apache.service.PermissionService { + class=org.apache.service.impl.DefaultPermissionService + } + + org.apache.service.notification.NotificationService { + class=org.apache.service.notification.DefaultNotificationService + mail.transport.protocol=smtp + mail.host=smtp.apache.org + mail.user=jmeter + mail.from=jmeter-auto@apache.org + mail.replyTo=jmeter@apache.org + bcc.default=james@apache.org + email.active=@email_active@ + mail.smtp.connectiontimeout=60000 + mail.smtp.timeout=120000 + } + + org.apache.service.UserGroupService { + class=org.apache.service.impl.DefaultUserGroupService + superUsers { + mstover + plin + sebb + } + } + + org.apache.service.domain_event.DomainEventService { + class=org.apache.service.domain_event.BasicEventService + eventDefinitions=@webapp_path@/WEB-INF/eventDefs + vmFile= + components { + pmService=org.apache.service.JMeterBusinessLogicService + userGroupService=org.apache.service.UserGroupService + emailService=org.apache.service.notification.NotificationService + repository=org.apache.service.sql.ObjectMappingService + } + eventRetrieval= + } + + org.apache.service.DocumentService { + class=org.apache.service.impl.DefaultDocumentService + url=@webdav_server@ + proxy=proxy.apache.org { + port=8080 + username=******* + password=******** + } + } + + cvsCommitService { + class=org.apache.service.impl.JMeterCommitProcessor + CommitProcessor.delay=5 + } + } + } diff --git a/bin/testfiles/testblank.csv b/bin/testfiles/testblank.csv new file mode 100644 index 00000000000..b23a1766e98 --- /dev/null +++ b/bin/testfiles/testblank.csv @@ -0,0 +1,4 @@ +a1,b1,c1,d1 +a2,b2,c2,d2 + +The previous line is blank, and should be treated as EOF \ No newline at end of file diff --git a/bin/testfiles/testempty.csv b/bin/testfiles/testempty.csv new file mode 100644 index 00000000000..b8583e70f5d --- /dev/null +++ b/bin/testfiles/testempty.csv @@ -0,0 +1,4 @@ +,b1,c1,d1 +a2,,c2,d2 +a3,b3,,d3 +a4,b4,c4, \ No newline at end of file diff --git a/bin/testfiles/testheader.csv b/bin/testfiles/testheader.csv new file mode 100644 index 00000000000..906aa1e9d1d --- /dev/null +++ b/bin/testfiles/testheader.csv @@ -0,0 +1,5 @@ +A|B|C|"D|1" +a1|b1|c1|d1 +a2|b2|c2|d2 +a3|b3|c3|d3 +a4|b4|c4|d4 \ No newline at end of file diff --git a/bin/testfiles/testquoted.csv b/bin/testfiles/testquoted.csv new file mode 100644 index 00000000000..04a56a573d7 --- /dev/null +++ b/bin/testfiles/testquoted.csv @@ -0,0 +1,6 @@ +A|B|C|"D|1" +a1|b1|"c1"|d1 +a2|b2|c2|d2 +a3|b3|c3|"d3" +a4|b4|c4|"d4 +Previous line is malformed \ No newline at end of file diff --git a/bin/testfiles/testutf8.csv b/bin/testfiles/testutf8.csv new file mode 100644 index 00000000000..f775cc3801d --- /dev/null +++ b/bin/testfiles/testutf8.csv @@ -0,0 +1,4 @@ +a1,b1,"ç1",d1 +a2,b2,"ç2",d2 +a3,b3,"ç3",d3 +a4,b4,"ç4",d4 \ No newline at end of file diff --git a/bin/upgrade.properties b/bin/upgrade.properties new file mode 100644 index 00000000000..ef7f3d3e107 --- /dev/null +++ b/bin/upgrade.properties @@ -0,0 +1,99 @@ +# Class, property and value upgrade equivalences. + +## 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. + +# +# Format is as follows -- +# for renamed test element & GUI classes: +# old.class.Name=new.class.Name +# old.class.Name|guiClassName=new.class.Name +# (e.g. for ConfigTestElement) +# +# for renamed / deleted properties: +# class.Name/Old.propertyName=newPropertyName +# if newPropertyName is omitted, then property is deleted +# +# for renamed values: +# old.class.Name.old.propertyName/oldValue=newValue +# + +org.apache.jmeter.protocol.http.config.gui.UrlConfigGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui +org.apache.jmeter.assertions.Assertion=org.apache.jmeter.assertions.ResponseAssertion +org.apache.jmeter.protocol.http.sampler.HTTPSamplerFull=org.apache.jmeter.protocol.http.sampler.HTTPSampler +org.apache.jmeter.control.gui.RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController + +org.apache.jmeter.timers.gui.ConstantThroughputTimerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.timers.ConstantThroughputTimer/ConstantThroughputTimer.throughput=throughput + +org.apache.jmeter.protocol.jdbc.control.gui.JdbcTestSampleGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler/JDBCSampler.query=query +#org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler.JDBCSampler.dataSource/NULL= + +# Convert DBconfig +org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.url=dbUrl +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.driver=driver +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.query=query +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.username=username +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.password=password + +# Convert PoolConfig +org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connections= +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connPoolClass= +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.maxuse=poolMax + +# SQL Config +org.apache.jmeter.config.ConfigTestElement/JDBCSampler.query=query +org.apache.jmeter.protocol.http.control.Header/TestElement.name=Header.name + +# Upgrade AccessLogSampler +org.apache.jmeter.protocol.http.control.gui.AccessLogSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.log_file=logFile +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.port=portString +#Is the following used now? +#org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.generator_class_name= +#Looks to be a new field +#filterClassName +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.domain=domain +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.parser_class_name=parserClassName +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.image_parser=imageParsing + +# Renamed class +org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui + +# These classes have been deleted; there's no defined replacement +org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui=org.apache.jmeter.config.gui.ObsoleteGui +org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui=org.apache.jmeter.config.gui.ObsoleteGui +# Should probably map to something other than ObsoleteGui... +org.apache.jmeter.threads.ReflectionThreadGroup=org.apache.jmeter.config.gui.ObsoleteGui + +# Convert BSFSamplerGui +org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.filename=filename +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.language=scriptLanguage +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.parameters=parameters +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.query=script + +# Obsolete Http user Parameters modifier test element +# Note: ConfigTestElement is the test element associated with ObsoleteGui +org.apache.jmeter.protocol.http.modifier.UserParameterModifier=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui=org.apache.jmeter.config.gui.ObsoleteGui + +# Obsolete Graph Full Results listener +org.apache.jmeter.visualizers.GraphAccumVisualizer=org.apache.jmeter.config.gui.ObsoleteGui \ No newline at end of file diff --git a/bin/user.properties b/bin/user.properties new file mode 100644 index 00000000000..c7f28554c41 --- /dev/null +++ b/bin/user.properties @@ -0,0 +1,64 @@ +# Sample user.properties file +# +## 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. + +#--------------------------------------------------------------------------- +# Classpath configuration +#--------------------------------------------------------------------------- +# +# List of paths (separated by ;) to search for additional JMeter plugin classes, +# for example new GUI elements and samplers. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib/ext directory. +# Do not use this for utility ir plugin dependecy jars. +#search_paths=/app1/lib;/app2/lib + +# List of paths that JMeter will search for utility and plugin dependency classes. +# Use your platform path separator to separate multiple paths. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory. +# All entries will be added to the class path of the system class loader +# and also to the path of the JMeter internal loader. +# Paths with spaces may cause problems for the JVM +#user.classpath=../classes;../lib;../app1/jar1.jar;../app2/jar2.jar + +# List of paths (separated by ;) that JMeter will search for utility +# and plugin dependency classes. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory +# or given by the user.classpath property. +# All entries will be added to the path of the JMeter internal loader only. +# For plugin dependencies using plugin_dependency_paths should be preferred over +# user.classpath. +#plugin_dependency_paths=../dependencies/lib;../app1/jar1.jar;../app2/jar2.jar + +#--------------------------------------------------------------------------- +# Logging configuration +#--------------------------------------------------------------------------- +#log_level.jorphan.reflect=DEBUG +# Warning: enabling the next debug line causes javax.net.ssl.SSLException: Received fatal alert: unexpected_message +# for certain sites when used with the default HTTP Sampler +#log_level.jmeter.util.HttpSSLProtocolSocketFactory=DEBUG +#log_level.jmeter.util.JsseSSLManager=DEBUG + +# Enable Proxy request debug +#log_level.jmeter.protocol.http.proxy.HttpRequestHdr=DEBUG diff --git a/build.properties b/build.properties new file mode 100644 index 00000000000..4cb36c1f30e --- /dev/null +++ b/build.properties @@ -0,0 +1,364 @@ +# 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. + +# **** External jars (not built as part of JMeter) and needed for build/release **** + +# N.B. +# When updating this file, please also update the versions in +# - res/maven/ApacheJMeter_parent.pom +# - eclipse.classpath +# - licenses/ - file name and contents +# - xdocs/changes.xml +# +# Also, please update the lib/ directory ignore list +# [Please don't use wild-card versions, because that makes it harder to detect obsolete jars] + +# property name conventions: +# +# xxx.jar - name of the jar as used in JMeter +# +# The following properties are used to download the jars if necessary. +# +# xxx.loc - example location where the jar or zip can be found (omit trailing /) +# xxx.md5 - MD5 hash of the jar (used to check downloads) +# +# xxx.zip - name of zip file (if the jar is not available as an independent download) +# xxx.ent - the jar entry name in Zip file + +# Note that all the jars (apart from velocity and the Geronimo API jars) +# are contained in the JMeter binary release. + +maven2.repo = https://repo1.maven.org/maven2 + +apache-bsf.version = 2.4.0 +apache-bsf.jar = bsf-${apache-bsf.version}.jar +apache-bsf.loc = ${maven2.repo}/bsf/bsf/${apache-bsf.version} +apache-bsf.md5 = 16e82d858c648962fb5c959f21959039 + +avalon-framework.version = 4.1.4 +avalon-framework.jar = avalon-framework-${avalon-framework.version}.jar +avalon-framework.loc = ${maven2.repo}/avalon-framework/avalon-framework/${avalon-framework.version} +avalon-framework.md5 = 2C5306A09B22BD06A78343C0B55D021F + +beanshell.version = 2.0b5 +beanshell.jar = bsh-${beanshell.version}.jar +beanshell.loc = ${maven2.repo}/org/beanshell/bsh/${beanshell.version} +beanshell.md5 = 02F72336919D06A8491E82346E10B4D5 + +# Bouncy Castle jars (compile and test only - not distributed) +# Currently only needed for SMIMEAssertion +# N.B. hashes should be obtained from the page: http://www.bouncycastle.org/checksums.html +bcmail.version = 1.49 +bcmail.jar = bcmail-jdk15on-${bcmail.version}.jar +bcmail.loc = ${maven2.repo}/org/bouncycastle/bcmail-jdk15on/${bcmail.version} +bcmail.md5 = 25686fe5c9fc984ee7c63b8e1a3f6509 + +bcprov.version = 1.49 +bcprov.jar = bcprov-jdk15on-${bcprov.version}.jar +bcprov.loc = ${maven2.repo}/org/bouncycastle/bcprov-jdk15on/${bcprov.version} +bcprov.md5 = 20f367d41a546f2c844314da5d97ea12 + +bcpkix.version = 1.49 +bcpkix.jar = bcpkix-jdk15on-${bcprov.version}.jar +bcpkix.loc = ${maven2.repo}/org/bouncycastle/bcpkix-jdk15on/${bcprov.version} +bcpkix.md5 = cb025ef84fb991e14fdf62f6bef7be53 + +commons-codec.version = 1.10 +commons-codec.jar = commons-codec-${commons-codec.version}.jar +commons-codec.loc = ${maven2.repo}/commons-codec/commons-codec/${commons-codec.version} +commons-codec.md5 = 353cf6a2bdba09595ccfa073b78c7fcb + +commons-collections.version = 3.2.1 +commons-collections.jar = commons-collections-${commons-collections.version}.jar +commons-collections.loc = ${maven2.repo}/commons-collections/commons-collections/${commons-collections.version} +commons-collections.md5 = 13BC641AFD7FD95E09B260F69C1E4C91 + +commons-httpclient.version = 3.1 +commons-httpclient.jar = commons-httpclient-${commons-httpclient.version}.jar +commons-httpclient.loc = ${maven2.repo}/commons-httpclient/commons-httpclient/${commons-httpclient.version} +commons-httpclient.md5 = 8AD8C9229EF2D59AB9F59F7050E846A5 + +commons-io.version = 2.4 +commons-io.jar = commons-io-${commons-io.version}.jar +commons-io.loc = ${maven2.repo}/commons-io/commons-io/${commons-io.version} +commons-io.md5 = 7f97854dc04c119d461fed14f5d8bb96 + +commons-jexl.version = 1.1 +commons-jexl.jar = commons-jexl-${commons-jexl.version}.jar +commons-jexl.loc = ${maven2.repo}/commons-jexl/commons-jexl/${commons-jexl.version} +commons-jexl.md5 = 3F7735D20FCE1DBE05F62FF7A7B178DC + +commons-jexl2.version = 2.1.1 +commons-jexl2.jar = commons-jexl-${commons-jexl2.version}.jar +commons-jexl2.loc = ${maven2.repo}/org/apache/commons/commons-jexl/${commons-jexl2.version} +commons-jexl2.md5 = 4ad8f5c161dd3a50e190334555675db9 + +commons-lang3.version = 3.3.2 +commons-lang3.jar = commons-lang3-${commons-lang3.version}.jar +commons-lang3.loc = ${maven2.repo}/org/apache/commons/commons-lang3/${commons-lang3.version} +commons-lang3.md5 = 3128bf75a2549ebe38663401191bacab + + +commons-logging.version = 1.2 +commons-logging.jar = commons-logging-${commons-logging.version}.jar +commons-logging.loc = ${maven2.repo}/commons-logging/commons-logging/${commons-logging.version} +# Checksum from binary release and Maven differ, but contents of jar are identical +#commons-logging.md5 = E2C390FE739B2550A218262B28F290CE +commons-logging.md5 = 040b4b4d8eac886f6b4a2a3bd2f31b00 + +commons-math3.version = 3.5 +commons-math3.jar = commons-math3-${commons-math3.version}.jar +commons-math3.loc = ${maven2.repo}/org/apache/commons/commons-math3/${commons-math3.version} +commons-math3.md5 = 096ce126e481ebf22fbf8b96fa7243f9 + +commons-net.version = 3.3 +commons-net.jar = commons-net-${commons-net.version}.jar +commons-net.loc = ${maven2.repo}/commons-net/commons-net/${commons-net.version} +commons-net.md5 = c077ca61598e9c21f43f8b6488fbbee9 + +commons-pool2.version = 2.3 +commons-pool2.jar = commons-pool2-${commons-pool2.version}.jar +commons-pool2.loc = ${maven2.repo}/org/apache/commons/commons-pool2/${commons-pool2.version} +commons-pool2.md5 = 9f406b4acc111aa8070db5a899149e70 + +# dnsjava for DNSCacheManager +dnsjava.version = 2.1.7 +dnsjava.jar = dnsjava-${dnsjava.version}.jar +dnsjava.loc = ${maven2.repo}/dnsjava/dnsjava/${dnsjava.version} +dnsjava.md5 = 11363bd58696feae207a992da2ce7a90 + +excalibur-instrument.version = 1.0 +excalibur-instrument.jar = excalibur-instrument-${excalibur-instrument.version}.jar +excalibur-instrument.loc = ${maven2.repo}/excalibur-instrument/excalibur-instrument/${excalibur-instrument.version} +excalibur-instrument.md5 = 81BF95737C97A46836EA5F21F7C82719 + +excalibur-logger.version = 1.1 +excalibur-logger.jar = excalibur-logger-${excalibur-logger.version}.jar +excalibur-logger.loc = ${maven2.repo}/excalibur-logger/excalibur-logger/${excalibur-logger.version} +excalibur-logger.md5 = E8246C546B7B0CAFD65947E9B80BB884 + +excalibur-datasource.version = 2.1 +excalibur-datasource.jar = excalibur-datasource-${excalibur-datasource.version}.jar +excalibur-datasource.loc = ${maven2.repo}/excalibur-datasource/excalibur-datasource/${excalibur-datasource.version} +excalibur-datasource.md5 = 7a57ccdfeda3a4f157016f5a9270a4f9 + +# pool has been split into 3 parts +excalibur-pool.version = 2.1 +excalibur-pool-api.version = ${excalibur-pool.version} +excalibur-pool-api.jar = excalibur-pool-api-${excalibur-pool-api.version}.jar +excalibur-pool-api.loc = ${maven2.repo}/excalibur-pool/excalibur-pool-api/${excalibur-pool-api.version} +excalibur-pool-api.md5 = f9a224e1ee0896764aadbf7ddd253acc + +excalibur-pool-impl.version = ${excalibur-pool.version} +excalibur-pool-impl.jar = excalibur-pool-impl-${excalibur-pool-impl.version}.jar +excalibur-pool-impl.loc = ${maven2.repo}/excalibur-pool/excalibur-pool-impl/${excalibur-pool-impl.version} +excalibur-pool-impl.md5 = 8be9c177894998090b4662326d7f22de + +excalibur-pool-instrumented.version = ${excalibur-pool.version} +excalibur-pool-instrumented.jar = excalibur-pool-instrumented-${excalibur-pool-instrumented.version}.jar +excalibur-pool-instrumented.loc = ${maven2.repo}/excalibur-pool/excalibur-pool-instrumented/${excalibur-pool-instrumented.version} +excalibur-pool-instrumented.md5 = 1b5425fe0fe63dc67da6fe995db6be31 + +# Common file containing both htmlparser and htmllexer jars +htmlparser.version = 2.1 +htmllexer.loc = ${maven2.repo}/org/htmlparser/htmllexer/${htmlparser.version} +htmllexer.jar = htmllexer-${htmlparser.version}.jar +htmllexer.md5 = 1cb7184766a0c52f4d98d671bb08be19 + +htmlparser.loc = ${maven2.repo}/org/htmlparser/htmlparser/${htmlparser.version} +htmlparser.jar = htmlparser-${htmlparser.version}.jar +htmlparser.md5 = aa05b921026c228f92ef8b4a13c26f8d + +# Apache HttpClient 4.x +httpclient.version = 4.2.6 +# +httpclient.jar = httpclient-${httpclient.version}.jar +httpclient.loc = ${maven2.repo}/org/apache/httpcomponents/httpclient/${httpclient.version} +httpclient.md5 = 7bae53a30550dd3eb62db72ab08fcd94 + +# Required for HttpClient +httpmime.jar = httpmime-${httpclient.version}.jar +httpmime.loc = ${maven2.repo}/org/apache/httpcomponents/httpmime/${httpclient.version} +httpmime.md5 = 291ec6eac9dfb76f2b8c4f1b647b9a21 + +# Required for HttpClient +httpcore.version = 4.2.5 +httpcore.jar = httpcore-${httpcore.version}.jar +httpcore.loc = ${maven2.repo}/org/apache/httpcomponents/httpcore/${httpcore.version} +httpcore.md5 = 7e23d35d533b24c1f385724e8b5ba623 + +jakarta-oro.version = 2.0.8 +jakarta-oro.jar = oro-${jakarta-oro.version}.jar +jakarta-oro.loc = ${maven2.repo}/oro/oro/${jakarta-oro.version} +jakarta-oro.md5 = 42E940D5D2D822F4DC04C65053E630AB + +jcharts.version = 0.7.5 +jcharts.jar = jcharts-${jcharts.version}.jar +jcharts.loc = ${maven2.repo}/jcharts/jcharts/${jcharts.version} +jcharts.md5 = 13927D8077C991E7EBCD8CB284746A7A + +jdom.version = 1.1.3 +jdom.jar = jdom-${jdom.version}.jar +jdom.loc = ${maven2.repo}/org/jdom/jdom/${jdom.version} +jdom.md5 = 140bfed13341fe2039eee0f26a16d705 + +rhino.version = 1.7R5 +rhino.jar = rhino-${rhino.version}.jar +rhino.loc = ${maven2.repo}/org/mozilla/rhino/${rhino.version} +rhino.md5 = 515233bd8a534c0468f6e397fc6b1925 + +jodd-core.version = 3.6.4 +jodd-core.jar = jodd-core-${jodd-core.version}.jar +jodd-core.loc = ${maven2.repo}/org/jodd/jodd-core/${jodd-core.version} +jodd-core.md5 = 0e1dc3aaf0c56b3c02a4399561289a01 + +jodd-lagarto.version = 3.6.4 +jodd-lagarto.jar = jodd-lagarto-${jodd-lagarto.version}.jar +jodd-lagarto.loc = ${maven2.repo}/org/jodd/jodd-lagarto/${jodd-lagarto.version} +jodd-lagarto.md5 = 7a361d48f9f25faf6f97fa43bf29b757 + +jodd-log.version = 3.6.4 +jodd-log.jar = jodd-log-${jodd-log.version}.jar +jodd-log.loc = ${maven2.repo}/org/jodd/jodd-log/${jodd-log.version} +jodd-log.md5 = bb2807fbc6143acceb1776c4f0b31ca0 + +jsoup.version = 1.8.1 +jsoup.jar = jsoup-${jsoup.version}.jar +jsoup.loc = ${maven2.repo}/org/jsoup/jsoup/${jsoup.version} +jsoup.md5 = 3e473766c65d0f19bfeceaf810ef2f07 + +junit.version = 4.12 +junit.jar = junit-${junit.version}.jar +junit.loc = ${maven2.repo}/junit/junit/${junit.version} +junit.md5 = 5b38c40c97fbd0adee29f91e60405584 + +logkit.version = 2.0 +logkit.jar = logkit-${logkit.version}.jar +logkit.loc = ${maven2.repo}/logkit/logkit/${logkit.version} +logkit.md5 = 8D82A3E91AAE216D0A2A40B837A232FF + +mongo-java-driver.version = 2.11.3 +mongo-java-driver.jar = mongo-java-driver-${mongo-java-driver.version}.jar +mongo-java-driver.loc = ${maven2.repo}/org/mongodb/mongo-java-driver/${mongo-java-driver.version} +mongo-java-driver.md5 = 90647a53231eb75715fda30759ff4ff7 + +rsyntaxtextarea.version = 2.5.6 +rsyntaxtextarea.jar = rsyntaxtextarea-${rsyntaxtextarea.version}.jar +rsyntaxtextarea.loc = ${maven2.repo}/com/fifesoft/rsyntaxtextarea/${rsyntaxtextarea.version} +rsyntaxtextarea.md5 = 064450da76e07f5a496dfded67577bdb + +slf4j-api.version = 1.7.10 +slf4j-api.jar = slf4j-api-${slf4j-api.version}.jar +slf4j-api.loc = ${maven2.repo}/org/slf4j/slf4j-api/${slf4j-api.version} +slf4j-api.md5 = 3459bdfbe4a9abed1b5b32d2e0c520ee + +# Implementation to be used (currently NOP) +slf4j-nop.version = 1.7.10 +slf4j-nop.jar = slf4j-nop-${slf4j-nop.version}.jar +slf4j-nop.loc = ${maven2.repo}/org/slf4j/slf4j-nop/${slf4j-nop.version} +slf4j-nop.md5 = bceaa218daa8d06d7ed151daadc5be78 + +soap.version = 2.3.1 +soap.jar = soap-${soap.version}.jar +soap.loc = ${maven2.repo}/soap/soap/${soap.version} +soap.md5 = AA1845E01FEE94FE4A63BBCAA55AD486 + +jtidy.version = r938 +jtidy.jar = jtidy-${jtidy.version}.jar +jtidy.loc = ${maven2.repo}/net/sf/jtidy/jtidy/${jtidy.version} +jtidy.md5 = 6A9121561B8F98C0A8FB9B6E57F50E6B + +# Apache Tika to extract text from various documents +tika-core.version = 1.8 +tika-core.jar = tika-core-${tika-core.version}.jar +tika-core.loc = ${maven2.repo}/org/apache/tika/tika-core/${tika-core.version} +tika-core.md5 = 7c3ec7b588473aa45c77af6972905d17 + +tika-parsers.version = 1.8 +tika-parsers.jar = tika-parsers-${tika-parsers.version}.jar +tika-parsers.loc = ${maven2.repo}/org/apache/tika/tika-parsers/${tika-parsers.version} +tika-parsers.md5 = 45e8751adef7e627cc1f7c79c7b017d4 + +# XStream can be found at: http://xstream.codehaus.org/ +xstream.version = 1.4.8 +xstream.jar = xstream-${xstream.version}.jar +xstream.loc = ${maven2.repo}/com/thoughtworks/xstream/xstream/${xstream.version} +xstream.md5 = 4551a29c38f22ed25eaf109eda50ff03 + +# XMLPull is required by XStream 1.4.x +xmlpull.version = 1.1.3.1 +xmlpull.jar = xmlpull-${xmlpull.version}.jar +xmlpull.loc = ${maven2.repo}/xmlpull/xmlpull/${xmlpull.version} +xmlpull.md5 = cc57dacc720eca721a50e78934b822d2 + +xpp3.version = 1.1.4c +xpp3.jar = xpp3_min-${xpp3.version}.jar +xpp3.loc = ${maven2.repo}/xpp3/xpp3_min/${xpp3.version} +xpp3.md5 = DCD95BCB84B09897B2B66D4684C040DA + +# Xalan can be found at: http://xml.apache.org/xalan-j/ +xalan.version = 2.7.2 +xalan.jar = xalan-${xalan.version}.jar +xalan.loc = ${maven2.repo}/xalan/xalan/${xalan.version} +xalan.md5 = 6aa6607802502c8016b676f25f8e4873 + +serializer.version = 2.7.2 +serializer.jar = serializer-${serializer.version}.jar +serializer.loc = ${maven2.repo}/xalan/serializer/${serializer.version} +serializer.md5 = e8325763fd4235f174ab7b72ed815db1 + +# Xerces can be found at: http://xerces.apache.org/xerces2-j/ +xerces.version = 2.11.0 +xerces.jar = xercesImpl-${xerces.version}.jar +xerces.loc = ${maven2.repo}/xerces/xercesImpl/${xerces.version} +# Checksum from binary release and Maven differ, but contents of jar are identical (apart from EOLs in text files) +#xerces.md5 = DA09B75B562CA9A8E9A535D2148BE8E4 +xerces.md5 = 43584adc1f895628055bad0aa98a1007 + +xml-apis.version = 1.4.01 +xml-apis.jar = xml-apis-${xml-apis.version}.jar +xml-apis.loc = ${maven2.repo}/xml-apis/xml-apis/${xml-apis.version} +xml-apis.md5 = 7eaad6fea5925cca6c36ee8b3e02ac9d + +# Codecs were previously provided by Batik +xmlgraphics-commons.version = 1.5 +xmlgraphics-commons.jar = xmlgraphics-commons-${xmlgraphics-commons.version}.jar +xmlgraphics-commons.loc = ${maven2.repo}/org/apache/xmlgraphics/xmlgraphics-commons/${xmlgraphics-commons.version} +xmlgraphics-commons.md5 = 86090bc1cfbb6c7bb0efee2d6c6fd7b6 + +# JavaMail jars (N.B. these are available under CDDL) +javamail.version = 1.5.0-b01 +javamail.jar = mail-${javamail.version}.jar +javamail.loc = ${maven2.repo}/javax/mail/mail/${javamail.version} +javamail.md5 = 7b56e34995f7f1cb55d7806b935f90a4 + +# Geronimo JMS jar +jms.version = 1.1.1 +jms.jar = geronimo-jms_1.1_spec-${jms.version}.jar +jms.loc = ${maven2.repo}/org/apache/geronimo/specs/geronimo-jms_1.1_spec/${jms.version} +jms.md5 = d80ce71285696d36c1add1989b94f084 + +# The following jars are only needed for source distributions +# They are used for building the documentation +velocity.version = 1.7 +velocity.jar = velocity-${velocity.version}.jar +velocity.loc = ${maven2.repo}/org/apache/velocity/velocity/${velocity.version} +velocity.md5 = 3692dd72f8367cb35fb6280dc2916725 + +# required by Velocity +commons-lang.version = 2.6 +commons-lang.jar = commons-lang-${commons-lang.version}.jar +commons-lang.loc = ${maven2.repo}/commons-lang/commons-lang/${commons-lang.version} +commons-lang.md5 = 4d5c1693079575b362edf41500630bbd diff --git a/build.xml b/build.xml new file mode 100644 index 00000000000..d27da4f7d2d --- /dev/null +++ b/build.xml @@ -0,0 +1,3083 @@ + + + + + + N.B. To build JMeter from a release you need both the binary and source archives, + and these must be unpacked into the same directory structure. + + To download additional jars needed for building the code and documentation: + + ant download_jars + + + To build JMeter from source: + ant [install] + + To rebuild: + ant clean install + + To update documentation + ant docs-site + ant docs-printable + To build API documentation (Javadoc) + ant docs-api + To build all the docs + ant docs-all + + To build all and package up the files for distribution + ant distribution -Djmeter.version=vvvv [-Dsvn.revision=nnnnn] + + Add -Ddisable-svnCheck=true to disable svn check, if you build from src archive or offline + Add -Ddisable-check-versions=true to disable matching current svn revision and JMeterVersion.java, + if you want build your own custom JMeter package. + + To create a nightly build (separate bin/src/lib jars): + ant nightly [-Dsvn.revision=nnnnn] + + To create tar and tgz of the web-site documentation (docs and api) + ant site [ -Djmeter.version=vvvv ] + + + For more info: + ant -projecthelp + + To diagnose usage of deprecated APIs: + ant -Ddeprecation=on clean compile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jmeter.version = ${jmeter.version} + display.version = ${display.version} + implementation.version = ${implementation.version} + + + + + + + + + + + + + svn.revision = ${svn.revision} + jmeter.version = ${jmeter.version} + display.version = ${display.version} + implementation.version = ${implementation.version} + + + + + eclipse.anakia = ${eclipse.anakia} + + + + + + + + + + + + + AnakiaTask is not present, documentation will not be generated. + + + + + + Velocity version appears to be older than 1.5: the documentation may be generated with incorrect line endings. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cannot find all the required 3rd party libraries. + If building from a release, you can get most of them from the binary archive. + Use "ant download_jars" to download any missing jars. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Converting work files to eol=${eoltype} in ${workdir} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Creating JMeter distribution ${dist.name} ${svn.revision} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Updating POM files to version ${jmeter.version} + + + + + + + + Copying jar files ready for signing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Processing ${tempfile} using svnmucc on repo: ${repoType} + ${repo} + ${message} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Version = ${jmeter.version} ${RC} + + + + + + + + + + + Version = ${jmeter.version} ${RC} + + + + + + + + + + + + + + Old Version = ${jmeter.old.version} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gump properties for this run + jmeter.version = ${jmeter.version} + gump.run = ${gump.run} + date.projectfile = ${date.projectfile} + version.projectfile = ${version.projectfile} + Build file: + version.build = ${version.build} + Java properties: + target.java.version = ${target.java.version} + src.java.version = ${src.java.version} + optimize = ${optimize} + deprecation = ${deprecation} + encoding = ${encoding} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Updating overview to ${docversion} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fixing EOL + + Removing unnecessary </br> tags + + + + + + + + + + + + + + + + + + + + Fixing EOL + + Removing unnecessary </br> tags + + + Copying files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Error detected in server log file. See above. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CSV Files are not identical. + ${batchtest.inp}${file.separator}${batchtest.name}.csv + ${batchtest.out}${file.separator}${batchtest.name}.csv + + + + + + + + XML Files are not identical. + ${batchtest.inp}${file.separator}${batchtest.name}.xml + ${batchtest.out}${file.separator}${batchtest.name}.xml + + + ${batchtest.name} output files compared OK + + + + + + + Error detected in log file. See above. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + gump.run = ${gump.run} + java.awt.headless = ${java.awt.headless} + test.headless = ${test.headless} + user.dir = ${user.dir} + basedir = ${basedir} + test dir = ${build.test} + test dir gump = ${build.test.gump} + testsaveservice.saveout = ${testsaveservice.saveout} + test.encoding = ${test.encoding} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Checking ${jar} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bad Checksum: for ${file} + expected ${md5} + actual ${MD5} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000000..9e9b698e584 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doap_JMeter.rdf b/doap_JMeter.rdf new file mode 100644 index 00000000000..d743adaecae --- /dev/null +++ b/doap_JMeter.rdf @@ -0,0 +1,330 @@ + + + + + + 2006-02-17 + + Apache JMeter + + + Pure Java application for load and functional testing + Apache JMeter may be used to test performance both on static and dynamic resources (files, Servlets, Perl scripts, Java Objects, Data Bases and Queries, FTP Servers and more). It can be used to simulate a heavy load on a server, network or object to test its strength or to analyze overall performance under different load types. You can use it to make a graphical analysis of performance or to test your server/script/object behavior under heavy concurrent load. + + + + Java + + + + Apache Jakarta JMeter + 1998-12-15 + 1.0 + + + + + Apache Jakarta JMeter + 1999-01-25 + 1.0.1 + + + + + Apache Jakarta JMeter + 1999-02-05 + 1.0.2 + + + + + Apache Jakarta JMeter + 1999-02-24 + 1.1 + + + + + Apache Jakarta JMeter + 1999-03-17 + 1.2 + + + + + Apache Jakarta JMeter + 1999-04-16 + 1.3 + + + + + Apache Jakarta JMeter + 1999-07-11 + 1.4 + + + + + Apache Jakarta JMeter + 2000-09-30 + 1.5 + + + + + Apache Jakarta JMeter + 2000-12-22 + 1.5.1 + + + + + Apache Jakarta JMeter + 2001-06-12 + 1.6.1 + + + + + Apache Jakarta JMeter + 2002-09-27 + 1.7 + + + + + Apache Jakarta JMeter + 2002-03-07 + 1.7.1 + + + + + Apache Jakarta JMeter + 2002-06-18 + 1.7.3 + + + + + Apache Jakarta JMeter + 2002-08-16 + 1.8 + + + + + Apache Jakarta JMeter + 2003-01-13 + 1.8.1 + + + + + Apache Jakarta JMeter + 2003-08-07 + 1.9 + + + + + Apache Jakarta JMeter + 2003-08-17 + 1.9.1 + + + + + Apache Jakarta JMeter + 2004-04-04 + 2.0.0 + + + + + Apache Jakarta JMeter + 2004-05-24 + 2.0.1 + + + + + Apache Jakarta JMeter + 2004-11-19 + 2.0.2 + + + + + Apache Jakarta JMeter + 2005-03-23 + 2.0.3 + + + + + Apache Jakarta JMeter + 2005-08-24 + 2.1 + + + + + Apache Jakarta JMeter + 2005-10-04 + 2.1.1 + + + + + Apache Jakarta JMeter + 2006-06-14 + 2.2 + + + + + Apache Jakarta JMeter + 2007-09-04 + 2.3RC4 + + + + + Apache Jakarta JMeter + 2007-09-29 + 2.3 final + + + + + Apache Jakarta JMeter + 2007-11-30 + 2.3.1 final + + + + + Apache Jakarta JMeter + 2008-06-14 + 2.3.2 final + + + + + Apache Jakarta JMeter + 2009-05-24 + 2.3.3 final + + + + + Apache Jakarta JMeter + 2009-06-21 + 2.3.4 final + + + + + Apache Jakarta JMeter + 2010-07-12 + 2.4 final + + + + + Apache Jakarta JMeter + 2011-08-17 + 2.5 final + + + + + Apache Jakarta JMeter + 2011-10-03 + 2.5.1 final + + + + + Apache JMeter + 2012-02-01 + 2.6 final + + + + + Apache JMeter + 2012-05-27 + 2.7 final + + + + + Apache JMeter + 2012-10-06 + 2.8 final + + + + + Apache JMeter + 2013-01-28 + 2.9 final + + + + + Apache JMeter + 2013-10-21 + 2.10 final + + + + + Apache JMeter + 2014-01-05 + 2.11 final + + + + + Apache JMeter + 2014-11-10 + 2.12 final + + + + + Apache JMeter + 2015-03-14 + 2.13 final + + + + + + + + + + diff --git a/docs/building.html b/docs/building.html new file mode 100644 index 00000000000..8a7b22aa01c --- /dev/null +++ b/docs/building.html @@ -0,0 +1,91 @@ + +Apache JMeter + - + Building JMeter and Add-Ons
Logo ASF
Apache JMeter

Building JMeter and Add-Ons

+ +Note to developers: +This is a very brief overview. +There is more infomation on the JMeter Wiki or in eclipse.readme in root folder of sources. + +

Building Add-Ons

+

+There is no need to build JMeter if you just want to build an add-on. +Just download the binary archive and add the jars to the classpath or use Maven artifacts to build your add-ons. +You may want to also download the source so it can be used by the IDE. + +

+

See the extras/addons* files in the source tree for some suggestions

+ +

Building JMeter

+

Acquiring the source

+

The full source is distributed alongside the binary, it can also be downloaded from SVN or found on Apache JMeter Github Mirror .

+

+The source archive and SVN do not contain any of the required library files. +These need to be downloaded by running the Ant command: +

+ant download_jars
+
+

+

Or you can download the binary distribution archive for a release and unpack it into the same directory structure as the source. +This will ensure that the lib/ directory contains the jar files needed for running JMeter. +There are a few additional jars that are needed to build JMeter, download these using: +

+ant download_jars
+
+This will retrieve any missing jars. +

+

Compiling and packaging JMeter using Ant

+

+JMeter can be built entirely using Ant. +The basic command is: +

+ant [install]
+
+See build.xml for the other targets that can be used. +

+

Compiling and packaging JMeter using Eclipse

+

+Once you have downloaded the source from SVN or the release archives and run the ant download_jars target to +install the dependent jars, you can configure Eclipse. The easiest way to do this is to replace the Eclipse .classpath +file with the eclipse.classpath file provided with JMeter. This will set up the source-paths and most of the libraries. +

+Ensure your read eclipse.readme for project configuration. +

+

+
\ No newline at end of file diff --git a/docs/changes.html b/docs/changes.html new file mode 100644 index 00000000000..75e16864ac8 --- /dev/null +++ b/docs/changes.html @@ -0,0 +1,577 @@ + +Apache JMeter + - + Changes
Logo ASF
Apache JMeter

Changes

+ +
+This page details the changes made in the current version only. +
+Earlier changes are detailed in the History of Previous Changes. +
+ + + + +

Version 2.13

+ +Summary + + +

New and Noteworthy

+ + + + +

New Elements

+ +

New Async BackendListener with Graphite implementation

+

A new Async BackendListener has been added to allow sending result data to a backend listener. +JMeter ships with a GraphiteBackendListenerClient that allows sending results to a Graphite server using Pickle ot Plaintext protocols. +You can implement your own backend by extending AbstractBackendListenerClient. This backend could be +a database (JDBC), a Message Oriented Middleware (JMS), a Webservice or anything you want. +

+
+

This is the kind of Live Dashboard you can obtain using Grafana and InfluxDB
+Read this for more details.

+
Grafana dashboard
Grafana dashboard
+ +

Core Improvements

+ +

New connect time metric

+

Starting with this version a new metric called connectTime has been added. It represents the time to establish connection. +By default it is not saved to CSV or XML, to have it saved add to user.properties:
+ +jmeter.save.saveservice.connect_time=true + +

+
+
+ +

Aggregate Graph and Report

+

The listeners Aggregate Graph and Aggregate Report previously showed only the 90 percentile (historical behavior), the 95 percentile and the 99 percentile have been added and are customizable. +To setup the percentiles value you want, add to user.properties:
+ +aggregate_rpt_pct1=90
+aggregate_rpt_pct2=95
+aggregate_rpt_pct3=99 +
+

+

+ +

HTTP(S) Test Script Recorder

+

Now component is able to detect authentication schemes and automatically adds a pre-configured HTTP Authorization Manager with the correct Mechanism. +

+ +

HTTP Request

+

The CalDAV verbs (Calendar extensions to WebDAV) REPORT and MKCALENDAR have been added in the HTTP Request sampler. +

+

+ +

JDBC Request

+

The ResultSet can be get as a object, this allows to handle more easily the results after in BeanShell, JSR223 scripts... +

+

+ +

Distributed Testing

+

To allow better usage of Distributed Testing in the cloud, retry behaviour has been added when starting test on servers. +Read this for more details. +

+

+ +

Distributed Testing performance

+

Since JMeter 2.13, Stripping modes (StrippingBatch being the default mode) now also strip responses from SubResults improving consumed network bandwidth. +

+ +

Documentation refresh

+

A new style for website (responsive and more up to date) has been created by Felix Schumacher. +Documentations have been refreshed particularly: +

+

+ +

GUI Improvements

+ +

Module Controller

+

The Module Controller now shows the target controller in a tree view (instead of combo list). +

+

+ +

Toolbar

+

JMeter's toolbar has been refreshed for some icons (start, toogle, etc.). Three sizes are now avialable for the icons: 22x22, 32x32 and 48x48.
+The property to define your prefered size is: +

jmeter.toolbar.icons.size=value
+with the value 22x22 (default size), 32x32 or 48x48.

+

The toolbar with 22x22 pixels icons +

+

+ +

The toolbar with 32x32 pixels icons +

+

+ +

The toolbar with 48x48 pixels icons +

+

+ +

HTTP(S) Test Script Recorder

+

If your Test Plan does not contains a Recording Controller, a new warning message will appear if the + HTTP(S) Test Script Recorder is configured to send the samples into a Recording Controller. +

+

+ + + +

Incompatible changes

+ +
    +
  • Since 2.13, Aggregate Graph, Summary Report and Aggregate Report now export percentages to %, before they exported the decimal value which differed from what was shown in GUI
  • +
  • Third party plugins may be impacted by fix of + Bug + 57586, ensure that your subclass of HttpTestSampleGui implements ItemListener if you relied on parent class doing so.
  • +
  • Report package has been removed, ApacheJMeter_report.jar is not generated anymore as a consequence, see + Bug + 57269
  • +
+ + + +

Improvements

+ +

HTTP Samplers and Test Script Recorder

+
    +
  • + Bug + 25430 + - + HTTP(S) Test Script Recorder : Make it populate HTTP Authorization Manager. Partly based on a patch from Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • + Bug + 57381 + - + HTTP(S) Test Script Recorder should display an error if Target Controller references a Recording Controller and no Recording Controller exists. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57488 + - + Performance : Improve SSLContext reset for Two-way SSL Authentication
  • +
  • + Bug + 57565 + - + SamplerCreator : Add method to allow implementations to add children to created sampler
  • +
  • + Bug + 57606 + - + HTTPSamplerBase#errorResult changes the sample label on exception
  • +
  • + Bug + 57613 + - + HTTP Sampler : Added CalDAV verbs (REPORT, MKCALENDAR). Contributed by Richard Brigham (richard.brigham at teamaol.com)
  • +
  • + Bug + 48799 + - + Add time to establish connection to available sample metrics. Implemented by Andrey Pokhilko (andrey at blazemeter.com) and contributed by BlazeMeter Ltd. and Pieter Ennes (apache.org at spam.ennes.nl)
  • +
  • + Bug + 57500 + - + Introduce retry behavior for distributed testing. Implemented by Andrey Pokhilko and Dzimitry Kashlach and contributed by BlazeMeter Ltd.
  • +
+ +

Other samplers

+
    +
  • + Bug + 57322 + - + JDBC Test elements: add ResultHandler to deal with ResultSets(cursors) returned by callable statements. Contributed by Yngvi Þór Sigurjónsson (blitzkopf at gmail.com)
  • +
+ +

Controllers

+
    +
  • + Bug + 57561 + - + Module controller UI : Replace combobox by tree. Contributed by Maciej Franek (maciej.franek at gmail.com)
  • +
  • + Bug + 57648 + - + TestFragment should be disabled when created. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
+ +

Listeners

+
    +
  • + Bug + 55932 + - + Create a Async BackendListener to allow easy plug of new listener (Graphite, JDBC, Console,...)
  • +
  • + Bug + 57246 + - + BackendListener : Create a Graphite implementation
  • +
  • + Bug + 57217 + - + Aggregate graph and Aggregate report improvements (3 configurable percentiles, same data in both, factor out code). Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57537 + - + BackendListener : Allow implementations to drop samples
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
+ +

Functions

+
    +
  • + Bug + 54453 + - + Performance enhancements : Replace Random by ThreadLocalRandom in __Random function
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 57518 + - + Icons for toolbar with several sizes
  • +
  • + Bug + 57605 + - + When there is an error loading Test Plan, SaveService.loadTree returns null leading to NPE in callers
  • +
  • + Bug + 57269 + - + Drop org.apache.jmeter.reports package
  • +
  • + Bug + 53764 + - + Website : Create a new style for website
  • +
+

Non-functional changes

+
    +
  • Updated to jsoup-1.8.1.jar (from 1.7.3)
  • +
  • Updated to tika-core and tika-parsers 1.7 (from 1.6)
  • +
  • Updated to commons-codec-1.10.jar (from 1.9)
  • +
  • Updated to dnsjava-2.1.7.jar (from 2.1.6)
  • +
  • Updated to jodd-3.6.4.jar (from 3.6.1)
  • +
  • Updated to junit-4.12.jar (from 4.11)
  • +
  • Updated to rhino-1.7R5 (from 1.7R4)
  • +
  • Updated to rsyntaxtextarea-2.5.6 (from 2.5.3)
  • +
  • Updated to slf4j-1.7.10 (from 1.7.5)
  • +
  • + Bug + 57276 + - + RMIC no longer needed since Java 5
  • +
  • + Bug + 57310 + - + Replace System.getProperty("file.separator") with File.separator throughout (Also "path.separator" with File.pathSeparator)
  • +
  • + Bug + 57389 + - + Fix potential NPE in converters
  • +
  • + Bug + 57417 + - + Remove unused method isTemporary from NullProperty. This was a leftover from a refactoring done in 2003.
  • +
  • + Bug + 57418 + - + Remove unused constructor from Workbench
  • +
  • + Bug + 57419 + - + Remove unused interface ModelListener.
  • +
  • + Bug + 57466 + - + IncludeController : Remove an unneeded set creation. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • Added property loggerpanel.usejsyntaxtext to disable the use of JSyntaxTextArea for the Console Logger (in case of memory or other issues)
  • +
  • + Bug + 57586 + - + HttpTestSampleGui: Remove interface ItemListener implementation
  • +
+ + + +

Bug fixes

+ +

HTTP Samplers and Test Script Recorder

+
    +
  • + Bug + 57385 + - + Getting empty thread name in xml result for HTTP requests with "Follow Redirects" set. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57579 + - + NullPointerException error is raised on main sample if "RETURN_NO_SAMPLE" is used (default) and "Use Cache-Control / Expires header..." is checked in HTTP Cache Manager
  • +
+ +

Other Samplers

+
    +
+ +

Controllers

+
    +
  • + Bug + 57447 + - + Use only the user listed DNS Servers, when "use custom DNS resolver" option is enabled.
  • +
+ +

Listeners

+
    +
  • + Bug + 57262 + - + Aggregate Report, Aggregate Graph and Summary Report export : headers use keys instead of labels
  • +
  • + Bug + 57346 + - + Summariser : The + (difference) reports show wrong elapsed time and throughput
  • +
  • + Bug + 57449 + - + Distributed Testing: Stripped modes do not strip responses from SubResults (affects load tests that use Download of embedded resources). Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57562 + - + View Results Tree CSS/JQuery Tester : Nothing happens when there is an error in syntax and an exception occurs in jmeter.log
  • +
  • + Bug + 57514 + - + Aggregate Graph, Summary Report and Aggregate Report show wrong percentage reporting in saved file
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 57607 + - + Constant Throughput Timer : Wrong throughput computed in shared modes due to rounding error
  • +
+ +

General

+
    +
  • + Bug + 57365 + - + Selected LAF is not correctly setup due to call of UIManager.setLookAndFeel too late. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57364 + - + Options < Look And Feel does not update all windows LAF. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57394 + - + When constructing an instance with ClassTools#construct(String, int) the integer was ignored and the default constructor was used instead.
  • +
  • + Bug + 57440 + - + OutOfMemoryError after introduction of JSyntaxTextArea in LoggerPanel due to disableUndo not being taken into account.
  • +
  • + Bug + 57569 + - + FileServer.reserveFile - inconsistent behaviour when hasHeader is true
  • +
  • + Bug + 57555 + - + Cannot use JMeter 2.12 as a maven dependency. Contributed by Pascal Schumacher (pascal.schumacher at t-systems.com)
  • +
  • + Bug + 57608 + - + Fix start script compatibility with old Unix shells, e.g. on Solaris
  • +
+ + + +

Thanks

+

We thank all contributors mentioned in bug and improvement sections above: +

    +
  • Ubik Load Pack
  • +
  • Yngvi Þór Sigurjónsson (blitzkopf at gmail.com)
  • +
  • Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • BlazeMeter Ltd.
  • +
  • Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • Pascal Schumacher (pascal.schumacher at t-systems.com)
  • +
  • Maciej Franek (maciej.franek at gmail.com)
  • +
  • Richard Brigham (richard.brigham at teamaol.com)
  • +
  • Pieter Ennes (apache.org at spam.ennes.nl)
  • +
+ +
+We also thank bug reporters who helped us improve JMeter.
+For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes: +
    +
  • Chaitanya Bhatt (bhatt.chaitanya at gmail.com) for his thorough testing of new BackendListener and Graphite Client implementation.
  • +
  • Marcelo Jara (marcelojara at hotmail.com) for his clear report on + Bug + 57607.
  • +
+ +Apologies if we have omitted anyone else. +

+ + +

Known bugs

+ +
    +
  • The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).
  • + +
  • +The numbers that appear to the left of the green box are the number of active threads / total number of threads, +the total number of threads only applies to a locally run test, otherwise it will show 0 (see + Bug + 55510). +
  • + +
  • +Note that there is a bug in Java +on some Linux systems that manifests itself as the following error when running the test cases or JMeter itself: +
    + [java] WARNING: Couldn't flush user prefs:
    + java.util.prefs.BackingStoreException:
    + java.lang.IllegalArgumentException: Not supported: indent-number
    +
    +This does not affect JMeter operation. This issue is fixed since Java 7b05. +
  • + +
  • +Note that under some windows systems you may have this WARNING: +
    +java.util.prefs.WindowsPreferences 
    +WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0
    +x80000002. Windows RegCreateKeyEx(...) returned error code 5.
    +
    +The fix is to run JMeter as Administrator, it will create the registry key for you, then you can restart JMeter as a normal user and you won't have the warning anymore. +
  • + +
  • +With Java 1.6 and Gnome 3 on Linux systems, the JMeter menu may not work correctly (shift between mouse's click and the menu). +This is a known Java bug (see + Bug + 54477). +A workaround is to use a Java 7 runtime (OpenJDK or Oracle JDK). +
  • + +
  • +With Oracle Java 7 and Mac Book Pro Retina Display, the JMeter GUI may look blurry. +This is a known Java bug, see Bug JDK-8000629. +A workaround is to use a Java 7 update 40 runtime which fixes this issue. +
  • + +
  • +You may encounter the following error: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints + if you run a HTTPS request on a web site with a SSL certificate (itself or one of SSL certificates in its chain of trust) with a signature + algorithm using MD2 (like md2WithRSAEncryption) or with a SSL certificate with a size lower than 1024 bits. +This error is related to increased security in Java 7 version u16 (MD2) and version u40 (Certificate size lower than 1024 bits), and Java 8 too. +
    +To allow you to perform your HTTPS request, you can downgrade the security of your Java installation by editing +the Java jdk.certpath.disabledAlgorithms property. Remove the MD2 value or the constraint on size, depending on your case. +
    +This property is in this file: +
    JAVA_HOME/jre/lib/security/java.security
    +See + Bug + 56357 for details. +
  • + +
+ +
\ No newline at end of file diff --git a/docs/changes_history.html b/docs/changes_history.html new file mode 100644 index 00000000000..a7b71b245b2 --- /dev/null +++ b/docs/changes_history.html @@ -0,0 +1,7245 @@ + +Apache JMeter + - + History of Previous Changes
Logo ASF
Apache JMeter

History of Previous Changes

+
+This page details the changes made in previous versions only. +
+Current changes are detailed in Changes. +
+

Changes sections are chronologically ordered from top (most recent) to bottom +(least recent)

+ + + +

Version 2.12

+ +Summary + + +

New and Noteworthy

+ + + + +

Java 8 support

+

Now, JMeter 2.12 is compliant with Java 8.

+ +

New Elements

+

Critical Section Controller

+

The Critical Section Controller allow to serialize the execution of a section in your tree. +Only one instance of the section will be executed at the same time during the test.

+
+ +

DNS Cache Manager

+

The new configuration element DNS Cache Manager(see + Bug + 56841) improves the testing of: +

    +
  • CDN (Content Delivery Network)
  • +
  • DNS load balancing.
  • +
  • Load Balancers like Amazon Elastic Load Balancer
  • +
+

+
+

Core Improvements

+ +

Smarter Recording of Http Test Plans

+

Test Script Recorder has been improved in many ways

+
    +
  • Better matching of Variables in Requests, making Test Script Recorder variabilize your sampler during recording more versatile
  • +
  • Ability to filter from View Results Tree the Samples that are excluded from recording, this lets you concentrate on recorded Samplers analysis and not bother with useless Sample Results +
    +
  • +
  • Better defaults for recording, since this version Recorder will number created Samplers letting you find them much easily in View Results Tree. + Grouping of Samplers under Transaction Controller will be smarter making all requests emitted by a web page be children as new Transaction Controller
  • +
+ +

Support of Webdav requests

+

You can now test against WebDav server using HttpClient4 Implementation of Http Request

+
+ +

Better handling of embedded resources

+

When download embedded resources is checked, JMeter now uses User Agent header to download or not resources embedded within conditionnal comments as per About conditional comments.

+ +

Ability to customize Cache Manager (Browser cache simulation) handling of cached resources

+

You can now configure the behaviour of JMeter when a resource is found in Cache, this can be controlled with cache_manager.cached_resource_mode property

+
+ + +

JMS Publisher / JMS Point-to-Point

+

Add JMSPriority and JMSExpiration fields for these samplers.

+
+ +
+ +

Mail Reader Sampler

+

You can now specify the number of messages that want you retrieve (before all messages were retrieved). +In addition, you can fetch only the message header now.

+
+ +

SMTP Sampler

+

Adding the Connection timeout and the Read timeout to the SMTP Sampler.

+
+ +

Synchronizing Timer

+

Adding a timeout to define the maximum time to waiting of the group of virtual users.

+
+ +

Performance improvements

+

A big improvement in performances of Functions has been made by lifting useless synchronization. It concerns all functions except __StringFromFile, __XPath and __BeanShell, see + Bug + 57114

+

__jexl2 performances have been improved to avoid contention point, see + Bug + 56708

+ +

GUI Improvements

+ +

Undo/Redo support

+

Undo / Redo has been introduced and allows user to undo/redo changes made on Test Plan Tree. This feature (ALPHA MODE) is disabled by default, to enable it set property undo.history.size=25

+
+ +

View Results Tree

+

Improve the ergonomics of View Results Tree by changing placement of Renderers and allowing custom ordering +(with the property view.results.tree.renderers_order).

+
+ +

Response Time Graph

+

Adding the ability for the Response Time Graph listener to save/restore format its settings in/from the jmx file.

+
+ +

Log Viewer

+

Starting with this version, the last lines of JMeter's log file (jmeter.log) can be viewed directly in GUI by clicking on Warning icon in the upper right corner. +This will unfold the Log Viewer panel and show logs.

+
+ +

File Opening

+

Now, "Open File dialog" uses last opened file folder as start folder, see + Bug + 52707

+ + + +

Known bugs

+ +
    +
  • The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).
  • + +
  • +The numbers that appear to the left of the green box are the number of active threads / total number of threads, +the total number of threads only applies to a locally run test, otherwise it will show 0 (see + Bug + 55510). +
  • + +
  • +Note that there is a bug in Java +on some Linux systems that manifests itself as the following error when running the test cases or JMeter itself: +
    + [java] WARNING: Couldn't flush user prefs:
    + java.util.prefs.BackingStoreException:
    + java.lang.IllegalArgumentException: Not supported: indent-number
    +
    +This does not affect JMeter operation. This issue is fixed since Java 7b05. +
  • + +
  • +Note that under some windows systems you may have this WARNING: +
    +java.util.prefs.WindowsPreferences 
    +WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0
    +x80000002. Windows RegCreateKeyEx(...) returned error code 5.
    +
    +The fix is to run JMeter as Administrator, it will create the registry key for you, then you can restart JMeter as a normal user and you won't have the warning anymore. +
  • + +
  • +With Java 1.6 and Gnome 3 on Linux systems, the JMeter menu may not work correctly (shift between mouse's click and the menu). +This is a known Java bug (see + Bug + 54477 ). +A workaround is to use a Java 7 runtime (OpenJDK or Oracle JDK). +
  • + +
  • +With Oracle Java 7 and Mac Book Pro Retina Display, the JMeter GUI may look blurry. +This is a known Java bug, see Bug JDK-8000629. +A workaround is to use a Java 7 update 40 runtime which fixes this issue. +
  • + +
  • +You may encounter the following error: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints + if you run a HTTPS request on a web site with a SSL certificate (itself or one of SSL certificates in its chain of trust) with a signature + algorithm using MD2 (like md2WithRSAEncryption) or with a SSL certificate with a size lower than 1024 bits. +This error is related to increased security in Java 7 version u16 (MD2) and version u40 (Certificate size lower than 1024 bits), and Java 8 too. +
    +To allow you to perform your HTTPS request, you can downgrade the security of your Java installation by editing +the Java jdk.certpath.disabledAlgorithms property. Remove the MD2 value or the constraint on size, depending on your case. +
    +This property is in this file: +
    JAVA_HOME/jre/lib/security/java.security
    +See + Bug + 56357 for details. +
  • + +
+ + + +

Incompatible changes

+ +
    +
  • Since JMeter 2.12, active threads in all thread groups and active threads in current thread group are saved by default to CSV or XML results, see + Bug + 57025. +This is usually the expected behaviour as you want to have the number of running threads during the test. But if you want to revert to previous behaviour, set property jmeter.save.saveservice.thread_counts=false
  • +
  • Since JMeter 2.12, Mail Reader Sampler will show 1 for number of samples instead of number of messages retrieved, see + Bug + 56539
  • +
  • Since JMeter 2.12, when using Cache Manager, if resource is found in cache no SampleResult will be created, in previous version a SampleResult with empty content and 204 return code was returned, see + Bug + 54778. +You can choose between different ways to handle this case, see cache_manager.cached_resource_mode in jmeter.properties.
  • +
  • Since JMeter 2.12, Log Viewer will no more clear logs when closed and will have logs available even if closed. See + Bug + 56920. Read Hints and Tips > Enabling Debug logging +for details on configuring this component.
  • +
+ + + +

Bug fixes

+ +

HTTP Samplers and Test Script Recorder

+
    +
  • + Bug + 55998 - HTTP recording – Replacing port value by user defined variable does not work
  • +
  • + Bug + 56178 - keytool error: Invalid escaped character in AVA: - some characters must be escaped
  • +
  • + Bug + 56222 - NPE if jmeter.httpclient.strict_rfc2616=true and location is not absolute
  • +
  • + Bug + 56263 - DefaultSamplerCreator should set BrowserCompatible Multipart true
  • +
  • + Bug + 56231 - Move redirect location processing from HC3/HC4 samplers to HTTPSamplerBase#followRedirects()
  • +
  • + Bug + 56207 - URLs get encoded on redirects in HC3.1 & HC4 samplers
  • +
  • + Bug + 56303 - The width of target controller's combo list should be set to the current panel size, not on label size of the controllers
  • +
  • + Bug + 54778 - HTTP Sampler should not return 204 when resource is found in Cache, make it configurable with new property cache_manager.cached_resource_mode
  • +
+ +

Other Samplers

+
    +
  • + Bug + 55977 - JDBC pool keepalive flooding
  • +
  • + Bug + 55999 - Scroll bar on jms point-to-point sampler does not work when content exceeds display
  • +
  • + Bug + 56198 - JMSSampler : NullPointerException is thrown when JNDI underlying implementation of JMS provider does not comply with Context.getEnvironment contract
  • +
  • + Bug + 56428 - MailReaderSampler - should it use mail.pop3s.* properties?
  • +
  • + Bug + 46932 - Alias given in select statement is not used as column header in response data for a JDBC request.Based on report and analysis of Nicola Ambrosetti
  • +
  • + Bug + 56539 - Mail reader sampler: When Number of messages to retrieve is superior to 1, Number of samples should only show 1 not the number of messages retrieved
  • +
  • + Bug + 56809 - JMSSampler closes InitialContext too early. Contributed by Bradford Hovinen (hovinen at gmail.com)
  • +
  • + Bug + 56761 - JMeter tries to stop already stopped JMS connection and displays "The connection is closed"
  • +
  • + Bug + 57068 - No error thrown when negative duration is entered in Test Action
  • +
  • + Bug + 57078 - LagartoBasedHTMLParser fails to parse page that contains input with no type
  • +
  • + Bug + 57183 - JMSSampler: For input string: "" java.lang.NumberFormatException (for Expiration or Priority fields)
  • +
+ +

Controllers

+
    +
  • + Bug + 56243 - Foreach works incorrectly with indexes on subsequent iterations
  • +
  • + Bug + 56276 - Loop controller becomes broken once loop count evaluates to zero
  • +
  • + Bug + 56160 - StackOverflowError when using WhileController within IfController
  • +
  • + Bug + 56811 - "Start Next Thread Loop" in Result Status Action Handler or on Thread Group and "Go to next Loop iteration" in Test Action behave incorrectly with TransactionController that has "Generate Parent Sampler" checked
  • +
+ +

Listeners

+
    +
  • + Bug + 56706 - SampleResult#getResponseDataAsString() does not use encoding in response body impacting PostProcessors and ViewResultsTree. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57052 - ArithmeticException: / by zero when sampleCount is equal to 0
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 56162 - HTTP Cache Manager should not cache PUT/POST etc.
  • +
  • + Bug + 56227 - AssertionGUI : NPE in assertion on mouse selection
  • +
  • + Bug + 41319 - URLRewritingModifier : Allow Parameter value to be url encoded
  • +
+ +

Functions

+
    +
+ +

I18N

+ + +

General

+
    +
  • + Bug + 56059 - Older TestBeans incompatible with 2.11 when using TextAreaEditor
  • +
  • + Bug + 56080 - Conversion error com.thoughtworks.xstream.converters.ConversionException with Java 8 Early Access Build
  • +
  • + Bug + 56182 - Can't trigger bsh script using bshclient.jar; socket is closed unexpectedly
  • +
  • + Bug + 56360 - HashTree and ListedHashTree fail to compile with Java 8
  • +
  • + Bug + 56419 - JMeter silently fails to save results
  • +
  • + Bug + 56662 - Save as xml in a listener is not remembered
  • +
  • + Bug + 56367 - JMeter 2.11 on maven central triggers a not existing dependency rsyntaxtextarea 2.5.1, upgrade to 2.5.3
  • +
  • + Bug + 56743 - Wrong mailing list archives on mail2.xml. Contributed by Felix Schumacher (felix.schumacher at internetallee.de)
  • +
  • + Bug + 56763 - Removing the Oracle icons, not used by JMeter (and missing license)
  • +
  • + Bug + 54100 - Switching languages fails to preserve toolbar button states (enabled/disabled)
  • +
  • + Bug + 54648 - JMeter GUI on OS X crashes when using CMD+C (keyboard shortcut or UI menu entry) on an element from the tree
  • +
  • + Bug + 56962 - JMS GUIs should disable all fields affected by jndi.properties checkbox
  • +
  • + Bug + 57061 - Save as Test Fragment fails to clone deeply selected node. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57075 - BeanInfoSupport.MULTILINE attribute is not processed
  • +
  • + Bug + 57076 - BooleanPropertyEditor#getAsText() must return a value that is in getTags()
  • +
  • + Bug + 57088 - NPE in ResultCollector.testEnded
  • +
+ + + +

Improvements

+ +

HTTP Samplers and Test Script Recorder

+
    +
  • + Bug + 55959 - Improve error message when Test Script Recorder fails due to I/O problem
  • +
  • + Bug + 52013 - Test Script Recorder's Child View Results Tree does not take into account Test Script Recorder excluded/included URLs. Based on report and analysis of James Liang
  • +
  • + Bug + 56119 - File uploads fail every other attempt using timers. Enable idle timeouts for servers that don't send Keep-Alive headers.
  • +
  • + Bug + 56272 - MirrorServer should support query parameters for status and redirects
  • +
  • + Bug + 56772 - Handle IE Conditional comments when parsing embedded resources
  • +
  • + Bug + 57026 - HTTP(S) Test Script Recorder : Better default settings. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57107 - Patch proposal: Add DAV verbs to HTTP Sampler. Contributed by Philippe Jung (apache at famille-jung.fr)
  • +
  • + Bug + 56357 - Certificates does not conform to algorithm constraints: Adding a note to indicate how to remove of the Java installation these new security constraints
  • +
+ +

Other samplers

+
    +
  • + Bug + 56033 - Add Connection timeout and Read timeout to SMTP Sampler
  • +
  • + Bug + 56429 - MailReaderSampler - no need to fetch all Messages if not all wanted
  • +
  • + Bug + 56427 - MailReaderSampler enhancement: read message header only
  • +
  • + Bug + 56510 - JMS Publisher/Point to Point: Add JMSPriority and JMSExpiration
  • +
+ +

Controllers

+
    +
  • + Bug + 56728 - New Critical Section Controller to serialize blocks of a Test. Based partly on a patch contributed by Mikhail Epikhin(epihin-m at yandex.ru)
  • +
  • + Bug + 57145 - RandomController : Use ThreadLocalRandom instead of Random for better performances
  • +
+ +

Listeners

+
    +
  • + Bug + 56228 - View Results Tree : Improve ergonomy by changing placement of Renderers and allowing custom ordering
  • +
  • + Bug + 56349 - "summary" is a bad name for a Generate Summary Results component, documentation clarified
  • +
  • + Bug + 56769 - Adds the ability for the Response Time Graph listener to save/restore format settings in/from the jmx file
  • +
  • + Bug + 57025 - SaveService : Better defaults, save thread counts by default
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 56691 - Synchronizing Timer : Add timeout on waiting
  • +
  • + Bug + 56701 - HTTP Authorization Manager/ Kerberos Authentication: add port to SPN when server port is neither 80 nor 443. Based on patches from Dan Haughey (dan.haughey at swinton.co.uk) and Felix Schumacher (felix.schumacher at internetallee.de)
  • +
  • + Bug + 56841 - New configuration element: DNS Cache Manager to improve the testing of CDN. Based on patch from Dzmitry Kashlach (dzmitrykashlach at gmail.com), and contributed by BlazeMeter Ltd.
  • +
  • + Bug + 52061 - Allow access to Request Headers in Regex Extractor. Based on patch from Dzmitry Kashlach (dzmitrykashlach at gmail.com), and contributed by BlazeMeter Ltd.
  • +
+ +

Functions

+
    +
  • + Bug + 56708 - __jexl2 doesn't scale with multiple CPU cores. Based on analysis and patch contributed by Mikhail Epikhin(epihin-m at yandex.ru)
  • +
  • + Bug + 57114 - Performance : Functions that only have values as instance variable should not synchronize execute. Based on analysis by Ubik Load Pack support and Vladimir Sitnikov, patch contributed by Vladimir Sitnikov (sitnikov.vladimir at gmail.com)
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 21695 - Unix jmeter start script assumes it is on PATH, not a link
  • +
  • + Bug + 56292 - Add the check of the Java's version in startup files and disable some options when is Java v8 engine
  • +
  • + Bug + 56298 - JSR223 language display does not show which engine will be used
  • +
  • + Bug + 56455 - Batch files: drop support for non-NT Windows shell scripts
  • +
  • + Bug + 52707 - Make Open File dialog use last opened file folder as start folder. Based on patch from Dzmitry Kashlach (dzmitrykashlach at gmail.com), and contributed by BlazeMeter Ltd.
  • +
  • + Bug + 56807 - Ability to force flush of ResultCollector file. Contributed by Andrey Pohilko (apc4 at ya.ru)
  • +
  • + Bug + 56921 - Templates : Improve Recording template to ignore embedded resources case and URL parameters. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 42248 - Undo-redo support on Test Plan tree modification. Developed by Andrey Pohilko (apc4 at ya.ru) and contributed by BlazeMeter Ltd. Additional contribution by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 56920 - LogViewer : Make it receive all log events even when it is closed. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 57083 - simplified the CachedResourceMode enum. Contributed by Graham Russel (graham at ham1.co.uk)
  • +
  • + Bug + 57082 - ComboStringEditor : Added hashCode to an inner class which overwrote equals. Contributed by Graham Russel (graham at ham1.co.uk)
  • +
  • + Bug + 57081 - Updating checkstyle to only check for tabs in java, xml, xsd, dtd, htm, html and txt files (not images!). Contributed by Graham Russell (graham at ham1.co.uk)
  • +
  • + Bug + 56178 - Really replace backslashes in user name before generating proxy certificate. Contributed by Graham Russel (graham at ham1.co.uk)
  • +
  • + Bug + 57084 - Close socket after usage in BeanShellClient. Contributed by Graham Russel (graham at ham1.co.uk)
  • +
+

Non-functional changes

+
    +
  • + Bug + 57117 - Increase the default cipher for HTTPS Test Script Recorder from SSLv3 to TLS
  • +
  • Updated to commons-lang3 3.3.2 (from 3.1)
  • +
  • Updated to commons-codec 1.9 (from 1.8)
  • +
  • Updated to commons-logging 1.2 (from 1.1.3)
  • +
  • Updated to tika 1.6 (from 1.4)
  • +
  • Updated to xercesImpl 2.11.0 (from 2.9.1)
  • +
  • Updated to xml-apis 1.4.01 (from 1.3.04)
  • +
  • Updated to xstream 1.4.8 (from 1.4.4)
  • +
  • Updated to jodd 3.6.1 (from 3.4.10)
  • +
  • Updated to rsyntaxtextarea 2.5.3 (from 2.5.1)
  • +
  • Updated xalan and serializer to 2.7.2 (from 2.7.1)
  • +
  • Updated to jsoup-1.8.1.jar (from 1.7.3)
  • +
+ +

Thanks

+

We thank all contributors mentioned in bug and improvement sections above: +

    +
  • James Liang (jliang at andera.com)
  • +
  • Emmanuel Bourg (ebourg at apache.org)
  • +
  • Nicola Ambrosetti (ambrosetti.nicola at gmail.com)
  • +
  • Ubik Load Pack
  • +
  • Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • Dan Haughey (dan.haughey at swinton.co.uk)
  • +
  • Felix Schumacher (felix.schumacher at internetallee.de)
  • +
  • Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • Andrey Pohilko (apc4 at ya.ru)
  • +
  • Bradford Hovinen (hovinen at gmail.com)
  • +
  • BlazeMeter Ltd.
  • +
  • Graham Russell (graham at ham1.co.uk)
  • +
  • Philippe Jung (apache at famille-jung.fr)
  • +
  • Vladimir Sitnikov (sitnikov.vladimir at gmail.com)
  • +
+ +
+We also thank bug reporters who helped us improve JMeter.
+For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes: +
    +
  • Oliver LLoyd (email at oliverlloyd.com) for his help on + Bug + 56119
  • +
  • Vladimir Ryabtsev (greatvovan at gmail.com) for his help on + Bug + 56243 and + Bug + 56276
  • +
  • Adrian Speteanu (asp.adieu at gmail.com) and Matt Kilbride (matt.kilbride at gmail.com) for their feedback and tests on + Bug + 54648
  • +
  • Shmuel Krakower (shmulikk at gmail.com) for his tests and reports on Undo/Redo feature
  • +
+ +Apologies if we have omitted anyone else. +

+ + + +

Version 2.11

+ +Summary + + +

New and Noteworthy

+ + +

HTTP(S) Test Script Recorder improvements

+

+Following improvements have been made since major changes introduced in JMeter 2.10 on HTTP(S) Test Script Recorder: +

    +
  • Better detection of missing or invalid configuration of keytool utility
  • +
  • New system property keytool.directory (see system.properties) lets you configure directory containing keytool in case on non-standard installation
  • +
+

+ +

JMS Publisher/Point to Point : Add ability to set typed values in JMS header properties

+

In the samplers JMS Publisher and JMS Point-to-Point, you can now set up the class of values for the JMS header properties. Previously only String was possible.

+

+

+

+ +

View Results Tree : Add an XPath Tester

+

In View Results Tree listener, a new XPath tester can be used to test XPATH expressions.

+

+

+

+ +

Ability to choose the client alias for the cert key in JsseSslManager such that Mutual SSL auth testing can be made more flexible

+

When testing client based certificate authentications you have now better control on certificate you use through a new field "Variable name holding certificate alias", this +field lets you select the certificate you want to send to server to authenticate. You can use a CSV Data Set as a holder for the variable value.

+

+

+

+ +

Add a "Save as Test Fragment" option

+

In the file menu, a new option allow to save a group of elements as a Test fragment.

+

+

+

+ +

Summariser is be enabled by default in Non GUI mode

+

When you run JMeter from command line, now JMeter displays some statistics from the Summariser mode.

+

+

+

+ +

Transaction Controller:Change default property "Include duration of timer..." for newly created element

+

Starting from 2.11, Transaction Controller is configured by default to exclude processing time of pre/post processors as long as timers pause.

+

+

+

+ + + + + + + +

Known bugs

+ +
    +
  • The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).
  • + +
  • Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see + Bug + 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +
  • + +
  • +The numbers that appear to the left of the green box are the number of active threads / total number of threads, +the total number of threads only applies to a locally run test, otherwise it will show 0 (see + Bug + 55510). +
  • + +
  • +Note that there is a bug in Java +on some Linux systems that manifests itself as the following error when running the test cases or JMeter itself: +
    + [java] WARNING: Couldn't flush user prefs:
    + java.util.prefs.BackingStoreException:
    + java.lang.IllegalArgumentException: Not supported: indent-number
    +
    +This does not affect JMeter operation. This issue is fixed since Java 7b05. +
  • + +
  • +With Java 1.6 and Gnome 3 on Linux systems, the JMeter menu may not work correctly (shift between mouse's click and the menu). +This is a known Java bug (see + Bug + 54477 ). +A workaround is to use a Java 7 runtime (OpenJDK or Oracle JDK). +
  • + +
  • +With Oracle Java 7 and Mac Book Pro Retina Display, the JMeter GUI may look blurry. +This is a known Java bug, see Bug JDK-8000629. +A workaround is to use a Java 7 update 40 runtime which fixes this issue. +
  • +
+ + + +

Incompatible changes

+ +
    +
  • When creating a new Transaction Controller, property "Include duration of timer and pre-post processors in generated sample" will be unchecked starting from version 2.11
  • +
  • In Non GUI mode, since 2.11 summariser is enabled with a 30 seconds frequency
  • +
  • JMeter is more lenient with redirect handling and relaxes on RFC2616 by allowing relative locations. See property "jmeter.httpclient.strict_rfc2616" in jmeter.properties to change this behaviour, see + Bug + 55717
  • +
  • When creating a new Response Assertion, property "Pattern Matching Rules" now defaults to Substring starting from version 2.11
  • +
+ + + +

Bug fixes

+ +

HTTP Samplers and Test Script Recorder

+
    +
  • + Bug + 55815 - Proxy#getDomainMatch does not handle wildcards correctly
  • +
  • + Bug + 55717 - Bad handling of Redirect when URLs are in relative format by HttpClient4 and HttpClient3.1
  • +
+ +

Other Samplers

+
    +
  • + Bug + 55685 - OS Sampler: timeout option don't save and restore correctly value and don't init correctly timeout
  • +
+ +

Controllers

+
    +
  • + Bug + 55816 - Transaction Controller with "Include duration of timer..." unchecked does not ignore processing time of last child sampler
  • +
+ +

Listeners

+
    +
  • + Bug + 55826 - Unsynchronised concurrent accesses to list in field RespTimeGraphVisualizer.internalList
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 55694 - Assertions and Extractors : Avoid NullPointerException when scope is variable and variable is missing
  • +
  • + Bug + 55721 - HTTP Cache Manager - no-store directive is wrongly interpreted
  • +
+ +

Functions

+
    +
  • + Bug + 55871 - Wrong result with intSum() function when a space character is present before/after the number. Contributed by Milamber based on a proposal by James Liang.
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 55739 - Remote Test : Total threads in GUI mode shows invalid total number of threads
  • +
+ + + +

Improvements

+ +

HTTP Samplers and Proxy

+
    +
+ +

Other samplers

+
    +
  • + Bug + 55589 - JMS Publisher/Point to Point : Add ability to set typed values in JMS header properties.
  • +
+ +

Controllers

+
    +
  • + Bug + 55854 - Transaction Controller:Change default property "Include duration of timer..." for newly created element
  • +
+ +

Listeners

+ + +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 55908 - Response assertion : Change Pattern Matching Rules default to Substring on creation for better performances
  • +
  • + Bug + 54977 - Ability to choose the client alias for the cert key in JsseSslManager such that Mutual SSL auth testing can be made more flexible. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 55693 - Add a "Save as Test Fragment" option
  • +
  • + Bug + 55753 - Improve FilePanel behaviour to start from the value set in Filename field if any. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 55756 - HTTP Mirror Server : Add ability to set Headers
  • +
  • + Bug + 55852 - Be more lenient in parsing when charset value is surrounded with single quotes
  • +
  • + Bug + 55857 - Performance : AbstractProperty should test for emptiness to avoid Exception throwing
  • +
  • + Bug + 55858 - Startup Performance : On Startup, BeanInfoSupport should test for key availability instead of throwing
  • +
  • + Bug + 55865 - Performance :Disable stale check by default in HttpClient 4 and 3.1
  • +
  • + Bug + 55512 - Summariser should be enabled by default in Non GUI mode
  • +
+ +

Non-functional changes

+
    +
  • Updated to rsyntaxtextarea-2.5.1.jar (from 2.5.0)
  • +
  • Updated to jodd-core-3.4.9.jar from (3.4.8) and jodd-lagarto-3.4.9.jar (from 3.4.9)
  • +
  • Updated to jsoup-1.7.3.jar (from 1.7.2)
  • +
  • Updated to mail-1.5.0-b01 (from 1.4.4)
  • +
  • Updated to mongo-java-driver-2.11.3 (from 2.11.2)
  • +
+ +

Thanks

+

We thank all contributors mentioned in bug and improvement sections above: +

    +
  • James Liang (jliang at andera.com)
  • +
  • UBIK Load Pack (support at ubikloadpack.com)
  • +
+
+We also thank bug reporters who helped us improve JMeter.
+For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes: +
    +
  • John Natsioulas (john_natsioulas at yahoo.com.au)
  • +
  • Antonio Gomes Rodrigues (ra0077 at gmail.com)
  • +
+ +Apologies if we have omitted anyone else. +

+ + + + +

Version 2.10

+ +Summary + + +

New and Noteworthy

+ +

Core Improvements

+ +

New Performance improvements

+

+

    +
  • A Huge performance improvement has been made on High Throughput Tests (no pause), see + Bug + 54777
  • +
  • An issue with unnecessary SSL Context reset has been fixed which improves performances of pure HTTP tests, see + Bug + 55023
  • +
  • Important performance improvement in parsing of Embedded resource in HTML pages thanks to a switch to JODD/Lagarto HTML Parser, see + Bug + 55632
  • +
+

+ +

New CSS/JQuery Tester in View Tree Results

+

A new CSS/JQuery Tester in View Tree Results that makes CSS/JQuery Extractor a first class +citizen in JMeter, you can now test your expressions very easily

+

+

+

+ +

Many improvements in HTTP(S) Recording have been made

+

+

+
+The "HTTP Proxy Server" test element has been renamed as "HTTP(S) Test Script Recorder". +
+ +

+ +

You can now load test MongoDB through new MongoDB Source Config

+

+

+

+

+

+

+ +

Kerberos authentication has been added to Auth Manager

+

+

+

+ +

Device can now be used in addition to source IP address

+ +

+

+

+ +

You can now do functional testing of MongoDB scripts through new MongoDB Script

+

+

+

+ +

Timeout has been added to OS Process Sampler

+

+

+

+ +

Query timeout has been added to JDBC Request

+

+

+

+ +

New functions (__urlencode and __urldecode) are now available to encode/decode URL encoded chars

+

+

+

+ +

Continuous Integration is now eased by addition of a new flag that forces NON-GUI JVM to exit after test end

+

See jmeter property:

+jmeterengine.force.system.exit +

+ +

HttpSampler now allows DELETE Http Method to have a body (works for HC4 and HC31 implementations). This allows for example to test Elastic Search APIs

+

+

+

+ +

2 implementations of HtmlParser have been added to improve Embedded resources parsing

+

+You can choose the implementation to use for parsing Embedded resources in HTML pages: +See jmeter.properties and look at property "htmlParser.className". +

    +
  • org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser for optimal performances
  • +
  • org.apache.jmeter.protocol.http.parser.JSoupBasedHtmlParser for most accurate parsing and functional testing
  • +
+

+ +

Distributed testing has been improved

+

+

    +
  • +Number of threads on each node are now reported to controller. +

    +

    +

    +

    +

    +

    + +
  • +
  • Performance improvement on BatchSampleSender( + Bug + 55423)
  • +
  • Addition of 2 SampleSender modes (StrippedAsynch and StrippedDiskStore), see jmeter.properties
  • +
+

+ +

ModuleController has been improved to better handle changes to referenced controllers

+ +

Improved class loader configuration, see + Bug + 55503

+

+

    +
  • New property "plugin_dependency_paths" for plugin dependencies
  • +
  • Properties "search_paths", "user.classpath" and "plugin_dependency_paths" + now automatically add all jars from configured directories
  • +
+

+ +

Best-practices section has been improved, ensure you read it to get the most out of JMeter

+

See Best Practices +

+

GUI and ergonomy Improvements

+ + +

New Templates feature that allows you to create test plan from existing template or merge +template into your Test Plan

+

+

+

+

+

+

+ +

Workbench can now be saved

+

+

+

+ +

Syntax color has been added to scripts elements (BeanShell, BSF, and JSR223), MongoDB and JDBC elements making code much more readable and allowing UNDO/REDO through CTRL+Z/CTRL+Y

+

BSF Sampler with syntax color +

+

+

JSR223 Pre Processor with syntax color +

+

+ +

Better editors are now available for Test Elements with large text content, like HTTP Sampler, and JMS related Test Element providing line numbering and allowing UNDO/REDO through CTRL+Z/CTRL+Y

+ +

JMeter GUI can now be fully Internationalized, all remaining issues have been fixed

+
Currently French has all its labels translated. Other languages are partly translated, feel free to +contribute translations by reading Localisation (Translator's Guide)
+ +

Moving elements in Test plan has been improved in many ways

+
Drag and drop of elements in Test Plan tree is now much easier and possible on multiple nodes
+

+

+

+

+Note that due to this bug in Java, +you cannot drop a node after last node. The workaround is to drop it before this last node and then Drag and Drop the last node +before the one you just dropped. +

+
New shortcuts have been added to move elements in the tree.
+

(alt + Arrow Up) and (alt + Arrow Down) move the element within the parent node
+(alt + Arrow Left) and (alt + Arrow Right) move the element up and down in the tree depth

+ +

Response Time Graph Y axis can now be scaled

+

+

+

+ +

JUnit Sampler gives now more details on configuration errors

+ + + + +

Known bugs

+ +
    +
  • The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).
  • + +
  • Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see + Bug + 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +
  • + +
  • Webservice sampler does not consider the HTTP response status to compute the status of a response, thus a response 500 containing a non empty body will be considered as successful, see + Bug + 54006. +To workaround this issue, ensure you always read the response and add a Response Assertion checking text inside the response. +
  • + +
  • +The numbers that appear to the left of the green box are the number of active threads / total number of threads, +these only apply to a locally run test; they do not include any threads started on remote systems when using client-server mode, (see + Bug + 54152). +
  • + +
  • +Note that there is a bug in Java +on some Linux systems that manifests itself as the following error when running the test cases or JMeter itself: +
    + [java] WARNING: Couldn't flush user prefs:
    + java.util.prefs.BackingStoreException:
    + java.lang.IllegalArgumentException: Not supported: indent-number
    +
    +This does not affect JMeter operation. This issue is fixed since Java 7b05. +
  • + +
  • +With Java 1.6 and Gnome 3 on Linux systems, the JMeter menu may not work correctly (shift between mouse's click and the menu). +This is a known Java bug (see + Bug + 54477 ). +A workaround is to use a Java 7 runtime (OpenJDK or Oracle JDK). +
  • + +
  • +With Oracle Java 7 and Mac Book Pro Retina Display, the JMeter GUI may look blurry. +This is a known Java bug, see Bug JDK-8000629. +A workaround is to use a Java 7 update 40 runtime which fixes this issue. +
  • +
+ + + +

Incompatible changes

+ +
    +
  • SMTP Sampler now uses eml file subject if subject field is empty
  • + +
  • With this version autoFlush has been turned off on PrintWriter in charge of writing test results. +This results in improved throughput for intensive tests but can result in more test data loss in case +of JMeter crash (extremely rare). To revert to previous behaviour set jmeter.save.saveservice.autoflush property to true.
  • + +
  • +Shortcut for Function Helper Dialog is now CTRL+SHIFT+F1 (CMD + SHIFT + F1 for Mac OS). +The original key sequence (Ctrl+F1) did not work in some locations (it is consumed by the Java Swing ToolTipManager). +It was therefore necessary to change the shortcut. +
  • + +
  • +Webservice (SOAP) Request has been removed by default from GUI as Element is deprecated. (Use HTTP Request +with Body Data, see also the Template Building a SOAP Webservice Test Plan), if you need to show it, see property not_in_menu in jmeter.properties +
  • + +
  • +Transaction Controller now sets Response Code of Generated Parent Sampler +(if Generated Parent Sampler is checked) to response code of first failing child in case of failure of one of the children, in previous versions Response Code was empty. +
  • + +
  • +In previous versions, IncludeController could run Test Elements located inside a Thread Group, this behaviour (which was not documented) +ould result in weird behaviour, it has been removed in this version (see + Bug + 55464). +The correct way to include Test Elements is to use Test Fragment as stated in documentation of Include Controller. +
  • + +
  • +The retry count for the HttpClient 3.1 and HttpClient 4.x samplers has been changed to 0. +Previously the default was 1, which could cause unexpected additional traffic. +
  • + +
  • Starting with this version, the HTTP(S) Test Script Recorder tries to detect when a sample is the result of a previous +redirect. If the current response is a redirect, JMeter will save the redirect URL. When the next request is received, +it is compared with the saved redirect URL and if there is a match, JMeter will disable the generated sample. +To revert to previous behaviour, set the property proxy.redirect.disabling=false +
  • + +
  • Starting with this version, in HTTP(S) Test Script Recorder if Grouping is set to Put each group in a new Transaction Controller, +the Recorder will create Transaction Controller instances with Include duration of timer and pre-post processors in generated sample set +to false. This default value reflect more accurately response time. +
  • + +
  • __escapeOroRegexpChars function (which escapes ORO reserved characters) no longer trims the value (see + Bug + 55328) +
  • + +
  • The commons-lang-2.6.jar has been removed from embedded libraries in jmeter/lib folder as it is not needed by JMeter at run-time +(it is only used by Apache Velocity for generating documentation). +If you use any plugin or third-party code that depends on it, you need to add it in jmeter/lib folder +
  • +
+ + + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • + Bug + 54627 - JMeter Proxy GUI: Type of sampler setting takes the whole screen when there are samplers with long names.
  • +
  • + Bug + 54629 - HTMLParser does not extract <object> tag urls.
  • +
  • + Bug + 55023 - SSL Context reuse feature (51380) adversely affects non-ssl request performance/throughput. based on analysis by Brent Cromarty (brent.cromarty at yahoo.ca)
  • +
  • + Bug + 55092 - Log message "WARN - jmeter.protocol.http.sampler.HTTPSamplerBase: Null URL detected (should not happen)" displayed when embedded resource URL is malformed.
  • +
  • + Bug + 55161 - Useless processing in SoapSampler.setPostHeaders. Contributed by Adrian Nistor (nistor1 at illinois.edu)
  • +
  • + Bug + 54482 - HC fails to follow redirects with non-encoded chars.
  • +
  • + Bug + 54142 - HTTP Proxy Server throws an exception when path contains "|" character.
  • +
  • + Bug + 55388 - HC3 does not allow IP Source field to override httpclient.localaddress.
  • +
  • + Bug + 55450 - HEAD redirects should remain as HEAD
  • +
  • + Bug + 55455 - HTTPS with HTTPClient4 ignores cps setting
  • +
  • + Bug + 55502 - Proxy generates empty http:/ entries when recording
  • +
  • + Bug + 55504 - Proxy incorrectly issues CONNECT requests when browser prompts for certificate override
  • +
  • + Bug + 55506 - Proxy should deliver failed requests to any configured Listeners
  • +
  • + Bug + 55545 - HTTP Proxy Server GUI should not allow both Follow and Auto redirect to be selected
  • +
+ +

Other Samplers

+
    +
  • + Bug + 54913 - JMSPublisherGui incorrectly restores its state. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 55027 - Test Action regression, duration value is not recorded (nightly build).
  • +
  • + Bug + 55163 - BeanShellTestElement fails to quote string when calling testStarted(String)/testEnded(String).
  • +
  • + Bug + 55349 - NativeCommand hangs if no input file is specified and the application requests input.
  • +
  • + Bug + 55462 - System Sampler should not change the sampler label if a sample fails
  • +
+ +

Controllers

+
    +
  • + Bug + 54467 - Loop Controller: compute loop value only once per parent iteration.
  • +
  • + Bug + 54985 - Make Transaction Controller set Response Code of Generated Parent Sampler to response code of first failing child in case of failure of one of its children. Contributed by Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • + Bug + 54950 - ModuleController : Changes to referenced Module are not taken into account if changes occur after first run and referenced node is disabled.
  • +
  • + Bug + 55201 - ForEach controller excludes start index and includes end index (clarified documentation).
  • +
  • + Bug + 55334 - Adding Include Controller to test plan (made of Include Controllers) without saving TestPlan leads to included code not being taken into account until save.
  • +
  • + Bug + 55375 - StackOverflowError with ModuleController in Non-GUI mode if its name is the same as the target node.
  • +
  • + Bug + 55464 - Include Controller running included thread group
  • +
+ +

Listeners

+
    +
  • + Bug + 54589 - View Results Tree have a lot of Garbage characters if html page uses double-byte charset.
  • +
  • + Bug + 54753 - StringIndexOutOfBoundsException at SampleResult.getSampleLabel() if key_on_threadname=false when using Statistical mode.
  • +
  • + Bug + 54685 - ArrayIndexOutOfBoundsException if "sample_variable" is set in client but not server.
  • +
  • + Bug + 55111 - ViewResultsTree: text not refitted if vertical scrollbar is required. Contributed by Milamber
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 54540 - "HTML Parameter Mask" are not marked deprecated in the IHM.
  • +
  • + Bug + 54575 - CSS/JQuery Extractor : Choosing JODD Implementation always uses JSOUP.
  • +
  • + Bug + 54901 - Response Assertion GUI behaves weirdly.
  • +
  • + Bug + 54924 - XMLAssertion uses JMeter JVM file.encoding instead of response encoding and does not clean threadlocal variable.
  • +
  • + Bug + 53679 - Constant Throughput Timer bug with localization. Reported by Ludovic Garcia
  • +
+ +

Functions

+ + +

I18N

+
    +
  • + Bug + 55437 - ComboStringEditor does not translate EDIT and UNDEFINED strings on language change
  • +
  • + Bug + 55501 - Incorrect encoding for French description of __char function. Contributed by Antonio Gomes Rodrigues (ra0077 at gmail.com)
  • +
+ +

General

+
    +
  • + Bug + 54504 - Resource string not found: [clipboard_node_read_error].
  • +
  • + Bug + 54538 - GUI: context menu is too big.
  • +
  • + Bug + 54847 - Cut & Paste is broken with tree multi-selection. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 54870 - Tree drag and drop may lose leaf nodes (affected nightly build). Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 55056 - wasted work in Data.append(). Contributed by Adrian Nistor (nistor1 at illinois.edu)
  • +
  • + Bug + 55129 - Change Javadoc generation per CVE-2013-1571, VU#225657.
  • +
  • + Bug + 55187 - Integer overflow when computing ONE_YEAR_MS in HTTP CacheManager.
  • +
  • + Bug + 55208 - JSR223 language entries are duplicated; fold to lower case.
  • +
  • + Bug + 55203 - TestBeanGUI - wrong language settings found.
  • +
  • + Bug + 55065 - Useless processing in Spline3.converge(). Contributed by Adrian Nistor (nistor1 at illinois.edu)
  • +
  • + Bug + 55064 - Useless processing in ReportTreeListener.isValidDragAction(). Contributed by Adrian Nistor (nistor1 at illinois.edu)
  • +
  • + Bug + 55242 - BeanShell Client jar throws exceptions after upgrading to 2.8.
  • +
  • + Bug + 55288 - JMeter should default to 0 retries for HTTP requests.
  • +
  • + Bug + 55405 - ant download_jars task fails if lib/api or lib/doc are missing. Contributed by Antonio Gomes Rodrigues.
  • +
  • + Bug + 55427 - TestBeanHelper should ignore properties not supported by GenericTestBeanCustomizer
  • +
  • + Bug + 55459 - Elements using ComboStringEditor lose the input value if user selects another Test Element
  • +
  • + Bug + 54152 - In distributed testing : activeThreads always show 0 in GUI and Summariser
  • +
  • + Bug + 55509 - Allow Plugins to be notified of remote thread number progression
  • +
  • + Bug + 55572 - Detail popup of parameter does not show a Scrollbar when content exceeds display
  • +
  • + Bug + 55580 - Help pane does not scroll to start for <a href="#"> links
  • +
  • + Bug + 55600 - JSyntaxTextArea : Strange behaviour on first undo
  • +
  • + Bug + 55655 - NullPointerException when Remote stopping /shutdown all if one engine did not start correctly. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 55657 - Remote and Local Stop/Shutdown buttons state does not take into account local / remote status
  • +
+ + + +

Improvements

+ +

HTTP Samplers and Proxy

+
    +
  • HTTP Request: Small user interaction improvements in Row parameter Detail Box. Contributed by Milamber
  • +
  • + Bug + 55255 - Allow Body in HTTP DELETE method to support API that use it (like ElasticSearch).
  • +
  • + Bug + 53480 - Add Kerberos support to Http Sampler (HttpClient4). Based on patch by Felix Schumacher (felix.schumacher at internetallee.de)
  • +
  • + Bug + 54874 - Support device in addition to source IP address. Based on patch by Dan Fruehauf (malkodan at gmail.com)
  • +
  • + Bug + 55488 - Add .ico and .woff file extension to default suggested exclusions in proxy recorder. Contributed by Antonio Gomes Rodrigues
  • +
  • + Bug + 55525 - Proxy should support alias for keyserver entry
  • +
  • + Bug + 55531 - Proxy recording and redirects. Added code to disable redirected samples.
  • +
  • + Bug + 55507 - Proxy SSL recording does not handle external embedded resources well
  • +
  • + Bug + 55632 - Have a new implementation of htmlParser for embedded resources parsing with better performances
  • +
  • + Bug + 55653 - HTTP(S) Test Script Recorder should set TransactionController property "Include duration of timer and pre-post processors in generated sample" to false
  • +
+ +

Other samplers

+
    +
  • + Bug + 54788 - JMS Point-to-Point Sampler - GUI enhancements to increase readability and ease of use. Contributed by Bruno Antunes (b.m.antunes at gmail.com)
  • +
  • + Bug + 54798 - Using subject from EML-file for SMTP Sampler. Contributed by Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • + Bug + 54759 - SSLPeerUnverifiedException using HTTPS , property documented.
  • +
  • + Bug + 54896 - JUnit sampler gives only "failed to create an instance of the class" message with constructor problems.
  • +
  • + Bug + 55084 - Add timeout support for JDBC Request. Contributed by Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • + Bug + 55403 - Enhancement to OS sampler: Support for timeout
  • +
  • + Bug + 55518 - Add ability to limit number of cached PreparedStatements per connection when "Prepared Select Statement", "Prepared Update Statement" or "Callable Statement" query type is selected
  • +
+ +

Controllers

+
    +
  • + Bug + 54271 - Module Controller breaks if test plan is renamed.
  • +
+ +

Listeners

+
    +
  • + Bug + 54532 - Improve Response Time Graph Y axis scale with huge values or small values (< 1000ms). Add a new field to define increment scale. Contributed by Milamber based on patch by Luca Maragnani (luca.maragnani at gmail.com)
  • +
  • + Bug + 54576 - View Results Tree : Add a CSS/JQuery Tester.
  • +
  • + Bug + 54777 - Improve Performance of default ResultCollector. Based on patch by Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • + Bug + 55389 - Show IP source address in request data
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 54789 - XPath Assertion - GUI enhancements to increase readability and ease of use.
  • +
+ +

Functions

+
    +
  • + Bug + 54991 - Add functions to encode/decode URL encoded chars (__urlencode and __urldecode). Contributed by Milamber.
  • +
+ +

I18N

+
    +
  • + Bug + 55241 - Need GUI Editor to process fields which are based on Enums with localised display strings
  • +
  • + Bug + 55440 - ComboStringEditor should allow tags to be language dependent
  • +
  • + Bug + 55432 - CSV Dataset Config loses sharing mode when switching languages
  • +
+ +

General

+
    +
  • + Bug + 54584 - MongoDB plugin. Based on patch by Jan Paul Ettles (janpaulettles at gmail.com)
  • +
  • + Bug + 54669 - Add flag forcing non-GUI JVM to exit after test. Contributed by Scott Emmons
  • +
  • + Bug + 42428 - Workbench not saved with Test Plan. Contributed by Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • + Bug + 54825 - Add shortcuts to move elements in the tree. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 54834 - Improve Drag & Drop in the jmeter tree. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 54839 - Set the application name on Mac. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 54841 - Correctly handle the quit shortcut on Mac Os (CMD-Q). Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 54844 - Set the application icon on Mac Os. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 54864 - Enable multi selection drag & drop in the tree without having to start dragging before releasing Shift or Control. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 54945 - Add Shutdown Hook to enable trapping kill or CTRL+C signals.
  • +
  • + Bug + 54990 - Download large files avoiding outOfMemory.
  • +
  • + Bug + 55085 - UX Improvement : Ability to create New Test Plan from Templates. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 55172 - Provide plugins a way to add Top Menu and menu items.
  • +
  • + Bug + 55202 - Add syntax color for scripts elements (BeanShell, BSF, and JSR223) and JDBC elements with RSyntaxTextArea. Contributed by Milamber based on patch by Marko Vlahovic (vlahovic74 at gmail.com)
  • +
  • + Bug + 55175 - HTTPHC4Impl refactoring to allow better inheritance.
  • +
  • + Bug + 55236 - Templates - provide button to reload template details.
  • +
  • + Bug + 55237 - Template system should support relative fileName entries.
  • +
  • + Bug + 55423 - BatchSampleSender: Reduce locking granularity by moving listener.processBatch outside of synchronized block
  • +
  • + Bug + 55424 - Add Stripping to existing SampleSenders
  • +
  • + Bug + 55451 - Test Element GUI with JSyntaxTextArea scroll down when text content is long enough to add a Scrollbar
  • +
  • + Bug + 55513 - StreamCopier cannot be used with System.err or System.out as it closes the output stream
  • +
  • + Bug + 55514 - SystemCommand should support arbitrary input and output streams
  • +
  • + Bug + 55515 - SystemCommand should support chaining of commands
  • +
  • + Bug + 55606 - Use JSyntaxtTextArea for Http Request, JMS Test Elements
  • +
  • + Bug + 55651 - Change JMeter application icon to Apache plume icon
  • +
+ +

Non-functional changes

+
    +
  • Updated to jsoup-1.7.2
  • +
  • + Bug + 54776 - Update the dependency on Bouncy Castle to 1.48. Contributed by Emmanuel Bourg (ebourg at apache.org)
  • +
  • Updated to HttpComponents Client 4.2.6 (from 4.2.3)
  • +
  • Updated to HttpComponents Core 4.2.5 (from 4.2.3)
  • +
  • Updated to commons-codec 1.8 (from 1.6)
  • +
  • Updated to commons-io 2.4 (from 2.2)
  • +
  • Updated to commons-logging 1.1.3 (from 1.1.1)
  • +
  • Updated to commons-net 3.3 (from 3.1)
  • +
  • Updated to jdom-1.1.3 (from 1.1.2)
  • +
  • Updated to jodd-lagarto and jodd-core 3.4.8 (from 3.4.1)
  • +
  • Updated to junit 4.11 (from 4.10)
  • +
  • Updated to slf4j-api 1.7.5 (from 1.7.2)
  • +
  • Updated to tika 1.4 (from 1.3)
  • +
  • Updated to xmlgraphics-commons 1.5 (from 1.3.1)
  • +
  • Updated to xstream 1.4.4 (from 1.4.2)
  • +
  • Updated to BouncyCastle 1.49 (from 1.48)
  • +
  • + Bug + 54912 - JMeterTreeListener should use constants. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 54903 - Remove the dependency on the Activation Framework. Contributed by Emmanuel Bourg (ebourg at apache.org)
  • +
  • Moved commons-lang (2.6) to lib/doc as it's only needed by Velocity.
  • +
  • Re-organised and simplified NOTICE and LICENSE files.
  • +
  • + Bug + 55411 - NativeCommand could be useful elsewhere. Copied code to o.a.jorphan.exec.
  • +
  • + Bug + 55435 - ComboStringEditor could be simplified to make most settings final
  • +
  • + Bug + 55436 - ComboStringEditor should implement ClearGui
  • +
  • + Bug + 55463 - Component.requestFocus() is discouraged; use requestFocusInWindow() instead
  • +
  • + Bug + 55486 - New JMeter Logo. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
  • + Bug + 55548 - Tidy up use of TestElement.ENABLED; use TestElement.isEnabled()/setEnabled() throughout
  • +
  • + Bug + 55617 - Improvements to jorphan collection. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • + Bug + 55623 - Invalid/unexpected configuration values should not be silently ignored
  • +
  • + Bug + 55626 - Rename HTTP Proxy Server as HTTP(S) Test Script Recorder
  • +
+ +

Thanks

+

We thank all contributors mentioned in bug and improvement sections above: +

    +
  • Bruno Antunes (b.m.antunes at gmail.com)
  • +
  • Emmanuel Bourg (ebourg at apache.org)
  • +
  • Scott Emmons
  • +
  • Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • Luca Maragnani (luca.maragnani at gmail.com)
  • +
  • Milamber
  • +
  • Adrian Nistor (nistor1 at illinois.edu)
  • +
  • Antonio Gomes Rodrigues (ra0077 at gmail.com)
  • +
  • UBIK Load Pack (support at ubikloadpack.com)
  • +
  • Benoit Wiart (benoit.wiart at gmail.com)
  • +
+
+We also thank bug reporters who helped us improve JMeter.
+For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes: +
    +
  • Immanuel Hayden (immanuel.hayden at gmail.com)
  • +
  • Danny Lade (dlade at web.de)
  • +
  • Brent Cromarty (brent.cromarty at yahoo.ca)
  • +
  • Wolfgang Heider (wolfgang.heider at racon.at)
  • +
  • Shmuel Krakower (shmulikk at gmail.com)
  • +
+ + +Apologies if we have omitted anyone else. +

+ + + +

Version 2.9

+ +

New and Noteworthy

+ +

Core Improvements:

+ +

* A new Extractor that uses CSS or jquery-like selector syntax has been introduced, +it allows using either JODD or JSOUP implementations

+

+

+

+

Result: the title of the page in a JMeter variable +

+

+

* JMeter can now handle different types of documents (PDF, MsOffice files, Apache OpenOffice's files...) + within different elements

+
    +
  • Regular Expression Extractor, extract text from documents
  • +
  • Assertion Response, check text in documents
  • +
  • View Results Tree, view as a text the documents
  • +
+

+

+

+ +

* A new Regex User Parameters Pre-Processor that enables injecting input parameter names and values +using a reference extracted by Regular Expression Extractor from a previous response

+

+

+

+ +

* TCP Sampler: new options

+

TCP Sampler has been enhanced with new options to allow setting Close Connection, + SO_LINGER and End of line(EOL) byte value +

+

+

* A new function __escapeOroRegexpChars(,) has been introduced quote ORO regexp meta characters

+

* ForEach Controller: new fields

+

ForEach Controller has now 2 new fields to control start and end of loop +

+

+

* Result Status Action Handler now has a new option to "Start next thread loop"

+

+

+

+ +

* JMS Publisher: new option

+

JMS Publisher can now send Bytes Messages

+
+ +

* Memory and performance improvements

+

Significant improvements have been done in this version on memory usage per Thread and CPU when more +than one Post Processor is used as child of a Sampler

+

JSR223 Elements (enable using Groovy, Scala... as scripting languages) have been improved to enable caching +of Compilation results when scripts are passed in Text area

+
+ +

Some configuration defaults have changed to improve performances by default(see + Bug + 54412), +see description in New and Noteworthy section. +

    +
  • Distributed testing now uses MODE_STRIPPED_BATCH, which returns samples in batch mode (every 100 samples + or every minute by default). Note also that MODE_STRIPPED_BATCH strips response data from SampleResult, + so if you need it change to another mode (mode property in jmeter.properties)
  • +
  • Result data are now saved to CSV by default (jmeter.save.saveservice.output_format in jmeter.properties)
  • +
+

+ +

* XPath Assertion now enables using a JMeter variable as input

+

+

+

+ +

GUI and ergonomy Improvements:

+

* Search feature has been improved to search within more internal fields of elements and expand search results

+

* Copy/paste is now possible between 2 JMeter instances >= 2.9 version

+

Copy element(s) from one JMeter instance: +

+

+

Paste element(s) into a second JMeter instance: +

+

+

* HTTP Header Manager

+

Allow copy from clipboard to HeaderPanel, headers are supposed to be separated by new line + and have the following form name:value +

+

+

* Module Controller

+

Module Controller has been improved to better render referenced controller and expand it by clicking on a new button +

+

+

* HTTP Proxy Server

+

HTTP Proxy Server now has a button to add a set of default exclusions for URL patterns, +this list can be configured through property : proxy.excludes.suggested +

+

+

* Rendering of target controller has been improved in HTTP Proxy Server

+ +

HTTP Proxy Server recording:

+

* HTTP Proxy Server now automatically uses HTTP Request with Raw Post Body mode for +samples that only have one unnamed argument (JSON, XML, GWT...)

+

* HTTP Proxy Server does not force user to select the type of Sampler in HTTP Sampler Settings, + this allows easier switch between implementations as Sampler do not have this information set anymore

+

+

+

+

* SamplerCreator interface has been enriched to meet new requirements for plug-in providers

+

* It is now possible to create binary sampler for x-www-form-urlencoded POST request by +modifying proxy.binary.types property to add application/x-www-form-urlencoded

+

* Improved timestamp format auto-detection when reading CSV files

+ + + +

Known bugs

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see + Bug + 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ +

Webservice sampler does not consider the HTTP response status to compute the status of a response, thus a response 500 containing a non empty body will be considered as successful, see + Bug + 54006. +To workaround this issue, ensure you always read the response and add a Response Assertion checking text inside the response. +

+ +

+Changing language can break part of the configuration of the following elements (see + Bug + 53679): +

    +
  • CSV Data Set Config (sharing mode will be lost)
  • +
  • Constant Throughput Timer (Calculate throughput based on will be lost)
  • +
+

+ +

+The numbers that appear to the left of the green box are the number of active threads / total number of threads, +these only apply to a locally run test; they do not include any threads started on remote systems when using client-server mode, (see + Bug + 54152). +

+ +

+Note that there is a bug in Java on some Linux systems that manifests +itself as the following error when running the test cases or JMeter itself: +

+ [java] WARNING: Couldn't flush user prefs:
+ java.util.prefs.BackingStoreException:
+ java.lang.IllegalArgumentException: Not supported: indent-number
+
+This does not affect JMeter operation. +

+ + + +

Incompatible changes

+ +

JMeter requires now a Java 6 runtime or higher.

+ +

Some configuration defaults have changed to improve performances by default (see + Bug + 54412), +see description in New and Noteworthy section.

+ +

Webservice sampler now adds to request the headers that are set through Header Manager, these were previously ignored

+ +

jdbcsampler.cachesize property has been removed, it previously limited the size of a per connection cache of Map < String, +PreparedStatement > , it also limited the size of this +map which held the PreparedStatement for SQL queries. This limitation provoked a bug + Bug + 53995. +It has been removed so now size of these 2 maps is not limited anymore. This change changes behaviour as starting from +this version no PreparedStatement will be closed during the test.

+ +

Starting with this version, there are some important changes on JSR223 Test Elements: +

    +
  • JSR223 Test Elements that have an invalid filename (not existing or unreadable) will make test fail instead of + making the element silently work
  • +
  • In JSR223 Test Elements: responseCodeOk, responseMessageOK and successful are set before + script is executed, if responseData is set it will not be overriden anymore by a toString() on script return value
  • +
+

+ +

View Results Tree now considers response with missing content type as text.

+ +

In remote Test mode, JMeter now exits in error if one of the remote engines cannot be configured, +previously it started the test with available engines.

+ + + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • Don't log spurious warning messages when using concurrent pool embedded downloads with Cache Manager or CookieManager
  • +
  • + Bug + 54057- Proxy option to set user and password at startup (-u and -a) not working with HTTPClient 4
  • +
  • + Bug + 54187 - Request tab does not show headers if request fails
  • +
  • + Bug + 53840 - Proxy Recording : Response message: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "" "
  • +
  • + Bug + 54351 - HC4 and URI fragments is failing
  • +
+ +

Other Samplers

+
    +
  • + Bug + 53997 - LDAP Extended Request: Escape ampersand (&), left angle bracket (<) +and right angle bracket (>) in search filter tag in XML response data
  • +
  • + Bug + 53995 - AbstractJDBCTestElement shares PreparedStatement between multi-threads
  • +
  • + Bug + 54119 - HTTP 307 response is not redirected
  • +
  • + Bug + 54326 - AjpSampler send file in post throws FileNotFoundException
  • +
  • + Bug + 54331 - AjpSampler throws null pointer on GET request that are protected
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • + Bug + 54088 - The type video/f4m is text, not binary
  • +
  • + Bug + 54166 - ViewResultsTree could not render the HTML response: handle failure to parse HTML
  • +
  • + Bug + 54287 - Incorrect Timestamp in Response Time Graph when using a date with time in Date format field
  • +
  • + Bug + 54451 - Response Time Graph reports wrong times when the are many samples for same time
  • +
  • + Bug + 54459 - CSVSaveService does not handle date parsing very well
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 54058 - In HTTP Request Defaults, the value of field "Embedded URLs must match: is not saved if the check box "Retrieve All Embedded Resources" is not checked.
  • +
  • + Bug + 54375 - Regular Expression Extractor : When regex syntax is wrong, post processing is stopped
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 53975 - Variables replacement doesn't work with option "Delay thread creation until needed"
  • +
  • + Bug + 54055 - View Results tree: = signs are stripped from parameter values at HTTP tab
  • +
  • + Bug + 54129 - Search Feature does not find text although existing in elements
  • +
  • + Bug + 54023 - Unable to start JMeter from a root directory and if the full path of JMeter installation contains one or more spaces (Unix/linux)
  • +
  • + Bug + 54172 - Duplicate shortcut key not working and CTRL+C / CTRL+V / CTRL+V do not cancel default event
  • +
  • + Bug + 54057 - Proxy option to set user and password at startup (-u and -a) not working with HTTPClient 4
  • +
  • + Bug + 54267 - Start Next Thread Loop setting doesn't work in custom thread groups
  • +
  • + Bug + 54413 - DataStrippingSampleSender returns 0 for number of bytes of any response
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • + Bug + 54185 - Allow query strings in paths that start with HTTP or HTTPS
  • +
+ +

Other samplers

+
    +
  • + Bug + 54004 - Webservice Sampler : Allow adding headers to request with Header Manager
  • +
  • + Bug + 54106 - JSR223TestElement should check for file existence when a filename is set instead of using Text Area content
  • +
  • + Bug + 54107 - JSR223TestElement : Enable compilation and caching of Script Text
  • +
  • + Bug + 54109 - JSR223TestElement : SampleResult properties should be set before entering script to allow user setting different code
  • +
  • + Bug + 54230 - TCP Sampler, additions of "Close Connection", "SO_LINGER" and "End of line(EOL) byte value" options
  • +
  • + Bug + 54182 - Support sending of ByteMessage for JMS Publisher.
  • +
+ +

Controllers

+
    +
  • + Bug + 54131 - ForEach Controller : Add start and end index for looping over variables
  • +
  • + Bug + 54132 - Module Controller GUI : Improve rendering of referenced controller
  • +
  • + Bug + 54155 - ModuleController : Add a shortcut button to unfold the tree up to referenced controller and highlight it
  • +
+ +

Listeners

+
    +
  • + Bug + 54200 - Add support of several document types (like Apache OpenOffice's files, MS Office's files, PDF's files, etc.) +to the elements View Results Tree, Assertion Response and Regular Expression Extractor (using Apache Tika)
  • +
  • + Bug + 54226 - View Results Tree : Show response even when server does not return ContentType header
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 54259 - Introduce a new Extractor that uses CSS or jquery-like selector syntax
  • +
  • + Bug + 45772 - RegEx User Parameters Post Processor
  • +
  • + Bug + 54160 - Add support for xpath assertion to apply to a JMeter variable.
  • +
+ +

Functions

+ + +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 54005 - HTTP Mirror Server : Add special headers "X-" to control Response status and response content
  • +
  • + Bug + 53875 - Include suggested defaults for URL filters on HTTP Proxy
  • +
  • + Bug + 54031 - Add tooltip to running/total threads indicator
  • +
  • Webservice (SOAP) Request has been deprecated
  • +
  • + Bug + 54161 - Proxy : be able to create binary sampler for x-www-form-urlencoded POST request
  • +
  • + Bug + 54154 - HTTP Proxy Server should not force user to select the type of Sampler in HTTP Sampler Settings
  • +
  • + Bug + 54165 - Proxy Server: Improve rendering of target controller
  • +
  • + Bug + 46677 - Copying Test Elements between test plans
  • +
  • + Bug + 54204 - Result Status Action Handler : Add start next thread loop option
  • +
  • + Bug + 54232 - Search Feature : Add a button to search and expand results
  • +
  • + Bug + 54251 - Add tristate checkbox implementation
  • +
  • + Bug + 54257 - Enhance SamplerCreator interface to meet new requirements
  • +
  • + Bug + 54258 - Proxy : Use Raw Post Body when Sampler has one unnamed argument, useful for Samplers using POST method by of type JSON, XML, GWT body
  • +
  • + Bug + 54268 - Improve CPU and memory usage
  • +
  • + Bug + 54376 - ScopePanel : Allow configuring more precisely scopes
  • +
  • + Bug + 54412 - Changing JMeter defaults to ensure better performances by default
  • +
  • + Bug + 54414 - Remote Test should not start if one of the engines fails to start correctly
  • +
+ +

Non-functional changes

+
    +
  • + Bug + 53956 - Add ability to paste (a list of values) from clipboard for Header Manager
  • +
  • Updated to HttpComponents Client 4.2.3 (from 4.2.1)
  • +
  • Updated to HttpComponents Core 4.2.3 (from 4.2.2)
  • +
  • + Bug + 54110 - BSFTestElement and JSR223TestElement should use shared super-class for common fields
  • +
  • + Bug + 54199 - Move to Java 6
  • +
  • Upgraded to rhino 1.7R4
  • +
+ + + +

Version 2.8

+ +

New and Noteworthy

+ +

Core Improvements:

+ +

Thread Group: New Option Delay thread creation until needed

+

New Option "Delay thread creation until needed" that will create and start threads when needed instead of creating them on Test startup
+This new feature allows running tests with a huge number of short lived threads. +

+

+ +

HTTP Cookie Manager (IPv6 support)

+

Add HTTPClient 4 cookie implementation in JMeter.
+Cookie Manager has now the default HC3.1 implementation and a new choice HC4 implementation (compliant with IPv6 address) +

+

+ +

Memory and performance improvements

+

Significant improvements have been done in this version on memory usage of JMeterThread

+

JSR223 Elements (enable using Groovy, scala... as scripting languages) have been improved to enable: +

    +
  • usage of Compilable interface when available to boost CPU usage
  • +
  • caching of Compilation when scripts are used as Files
  • +
+See JMeter Performances across versions +

+ +

OS Process Sampler

+

Allow defining files for stdout/stderr/stdin. +

+

+ +

HTTP Request: PATCH verb

+

Add PATCH verb to HTTP sampler +

+

+ +

HTTP Request: HTTPClient 4 is now the default implementation

+

HTTPClient 4 is now the default HTTP Request implementation (and for Proxy element when generating HTTP requests).
+Previously the default was the HTTP Java implementation (i.e. the implementation provided by the JVM) +

+

+ +

HTTP Request

+

Add Embedded URL Filter to HTTP Request Defaults Control (it was already present for HTTP Requests) +

+

+ +

Miscellanous

+
    +
  • CSV Dataset : Embedded new lines are now supported in quoted data
  • +
  • JMX files now contain the version of JMeter that created the file
  • +
  • JMeter Version is now available as property "jmeter.version"
  • +
+

Reporting Improvements:

+ +

Response Time Graph

+

Add a new visualizer Response Time Graph to draw a line graph showing the evolution of response time for a test +

+

+

Settings for Response Time Graph +

+

+ +

View Results in Table

+

Add latency to View Result in Table listener +

+

+ +

Aggregate Graph

+

Small improvements: legend at left or right is now on 1 column (instead of 1 large line), ... +

+

+ +

GUI and ergonomy Improvements:

+

HTTP Proxy Server simplifications

+

HTTPS Spoofing options have been removed from Proxy as HTTPS recording is directly available since JMeter 2.4. +

+

+ +

HTTP Proxy Server

+

Allow URL Filters to be pasted from clipboard +

+

+ +

Find in JMeter

+

CTRL + F for the new Find feature +

+ +ESC key now closes popups. +

+ +

User Interface in GNOME 3

+

Display 'Apache JMeter' title in app title bar in Gnome 3 +

+

+ + + +

Known bugs

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see + Bug + 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ +

+Changing language can break part of the configuration of the following elements (see + Bug + 53679): +

    +
  • CSV Data Set Config (sharing mode will be lost)
  • +
  • Constant Throughput Timer (Calculate throughput based on will be lost)
  • +
+

+ +

+Note that there is a bug in Java on some Linux systems that manifests +itself as the following error when running the test cases or JMeter itself: +

+ [java] WARNING: Couldn't flush user prefs:
+ java.util.prefs.BackingStoreException:
+ java.lang.IllegalArgumentException: Not supported: indent-number
+
+This does not affect JMeter operation. +

+ + + +

Incompatible changes

+ +

+When using CacheManager, JMeter now caches responses for GET queries provided header Cache-Control is different from "no-cache" as described in specification. +Furthermore it doesn't put anymore in Cache deprecated entries for "no-cache" responses. See + Bug + 53521 and + Bug + 53522 +

+ +

+A major change has occured on JSR223 Test Elements, previously variables set up before script execution where stored in ScriptEngineManager which was created once per execution, +now ScriptEngineManager is a singleton shared by all JSR223 elements and only ScriptEngine is created once per execution, variables set up before script execution are now stored +in Bindings created on each execution, see + Bug + 53365. +

+ +

+JSR223 Test Elements using Script file are now Compiled if ScriptEngine supports this feature, see + Bug + 53520. +

+ +

+Shortcut for Function Helper Dialog is now CTRL+F1 (CMD + F1 for Mac OS), CTRL+F (CMD+F1 for Mac OS) now opens Search Dialog. +

+ +

+By default, the TestCompiler now stores details of which pairs it has seen in Controller instances rather than in a static Set. +[ + Bug + 53796] +This gives much better memory behaviour for delayed start test plans, as memory used is proportional to the number of concurrent threads. +With the static Set memory usage was proportional to the total thread count. +This change is very unlikely to cause a problem. +The original behaviour can be restored by setting the property TestCompiler.useStaticSet=true +

+ +

+HTTPS Spoofing options have been removed from Proxy as HTTPS recording is directly available since JMeter 2.4. +

+ + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • + Bug + 53521 - Cache Manager should cache content with Cache-control=private
  • +
  • + Bug + 53522 - Cache Manager should not store at all response with header "no-cache" and store other types of Cache-Control having max-age value
  • +
  • + Bug + 53838 - Pressing "Stop" does not interrupt the TCP sampler
  • +
  • + Bug + 53911 - JmeterKeystore does not allow for key down the list of certificate
  • +
+ +

Other Samplers

+
    +
  • + Bug + 53348 - JMeter JMS Point-to-Point Request-Response sampler doesn't work when Request-queue and Receive-queue are different
  • +
  • + Bug + 53357 - JMS Point to Point reports too high response times in Request Response Mode
  • +
  • + Bug + 53440 - SSL connection leads to ArrayStoreException on JDK 6 with some KeyManagerFactory SPI
  • +
  • + Bug + 53511 - access log sampler SessionFilter throws NullPointerException - cookie manager not initialized properly
  • +
  • + Bug + 53715 - JMeter does not load WSDL
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • + Bug + 53742 - When jmeter.save.saveservice.sample_count is set to true, elapsed time read by listener is always equal to 0
  • +
  • + Bug + 53774 - RequestViewRaw does not show headers unless samplerData is non-null
  • +
  • + Bug + 53802 - IdleTime values are not saved to CSV log
  • +
  • + Bug + 53874 - View Results Tree : If some parameter containing special characters like % is not encoded, RequestViewHTTP fails with java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern and Response is not displayed
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 51512 - Cookies aren't inserted into HTTP request with IPv6 Host header
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 53365 - JSR223TestElement should cache ScriptEngineManager
  • +
  • + Bug + 53520 - JSR223 Elements : Use Compilable interface to improve performances on File scripts
  • +
  • + Bug + 53501 - Synchronization timer blocks test end.
  • +
  • + Bug + 53750 - TestCompiler saves unnecessary entries in pairing collection
  • +
  • + Bug + 52266 - Code:Inconsistent synchronization
  • +
  • + Bug + 53841 - CSVSaveService reads file using JVM default file encoding instead of using the one configured in saveservice.properties
  • +
  • + Bug + 53953 New: Typo in monitor test plan documentation
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • + Bug + 53675 - Add PATCH verb to HTTP sampler
  • +
  • + Bug + 53931 - Define HTTPClient 4 for the default HTTP Request (and Proxy element to generate the HTTP requests). Before the default, it was the HTTP Java Sampler
  • +
  • + Bug + 53934 - Removes HTTPS spoofing options in JMeter HTTP Proxy Server. Since JMeter 2.4, the HTTPS protocol is directly supported by the proxy
  • +
+ +

Other samplers

+
    +
  • + Bug + 55310 - TestAction should implement Interruptible
  • +
  • + Bug + 53318 - Add Embedded URL Filter to HTTP Request Defaults Control
  • +
  • + Bug + 53782 - Enhance JavaSampler handling of JavaSamplerClient cleanup to use less memory
  • +
  • + Bug + 53168 - OS Process - allow specification of stdout/stderr/stdin
  • +
  • + Bug + 53844 - JDBC related elements should check class of Variable Name supposed to contain JDBC Connection Configuration to avoid ClassCastException
  • +
+ +

Controllers

+
    +
  • + Bug + 53671 - tearDown thread group to run even if shutdown test happens
  • +
+ +

Listeners

+
    +
  • + Bug + 53566 - Don't log partial responses to the jmeter log
  • +
  • + Bug + 53716 - Small improvements in aggregate graph: legend at left or right is now on 1 column (instead of 1 large line), no border to the reference's square color, reduce width on some fields
  • +
  • + Bug + 53718 - Add a new visualizer 'Response Time Graph' to draw a line graph showing the evolution of response time for a test
  • +
  • + Bug + 53738 - Keep track of number of threads started and finished
  • +
  • + Bug + 53753 - Summariser: no point displaying fractional time in most cases
  • +
  • + Bug + 53749 - TestListener interface could perhaps be split up. +This should reduce per-thread memory requirements and processing, +as only test elements that actually use testIterationStart functionality now need to be handled.
  • +
  • + Bug + 53941 - Add latency to View Result table listener
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 53755 - Adding a HttpClient 4 cookie implementation in JMeter. +Cookie Manager has now the default HC3.1 implementation and a new choice HC4 implementation (compliant with IPv6 address)
  • +
+ +

Functions

+
    +
  • + Bug + 51527 - __time() function : add another option to __time() to provide *seconds* since epoch
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 53364 - Sort list of Functions in Function Helper Dialog
  • +
  • + Bug + 53418 - New Option "Delay thread creation until needed" that will create and start threads when needed instead of creating them on Test startup
  • +
  • + Bug + 42245 - Show clear passwords in HTTP Authorization Manager
  • +
  • + Bug + 53616 - Display 'Apache JMeter' title in app title bar in Gnome 3
  • +
  • + Bug + 53759 - ClientJMeterEngine perfoms unnecessary traverse using SearchByClass(TestListener)
  • +
  • + Bug + 52601 - CTRL + F for the new Find feature
  • +
  • + Bug + 53796 - TestCompiler uses static Set which can grow huge
  • +
  • + Bug + 53673 - Add JMeter version in the jmx file
  • +
  • Add support for HeapDump to the JMeter non-GUI and GUI client
  • +
  • + Bug + 53862 - Would be nice to have the JMeter Version available as a property
  • +
  • + Bug + 53806 - FileServer should provide thread-safe parsing
  • +
  • + Bug + 53807 - CSV Dataset does not handle embedded new lines in quoted data
  • +
  • + Bug + 53879 - GUI : Allow Popups to be closed with ESC key
  • +
  • + Bug + 53876 - Allow URL Filters (HTTP Proxy) to be pasted from clipboard
  • +
+ +

Non-functional changes

+
    +
  • + Bug + 53311 - JMeterUtils#runSafe should not throw Error when interrupted
  • +
  • Updated to commons-net-3.1 (from 3.0.1)
  • +
  • Updated to HttpComponents Core 4.2.2 (from 4.1.4) and HttpComponents Client 4.2.1 (from 4.1.3)
  • +
  • + Bug + 53765 - Switch to commons-lang3-3.1
  • +
  • + Bug + 53884 - wrong Maven groupId for commons-lang
  • +
+ + + +

Version 2.7

+ +

New and Noteworthy

+ +

OS Process Sampler

+

A new System Sampler that can be used to execute commands on the local machine. +

+

+ +

OS Process Sampler results example with DNS lookup command 'dig' +

+

+ +

JMS Samplers improvements

+

Addition of a "Non Persistent Delivery" option to send "Non-Persistent" (Guaranteed to be delivered at most once. Message loss is not a concern.) JMS messages +

+

+ +

Support sending of JMS Object Messages to enable sending Objects unmarshalled from XML by XStream +

+

+ +

Enable setting JMS Properties through JMS Publisher sampler +

+

+ +

Test Action sampler

+

Allow premature exit from a loop +

+

+ +

Webservice Sampler improvements

+

Add a jmeter property soap.document_cache to control size of Document Cache +

+

+ +

Make Maintain HTTP Session configurable +

+

+ +

Aggregate graph: Clustered Bar char with average, median, 90% line, min and max columns

+

Aggregate graph changes to Clustered Bar chart, add more columns (median, 90% line, min, max) and options, fixed some bugs +

+

+ +

New settings for aggregate graph +

+

+ +

Improvements of HTML report design generated by JMeter Ant task in extras folder

+

HTML report example +

+

+ +

HTML report example with some assertion errors +

+

+ +

Mailer Visualizer

+

    +
  • Enable authentication, and connection security with SSL or TLS
  • +
  • Improve GUI design
  • +
  • Add internationalisation (i18n) support
  • +
+
+

+ +

New Visual Indicator of number of ERROR/FATAL messages in logs

+

Indicator shows number of ERROR/FATAL messsages in logs, it can be clicked to toggle Log Viewer panel +

+

+ +

Dialog box to show detail of a parameter row

+

Add a detail button on parameters table to show detail of a Row +

+

+ +

Detail box example +

+

+ +

Plugin writers

+

+New interface org.apache.jmeter.engine.util.ConfigMergabilityIndicator has been introduced to tell whether a ConfigTestElement can be merged in Sampler (see + Bug + 53042):
+

public boolean applies(ConfigTestElement configElement);
+

+ +

New interface org.apache.jmeter.protocol.http.proxy.SamplerCreator to allow plugging HTTP based samplers that differ from default HTTP Samplers through Proxy during Recording Phase (see + Bug + 52674):
+

public String[] getManagedContentTypes();
+
public HTTPSamplerBase createSampler(HttpRequestHdr request, Map<String, String> pageEncodings, Map<String, String> formEncodings);
+
public void populateSampler(HTTPSamplerBase sampler, HttpRequestHdr request, Map<String, String> pageEncodings, Map<String, String> formEncodings) throws Exception;
+

+ + + + +

Known bugs

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see + Bug + 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ + + +

Incompatible changes

+ +

+When doing replacement of User Defined Variables, Proxy will not substitute partial values anymore when "Regexp matching" is used. It will use Perl 5 word matching ("\b") +

+ +

+In User Defined Variables, Test Plan, HTTP Sampler Arguments Table, Java Request Defaults, JMS Sampler and Publisher, LDAP Request Defaults and LDAP Extended Request Defaults, rows with +empty Name and Value are no more saved. +

+ +

+JMeter now expands the Test Plan tree to the testplan level and no further and selects the root of the tree. Furthermore default value of onload.expandtree is false. +

+ +

+Graph Full Results Listener has been removed. +

+ +

+When calling "Clear All" command, if Log Viewer is displayed its content will be cleared. +

+ + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • + Bug + 52613 - Using Raw Post Body option, text gets encoded
  • +
  • + Bug + 52781 - Content-Disposition header garbled even if browser compatible headers is checked (HC4)
  • +
  • + Bug + 52796 - MonitorHandler fails to clear variables when starting a new parse
  • +
  • + Bug + 52871 - Multiple Certificates not working with HTTP Client 4
  • +
  • + Bug + 52885 - Proxy : Recording issues with HTTPS, cookies starting with secure are partly truncated
  • +
  • + Bug + 52886 - Proxy : Recording issues with HTTPS when spoofing is on, secure cookies are not always changed
  • +
  • + Bug + 52897 - HTTPSampler : Using PUT method with HTTPClient4 and empty Content Encoding and sending files leads to NullPointerException
  • +
  • + Bug + 53145 - HTTP Sampler - function in path evaluated too early
  • +
+ +

Other Samplers

+
    +
  • + Bug + 51737 - TCPSampler : Packet gets converted/corrupted
  • +
  • + Bug + 52868 - BSF language list should be sorted
  • +
  • + Bug + 52869 - JSR223 language list currently uses BSF list which is wrong
  • +
  • + Bug + 52932 - JDBC Sampler : Sampler is not marked in error in an Exception which is not of class IOException, SQLException, IOException occurs
  • +
  • + Bug + 52916 - JDBC Exception if there is an empty user defined variable
  • +
  • + Bug + 52937 - Webservice Sampler : Clear Soap Documents Cache at end of Test
  • +
  • + Bug + 53027 - Jmeter starts throwing exceptions while using SMTP Sample in a test plan with HTTP Cookie Mngr or HTTP Request Defaults
  • +
  • + Bug + 53072 - JDBC PREPARED SELECT statements should return results in variables like non prepared SELECT
  • +
+ +

Controllers

+
    +
  • + Bug + 52968 - Option Start Next Loop in Thread Group does not mark parent Transaction Sampler in error when an error occurs
  • +
  • + Bug + 50898 - IncludeController : NullPointerException loading script in non-GUI mode if Includers use same element name
  • +
+ +

Listeners

+
    +
  • + Bug + 43450 - Listeners/Savers assume SampleResult count is always 1; fixed Generate Summary Results
  • +
+ +

Assertions

+ + +

Functions

+
    +
+ +

I18N

+ + +

General

+
    +
  • + Bug + 52639 - JSplitPane divider for log panel should be hidden if log is not activated
  • +
  • + Bug + 52672 - Change Controller action deletes all but one child samplers
  • +
  • + Bug + 52694 - Deadlock in GUI related to non AWT Threads updating GUI
  • +
  • + Bug + 52678 - Proxy : When doing replacement of UserDefinedVariables, partial values should not be substituted
  • +
  • + Bug + 52728 - CSV Data Set Config element cannot coexist with BSF Sampler in same Thread Plan
  • +
  • + Bug + 52762 - Problem with multiples certificates: first index not used until indexes are restarted
  • +
  • + Bug + 52741 - TestBeanGUI default values do not work at second time or later
  • +
  • + Bug + 52783 - oro.patterncache.size property never used due to early init
  • +
  • + Bug + 52789 - Proxy with Regexp Matching can fail with NullPointerException in Value Replacement if value is null
  • +
  • + Bug + 52645 - Recording with Proxy leads to OutOfMemory
  • +
  • + Bug + 52679 - User Parameters columns narrow
  • +
  • + Bug + 52843 - Sample headerSize and bodySize not being accumulated for subsamples
  • +
  • + Bug + 52967 - The function __P() couldn't use default value when running with remote server in GUI mode.
  • +
  • + Bug + 50799 - Having a non-HTTP sampler in a http test plan prevents multiple header managers from working
  • +
  • + Bug + 52997 - Jmeter should not exit without saving Test Plan if saving before exit fails
  • +
  • + Bug + 53136 - Catching Throwable needs to be carefully handled
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
+ +

Other samplers

+
    +
  • + Bug + 52775 - JMS Publisher : Add Non Persistent Delivery option
  • +
  • + Bug + 52810 - Enable setting JMS Properties through JMS Publisher sampler
  • +
  • + Bug + 52938 - Webservice Sampler : Add a jmeter property soap.document_cache to control size of Document Cache
  • +
  • + Bug + 52939 - Webservice Sampler : Make MaintainSession configurable
  • +
  • + Bug + 53073 - Allow to assign the OUT result of a JDBC CALLABLE to JMeter variables
  • +
  • + Bug + 53164 - New System Sampler
  • +
  • + Bug + 53172 - OS Process Sampler - allow specification of Environment Variables
  • +
  • + Bug + 52936 - JMS Publisher : Support sending of JMS Object Messages
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • + Bug + 52603 - MailerVisualizer : Enable SSL , TLS and Authentication
  • +
  • + Bug + 52698 - Remove Graph Full Results Listener
  • +
  • + Bug + 53070 - Change Aggregate graph to Clustered Bar chart, add more columns (median, 90% line, min, max) and options, fixed some bugs
  • +
  • + Bug + 53246 - Mailer Visualizer: improve GUI design and I18N
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
+ +

Functions

+
    +
+ +

I18N

+
    +
  • Mailer Visualizer has been internationalized. French translation added. (see + Bug + 53246)
  • +
+ +

General

+
    +
  • + Bug + 45839 - Test Action : Allow premature exit from a loop
  • +
  • + Bug + 52614 - MailerModel.sendMail has strange way to calculate debug setting
  • +
  • + Bug + 52782 - Add a detail button on parameters table to show detail of a Row
  • +
  • + Bug + 52674 - Proxy : Add a Sampler Creator to allow plugging HTTP based samplers using potentially non textual POST Body (AMF, Silverlight...) and customizing them for others
  • +
  • + Bug + 52934 - GUI : Open Test plan with the tree expanded to the testplan level and no further and select the root of the tree
  • +
  • + Bug + 52941 - Improvements of HTML report design generated by JMeter Ant task extra
  • +
  • + Bug + 53042 - Introduce a new method in Sampler interface to allow Sampler to decide wether a config element applies to Sampler
  • +
  • + Bug + 52771 - Documentation : Added RSS feed on JMeter Home page under link "Subscribe to What's New"
  • +
  • + Bug + 42784 - Show the number of errors logged in the GUI
  • +
  • + Bug + 53256 - Make Clear All command clean LogViewer content
  • +
  • + Bug + 53261 - Make "Error/fatal" counter added in + Bug + 42784 open Log Viewer panel when Warn Indicator is clicked
  • +
+ +

Non-functional changes

+
    +
  • Upgraded to rhino 1.7R3 (was js-1.7R2.jar). +Note: the Maven coordinates for the jar were changed from rhino:js to org.mozilla:rhino. +This does not affect JMeter directly, but might cause problems if using JMeter in a Maven project +with other code that depends on an earlier version of the Rhino Javascript jar. +
  • +
  • + Bug + 52675 - Refactor Proxy and HttpRequestHdr to allow Sampler Creation by Proxy
  • +
  • + Bug + 52680 - Mention version in which function was introduced
  • +
  • + Bug + 52788 - HttpRequestHdr : Optimize code to avoid useless work
  • +
  • JMeter Ant (ant-jmeter-1.1.1.jar) task was upgraded from 1.0.9 to 1.1.1
  • +
  • Updated to commons-io 2.2 (from 2.1)
  • +
  • + Bug + 53129 - Upgrade XStream from 1.3.1 to 1.4.2
  • +
  • Updated to httpcomponents-client 4.1.3 (from 4.1.2)
  • +
  • Updated JMeter distributed testing guide (jmeter_distributed_testing_step_by_step.pdf). Changes source format to OpenOffice odt (from sxw)
  • +
+ + + +

Version 2.6

+ +

New and Noteworthy

+ +

Toolbar

+

A new toolbar on JMeter's main window +

+

+ +

JMeter start test button

+

A new menu option and button allow to start a test ignoring the Pause Timers +

+

+ +

JMeter GUI Look and Feel

+

Allow System or CrossPlatform LAF to be set from options menu +

+

+ +

JMeter GUI - duplicate node

+

Add "duplicate node" in context menu +

+

+ +

JMeter tree view - search facility

+

Functionality to search by keyword in Samplers Tree View +

+

+ +

HTTP Request - raw request pane

+

Improve HTTP Request GUI to better show parameters without name (GWT RPC request or SOAP request for example) +

+

+ +

HTTP Request - other changes

+

    +
  • Allow multiple selection in arguments panel
  • +
  • Allow to add (paste) entries from the clipboard to an arguments list
  • +
  • Ability to move variables up or down in HTTP Request
  • +
+
+

+ +

HTTP Request - file protocol

+

Better support for file: protocol in HTTP sampler +

+

+

Retrieve embedded resources with file: protocol +

+

+ +

HTTP Request - Ignore embedded resources failed

+

Enable "ignore failed" for embedded resources +

+

+

Parent success with a embedded resource failed +

+

+ +

View Results in Table - child sample display

+

Add option to TableVisualiser to display child samples instead of parent +

+

+ +

Key Store - multiple certificates

+

Allowing multiple certificates (JKS) +

+

+ +

Aggregate graph improvements

+

Some improvements on Aggregate Graph Listener: +

  • new GUI for settings
  • +
  • dynamic graph size
  • +
  • allow to change fonts for title graph and legend
  • +
  • allow to change bar color (background and text values)
  • +
  • allow to draw or not bars outlines
  • +
  • allow to select only some samplers by a regexp filter
  • +
  • allow to define Y axis maximum scale
  • +
+
+

+ +

Aggregate Graph bar +

+

+ +

Counter - new reset option

+

Add an option to reset counter on each Thread Group iteration +

+

+ +

Functions

+

    +
  • Add a new function __RandomString to generate random Strings
  • +
  • Add a new function __TestPlanName returning the name of the current "Test Plan"
  • +
  • Add a new function __machineIP returning IP address
  • +
  • Add a new function __jexl2 to support Jexl2
  • +
+
+

+ +

User Defined Variable improvements

+

  • Add a comment field in User Defined Variables
  • +
  • Allow to add (paste) entries from the clipboard to an arguments list
  • +
  • Ability to move up or down variables in User Defined Variables
  • +
+
+

+ +

View Results Tree

+

In View Results Tree rather than showing just a message if the results are to big, show as much of the result as are configured +

+

+ +

Controllers - change elements

+

Add ability to Change Controller elements +

+

+ +

JDBC pre- and post-processor

+

Add JDBC pre- and post-processor +

+

+ +

JDBC transaction isolation option

+

Allow to set the transaction isolation in the JDBC Connection Configuration +

+

+ +

Poisson Timer

+

Add a Poisson based timer +

+

+ +

GUI and OS interaction

+

Support for file Drag and Drop. +

+

+ +

Confirm Remove Dialog box

+

Add a dialog box to confirm removing the element(s) when Remove action is called +

+The dialogue can be skipped by setting the JMeter property confirm.delete.skip=true +

+ +

Remote batching support

+

Use external store to hold samples during distributed testing, +Added DiskStore remote sample sender: like Hold, but saves samples to disk until end of test +

+

+ +

JMS Subscriber sampler

+

With JMS Subscriber, ability to use Selectors +

+

+ +

New Logger Panel

+

A new Log Viewer has been added to the GUI and can be enabled from menu Options > Log Viewer: +

+

+

This Log Viewer shows the jmeter.log file, and useful (for example) to debug BeanShell/BSF scripts: +

+

+ +

The menu item Options / Choose Language is now fully functional

+

+The menu item Options / Choose Language now changes all the displayed text to the new language provided +all messages are translated. You can help on this by translating into your language. +

+ +

Legacy JMX and JTL Avalon format support restored

+

+Support for reading/writing the original Avalon XML format of JMX (script) and JTL (sample result) files was dropped in JMeter version 2.4. +JMeter can now read the Avalon format files again, however there is no support for saving files in the old format. +

+ +

JMeter jars available from Maven repository

+

+JMeter jars are now available from Maven repository. +

+ + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode (see Bugs 40671, 41286, 44973, 50898). +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see + Bug + 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ + + +

Incompatible changes

+ +

+JMeter versions since 2.1 failed to create a container sample when loading embedded resources. +This has been corrected; can still revert to the + Bug + 51939 behaviour by setting the following property: +httpsampler.separate.container=false +

+

+Mirror server now uses default port 8081, was 8080 before 2.5.1. +

+

+TCP Sampler handles SocketTimeoutException, SocketException and InterruptedIOException differently since 2.6, when +these occurs, Sampler is marked as failed. +

+

+Sample Sender implementations now resolve their configuration on Client side since 2.6. +This behaviour can be changed with property sample_sender_client_configured (set it to false). +

+ +

+The HTTP User Parameter Modifier test element has been removed; it has been deprecated for a long time. +

+ + + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • + Bug + 51932 - CacheManager does not handle cache-control header with any attributes after max-age
  • +
  • + Bug + 51918 - GZIP compressed traffic produces errors, when multiple connections allowed
  • +
  • + Bug + 51939 - Should generate new parent sample if necessary when retrieving embedded resources
  • +
  • + Bug + 51942 - Synchronisation issue on CacheManager when Concurrent Download is used
  • +
  • + Bug + 51957 - Concurrent get can hang if a task does not complete
  • +
  • + Bug + 51925 - Calling Stop on Test leaks executor threads when concurrent download of resources is on
  • +
  • + Bug + 51980 - HtmlParserHTMLParser double-counts images used in links
  • +
  • + Bug + 52064 - OutOfMemory Risk in CacheManager
  • +
  • + Bug + 51919 - Random ConcurrentModificationException or NoSuchElementException in CookieManager#removeMatchingCookies when using Concurrent Download
  • +
  • + Bug + 52126 - HttpClient4 does not clear cookies between iterations
  • +
  • + Bug + 52129 - Reported Body Size is wrong when using HTTP Client 4 and Keep Alive connection
  • +
  • + Bug + 52137 - Problems with HTTP Cache Manager
  • +
  • + Bug + 52221 - Nullpointer Exception with use Retrieve Embedded Resource without HTTP Cache Manager
  • +
  • + Bug + 52310 - variable in IPSource failed HTTP request if "Concurrent Pool Size" is enabled
  • +
  • + Bug + 52371 - API Incompatibility - Methods in HTTPSampler2 now require PostMethod instead of HttpMethod[Base]. Reverted to original types.
  • +
  • + Bug + 49950 - Proxy : IndexOutOfBoundsException when recording with Proxy server
  • +
  • + Bug + 52409 - HttpSamplerBase#errorResult modifies sampleResult passed as parameter; +fix code which assumes that a new instance is created (i.e. when adding a sub-sample) +
  • +
  • + Bug + 52507 - Delete Http User Parameters modifier (deprecated, obsolete)
  • +
+ +

Other Samplers

+
    +
  • + Bug + 51996 - JMS Initial Context leak newly created Context when Multiple Thread enter InitialContextFactory#lookupContext at the same time
  • +
  • + Bug + 51691 - Authorization does not work for JMS Publisher and JMS Subscriber
  • +
  • + Bug + 52036 - Durable Subscription fails with ActiveMQ due to missing clientId field
  • +
  • + Bug + 52044 - JMS Subscriber used with many threads leads to javax.naming.NamingException: Something already bound with ActiveMQ
  • +
  • + Bug + 52072 - LengthPrefixedBinaryTcpClientImpl may end a sample prematurely
  • +
  • + Bug + 52390 - AbstractJDBCTestElement:Memory leak and synchronization issue in perConnCache
  • +
+ +

Controllers

+
    +
  • + Bug + 51865 - Infinite loop inside thread group does not work properly if "Start next loop after a Sample error" option set
  • +
  • + Bug + 51868 - A lot of exceptions in jmeter.log while using option "Start next loop" for thread
  • +
  • + Bug + 51866 - Counter under loop doesn't work properly if "Start next loop on error" option set for thread group
  • +
  • + Bug + 52296 - TransactionController + Children ThrouputController or InterleaveController leads to ERROR sampleEnd called twice java.lang.Throwable: Invalid call sequence when TPC does not run sample
  • +
  • + Bug + 52330 - With next-Loop-On-Error after error samples are not executed in next loop
  • +
+ +

Listeners

+
    +
  • + Bug + 52357 - View results in Table does not allow for multiple result samples
  • +
  • + Bug + 52491 - Incorrect parsing of Post data parameters in Tree Listener / Http Request view
  • +
+ +

Assertions

+
    +
  • + Bug + 52519 - XMLSchemaAssertion uses JMeter JVM file.encoding instead of response encoding
  • +
+ +

Functions

+
    +
  • The CRLF example for the char function was wrong; CRLF=(0xD,0xA), not (0xC,0xA)
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 51937 - JMeter does not handle missing TestPlan entry well
  • +
  • + Bug + 51988 - CSV Data Set Configuration does not resolve default delimiter for header parsing when variables field is empty
  • +
  • + Bug + 52003 - View Results Tree "Scroll automatically" does not scroll properly in case nodes are expanded
  • +
  • + Bug + 27112 - User Parameters should use scrollbars
  • +
  • + Bug + 52029 - Command-line shutdown only gets sent to last engine that was started
  • +
  • + Bug + 52093 - Toolbar ToolTips don't switch language
  • +
  • + Bug + 51733 - SyncTimer is messed up if you a interrupt a test plan
  • +
  • + Bug + 52118 - New toolbar : shutdown and stop buttons not disabled when no test is running
  • +
  • + Bug + 52125 - StatCalculator.addAll(StatCalculator calc) joins incorrect if there are more samples with the same response time in one of the TreeMap
  • +
  • + Bug + 52339 - JMeter Statistical mode in distributed testing shows wrong response time
  • +
  • + Bug + 52215 - Confusing synchronization in StatVisualizer, SummaryReport ,Summariser and issue in StatGraphVisualizer
  • +
  • + Bug + 52216 - TableVisualizer : currentData field is badly synchronized
  • +
  • + Bug + 52217 - ViewResultsFullVisualizer : Synchronization issues on root and treeModel
  • +
  • + Bug + 43294 - XPath Extractor namespace problems
  • +
  • + Bug + 52224 - TestBeanHelper does not support NOT_UNDEFINED == Boolean.FALSE
  • +
  • + Bug + 52279 - Switching to another language loses icons in Tree and logs error Can't obtain GUI class from ...
  • +
  • + Bug + 52280 - The menu item Options / Choose Language does not change all the displayed text to the new language
  • +
  • + Bug + 52376 - StatCalculator#addValue(T val, int sampleCount) should use long, not int
  • +
  • + Bug + 49374 - Encoding of embedded element URLs depend on the file.encoding property
  • +
  • + Bug + 52399 - URLRewritingModifier uses default file.encoding to match text content
  • +
  • + Bug + 50438 - code calculates average with integer math, expecting double value
  • +
  • + Bug + 52469 - Changes in Support of SSH-Tunneling of RMI traffic for Remote Testing
  • +
  • + Bug + 52466 - Upgrade Test Plan feature : NameUpdater does not upgrade properties
  • +
  • + Bug + 52503 - Unify File->Close and Window close file saving behaviour
  • +
  • + Bug + 52537 - Help does not scroll to correct anchor when file is first loaded
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+ + +

Other samplers

+
    +
  • + Bug + 51419 - JMS Subscriber: ability to use Selectors
  • +
  • + Bug + 52088 - JMS Sampler : Add a selector when REQUEST / RESPONSE is chosen
  • +
  • + Bug + 52104 - TCP Sampler handles badly errors
  • +
  • + Bug + 52087 - TCPClient interface does not allow for partial reads
  • +
  • + Bug + 52115 - SOAP/XML-RPC should not send a POST request when file to send is not found
  • +
  • + Bug + 40750 - TCPSampler : Behaviour when sockets are closed by remote host
  • +
  • + Bug + 52396 - TCP Sampler in "reuse connection mode" reuses previous sampler's connection even if it's configured with other host, port, user or password
  • +
  • + Bug + 52048 - BSFSampler, BSFPreProcessor and BSFPostProcessor should share the same GUI
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • + Bug + 52022 - In View Results Tree rather than showing just a message if the results are to big, show as much of the result as are configured
  • +
  • + Bug + 52201 - Add option to TableVisualiser to display child samples instead of parent
  • +
  • + Bug + 52214 - Save Responses to a file - improve naming algorithm
  • +
  • + Bug + 52340 - Allow remote sampling mode to be changed at run-time
  • +
  • + Bug + 52452 - Improvements on Aggregate Graph Listener (GUI and settings)
  • +
  • Resurrected OldSaveService to allow reading Avalon format JTL (result) files
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+ + +

Functions

+
    +
  • + Bug + 52006 - Create a function RandomString to generate random Strings
  • +
  • + Bug + 52016 - It would be useful to support Jexl2
  • +
  • __char() function now supports octal values
  • +
  • New function __machineIP returning IP address
  • +
  • + Bug + 51091 - New function returning the name of the current "Test Plan"
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 51892 - Default mirror port should be different from default proxy port
  • +
  • + Bug + 51817 - Moving variables up and down in User Defined Variables control
  • +
  • + Bug + 51876 - Functionality to search in Samplers TreeView
  • +
  • + Bug + 52019 - Add menu option to Start a test ignoring Pause Timers
  • +
  • + Bug + 52027 - Allow System or CrossPlatform LAF to be set from options menu
  • +
  • + Bug + 52037 - Remember user-set LaF over restarts.
  • +
  • + Bug + 51861 - Improve HTTP Request GUI to better show parameters without name (GWT RPC requests for example) (UNDER DEVELOPMENT)
  • +
  • + Bug + 52040 - Add a toolbar in JMeter main window
  • +
  • + Bug + 51816 - Comment Field in User Defined Variables control.
  • +
  • + Bug + 52052 - Using a delimiter to separate result-messages for JMS Subscriber
  • +
  • + Bug + 52103 - Add automatic scrolling option to table visualizer
  • +
  • + Bug + 52097 - Save As should point to same folder that was used to open a file if MRU list is used
  • +
  • + Bug + 52085 - Allow multiple selection in arguments panel
  • +
  • + Bug + 52099 - Allow to set the transaction isolation in the JDBC Connection Configuration
  • +
  • + Bug + 52116 - Allow to add (paste) entries from the clipboard to an arguments list
  • +
  • + Bug + 52160 - Don't display TestBeanGui items which are flagged as hidden
  • +
  • + Bug + 51886 - SampleSender configuration resolved partly on client and partly on server
  • +
  • + Bug + 52161 - Enable plugins to add own translation rules in addition to upgrade.properties. +Loads any additional properties found in META-INF/resources/org.apache.jmeter.nameupdater.properties files
  • +
  • + Bug + 42538 - Add "duplicate node" in context menu
  • +
  • + Bug + 46921 - Add Ability to Change Controller elements
  • +
  • + Bug + 52240 - TestBeans should support Boolean, Integer and Long
  • +
  • + Bug + 52241 - GenericTestBeanCustomizer assumes that the default value is the empty string
  • +
  • + Bug + 52242 - FileEditor does not allow output to be saved in a File
  • +
  • + Bug + 51093 - when loading a selection previously stored by "Save Selection As", show the file name in the blue window bar
  • +
  • + Bug + 50086 - Password fields not Hidden in JMS Publisher, JMS Subscriber, Mail Reader sampler, SMTP sampler and Database Configuration
  • +
  • + Bug + 29352 - Use external store to hold samples during distributed testing, Added DiskStore remote sample sender: like Hold, but saves samples to disk until end of test.
  • +
  • + Bug + 52333 - Reduce overhead in calculating SampleResult#nanoTimeOffset
  • +
  • + Bug + 52346 - Shutdown detects if there are any non-daemon threads left which prevent JVM exit.
  • +
  • + Bug + 52281 - Support for file Drag and Drop
  • +
  • + Bug + 52471 - Improve Mirror Server performance by Using Pool of threads instead of launching a Thread for each request
  • +
  • Resurrected OldSaveService to allow reading Avalon format JMX files (removed in 2.4)
  • +
  • Add a dialog box to confirm removing the element(s) when Remove action is called
  • +
  • + Bug + 41788 - Log viewer (console window) needed as an option
  • +
  • Add option to change the pause time (default 2000ms) in the daemon thread which checks for successful JVM exit. +The thread is not now started unless the pause time is greater than 0. +
  • +
+ +

Non-functional changes

+
    +
  • fixes to build.xml: support scripts; localise re-usable property names
  • +
  • + Bug + 51923 - Counter function bug or documentation issue ? (fixed docs)
  • +
  • Update velocity.jar to 1.7 (from 1.6.2)
  • +
  • Update js.jar to 1.7R3 (from 1.6R5)
  • +
  • Update commons-codec 1.5 => 1.6
  • +
  • Update commons-io 2.0.1 => 2.1
  • +
  • Update commons-jexl 2.0.1 => 2.1.1
  • +
  • Update jdom 1.1 => 1.1.2
  • +
  • Update junit 4.9 => 4.10
  • +
  • + Bug + 51954 - Generated documents include </br> entries which cause extra blank lines
  • +
  • + Bug + 52075 - JMeterProperty.clone() currently returns Object; it should return JMeterProperty
  • +
  • Updated httpcore to 4.1.4
  • +
  • + Bug + 49753 - Please publish jMeter artifacts on Maven central repository
  • +
+ + + +

Version 2.5.1

+ +

Summary of main changes

+ +
    +
  • HttpClient4 sampler now re-uses connections properly (previously it would use one per sample, which could quickly cause resource exhaustion).
  • +
  • Various fixes to JMS samplers
  • +
  • Functions are no longer spuriously invoked when used with a Configuration element
  • +
  • WebService sampler GUI has been re-organized for better design and more user-friendliness. Some improments on WSDL configuration assistant
  • +
  • Better handling of test shutdown. System.exit now only called if there is no other option; even this can be disabled.
  • +
+ + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

The If Controller may cause an infinite loop if the condition is always false from the first iteration. +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+ +

+The HttpClient4 and Commons HttpClient 3.1 samplers previously used a retry count of 3. +This has been changed to default to 1, to be compatible with the Java implementation. +The retry count can be overridden by setting the relevant JMeter property, for example: +

+httpclient4.retrycount=3
+httpclient3.retrycount=3
+
+

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • Fix HttpClient 4 sampler so it reuses HttpClient instances and connections where possible.
  • +
  • Temporary fix to HC4 sampler to work round HTTPCLIENT-1120.
  • +
  • + Bug + 51863 - Lots of ESTABLISHED connections with HttpClient 4 implementation (vs HttpClient 3.1 impl)
  • +
  • + Bug + 51750 - Retrieve all embedded resources doesn't follow IFRAME
  • +
  • + Bug + 51752 - HTTP Cache is broken when using "Retrieve all embedded resources" with concurrent pool
  • +
  • + Bug + 39219 - HTTP Server: You can't stop it after File->Open
  • +
  • + Bug + 51775 - Port number duplicates in Host header when capturing by HttpClient (3.1 and 4.x)
  • +
  • + Bug + 50617 - Monitor Results legend show "dead" server although values from the server are retrieved
  • +
+ +

Other Samplers

+
    +
  • + Bug + 50424 - Web Methods drop down list box inconsistent
  • +
  • + Bug + 43293 - Java Request fields not cleared when creating new sampler
  • +
  • + Bug + 51830 - Webservice Soap Request triggers too many popups when Webservice WSDL URL is down
  • +
  • WebService(SOAP) request - add a connect timeout to get the wsdl used to populate Web Methods when server doesn't response
  • +
  • + Bug + 51841 - JMS : If an error occurs in ReceiveSubscriber constructor or Publisher, then Connections will stay open
  • +
  • + Bug + 51691 - Authorization does not work for JMS Publisher and JMS Subscriber
  • +
  • + Bug + 51840 - JMS : Cache of InitialContext has some issues
  • +
  • + Bug + 47888 - JUnit Sampler re-uses test object
  • +
+ +

Controllers

+
    +
  • If Controller - Fixed two regressions introduced by + Bug + 50032 (see + Bug + 50618 too)
  • +
  • If Controller - Catches a StackOverflowError when a condition returns always false (after at least one iteration with return true) See + Bug + 50618
  • +
  • + Bug + 51869 - NullPointer Exception when using Include Controller
  • +
+ +

Listeners

+
    +
+ +

Assertions

+
    +
+ +

Functions

+
    +
  • + Bug + 48943 - Functions are invoked additional times when used in combination with a Config Element
  • +
+ +

I18N

+
    +
  • WebService(SOAP) request - add I18N for some labels
  • +
+ +

General

+
    +
  • + Bug + 51831 - Cannot disable UDP server or change the maximum UDP port
  • +
  • + Bug + 51821 - Add short-cut for Enabling / Disabling (sub)tree or branches in test plan.
  • +
  • + Bug + 47921 - Variables not released for GC after JMeterThread exits.
  • +
  • + Bug + 51839 - "... end of run" printed prematurely
  • +
  • + Bug + 51847 - Some Junit tests are Locale sensitive and fail if Locale is different from US
  • +
  • + Bug + 51855 - Parent samples may have slightly inaccurate elapsed times
  • +
  • + Bug + 51880 - The shutdown command is not working if I invoke it before all the thread are started
  • +
  • Remote Shut host menu item was not being enabled.
  • +
  • + Bug + 51888 - Occasional deadlock when stopping a testplan
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • + Bug + 51380 - Control reuse of cached SSL Context from iteration to iteration
  • +
  • + Bug + 51882 - HTTPHC3Client uses a default retry count of 3, make it configurable; default is now 1
  • +
  • Change the default HttpClient 4 sampler retry count to 1
  • +
+ +

Other samplers

+
    +
  • Beanshell Sampler now supports Interruptible interface
  • +
  • + Bug + 51605 - WebService(SOAP) Request - WebMethod field value changes surreptitiously for all the requests when a value is selected in a request
  • +
  • WebService(SOAP) Request - Reorganized GUI for better design and more user-friendliness
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • + Bug + 42246 - Need for a 'auto-scroll' option in "View Results Tree" and "Assertion Results"
  • +
  • View Results Tree: Regexp Tester - little improvements on user interface
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 51885 - Allow a JMeter Variable as input to XPathExtractor
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 51822 - (part 1) save 1 invocation of GuiPackage#getCurrentGui
  • +
  • Added AsynchSampleSender which sends samples from server to client asynchronously.
  • +
  • Upgraded to htmlparser 2.1; JavaMail 1.4.4; JUnit 4.9
  • +
+ +

Non-functional changes

+
    +
  • + Bug + 49976 - FormCharSetFinder visibility is default instead of public.
  • +
  • + Bug + 50917 - Property CookieManager.save.cookies not honored when set from test plan
  • +
  • Improve error logging when Javascript errors are detected.
  • +
  • Updated documentation footer
  • +
+ + + +

Version 2.5

+ +

Summary of main changes

+ +
    +
  • The HTTP implementation can now be selected at run-time, and JMeter now also supports Apache HttpComponents HttpClient 4.x. +Note that Commons HttpClient 3.1 is no longer actively developed, and support may be removed from JMeter in a future release. +
  • +
  • The HTTP sampler now allows concurrent downloads of embedded resources in an HTML page
  • +
  • The HTTP Sampler can now report the size of a request before decompression.
  • +
  • The JMS and Mail samplers have been improved.
  • +
  • The new Test Fragment Test Element makes using Include Controllers easier
  • +
  • There are various improvements to the View Results Tree Listener
  • +
  • + Bug + 30563 - Thread Group should have a start next loop option on Sample Error
  • +
  • There are two new Thread Group types - setUp and tearDown - which are run before and after the main Thread groups.
  • +
  • Client-Server mode now supports external stop/shutdown via UDP
    +multiple JMeter server instances can be started on the same host without needing to change the port property.
  • +
  • + Bug + 50516 - "Host" header in HTTP Header Manager is not included in generated HTTP request
  • +
+ +

+

    +
+

+ + + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+ +

+Unsupported methods are no longer converted to GET by the Commons HttpClient sampler. +

+ +

+Removed method public static long currentTimeInMs(). +This has been replaced by the instance method public long currentTimeInMillis(). +

+ +

+ProxyControl.getSamplerTypeName() now returns a String rather than an int. +This is internal to the workings of the JMeter Proxy & its GUI, so should not affect any user code. +

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • + Bug + 50178 - HeaderManager added as child of Thread Group can create concatenated HeaderManager names and OutOfMemoryException
  • +
  • + Bug + 50392 - value is trimmed when sending the request in Multipart
  • +
  • + Bug + 50686 - HeaderManager logging too verbose when merging instances
  • +
  • + Bug + 50963 - AjpSampler throws java.lang.StringIndexOutOfBoundsException
  • +
  • + Bug + 50516 - "Host" header in HTTP Header Manager is not included in generated HTTP request
  • +
  • + Bug + 50544 - In Apache Common Log the HEAD requests cause problems.
  • +
  • + Bug + 51268 - HTTPS request through an invalid proxy causes NullPointerException and does not show in result tree. +Rather than delegating to the JMeter thread handler for "unexpected" failures, ensure all Exceptions generate a sample error. +
  • +
  • + Bug + 51275 - Cookie Panel clearGui() sets incorrect default policy in Java 1.6
  • +
+ +

Other Samplers

+
    +
  • + Bug + 50173 - JDBCSampler discards ResultSet from a PreparedStatement
  • +
  • Ensure JSR223 Sampler has access to the current SampleResult
  • +
  • + Bug + 50977 - Unable to set TCP Sampler for individual samples
  • +
+ +

Controllers

+
    +
  • + Bug + 50032 - Last_Sample_Ok along with other controllers doesnt work correctly when the threadgroup has multiple loops
  • +
  • + Bug + 50080 - Transaction controller incorrectly creates samples including timer duration
  • +
  • + Bug + 50134 - TransactionController : Reports bad response time when it contains other TransactionControllers
  • +
+ +

Listeners

+
    +
  • + Bug + 50367 - Clear / Clear all in View results tree does not clear selected element
  • +
+ +

Assertions

+ + +

Functions

+
    +
  • + Bug + 50568 - Function __FileToString(): Could not read file when encoding option is blank/empty
  • +
+ +

I18N

+ + +

General

+
    +
  • + Bug + 49734 - Null pointer exception on stop Threads command (Run>Stop)
  • +
  • + Bug + 49666 - CSV Header read as data after EOF
  • +
  • + Bug + 45703 - Synchronizing Timer
  • +
  • + Bug + 50088 - fix getAvgPageBytes in SamplingStatCalculator so it returns what it should
  • +
  • + Bug + 50203 Cannot set property "jmeter.save.saveservice.default_delimiter=\t"
  • +
  • mirror-server.sh - fix classpath to use : separator (not ;)
  • +
  • + Bug + 50286 - URL Re-writing Modifier: extracted jsessionid value is incorrect when is between XML tags
  • +
  • +System.nanoTime() tends to drift relative to System.currentTimeMillis(). +Change SampleResult to recalculate offset each time. +Also enable reversion to using System.currentTimeMillis() only. +
  • +
  • + Bug + 50425 - Remove thread groups from Controller add menu
  • +
  • + + Bug + 50675 - CVS Data Set Config incompatible with Remote Start +Fixed RMI startup to provide location of JMX file relative to user.dir. +
  • +
  • + Bug + 50221 - Renaming elements in the tree does not resize label
  • +
  • + Bug + 51002 - Stop Thread if CSV file is not available. JMeter now treats IOError as EOF.
  • +
  • Define sun.net.http.allowRestrictedHeaders=true by default. This fixes + Bug + 51238.
  • +
  • + Bug + 51645 - CSVDataSet does not read UTF-8 files when file.encoding is UTF-8
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • AJP Sampler now implements Interruptible
  • +
  • Allow HTTP implementation to be selected at run-time
  • +
  • + Bug + 50684 - Optionally disable Content-Type and Transfer-Encoding in Multipart POST
  • +
  • + Bug + 50943 - Allowing concurrent downloads of embedded resources in html page
  • +
  • + Bug + 50170 - Bytes reported by http sampler is after GUnZip
    Add optional properties to allow change the method to get response size
  • +
  • Hiding the proxy password on HTTP Sampler (just on GUI, not in JMX file)
  • +
+ +

Other samplers

+
    +
  • + Bug + 49622 - Allow sending messages without a subject (SMTP Sampler)
  • +
  • + Bug + 49603 - Allow accepting expired certificates on Mail Reader Sampler
  • +
  • + Bug + 49775 - Allow sending messages without a body
  • +
  • + Bug + 49862 - Improve SMTPSampler Request output.
  • +
  • + Bug + 50268 - Adds static and dynamic destinations to JMS Publisher
  • +
  • JMS Subscriber - Add dynamic destination
  • +
  • + Bug + 50666 - JMSSubscriber: support for durable subscriptions
  • +
  • + Bug + 50937 - TCP Sampler does not provide for / honor connect timeout
  • +
  • + Bug + 50569 - Jdbc Request Sampler to optionally store result set object data
  • +
  • + Bug + 51011 - Mail Reader: upon authentication failure, tell what you tried
  • +
+ +

Controllers

+
    +
  • + Bug + 50475 - Introduction of a Test Fragment Test Element for a better Include flow
  • +
+ +

Listeners

+
    +
  • View Results Tree - Add a dialog's text box on "Sampler result tab > Parsed" to display the long value with a double click on cell
  • +
  • + Bug + 37156 - Formatted view of Request in Results Tree
  • +
  • + Bug + 49365 - Allow result set to be written to file in a path relative to the loaded script
  • +
  • + Bug + 50579 - Error count is long, sample count is int. Changed sample count to long.
  • +
  • View Results Tree - Add new size fields: response headers and response body (in bytes) - derived from + Bug + 43363
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 48015 - Proposal new icons for pre-processor, post-processor and assertion elements
  • +
  • + Bug + 50962 - SizeAssertionGui validation prevents the use of variables for the size
  • +
  • Size Assertion - Add response size scope (full, headers, body, code, message) - derived from + Bug + 43363
  • +
+ +

Functions

+
    +
  • + Bug + 49975 - New function returning the name of the current sampler
  • +
+ +

I18N

+
    +
  • Add French translation for the new labels and reduce size for some labels (by abbreviation) on HTTP Sample
  • +
+ +

General

+
    +
  • + Bug + 30563 - Thread Group should have a start next loop option on Sample Error
  • +
  • + Bug + 50347 - Eclipse setup instructions should remind user to download dependent jars
  • +
  • + Bug + 50490 - Setup and Post Thread Group enhancements for better test flow.
  • +
  • All BeansShell test elements now have the script variables "prev" and "Label" defined.
  • +
  • + Bug + 50708 - Classpath jar order in NewDriver not alphabetically
  • +
  • + Bug + 50659 - JMeter server does not support concurrent tests - prevent client from starting another
  • +
  • Added remote shutdown functionality
  • +
  • Client JMeter engine now supports external stop/shutdown via UDP
  • +
  • UDP shutdown can now use a range of ports, from jmeterengine.nongui.port=4445 to jmeterengine.nongui.maxport=4455, +allowing multiple JMeter instances on the same host without needing to change the port property.
  • +
  • Updated to httpcore 4.1.3 and httpclient 4.1.2
  • +
+ +

Non-functional changes

+
    +
  • + Bug + 50008 - Allow BatchSampleSender to be subclassed
  • +
  • + Bug + 50450 - use System.array copy in jacobi solver as, being native, is more performant.
  • +
  • + Bug + 50487 - runSerialTest verifies objects that never need persisting
  • +
  • Use Thread.setDefaultUncaughtExceptionHandler() instead of private ThreadGroup
  • +
  • Update to Commons Net 3.0
  • +
+ + + +

Version 2.4

+ +

Summary of main changes

+ +

+

    +
  • JMeter now requires at least Java 1.5.
  • +
  • HTTP Proxy can now record HTTPS sessions.
  • +
  • JUnit sampler now supports JUnit4 annotations.
  • +
  • Added JSR223 (javax.script) test elements.
  • +
  • MailReader Sampler can now use any protocol supported by the underlying implementation.
  • +
  • An SMTP Sampler has been added.
  • +
  • JMeter now allows users to provide their own Thread Group implementations.
  • +
  • View Results Tree now supports more display options, including search and Regex Testing.
  • +
  • StatCalculator performance is much improved; Aggregate Report etc. need far less memory.
  • +
  • +JMS samplers have been extensively reworked, and should no longer lose messages. +Correlation processing is improved. +JMS Publisher and Subscriber now support both Topics and Queues. +
  • +
  • Many other improvements have been made, please see below and in the manual.
  • +
+

+ + + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+ +

+HTTP Redirect now defaults to "Follow Redirects" rather than "Redirect Automatically". +This is to enable JMeter to track cookies that may be sent during redirects. +This does not affect existing test plans; it only affects the default for new HTTP Samplers. +

+ +

+The Avalon file format for JMX and JTL files is no longer supported. +Any such files will need to be converted by reading them in JMeter 2.3.4 and resaving them. +

+ +

+The XPath Assertion and XPath Extractor elements no longer fetch external DTDs by default; this can be changed in the GUI. +

+ +

+JMSConfigGui has been renamed as JMSSamplerGui. +This does not affect existing test plans. +

+ +

+The constructor public SampleResult(SampleResult res) has been changed to become a true "copy constructor". +It no longer calls addSubResult(). This may possibly affect some 3rd party add-ons. +

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • + Bug + 47445 - Using Proxy with https-spoofing secure cookies need to be unsecured
  • +
  • + Bug + 47442 - Missing replacement of https by http for certain conditions using https-spoofing
  • +
  • + Bug + 48451 - Error in: SoapSampler.setPostHeaders(PostMethod post) in the else branch
  • +
  • + Bug + 48542 - SoapSampler uses wrong response header field to decide if response is gzip encoded
  • +
  • + Bug + 48568 - CookieManager broken for AjpSampler
  • +
  • + Bug + 48570 - AjpSampler doesn't support query parameters (GET/POST)
  • +
  • + Bug + 46901 - HTTP Sampler does not process var/func refs correctly in first file parameter
  • +
  • + Bug + 43678 - Handle META tag http-equiv charset?
  • +
  • + Bug + 49294 - Images not downloaded from redirected-to pages
  • +
  • + Bug + 49560 - wrong "size in bytes" when following redirections
  • +
+ +

Other Samplers

+ + +

Controllers

+
    +
  • + Bug + 47385 - TransactionController should set AllThreads and GroupThreads
  • +
  • + Bug + 47940 - Module controller incorrectly creates the replacement Sub Tree
  • +
  • + Bug + 47592 - Run Thread groups consecutively with "Stop test" on error, JMeter will not mark to finished
  • +
  • + Bug + 48786 - Run Thread groups consecutively: with "Stop test now" on error or manual stop, JMeter leaves the green box active
  • +
  • + Bug + 48727 - Cannot stop test if all thread groups are disabled
  • +
+ +

Listeners

+
    +
  • + Bug + 48603 - Mailer Visualiser sends two emails for a single failed response
  • +
  • Correct calculation of min/max/std.dev for aggregated samples (Summary Report)
  • +
  • + Bug + 48889 - Wrong response time with mode=Statistical and num_sample_threshold > 1
  • +
  • + Bug + 47398 - SampleEvents are sent twice over RMI in distributed testing and non gui mode
  • +
+ +

Assertions

+
    +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • + Bug + 47646 - NullPointerException in the "Random Variable" element
  • +
  • Disallow adding any child elements to JDBC Configuration
  • +
  • BeanInfoSupport now caches getBeanDescriptor() - should avoid an NPE on non-Sun JVMs when using CSVDataSet (and some other TestBeans)
  • +
  • + Bug + 48350 - Deadlock on distributed testing with 2 clients
  • +
  • + Bug + 48901 - Endless wait by adding Synchronizing Timer
  • +
  • + Bug + 49149 - usermanual/index.html has typo in link to "Regular Expressions" page
  • +
  • + Bug + 49394 - Classcast Exception in ActionRouter.postActionPerformed
  • +
  • + Bug + 48136 - Essential files missing from source tarball.
    +Source archives now contain all source files, including source files previously only provided in the binary archives. +
  • +
  • + Bug + 48331 - XpathExtractor does not return XML string representations for a Nodeset
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • + Bug + 47622 - enable recording of HTTPS sessions
  • +
  • Allow Proxy Server to be specified on HTTP Sampler GUI and HTTP Config GUI
  • +
  • + Bug + 47461 - Update Cache Manager to handle Expires HTTP header
  • +
  • + Bug + 48153 - Support for Cache-Control and Expires headers
  • +
  • + Bug + 47946 - Proxy should enable Grouping inside a Transaction Controller
  • +
  • + Bug + 48300 - Allow override of IP source address for HTTP HttpClient requests
  • +
  • + Bug + 49083 - collapse '/pathsegment/..' in redirect URLs
  • +
+ +

Other samplers

+
    +
  • JUnit sampler now supports JUnit4 tests (using annotations)
  • +
  • + Bug + 47900 - Allow JMS SubscriberSampler to be interrupted
  • +
  • Added JSR223 Sampler
  • +
  • + Bug + 47556 - JMS-PointToPoint-Sampler Timeout field should use Strings
  • +
  • + Bug + 47947 - Mail Reader Sampler should allow port to be overridden
  • +
  • + Bug + 48155 - Multiple problems / enhancements with JMS protocol classes
  • +
  • Allow MailReader sampler to use arbitrary protocols
  • +
  • + Bug + 45053 - SMTP-Sampler for JMeter
  • +
  • + Bug + 49552 - Add Message Headers on SMTPSampler
  • +
  • +JMS Publisher and Subscriber now support both Topics and Queues. +Added read Timeout to JMS Subscriber. +General clean-up of JMS code. +
  • +
+ +

Controllers

+
    +
  • + Bug + 47909 - TransactionController should sum the latency
  • +
  • + Bug + 41418 - Exclude timer duration from Transaction Controller runtime in report
  • +
  • + Bug + 48749 - Allowing custom Thread Groups
  • +
  • + Bug + 43389 - Allow Include files to be found relative to the current JMX file
  • +
+ +

Listeners

+
    +
  • Added DataStrippingSample sender - supports "Stripped" and "StrippedBatch" modes.
  • +
  • Added Comparison Assertion Visualizer
  • +
  • + Bug + 47907 - Improvements (enhancements and I18N) Comparison Assertion and Comparison Visualizer
  • +
  • + Bug + 36726 - add search function to Tree View Listener
  • +
  • + Bug + 47869 - Ability to cleanup fields of SampleResult
  • +
  • + Bug + 47952 - Added JSR223 Listener
  • +
  • + Bug + 47474 - View Results Tree support for plugin renderers
  • +
  • Allow Idle Time to be saved to sample log files
  • +
  • + Bug + 48259 - Improve StatCalculator performance by using TreeMap
  • +
  • Listeners using SamplingStatCalculator have much reduced memory needs +as the Sample cache has been moved to the new CachingStatCalculator class. +In particular, Aggregate Report can now handle large numbers of samples. +
  • +
  • Aggregate Report and Summary Report now allow column headers to be optionally excluded
  • +
  • + Bug + 49506 - Add .csv File Extension in open dialog box from "read from file" functionality of listeners
  • +
  • + Bug + 49545 - Formatted (parsed) view of Sample Result in Results Tree
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 47338 - XPath Extractor forces retrieval of document DTD
  • +
  • Added Comparison Assertion
  • +
  • + Bug + 47952 - Added JSR223 PreProcessor and PostProcessor
  • +
  • Added JSR223 Assertion
  • +
  • Added BSF Timer and JSR223 Timer
  • +
  • + Bug + 48511 - add parent,child,all selection to regex extractor
  • +
  • Add Sampler scope selection to XPathExtractor
  • +
  • Regular Expression Extractor, Response Assertion and Size Assertion can now be applied to a JMeter variable
  • +
  • + Bug + 46790 - CSV Data Set Config should be able to parse CSV headers
  • +
+ +

Functions

+ + +

I18N

+ + +

General

+
    +
  • + Bug + 47223 - Slow Aggregate Report Performance (StatCalculator)
  • +
  • + Bug + 47980 - hostname resolves to 127.0.0.1 - specifiying IP not possible
  • +
  • + Bug + 47943 - DisabledComponentRemover is not used in Start class
  • +
  • HeapDumper class for runtime generation of dumps
  • +
  • Basic read-only JavaMail provider implementation for reading raw mail files
  • +
  • + Bug + 49540 - Sort "Add" menus alphabetically
  • +
+ +

Non-functional changes

+
    +
  • Beanshell, JavaMail and JMS API (Apache Geronimo) jars are now included in the binary archive.
  • +
  • Add TestBean Table Editor support
  • +
  • Removed all external libraries from SVN; added download_jars Ant target
  • +
  • Updated various jar files: +
      +
    • BeanShell - 2.0b4 => 2.0b5
    • +
    • Commons Codec - 1.3 => 1.4
    • +
    • Commons-Collections - 3.2 => 3.2.1
    • +
    • JTidy => r938
    • +
    • JUnit - 3.8.2 => 4.8.1
    • +
    • Logkit - 1.2 => 2.0
    • +
    • Xalan Serializer = 2.7.1 (previously erroneously shown as 2.9.1)
    • +
    • Xerces xml-apis = 1.3.04 (previously erroneously shown as 2.9.1)
    • +
    • Some jar files were renamed.
    • +
    +
  • +
+ + + +

Version 2.3.4

+ +

Summary of main changes

+ +

+This is a minor bug-fix release, mainly to correct some bugs that were accidentally added in 2.3.3. +

+ + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+ + +

Other Samplers

+
    +
  • + Bug + 47290 - Infinite loop on connection factory lookup (JMS)
  • +
  • JDBC Sampler should not close Prepared or Callable statements as these are cached
  • +
+ +

Controllers

+ + +

Listeners

+
    +
  • Change ResultCollector to only warn if the directory was not created
  • +
  • Fix some synchronisation issues in ResultCollector and SampleResult (wrong locks were being used)
  • +
+ +

I18N

+
    +
  • Fixed bug introduced in 2.3.3: JMeter does not start up if there is no messages.properties file for the default Locale.
  • +
+ +

General

+
    +
  • Fix problems with remote clients - bug introduced in 2.3.3
  • +
  • + Bug + 47377 - Make ClassFinder more robust and close zipfile resources
  • +
  • Fix some errors in generating the documentation (latent bug revealed in 2.3.3 when Velocity was upgraded)
  • +
+ +

Improvements

+ +

Other samplers

+
    +
  • + Bug + 47266 - FTP Request Sampler: allow specifying an FTP port, other than the default
  • +
+ + + +

Version 2.3.3

+ +

Summary of main changes

+ +

+The handling of test closedown is much improved. +The gradual "Shutdown" command now waits until all threads have stopped, +and does not report an error if threads don't stop within 5 seconds. +The immediate "Stop" command can now be used if "Shutdown" takes too long. +Also the immediate "Stop" command is able to interrupt samplers which support the new Interruptible interface (e.g. HTTP and SOAP, FTP). +This allows immediate completion of pending responses. +Non-GUI mode tests can also now be sent a "Shutdown" or "Stop" message. +Test Action now supports a "Stop Now" action, +as do the Thread Group and Result Status Action Handler Post Processor elements. +

+ +

+HTTP Cookie handling is improved, and HTTP POST can now use variable file names correctly. +HTTP, SOAP/XML-RPC and WebService(SOAP) sampler character encodings updated to be more consistent. +HTTP Samplers now support connection and response timeouts (requires JVM 1.5 for the HTTP Java sampler). +Together with the closedown improvements described above, this should avoid most cases where a test run hangs. +Multiple Header Manager elements are now supported for a single HTTP sampler. +The Proxy Server is improved, and no longer stores "Host" headers by default. +

+ +

+JDBC Request can optionally save the results of Select statements to variables. +JDBC Request now handles quoted strings and UTF-8, and can handle arbitrary variable types. +

+ +

+There are several new functions: +__char() function: allows arbitrary Unicode characters to be entered in fields. +__unescape() function: allows Java-escaped strings to be used. +_unescapeHtml() function: decodes Html-encoded text. +__escapeHtml() function: encodes text using Html-encoding. +A reference to a missing function - e.g. ${__missing(a)} - is now treated the same as a missing variable. +Previously the function name - and leading { - were dropped. This makes it easier to debug test plans. +

+ +

+Some Assertions can now be applied to sub-samples as well as (or instead of) just the parent sample. +There is a new Random Variable Configuration element. +

+ +

+JMS samplers are much improved (see details below). The TCP Sampler now supports some additional clients and is a bit more flexible. +

+ +

+Client-server mode has been improved, and the server can optionally use a fixed RMI port, which should help with setting up firewalls. +

+ +

+Various I18N changes have been made; language change works better (though not perfect yet). +There are improved French translations as well as new Polish and Brazilian Portugese translations. +

+ +

+The BeanShell jar is now included with the binary archive; there is no need to download it separately. +

+ + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+

+When loading sample results from a file, previous results are no longer cleared. +This allows one to merge multiple files. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. +

+

+The test elements "Save Results to a file" and "Generate Summary Results" are now shown as Listeners. +They were previously shown as Post-Processors, even though they are implemented as Listeners. +

+

+The Cookie Manager no longer saves incoming cookies as variables by default. +To save cookies as variables, define the property "CookieManager.save.cookies=true". +Also, cookies names are prefixed with "COOKIE_" before they are stored (this avoids accidental corruption of local variables) +To revert to the original behaviour, define the property "CookieManager.name.prefix= " (one or more spaces). +

+

+The Counter element is now shown as a Configuration element. +It was previously shown as a Pre-Processor, even though it is implemented as a Config item. +

+

+The above changes only affect the icons that are displayed and the locations in the GUI pop-up menus. +They do not affect test plans or test behaviour. +

+

+The PreProcessors are now invoked directly by the JMeterThread class, +rather than by the TestCompiler#configureSampler() method. (JMeterThread handles the PostProcessors). +This does not affect test plans or behaviour, but could perhaps affect 3rd party add-ons (very unlikely). +

+

+Moved the Scoping Rules sub-section from Section 3. "Building a Test Plan" to Section 4. "Elements of a test plan" +

+ +

+The While controller now trims leading and trailing spaces from the condition value before it is compared +with LAST, blank or false. +

+ +

+The "threadName" variable in the _jexl() and __javaScript() functions was previously misspelt as "theadName". +

+ +

+The following deprecated methods were removed from JOrphanUtils: booleanToString(boolean) and valueOf(boolean). +Java 1.4+ has these methods in the Boolean class. +

+ +

+The TestElement interface has some new methods: +

    +
  • void setProperty(String key, String value, String dflt)
  • +
  • void setProperty(String key, boolean value, boolean dflt)
  • +
  • void setProperty(String key, int value)
  • +
  • void setProperty(String key, int value, int dflt)
  • +
  • int getPropertyAsInt(String key, int defaultValue)
  • +
+These are implemented in the AbstractTestElement class which all elements should extend so this is unlikely to cause a problem. +

+

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • + Bug + 46332 - HTTP Cookie Manager ignores manually defined cookies (bug introduced in r707810)
  • +
  • Cookie Manager was not passing cookie policy to runtime threads so they always used compatibility mode
  • +
  • Add version attribute to JMeter Cookie class (needed for proper cookie support)
  • +
  • Cookie Manager now saves/restores cookie versions
  • +
  • Check validity of cookies before storing them.
  • + +
  • HTTPSamplers can now use variables in POSTed file names
  • +
  • Fix processing of first file name in HTTP POST so functions/variables work (bug introduced with multiple file support)
  • +
  • + Bug + 45831 - WS Sampler reports incorrect throughput if SOAP packet creation fails
  • +
  • HTTP, SOAP/XML-RPC and WebService(SOAP) sampler character encodings updated to be more consistent
  • + +
  • + Bug + 46148 - HTTP sampler fails on SSL requests when logging for jmeter.util is set to DEBUG
  • +
  • Fix Java 1.6 https error: java.net.SocketException: Unconnected sockets not implemented
  • + +
  • + Bug + 46838 - if there was no data, still need to set latency in HTTPSampler
  • +
  • + Bug + 46993 - Saving from Header Manager generates ClassCastException
  • +
  • + + Bug + 46690 - handling of 302 redirects with invalid relative paths. +JMeter now removes extraneous leading "../" segments (as do many browsers) +
  • +
  • + Bug + 44521 - empty variables for a POST in the HTTP Request don't get ignored
  • +
  • + Bug + 46977 - JMeter does not handle HTTP headers not delimited by whitespace
  • +
  • Fix bug in HTTP file: handling - read bytes, not characters in the default encoding.
  • + +
  • Remove Host from headers saved by the Proxy server, as that will normally be generated by the HTTP stack
  • +
  • + Bug + 45199 - don't try to replace blank variables in Proxy recording
  • +
  • Change HTTPS spoofing so https: links are replaced even when URL match fails
  • +
  • + Bug + 46436 - Improve error reporting in Proxy Gui
  • +
  • + Bug + 46435 - More verbose error msg for error 501 (Proxy Server)
  • +
+ +

Other Samplers

+
    +
  • The "prev" and "sampler" objects are now defined for BSF test elements
  • +
  • Fix NPE (in DataSourceElement) when using JDBC in client-server mode
  • +
  • + Bug + 45425 - JDBC Request does not support Unicode (changed sampler to use UTF-8)
  • +
  • + Bug + 46522 - Incorrect "Response data" in JDBC sample when column names are missing
  • +
  • + Bug + 46821 - JDBC select request doesn't store the first column in the variables
  • +
  • + Bug + 43791 - ensure QueueReceiver is closed in JMS Point to Point sampler
  • +
  • + Bug + 46016 - avoid possible NPE in JMSSampler
  • +
  • + Bug + 46142 - JMS Receiver now uses MessageID
  • +
  • + Bug + 45458 - Point to Point JMS in combination with authentication
  • +
  • + Bug + 45460 - JMS TestPlan elements depend on resource property
  • +
  • Various ReceiveSubscriber thread-safety fixes
  • +
  • JMSPublisher and Subscriber fixes: thread-safety, support dynamic locale changes, locale independence for JMX attribute values
  • +
  • FTP Sampler now logs out before disconnecting.
  • +
  • TCP sampler now calls setupTest() and teardownTest() methods
  • +
  • + Bug + 45887 - TCPSampler: timeout property incorrectly set
  • +
+ +

Controllers

+
    +
  • Fix NPE when using nested Transaction Controllers with parent samples
  • +
  • Fix processing of Transaction Controller parent mode so current sampler is set to actual sampler
  • +
  • + Bug + 44941 - Throughput controllers should not share global counters
  • +
  • + Bug + 47120 - Throughput Controller: change percent executions to total executions, the value is stored in a String and interpreted as 1 execution
  • +
  • + Bug + 47150 - ThreadGroup with a loop count of zero causes infinite loop
  • +
  • + Bug + 47009 - Insert parent caused child controller name to be reset
  • +
  • + Bug + 47165 - Using duplicate Module Controller names in command line mode causes NPE
  • +
+ +

Listeners

+
    +
  • Mailer Visualizer documentation now agrees with code i.e. failure/success counts need to be exceeded to trigger the mail.
  • +
  • Mailer Visualizer now shows the failure count
  • +
  • Mailer Visualiser - fix parsing of multiple e-mail address when using Test button
  • +
  • + Bug + 45976 - incomplete result file when using remote testing with more than 1 server
  • +
  • Fix Summariser so it works in client server mode
  • +
  • + Bug + 34096 - Duplicate samples not eliminated when writing to CSV files
  • +
  • Save "Include group Name in Label" setting in Aggregate and Summary reports
  • +
  • The JMeter variable "sample_variables" is sent to all server instances to ensure the data is available to the client.
  • +
  • CSVSaveService - check for EOF while reading quoted string
  • +
+ +

Assertions

+
    +
  • + Bug + 45749 - Response Assertion does not work with a substring that happens to be an invalid RE
  • +
  • + Bug + 45904 - Allow 'Not' Response Assertion to succeed with null sample
  • +
+ +

Functions

+
    +
  • Fix regex function - was failing to process $m$mid$n$ correctly
  • +
  • Protect against possible NPE in RegexFunction if called during test shutdown.
  • +
  • Avoid NPE if XPath function does not match any nodes
  • +
  • Correct the variable name "theadName" to "threadName" in the __jexl() and __javaScript() functions
  • +
  • A reference to a missing function - e.g. ${__missing(a)} - is now treated the same as a missing variable. Previously the function name - and leading { - were dropped.
  • +
+ +

I18N

+
    +
  • Fixed language change handling for menus (does not yet work for TestBeans)
  • +
  • Add HeaderAsPropertyRenderer to support header resource names; use this to fix locale changes in various GUI elements
  • +
  • + Bug + 46424 - corrections to French translation
  • +
  • + Bug + 46844 - "Library" label in test plan are not I18N
  • +
  • + Bug + 47064 - fixes for Mac LAF
  • +
  • + Bug + 47127 - Unable to change language to pl_PL
  • +
  • + Bug + 47137 - Labels in View Results Tree aren't I18N
  • +
  • + Bug + 46423 - I18N of Proxy Recorder
  • +
  • + Bug + 45928 - AJP/1.3 Sampler doesn't retrieve its label from messages.properties
  • +
+ +

General

+
    +
  • Prompt to overwrite an existing file when first saving a new test plan
  • +
  • Amend TestBeans to show the correct popup menu for Listeners
  • +
  • + Bug + 45185 - CSV dataset blank delimiter causes OOM
  • +
  • Fix incorrect GUI classifications: +"Save Results to a file" and "Generate Summary Results" are now shown as Listeners. +"Counter" is now shown as a Configuration element. +
  • +
  • + Bug + 41608 - misleading warning log message removed
  • +
  • + Bug + 46359 - BSF JavaScript Preprocessor cannot access sampler variable on first interation (Implement temporary work-round for BSF-22)
  • +
  • + Bug + 46407 - BSF elements do not load script files, attempt to interpret filename as script
  • +
  • Better handling of Exceptions during test shutdown
  • +
  • Fix potential thread safety issue in JMeterThread class
  • +
  • + Bug + 46491 - Incorrect value for the last variable in "CSV Data Set Config" (error in processing quoted strings)
  • + +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • + Bug + 45479 - Support for multiple HTTP Header Manager nodes
  • +
  • HTTP Samplers now support connection and request timeouts (requires Java 1.5 for Java Http sampler)
  • +
  • Apache SOAP 2.3.1 does not give access to HTTP response code/message, so WebService sampler now treats an empty response as an error
  • +
  • Mirror server now supports "X-Sleep" header - if this is set, the responding thread will wait for the specified number of milliseconds
  • +
  • + Bug + 45694 - Support GZIP compressed logs in Access Log Sampler
  • +
+ +

Other samplers

+
    +
  • JDBC Request can optionally save the results of Select statements to variables.
  • +
  • JDBC Request now handles quoted strings.
  • +
  • JDBC Request now handles arbitrary variable types.
  • +
  • LDAP result data now formatted with line breaks
  • +
  • + Bug + 45200 - MailReaderSampler: store the whole MIME message in the SamplerResult
  • +
  • + Bug + 45571 - JMS Sampler correlation enhancement
  • +
  • + Bug + 46030 - Extend TCP Sampler to Support Length-Prefixed Binary Data
  • +
  • Add classname field to TCP Sampler GUIs
  • +
+ +

Controllers

+
    +
  • Allow If Controller to use variable expressions (not just Javascript)
  • +
  • Trim spaces from While Controller condition before comparing against LAST, blank or false
  • +
+ +

Listeners

+
    +
  • Save Responses to a file can save the generated filename(s) to variables.
  • +
  • Add option to skip suffix generation in Save Responses to a File
  • +
  • + Bug + 43119 - Save Responses to file: optionally omit the file number
  • +
  • Add BSF Listener element
  • +
  • + Bug + 47176 - Monitor Results : improve load status graphic
  • +
  • + Bug + 40045 - Allow Results monitor to select a specific connector
  • +
  • Read XML JTL files more efficiently - pass samples to visualisers as they are read, rather than saving them all and then processing them
  • +
+ +

Assertions, Config, Pre- & Post-Processors

+
    +
  • + Bug + 45903 - allow Assertions to apply to sub-samples
  • +
  • Add Body (unescaped) source option to Regular Expression Extractor.
  • +
  • Random Variable - new configuration element to create random numeric variables
  • +
+ +

Functions

+
    +
  • Add OUT and log variables to __jexl() function
  • +
  • Use Script to evaluate __jexl() function so can have multiple statements.
  • +
  • Add log variable to the __javaScript() function
  • +
  • Added __char() function: allows arbitrary Unicode characters to be entered in fields.
  • +
  • Added __unescape() function: allows Java-escaped strings to be used.
  • +
  • Added __unescapeHtml() function: decodes Html-encoded text.
  • +
  • Added __escapeHtml() function: encodes text using Html-encoding.
  • +
+ +

I18N

+ + +

General

+
    +
  • Allow spaces in JMeter path names (apply work-round for Java Bug 4496398)
  • +
  • Process JVM_ARGS last in script files so users can override default settings
  • +
  • + Bug + 46636 - Allow server mode to optionally use a fixed rmi port
  • +
  • Make some samplers interruptible: HTTP (both), SoapSampler, FTPSampler
  • +
  • Test Action now supports "Stop Now" action, as do the Thread Group and Result Status Post Processor elements
  • +
  • The Menu items Stop and Shutdown now behave better. Shutdown will now wait until all threads exit. +In GUI mode it can be cancelled and Stop run instead. +Stop now reports if some threads will not exit, and exits if running in non-GUI mode
  • +
  • Add UDP server to wait for shutdown message if running in non-GUI mode; add UDP client to send the message.
  • +
  • + Bug + 41209 - JLabeled* and ToolTips
  • +
  • Include BeanShell 2.0b4 jar in binary download.
  • +
+ +

Non-functional changes

+
    +
  • Introduce AbstractListenerGui class to make it easier to create Listeners with no visual output
  • +
  • Assertions are run after PostProcessors; change order of pop-up menus accordingly
  • +
  • Remove unnecessary clone() methods from function classes
  • +
  • Moved PreProcessor invocation to JMeterThread class
  • +
  • Made HashTree Map field final
  • +
  • Improve performance of calling ResultCollector#isSampleWanted() for multiple samples
  • +
  • Updated to new versions of: xmlgraphics-commons (1.3.1), jdom (1.1), xstream (1.3.1), velocity (1.6.2)
  • +
+ + + + +

Version 2.3.2

+ +

Summary of main changes

+ +

Bug fixes

+

+Version 2.3.1 changed the way binary and text content types were determined as far as the View Results Tree Listener was concerned: +originally everything except "image/" content types were considered text, but 2.3.1 introduced a check +for specific content types. This has caused problems, +as several popular types were omitted and these were no longer shown by default in the Response tab. +Rather than try to list all the possible text types, JMeter now just checks for the following binary types: +

    +
  • image/*
  • +
  • audio/*
  • +
  • video/*
  • +
+All other types are now assumed to be text. +

+ +

+JMeter 2.3.1 introduced a bug in the Cookie Manager +- if "Clear Cookie each iteration" was selected, all threads would see the same cookies. +This bug has been corrected. +

+ +

Improvements

+ +

+The Proxy server can now record binary requests. +By default the content types +application/x-amf and application/x-java-serialized-object +will be treated as binary and saved in a file. +To change the content types, update the property proxy.binary.types. +

+ +

+The CSV Dataset configuration element has new file sharing options: per thread group, per thread, per identifier. +This allows for more flexible file processing, e.g. each thread can process the same data in the same order. +

+ +

Switch Controller now works properly with functions and variables, +and the condition can now be a name instead of a number. +Simple Controller now works properly under a While Controller

+ +

CSV fields in JTL files can now contain delimiters. +CSV and XML files can now contain additional variables (define the JMeter property sample_variables).

+ +

Response Assertion can now match on substrings (i.e. not regular expression). +Regex extractor can operate on variables.

+ +

+XPath processing is improved; Tidy errors are handled better. +

+ +

Save Table Data buttons added to Summary and Aggregate reports to allow easy saving of the calculated data.

+ +

+HTTP samplers can now save just the MD5 hash of responses, rather than the entire response. +As a special case, if the HTTP Sampler path starts with "http://" or "https://" then this is used as the full URL, +overriding the host and port fields. +The HTTP Samplers can now POST multiple files. +Webservice(SOAP) Sampler can now load local WSDL files using the "file:" protocol. +

+ +

+A simple HTTP Cache Manager has been added. This needs further development. +

+ +

+View Results Tree Listener now uses Tidy to display XML. +This should allow more content to be displayed succesfully. +It also avoids the need to download remote DTD files, which can slow the rendering considerably. +

+ +

+MailReader sampler now supports POP3S and IMAPS protocols. Individual mails are now added as sub-samples. +

+ +

+Various improvements to the BSF Sampler: now supports Jexl, and Javascript bug works properly. +Added BSF PreProcessor, PostProcessor and Assertion test elements. +All now have access to "props" JMeter Properties object. +

+ +

Number of classes loaded in non-GUI mode is much reduced.

+ +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves OK under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +To override the default local language, set the JMeter property "language" before starting JMeter. +

+

Incompatible changes

+
    +
  • +To reduce the number of classes loaded in non-GUI mode, +Functions will only be found if their classname contains the string +'.functions.' and does not contain the string '.gui.'. +All existing JMeter functions conform to this restriction. +To revert to earlier behaviour, comment or change the properties classfinder.functions.* in jmeter.properties. +
  • +
  • The reference value parameter for intSum() is now optional. +As a consequence, if a variable name is used, it must not be a valid integer.
  • +
  • The supplied TCPClient implementation no longer treats tcp.eolByte=0 as special. +To skip EOL checking, set tcp.eolByte=1000 (or some other value which is not a valid byte) +
  • +
  • +Leading and trailing spaces are trimmed from variable names in function calls. +For example, ${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY '. +
  • +
  • +Synchronization has been removed from the RunningSample class (it was not fully threadsafe anyway). +Developers of 3rd party add-ons that use the class may need to synchronize access. +
  • +
+ +

Bug fixes

+
    +
  • Check that the CSV delimiter is reasonable.
  • +
  • Fix Switch Controller to work properly with functions and variables
  • +
  • + Bug + 44011 - application/soap+xml not treated as a text type
  • +
  • + Bug + 43427 - Simple Controller is only partly executed in While loop
  • +
  • + Bug + 33954 - Stack Overflow in If/While controllers (may have been fixed previously)
  • +
  • + Bug + 44022 - Memory Leak when closing test plan
  • +
  • + Bug + 44042 - Regression in Cookie Manager (Bug introduced in 2.3.1)
  • +
  • + Bug + 41028 - JMeter server doesn't alert the user when the host is defined as a loopback address
  • +
  • + Bug + 44142 - Function __machineName causes NPE if parameters are omitted.
  • +
  • + Bug + 44144 - JMS point-to-point: request response test does not work
  • +
  • + Bug + 44314 - Not possible to add more than one SyncTimer
  • +
  • Capture Tidy console error output and log it
  • +
  • Fix problems using Tidy(tolerant parser) in XPath Assertion and XPath Extractor
  • +
  • + Bug + 44374 - improve timer calculation
  • +
  • Regular Expression Extractor now deletes all stale variables from previous matches.
  • +
  • + Bug + 44707 - Running remote test changes internal test plan
  • +
  • + Bug + 44625 - Cannot have two or more FTP samplers with different "put" and "get" actions
  • +
  • + Bug + 40850 - BeanShell memory leak
  • +
  • Ensure ResponseCode and ResponseMessage are set for successful JDBC samples
  • +
  • FTPSampler now detects and reports failure to open the remote file
  • +
  • Class directories defined in search_paths and user.classpath no longer need trailing "/"
  • +
  • + Bug + 44852 SOAP/ XML-RPC Request does not show Request details in View Results Tree
  • +
  • WebService(SOAP) Sampler ResponseData now includes the EOLs sent by server
  • +
  • + Bug + 44910 - close previous socket (if any) in TCP Sampler
  • +
  • + Bug + 44912 - Filter not working in Log Parser
  • +
  • The BeanShell and BSF component documentation made some incorrect references to the "SampleResponse" object; +this has been corrected to "SampleResult"
  • +
  • BSF Sampler now works properly with Javascript
  • +
  • Test Action "Stop Test" now works
  • +
  • + Bug + 42833 - Argument class uses LinkedHashMap in getArgumentsAsMap() to preserve ordering
  • +
  • + Bug + 45093 - SizeAssertion did not call getBytes()
  • +
  • + Bug + 45007 - Rewrite Location headers when using Proxy HTTPS spoofing
  • +
  • Use CRLF rather than LF in Proxy when returning headers to the client
  • +
  • + Bug + 45007 - fix content length header if content may have been changed
  • +
+ +

Improvements

+
    +
  • CSV files can now handle fields with embedded delimiters.
  • +
  • longSum() function added
  • +
  • + Bug + 43382 - configure Tidy output (warnings, errors) for XPath Assertion and Post-Processor
  • +
  • + Bug + 43984 - trim spaces from port field
  • +
  • Add optional comment to __log() function
  • +
  • Make Random function variable name optional
  • +
  • Reduce class loading in non-GUI mode by only looking for Functions in class names +that contain '.functions.' and don't contain '.gui.'
  • +
  • + Bug + 43379 - Switch Controller now supports selection by name as well as number
  • +
  • Can specify list of variable names to be written to JTL files (CSV and XML format)
  • +
  • Now checks that the remoteStart options -r and -R are only used with non_GUI -n option
  • +
  • + Bug + 44184 - Allow header to be saved with Aggregate Graph data
  • +
  • Added "Save Table Data" buttons to Aggregate and Summary Reports - save table as CSV format with header
  • +
  • Allow most functions to be used on the Test Plan. +Note __evalVar(), __split() and __regex() cannot be used on the Test Plan.
  • +
  • Allow Global properties to be loaded from a file, e.g. -Gglobal.properties
  • +
  • Add "Substring" option to Response Assertion
  • +
  • + Bug + 44378 - Turkish localisation
  • +
  • Add optional output variable name to Jexl function
  • +
  • Add application/vnd.wap.xhtml+xml as a text type
  • +
  • Add means to override maximum display size in View Results Tree - set the property: view.results.tree.max_size
  • +
  • Use Tidy to display XML in View Results Tree Listener (avoids fetching DTDs)
  • +
  • + Bug + 44487 - German translation
  • +
  • +As a special case, if the HTTP Sampler path starts with "http://" or "https://" then this is used as the full URL. +
  • +
  • + Bug + 44575 - Result Saver can now save only successful results
  • +
  • + Bug + 44650 - CSV Dataset now handles quoted column values
  • +
  • + Bug + 44600 - 1-ms resolution timer when running with Java 1.5+
  • +
  • + Bug + 44632 - Text input enhancement to FTP Sampler
  • +
  • + Bug + 42204 - add thread group name to Aggregate and Summary reports
  • +
  • FTP Sampler sets latency = time to login
  • +
  • FTP Sampler sets a URL if it can
  • +
  • + Bug + 41921 - add option for samplers to store MD5 of response; done for HTTP Samplers.
  • +
  • Regex Function can now also be applied to a variable rather than just the previous sample result.
  • +
  • Remove HTML Parameter Mask,HTTP User Parameter Modifier from menus as they are deprecated
  • +
  • + Bug + 44807 - allow session ids to be terminated by backslash
  • +
  • + Bug + 44784 - allow for broken server returning additional charset
  • +
  • Added TESTSTART.MS property / variable = test start time in milliseconds
  • +
  • Add POP3S and IMAPS protocols to Mail Reader Sampler.
  • +
  • Mail Reader Sampler now creates a sub-sample for each mail.
  • +
  • The supplied TCPClient implementation no longer treats tcp.eolByte=0 as special. +To skip EOL checking, set tcp.eolByte=1000 (or some other value which is not a valid byte) +
  • +
  • JUnit sampler GUI now also finds Test classes defined in user.classpath
  • +
  • +Leading and trailing spaces are trimmed from variable names in function calls. +For example, ${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY ' +
  • +
  • Webservice(SOAP) Sampler can now load local WSDL files using the file: protocol
  • +
  • + Bug + 44872 - Add "All Files" filter to Open File dialogs
  • +
  • Mirror server can now be run independently (mirror-server.cmd and mirror-server.sh)
  • +
  • + Bug + 19128 - Added multiple file POST support to HTTP Samplers
  • +
  • Allow use of special name LAST to mean the last test run; applies to -t, -l, -j flags
  • +
  • + Bug + 44418/42178 - CSV Dataset file handling improvements
  • +
  • Give BeanShell, Javascript and Jexl functions access to JMeter properties via the "props" object
  • +
  • Give BSF Sampler access to JMeter Properties via "props" object
  • +
  • Add Jexl as a supported BSF Sampler language
  • +
  • Give Beanshell test elements access to JMeter Properties via "props" object
  • +
  • Added BSF PreProcessor, PostProcessor and Assertion test elements
  • +
  • All BSF elements now have access to System.out via the variable "OUT"
  • +
  • Summariser updated to handle variable names
  • +
  • Synchronisation added to Summary and Aggregate Report to try to prevent occasional lost samples
  • +
  • + Bug + 44808, + Bug + 39641 - Proxy support for binary requests
  • +
  • + Bug + 28502 - HTTP Resource Cache
  • +
+ +

Non-functional changes

+
    +
  • Better handling of MirrorServer startup problems and improved unit test.
  • +
  • Build process now detects missing 3rd party libraries and reports need for both binary and source archives
  • +
  • Skip BeanShell tests if jar is not present
  • +
  • Update to Xerces 2.9.1, Xalan 2.7.1, Commons IO 1.4, Commons Lang 2.4, Commons-Logging 1.1.1, XStream 1.3, XPP3 1.1.4c
  • +
  • Use properties for log/logn function descriptions
  • +
  • Check that all jmx files in the demos directory can be loaded OK
  • +
  • Update copyright to 2008; use copy tag instead of numeric character in HTML output
  • +
  • Methods called from constructors must not be overridable: make GUI init methods private
  • +
  • Make static variables final if possible
  • +
  • Split changes into current and previous
  • +
+ + + +

Version 2.3.1

+

Summary of changes

+ +
JMeter Proxy
+ +

+The Proxy spoof function was broken in 2.3; it has been fixed. +Spoof now supports an optional parameter to limit spoofing to particular URLs. +This is useful for HTTPS pages that have insecure content - e.g. images/stylesheets may be accessed using HTTP. +Spoofed responses now drop the default port (443) from https links to make them work better. +

+

+Ignored proxy samples are now visible in Listeners - the label is enclosed in [ and ] as an indication. +Proxy documentation has been improved. +

+ +
GUI changes
+ +

The Add menus show element types in the order in which they are processed +- see Test Plan Execution Order. +It is no longer possible to add test elements to inappropriate parts of the tree +- e.g. samplers cannot be added directly under a test plan. +This also applies to Paste and drag and drop. +

+ +

+The File menu now supports a "Revert" option, which reloads the current file. +Also the last few file names used are remembered for easy reloading. +

+ +

+The Options Menu now supports Collapse All and Expand All items to collapse and expand the test tree. +

+ +
Remote testing
+ +

+The JMeter server now starts the RMI server directly (by default). +This simplifies testing, and means that the RMI server will be stopped when the server stops. +

+ +

+Functions can now be used in Listener filenames (variables do not work). +

+ +

+Command-line option -G can now be used to define properties for remote servers. +Option -X can be used to stop a remote server after a non-GUI run. +Server can be set to automatically exit after a single test (set property server.exitaftertest=true). +

+ +
Other enhancements
+ +

+JMeter startup no longer loads as many classes; this should reduce memory requirements. +

+ +

+Parameter and file support added to all BeanShell elements. +Javascript function now supports access to JMeter objects; +Jexl function always did have access, but the documentation has now been included. +New functions __eval() and __evalVar() for evaluating variables. +

+ +

+CSV files with the correct header column names are now automatically recognised when loaded. +There is no need to configure the properties. +

+ +

+The hostname can now be saved in CSV and XML output files. +New "Successes only" option added when saving result files. +Errors / Successes only option is now supported when loading XML and CSV files. +

+ +

+General documentation improvements. +

+ +
HTTP
+ +

PUT and DELETE should now work properly. +Cookie Manager no longer clears manually entered cookies. +

+

Now handles the META tag http-equiv charset

+ +
JDBC
+ +

JDBC Sampler now allows INOUT and OUT parameters for Called procedures. +JDBC Sampler now allows per-thread connections - set Max Connections = 0 in JDBC Config. +

+ +
+ + +

Incompatible changes

+
    +
  • JMeter server now creates the RMI registry by default. +If the RMI registry has already been started externally, this will generate a warning message, but the server will continue. +This should not affect JMeter testing. +However, if you are also using the RMI registry for other applications there may be problems. +For example, when the JMeter server shuts down it will stop the RMI registry. +Also user-written command files may need to be adjusted (the ones supplied with JMeter have been updated). +To revert to the earlier behaviour, define the JMeter property: server.rmi.create=false. +
  • +
  • The Proxy server removes If-Modified-Since and If-None-Match headers from generated Header Managers. +To revert to the previous behaviour, define the property proxy.headers.remove with no value
  • +
+ +

Bug fixes

+
    +
  • + Bug + 43430 - Count of active threads is incorrect for remote samples
  • +
  • Throughput Controller was not working for "all thread" counts
  • +
  • If a POST body is built from parameter values only, these are now encoded if the checkbox is set.
  • +
  • + Bug + 43584 - Assertion Failure Message contains a comma that is also used as the delimiter for CSV files
  • +
  • HTTP Mirror Server now always returns the exact same content, it used to return incorrect data if UTF-8 encoding was used for HTTP POST body, for example
  • +
  • + Bug + 43612 - HTTP PUT does not honor request parameters
  • +
  • + Bug + 43694 - ForEach Controller (empty collection processing error)
  • +
  • + Bug + 42012 - Variable Listener filenames do not get processed in remote tests. +Filenames can now include function references; variable references do not work.
  • +
  • Ensure Listener nodes get own save configuration when copy-pasted
  • +
  • Correct Proxy Server include and exclude matching description - port and query are included, contrary to previously documented.
  • +
  • Aggregate Graph and Aggregate Report Column Header is KB/Sec; fixed the values to be KB rather than bytes
  • +
  • +Fix SamplingStatCalculator so it no longer adds elapsed time to endTime, as this is handled by SampleResult. +This corrects discrepancies between Summary Report and Aggregate Report throughput calculation. +
  • +
  • Default HTTPSampleResult to ISO-8859-1 encoding
  • +
  • Fix default encoding for blank encoding
  • +
  • Fix Https spoofing (port problem) which was broken in 2.3
  • +
  • Fix HTTP (Java) sampler so http.java.sampler.retries means retries, i.e. does not include initial try
  • +
  • Fix SampleResult dataType checking to better detect TEXT documents
  • +
+ +

Improvements

+
    +
  • Add run_gui Ant target, to package and then start the JMeter GUI from Ant
  • +
  • Add File->Revert to easily drop the current changes and reload the project file currently loaded
  • +
  • + Bug + 31366 - Remember recently opened file(s)
  • +
  • + Bug + 43351 - Add support for Parameters and script file to all BeanShell test elements
  • +
  • SaveService no longer needs to instantiate classes
  • +
  • New functions: __eval() and __evalVar()
  • +
  • Menu items now appear in execution order
  • +
  • Test Plan items can now only be dropped/pasted/merged into parts of the tree where they are allowed
  • +
  • Property Display to show the value of System and JMeter properties and allow them to be changed
  • +
  • + Bug + 43451 - Allow Regex Extractor to operate on Response Code/Message
  • +
  • JDBC Sampler now allows INOUT and OUT parameters for Called procedures
  • +
  • JDBC Sampler now allows per-thread connections
  • +
  • Cookie Manager not longer clears cookies defined in the GUI
  • +
  • HTTP Parameters without names are ignored (except for POST requests with no file)
  • +
  • "Save Selection As" added to main menu; now checks only item is selected
  • +
  • Test Plan now has Paste menu item (paste was already supported via ^V)
  • +
  • If the default delimiter does not work when loading a CSV file, guess the delimiter by analysing the header line.
  • +
  • Add optional "loopback" protocol for HttpClient sampler
  • +
  • HTTP Mirror Server now supports blocking waiting for more data to appear, if content-length header is present in request
  • +
  • HTTP Mirror Server GUI now has the Start and Stop buttons in a more visible place
  • +
  • Server mode now creates the RMI registry; to disable set the JMeter property server.rmi.create=false
  • +
  • HTTP Sampler now supports using MIME Type field to specify content-type request header when body is constructed from parameter values
  • +
  • Enable exit after a single server test - define JMeter property server.exitaftertest=true
  • +
  • Added -G option to set properties in remote servers
  • +
  • Added -X option to stop remote servers after non-GUI run
  • +
  • + Bug + 43485 - Ability to specify keep-alive on SOAP/XML-RPC request
  • +
  • + Bug + 43678 - Handle META tag http-equiv charset
  • +
  • + Bug + 42555 - [I18N] Proposed corrections for the french translation
  • +
  • + Bug + 43727 - Test Action does not support variables or functions
  • +
  • The Proxy server removes If-Modified-Since and If-None-Match headers from generated Header Managers by default. +To change the list of removed headers, define the property proxy.headers.remove as a comma-separated list of headers to remove
  • +
  • The javaScript function now has access to JMeter variables and context etc. See JavaScript function
  • +
  • Use drop-down list for BSF Sampler language field
  • +
  • Add hostname to items that can be saved in CSV and XML output files.
  • +
  • Errors only flag is now supported when loading XML and CSV files
  • +
  • Ensure ResultCollector uses SaveService encoding
  • +
  • Proxy now rejects attempts to use it with https
  • +
  • Proxy spoofing can now use RE matching to determine which urls to spoof (useful if images are not https)
  • +
  • Proxy spoofing now drops the default HTTPS port (443) when converting https: links to http:
  • +
  • Add Successes Only logging and display
  • +
  • The JMeter log file name is formatted as a SimpleDateFormat (applied to the current date) if it contains paired single-quotes, .e.g. 'jmeter_'yyyyMMddHHmmss'.log'
  • +
  • Added Collapse All and Expand All Option menu items
  • +
  • Allow optional definition of extra content-types that are viewable as text
  • +
+ +

Non-functional Improvements

+
    +
  • Functor code tightened up; Functor can now be used with interfaces, as well as pre-defined targets and parameters.
  • +
  • Save graphics function now prompts before overwriting an existing file
  • +
  • Debug Sampler and Debug PostProcessor added.
  • +
  • Fixed up method names in Calculator and SamplingStatCalculator
  • +
  • Tidied up Listener documentation.
  • +
+ + + +

Version 2.3

+ +

Fixes since 2.3RC4

+ +

Bug fixes

+
    +
  • Fix NPE in SampleResultConverter - XStream PrettyPrintWriter cannot handle nulls
  • +
  • If Java HTTP sampler sees null ResponseMessage, replace with HTTP header
  • +
  • + Bug + 43332 - 2.3RC4 does not clear Guis based on TestBean
  • +
  • + Bug + 42948 - Problems with Proxy gui table fields in Java 1.6
  • +
  • Fixup broken jmeter-server script
  • +
  • + Bug + 43364 - option to revert If Controller to pre 2.3RC3 behaviour
  • +
  • + Bug + 43449 - Statistical Remote mode does not handle Latency
  • +
  • + Bug + 43450 (partial fix) - Allow SampleCount and ErrorCount to be saved to/restored from files
  • +
+ +

Improvements

+
    +
  • Add nameSpace option to XPath extractor
  • +
  • Add NULL parameter option to JDBC sampler
  • +
  • Add documentation links for Rhino and BeanShell to functions; clarify variables and properties
  • +
  • Ensure uncaught exceptions are logged
  • +
  • Look for user.properties and system.properties in JMeter bin directory if not found locally
  • +
+ +

Fixes since 2.3RC3

+
    +
  • Fixed NPE in Summariser (bug introduced in 2.3RC3)
  • +
  • Fixed setup of proxy port (bug introduced in 2.3RC3)
  • +
  • Fixed errors when running non-GUI on a headless host (bug introduced in 2.3RC3)
  • +
  • + Bug + 43054 - SSLManager causes stress tests to saturate and crash (bug introduced in 2.3RC3)
  • +
  • Clarified HTTP Request Defaults usage of the port field
  • +
  • + Bug + 43006 - NPE if icon.properties file not found
  • +
  • + Bug + 42918 - Size Assertion now treats an empty response as having zero length
  • +
  • + Bug + 43007 - Test ends before all threadgroups started
  • +
  • Fix possible NPE in HTTPSampler2 if 302 does not have Location header.
  • +
  • + Bug + 42919 - Failure Message blank in CSV output [now records first non-blank message]
  • +
  • Add link to Extending JMeter PDF
  • +
  • Allow for quoted charset in Content-Type parsing
  • +
  • + Bug + 39792 - ClientJMeter synchronisation needed
  • +
  • + Bug + 43122 - GUI changes not always picked up when short-cut keys used (bug introduced in 2.3RC3)
  • +
  • + Bug + 42947 - TestBeanGUI changes not picked up when short-cut keys used
  • +
  • Added serializer.jar (needed for update to xalan 2.7.0)
  • +
  • + Bug + 38687 - Module controller does not work in non-GUI mode
  • +
+ +

Improvements since 2.3RC3

+
    +
  • Add stop thread option to CSV Dataset
  • +
  • Updated commons-httpclient to 3.1
  • +
  • + Bug + 28715 - allow variable cookie values (set CookieManager.allow_variable_cookies=false to disable)
  • +
  • + Bug + 40873 - add JMS point-to-point non-persistent delivery option
  • +
  • + Bug + 43283 - Save action adds .jmx if not present; checks for existing file on Save As
  • +
  • Control+A key does not work for Save All As; changed to Control+Shift+S
  • +
  • + Bug + 40991 - Allow Assertions to check Headers
  • +
+ +

Version 2.3RC3

+ +

Known problems/restrictions:

+

+The JMeter remote server does not support multiple concurrent tests - each remote test should be run in a separate server. +Otherwise tests may fail with random Exceptions, e.g. ConcurrentModification Exception in StandardJMeterEngine. +See + Bug + 43168. +

+

+The default HTTP Request (not HTTPClient) sampler may not work for HTTPS connections via a proxy. +This appears to be due to a Java bug, see + Bug + 39337. +To avoid the problem, try a more recent version of Java, or switch to the HTTPClient version of the HTTP Request sampler. +

+

Transaction Controller parent mode does not support nested Transaction Controllers. +Doing so may cause a Null Pointer Exception in TestCompiler. +

+

Thread active counts are always zero in CSV and XML files when running remote tests. +

+

The property file_format.testlog=2.1 is treated the same as 2.2. +However JMeter does honour the 3 testplan versions.

+

+ + Bug + 22510 - JMeter always uses the first entry in the keystore. +

+

+Remote mode does not work if JMeter is installed in a directory where the path name contains spaces. +

+

+BeanShell test elements leak memory. +This can be reduced by using a file instead of including the script in the test element. +

+

+Variables and functions do not work in Listeners in client-server (remote) mode so they cannot be used +to name log files in client-server mode. +

+

+CSV Dataset variables are defined after configuration processing is completed, +so they cannot be used for other configuration items such as JDBC Config. +(see + Bug + 40394) +

+ +

Summary of changes (for more details, see below)

+

+Some of the main enhancements are: +

+
    +
  • Htmlparser 2.0 now used for parsing
  • +
  • HTTP Authorisation now supports domain and realm
  • +
  • HttpClient options can be specified via httpclient.parameters file
  • +
  • HttpClient now behaves the same as Java Http for SSL certificates
  • +
  • HTTP Mirror Server to allow local testing of HTTP samplers
  • +
  • HTTP Proxy supports XML-RPC recording, and other proxy improvements
  • +
  • __V() function allows support of nested variable references
  • +
  • LDAP Ext sampler optionally parses result sets and supports secure mode
  • +
  • FTP Sampler supports Ascii/Binary mode and upload
  • +
  • Transaction Controller now optionally generates a Sample with subresults
  • +
  • HTTPS session contexts are now per-thread, rather than shared. This gives better emulation of multiple users
  • +
  • BeanShell elements now support ThreadListener and TestListener interfaces
  • +
  • Coloured icons in Tree View Listener and elsewhere to better differentiate failed samples.
  • +
+

+The main bug fixes are: +

+
    +
  • HTTPS (SSL) handling now much improved
  • +
  • Various Remote mode bugs fixed
  • +
  • Control+C and Control+V now work in the test tree
  • +
  • Latency and Encoding now available in CSV log output
  • +
  • Test elements no longer default to previous contents; test elements no longer cleared when changing language.
  • +
+ +

Incompatible changes (usage):

+

+N.B. The javax.net.ssl properties have been moved from jmeter.properties to system.properties, +and will no longer work if defined in jmeter.properties. +
+The new arrangement is more flexible, as it allows arbitrary system properties to be defined. +

+

+SSL session contexts are now created per-thread, rather than being shared. +This generates a more realistic load for HTTPS tests. +The change is likely to slow down tests with many SSL threads. +The original behaviour can be enabled by setting the JMeter property: +

+https.sessioncontext.shared=true
+
+

+

+The LDAP Extended Sampler now uses the same panel for both Thread Bind and Single-Bind tests. +This means that any tests using the Single-bind test will need to be updated to set the username and password. +

+

+ + Bug + 41140: JMeterThread behaviour was changed so that PostProcessors are run in forward order +(as they appear in the test plan) rather than reverse order as previously. +The original behaviour can be restored by setting the following JMeter property: +
+jmeterthread.reversePostProcessors=true +

+

+The HTTP Authorisation Manager now has extra columns for domain and realm, +so the temporary work-round of using '\' and '@' in the username to delimit the domain and realm +has been removed. +

+

+Control-Z no longer used for Remote Start All - this now uses Control+Shift+R +

+

+HttpClient now uses pre-emptive authentication. +This can be changed by setting the following: +

+jmeter.properties:
+httpclient.parameters.file=httpclient.parameters
+
+httpclient.parameters:
+http.authentication.preemptive$Boolean=false
+
+

+ +

+The port field in HTTP Request Defaults is no longer ignored for https samplers if it is set to 80. +

+ +

Incompatible changes (development):

+

+N.B.The clear() method was defined in the following interfaces: Clearable, JMeterGUIComponent and TestElement. +The methods serve different purposes, so two of them were renamed: +the Clearable method is now clearData() and the JMeterGUIComponent method is now clearGui(). +3rd party add-ons may need to be rebuilt. +

+

+Calulator and SamplingStatCalculator classes no longer provide any formatting of their data. +Formatting should now be done using the jorphan.gui Renderer classes. +

+

+Removed deprecated method JMeterUtils.split() - use JOrphanUtils version instead. +

+

+Removed method saveUsingJPEGEncoder() from SaveGraphicsService. +It was unused so far, and used the only Sun-specific class in JMeter. +

+ + +

New functionality/improvements:

+
    +
  • Add Domain and Realm support to HTTP Authorisation Manager
  • +
  • HttpClient now behaves the same as the JDK http sampler for invalid certificates etc
  • +
  • Added httpclient.parameters.file to allow HttpClient parameters to be defined
  • +
  • + Bug + 33964 - Http Requests can send a file as the entire post body if name/type are omitted
  • +
  • + Bug + 41705 - add content-encoding option to HTTP samplers for POST requests
  • +
  • + Bug + 40933, + Bug + 40945 - optional RE matching when retrieving embedded resource URLs
  • +
  • + Bug + 27780 - (patch 19936) create multipart/form-data HTTP request without uploading file
  • +
  • + Bug + 42098 - Use specified encoding for parameter values in HTTP GET
  • +
  • + Bug + 42506 - JMeter threads now use independent SSL sessions
  • +
  • + Bug + 41707 - HTTP Proxy XML-RPC support
  • +
  • + Bug + 41880 - Add content-type filtering to HTTP Proxy Server
  • +
  • + Bug + 41876 - Add more options to control what the HTTP Proxy generates
  • +
  • + Bug + 42158 - Improve support for multipart/form-data requests in HTTP Proxy server
  • +
  • + Bug + 42173 - Let HTTP Proxy handle encoding of request, and undecode parameter values
  • +
  • + Bug + 42674 - default to pre-emptive HTTP authorisation if not specified
  • +
  • Support "file" protocol in HTTP Samplers
  • +
  • Http Autoredirects are now enabled by default when creating new samplers
  • + +
  • + Bug + 40103 - various LDAP enhancements
  • +
  • + Bug + 40369 - LDAP: Stable search results in sampler
  • +
  • + Bug + 40381 - LDAP: more descriptive strings
  • + +
  • BeanShell Post-Processor no longer ignores samples with zero-length result data
  • +
  • Added beanshell.init.file property to run a BeanShell script at startup
  • +
  • + Bug + 39864 - BeanShell init files now found from currrent or bin directory
  • +
  • BeanShell elements now support ThreadListener and TestListener interfaces
  • +
  • BSF Sampler passes additional variables to the script
  • + +
  • Added timeout for WebService (SOAP) Sampler
  • + +
  • + Bug + 40825 - Add JDBC prepared statement support
  • +
  • Extend JDBC Sampler: Commit, Rollback, AutoCommit
  • + +
  • + Bug + 41457 - Add TCP Sampler option to not re-use connections
  • + +
  • + Bug + 41522 - Use JUnit sampler name in sample results
  • + +
  • + Bug + 42223 - FTP Sampler can now upload files
  • + +
  • + Bug + 40804 - Change Counter default to max = Long.MAX_VALUE
  • + +
  • Use property jmeter.home (if present) to override user.dir when starting JMeter
  • +
  • New -j option to easily change jmeter log file
  • + +
  • HTTP Mirror Server Workbench element
  • + +
  • + Bug + 41253 - extend XPathExtractor to work with non-NodeList XPath expressions
  • +
  • + Bug + 42088 - Add XPath Assertion for booleans
  • + +
  • Added __V variable function to resolve nested variable names
  • + +
  • + Bug + 40369 - Equals Response Assertion
  • +
  • + Bug + 41704 - Allow charset encoding to be specified for CSV DataSet
  • +
  • + Bug + 41259 - Comment field added to all test elements
  • +
  • Add standard deviation to Summary Report
  • +
  • + Bug + 41873 - Add name to AssertionResult and display AssertionResult in ViewResultsFullVisualizer
  • +
  • + Bug + 36755 - Save XML test files with UTF-8 encoding
  • +
  • Use ISO date-time format for Tree View Listener (previously the year was not shown)
  • +
  • Improve loading of CSV files: if possible, use header to determine format; guess timestamp format if not milliseconds
  • +
  • + Bug + 41913 - TransactionController now creates samples as sub-samples of the transaction
  • +
  • + Bug + 42582 - JSON pretty printing in Tree View Listener
  • +
  • + Bug + 40099 - Enable use of object variable in ForEachController
  • + +
  • + Bug + 39693 - View Result Table uses icon instead of check box
  • +
  • + Bug + 39717 - use icons in the results tree
  • +
  • + Bug + 42247 - improve HCI
  • +
  • Allow user to cancel out of Close dialogue
  • +
+ +

Non-functional improvements:

+
    +
  • Functor calls can now be unit tested
  • +
  • Replace com.sun.net classes with javax.net
  • +
  • Extract external jar definitions into build.properties file
  • +
  • Use specific jar names in build classpaths so errors are detected sooner
  • +
  • Tidied up ORO calls; now only one cache, size given by oro.patterncache.size, default 1000
  • +
  • + Bug + 42326 - Order of elements in .jmx files changes
  • +
+ +

External jar updates:

+
    +
  • Htmlparser 2.0-20060923
  • +
  • xstream 1.2.1/xpp3_min-1.1.3.4.O
  • +
  • Batik 1.6
  • +
  • BSF 2.4.0
  • +
  • commons-collections 3.2
  • +
  • commons-httpclient-3.1-rc1
  • +
  • commons-jexl 1.1
  • +
  • commons-lang-2.3 (added)
  • +
  • JUnit 3.8.2
  • +
  • velocity 1.5
  • +
  • commons-io 1.3.1 (added)
  • +
+ +

Bug fixes:

+
    +
  • + Bug + 39773 - NTLM now needs local host name - fix other call
  • +
  • + Bug + 40438 - setting "httpclient.localaddress" has no effect
  • +
  • + Bug + 40419 - Chinese messages translation fix
  • +
  • + Bug + 39861 - fix typo
  • +
  • + Bug + 40562 - redirects no longer invoke RE post processors
  • +
  • + Bug + 40451 - set label if not set by sampler
  • +
  • Fix NPE in CounterConfig.java in Remote mode
  • +
  • + Bug + 40791 - Calculator used by Summary Report
  • +
  • + Bug + 40772 - correctly parse missing fields in CSV log files
  • +
  • + Bug + 40773 - XML log file timestamp not parsed correctly
  • +
  • + Bug + 41029 - JMeter -t fails to close input JMX file
  • +
  • + Bug + 40954 - Statistical mode in distributed testing shows wrong results
  • +
  • Fix ClassCast Exception when using sampler that returns null, e..g TestAction
  • +
  • + Bug + 41140 - Post-processors are run in reverse order
  • +
  • + Bug + 41277 - add Latency and Encoding to CSV output
  • +
  • + Bug + 41414 - Mac OS X may add extra item to -jar classpath
  • +
  • Fix NPE when saving thread counts in remote testing
  • +
  • + Bug + 34261 - NPE in HtmlParser (allow for missing attributes)
  • +
  • + Bug + 40100 - check FileServer type before calling close
  • +
  • + Bug + 39887 - jmeter.util.SSLManager: Couldn't load keystore error message
  • +
  • + Bug + 41543 - exception when webserver returns "500 Internal Server Error" and content-length is 0
  • +
  • + Bug + 41416 - don't use chunked input for text-box input in SOAP-RPC sampler
  • +
  • + Bug + 39827 - SOAP Sampler content length for files
  • +
  • Fix Class cast exception in Clear.java
  • +
  • + Bug + 40383 - don't set content-type if already set
  • +
  • Mailer Visualiser test button now works if test plan has not yet been saved
  • +
  • + Bug + 36959 - Shortcuts "ctrl c" and "ctrl v" don't work on the tree elements
  • +
  • + Bug + 40696 - retrieve embedded resources from STYLE URL() attributes
  • +
  • + Bug + 41568 - Problem when running tests remotely when using a 'Counter'
  • +
  • Fixed various classes that assumed timestamps were always end time stamps: +
      +
    • SamplingStatCalculator
    • +
    • JTLData
    • +
    • RunningSample
    • +
    +
  • +
  • + Bug + 40325 - allow specification of proxyuser and proxypassword for WebServiceSampler
  • +
  • Change HttpClient proxy definition to use NTCredentials; added http.proxyDomain property for this
  • +
  • + Bug + 40371 - response assertion "pattern to test" scrollbar problem
  • +
  • + Bug + 40589 - Unescape XML entities in embedded URLs
  • +
  • + Bug + 41902 - NPE in HTTPSampler when responseCode = -1
  • +
  • + Bug + 41903 - ViewResultsFullVisualizer : status column looks bad when you do copy and paste
  • +
  • + Bug + 41837 - Parameter value corruption in proxy
  • +
  • + Bug + 41905 - Can't cut/paste/select Header Manager fields in Java 1.6
  • +
  • + Bug + 41928 - Make all request headers sent by HTTP Request sampler appear in sample result
  • +
  • + Bug + 41944 - Subresults not handled recursively by ResultSaver
  • +
  • + Bug + 42022 - HTTPSampler does not allow multiple headers of same name
  • +
  • + Bug + 42019 - Content type not stored in redirected HTTP request with subresults
  • +
  • + Bug + 42057 - connection can be null if method is null
  • +
  • + Bug + 41518 - JMeter changes the HTTP header Content Type for POST request
  • +
  • + Bug + 42156 - HTTPRequest HTTPClient incorrectly urlencodes parameter value in POST
  • +
  • + Bug + 42184 - Number of bytes for subsamples not added to sample when sub samples are added
  • +
  • + Bug + 42185 - If a HTTP Sampler follows a redirect, and is set up to download images, then images are downloaded multiple times
  • +
  • + Bug + 39808 - Invalid redirect causes incorrect sample time
  • +
  • + Bug + 42267 - Concurrent GUI update failure in Proxy Recording
  • +
  • + Bug + 30120 - Name of simple controller is resetted if a new simple controller is added as child
  • +
  • + Bug + 41078 - merge results in name change of test plan
  • +
  • + Bug + 40077 - Creating new Elements copies values from Existing elements
  • +
  • + Bug + 42325 - Implement the "clear" method for the LogicControllers
  • +
  • + Bug + 25441 - TestPlan changes sometimes detected incorrectly (isDirty)
  • +
  • + Bug + 39734 - Listeners shared after copy/paste operation
  • +
  • + Bug + 40851 - Loop controller with 0 iterations, stops evaluating the iterations field
  • +
  • + Bug + 24684 - remote startup problems if spaces in the path of the jmeter
  • +
  • Use Listener configuration when loading CSV data files
  • +
  • Function methods setParameters() need to be synchronized
  • +
  • Fix CLI long optional argument to require "=" (as for short options)
  • +
  • Fix SlowSocket to work properly with Httpclient (both http and https)
  • +
  • + Bug + 41612 - Loop nested in If Controller behaves erratically
  • +
  • + Bug + 42232 - changing language clears UDV contents
  • +
  • Jexl function did not allow variables
  • +
+ +

Version 2.2

+ +

Incompatible changes:

+

+The time stamp is now set to the sampler start time (it was the end). +To revert to the previous behaviour, change the property sampleresult.timestamp.start to false (or comment it) +

+

The JMX output format has been simplified and files are not backwards compatible

+

+The JMeter.BAT file no longer changes directory to JMeter home, but runs from the current working directory. +The jmeter-n.bat and jmeter-t.bat files change to the directory containing the input file. +

+

+Listeners are now started slightly later in order to allow variable names to be used. +This may cause some problems; if so define the following in jmeter.properties: +
+jmeterengine.startlistenerslater=false +

+ +

+The GUI now expands the tree by default when loading a test plan. +This can be disabled by setting the JMeter property onload.expandtree=false +

+ +

Known problems:

+
    +
  • Post-processors run in reverse order (see + Bug + 41140)
  • +
  • Module Controller does not work in non-GUI mode
  • +
  • Aggregate Report and some other listeners use increasing amounts of memory as a test progresses
  • +
  • Does not always handle non-default encoding properly
  • +
  • Spaces in the installation path cause problems for client-server mode
  • +
  • Change of Language does not propagate to all test elements
  • +
  • SamplingStatCalculator keeps a List of all samples for calculation purposes; +this can cause memory exhaustion in long-running tests
  • +
  • Does not properly handle server certificates if they are expired or not installed locally
  • +
+ +

New functionality:

+
    +
  • Report function
  • +
  • XPath Extractor Post-Processor. Handles single and multiple matches.
  • +
  • Simpler JMX file format (2.2)
  • +
  • BeanshellSampler code can update ResponseData directly
  • +
  • + Bug + 37490 - Allow UDV as delay in Duration Assertion
  • +
  • Slow connection emulation for HttpClient
  • +
  • Enhanced JUnitSampler so that by default assert errors and exceptions are not appended to the error message. +Users must explicitly check append in the sampler
  • +
  • Enhanced the documentation for webservice sampler to explain how it works with CSVDataSet
  • +
  • Enhanced the documentation for javascript function to explain escaping comma
  • +
  • Allow CSV Data Set file names to be absolute
  • +
  • Report Tree compiler errors better
  • +
  • Don't reset Regex Extractor variable if default is empty
  • +
  • includecontroller.prefix property added
  • +
  • Regular Expression Extractor sets group count
  • +
  • Can now save entire screen as an image, not just the right-hand pane
  • +
  • + Bug + 38901 - Add optional SOAPAction header to SOAP Sampler
  • +
  • New BeanShell test elements: Timer, PreProcessor, PostProcessor, Listener
  • +
  • __split() function now clears next variable, so it can be used with ForEach Controller
  • +
  • + Bug + 38682 - add CallableStatement functionality to JDBC Sampler
  • +
  • Make it easier to change the RMI/Server port
  • +
  • Add property jmeter.save.saveservice.xml_pi to provide optional xml processing instruction in JTL files
  • +
  • Add bytes and URL to items that can be saved in sample log files (XML and CSV)
  • +
  • The Post-Processor "Save Responses to a File" now saves the generated file name with the +sample, and the file name can be included in the sample log file. +
  • +
  • Change jmeter.bat DOS script so it works from any directory
  • +
  • New -N option to define nonProxyHosts from command-line
  • +
  • New -S option to define system properties from input file
  • +
  • + Bug + 26136 - allow configuration of local address
  • +
  • Expand tree by default when loading a test plan - can be disabled by setting property onload.expandtree=false
  • +
  • + Bug + 11843 - URL Rewriter can now cache the session id
  • +
  • Counter Pre-Processor now supports formatted numbers
  • +
  • Add support for HEAD PUT OPTIONS TRACE and DELETE methods
  • +
  • Allow default HTTP implementation to be changed
  • +
  • Optionally save active thread counts (group and all) to result files
  • +
  • Variables/functions can now be used in Listener file names
  • +
  • New __time() function; define START.MS/START.YMD/START.HMS properties and variables
  • +
  • Add Thread Name to Tree and Table Views
  • +
  • Add debug functions: What class, debug on, debug off
  • +
  • Non-caching Calculator - used by Table Visualiser to reduce memory footprint
  • +
  • Summary Report - similar to Aggregate Report, but uses less memory
  • +
  • + Bug + 39580 - recycle option for CSV Dataset
  • +
  • + Bug + 37652 - support for Ajp Tomcat protocol
  • +
  • + Bug + 39626 - Loading SOAP/XML-RPC requests from file
  • +
  • + Bug + 39652 - Allow truncation of labels on AxisGraph
  • +
  • Allow use of htmlparser 1.6
  • +
  • + Bug + 39656 - always use SOAP action if it is provided
  • +
  • Automatically include properties from user.properties file
  • +
  • Add __jexl() function - evaluates Commons JEXL expressions
  • +
  • Optionally load JMeter properties from user.properties and system properties from system.properties.
  • +
  • + Bug + 39707 - allow Regex match against URL
  • +
  • Add start time to Table Visualiser
  • +
  • HTTP Samplers can now extract embedded resources for any required media types
  • +
+ +

Bug fixes:

+
    +
  • Fix NPE when no module selected in Module Controller
  • +
  • Fix NPE in XStream when no ResponseData present
  • +
  • Remove ?xml prefix when running with Java 1.5 and no x-jars
  • +
  • + Bug + 37117 - setProperty() function should return ""; added optional return of original setting
  • +
  • Fix CSV output time format
  • +
  • + Bug + 37140 - handle encoding better in RegexFunction
  • +
  • Load all cookies, not just the first; fix class cast exception
  • +
  • Fix default Cookie path name (remove page name)
  • +
  • Fixed resultcode attribute name
  • +
  • + Bug + 36898 - apply encoding to RegexExtractor
  • +
  • Add properties for saving subresults, assertions, latency, samplerData, responseHeaders, requestHeaders & encoding
  • +
  • + Bug + 37705 - Synch Timer now works OK after run is stopped
  • +
  • + Bug + 37716 - Proxy request now handles file Post correctly
  • +
  • HttpClient Sampler now saves latency
  • +
  • Fix NPE when using JavaScript function on Test Plan
  • +
  • Fix Base Href parsing in htmlparser
  • +
  • + Bug + 38256 - handle cookie with no path
  • +
  • + Bug + 38391 - use long when accumulating timer delays
  • +
  • + Bug + 38554 - Random function now uses long numbers
  • +
  • + Bug + 35224 - allow duplicate attributes for LDAP sampler
  • +
  • + Bug + 38693 - Webservice sampler can now use https protocol
  • +
  • + Bug + 38646 - Regex Extractor now clears old variables on match failure
  • +
  • + Bug + 38640 - fix WebService Sampler pooling
  • +
  • + Bug + 38474 - HTML Link Parser doesn't follow frame links
  • +
  • + Bug + 36430 - Counter now uses long rather than int to increase the range
  • +
  • + Bug + 38302 - fix XPath function
  • +
  • + Bug + 38748 - JDBC DataSourceElement fails with remote testing
  • +
  • + Bug + 38902 - sometimes -1 seems to be returned unnecessarily for response code
  • +
  • + Bug + 38840 - make XML Assertion thread-safe
  • +
  • + Bug + 38681 - Include controller now works in non-GUI mode
  • +
  • Add write(OS,IS) implementation to TCPClientImpl
  • +
  • Sample Result converter saves response code as "rc". Previously it saved as "rs" but read with "rc"; it will now also read with "rc". +The XSL stylesheets also now accept either "rc" or "rs"
  • +
  • Fix counter function so each counter instance is independent (previously the per-user counters were shared between instances of the function)
  • +
  • Fix TestBean Examples so that they work
  • +
  • Fix JTidy parser so it does not skip body tags with background images
  • +
  • Fix HtmlParser parser so it catches all background images
  • +
  • + Bug + 39252 set SoapSampler sample result from XML data
  • +
  • + Bug + 38694 - WebServiceSampler not setting data encoding correctly
  • +
  • Result Collector now closes input files read by listeners
  • +
  • + Bug + 25505 - First HTTP sampling fails with "HTTPS hostname wrong: should be 'localhost'"
  • +
  • + Bug + 25236 - remove double scrollbar from Assertion Result Listener
  • +
  • + Bug + 38234 - Graph Listener divide by zero problem
  • +
  • + Bug + 38824 - clarify behaviour of Ignore Status
  • +
  • + Bug + 38250 - jmeter.properties "language" now supports country suffix, for zh_CN and zh_TW etc
  • +
  • jmeter.properties file is now closed after it has been read
  • +
  • + Bug + 39533 - StatCalculator added wrong items
  • +
  • + Bug + 39599 - ConcurrentModificationException
  • +
  • HTTPSampler2 now handles Auto and Follow redirects correctly
  • +
  • + Bug + 29481 - fix reloading sample results so subresults not counted twice
  • +
  • + Bug + 30267 - handle AutoRedirects properly
  • +
  • + Bug + 39677 - allow for space in JMETER_BIN variable
  • +
  • Use Commons HttpClient cookie parsing and management. Fix various problems with cookie handling.
  • +
  • + Bug + 39773 - NTCredentials needs host name
  • +
+ +

Other changes

+
    +
  • Updated to HTTPClient 3.0 (from 2.0)
  • +
  • Updated to Commons Collections 3.1
  • +
  • Improved formatting of Request Data in Tree View
  • +
  • Expanded user documentation
  • +
  • Added MANIFEST, NOTICE and LICENSE to all jars
  • +
  • Extract htmlparser interface into separate jarfile to make it possible to replace the parser
  • +
  • Removed SQL Config GUI as no longer needed (or working!)
  • +
  • HTTPSampler no longer logs a warning for Page not found (404)
  • +
  • StringFromFile now callable as __StringFromFile (as well as _StringFromFile)
  • +
  • Updated to Commons Logging 1.1
  • +
+ + + + +
+

Version 2.1.1

+

New functionality:

+
    +
  • New Include Controller allows a test plan to reference an external jmx file
  • +
  • New JUnitSampler added for using JUnit Test classes
  • +
  • New Aggregate Graph listener is capable of graphing aggregate statistics
  • +
  • Can provide additional classpath entries using the property user.classpath and on the Test Plan element
  • +
+

Bug fixes:

+
    +
  • AccessLog Sampler and JDBC test elements populated correctly from 2.0 test plans
  • +
  • BSF Sampler now populates filename and parameters from saved test plan
  • +
  • + Bug + 36500 - handle missing data more gracefully in WebServiceSampler
  • +
  • + Bug + 35546 - add merge to right-click menu
  • +
  • + Bug + 36642 - Summariser stopped working in 2.1
  • +
  • + Bug + 36618 - CSV header line did not match saved data
  • +
  • JMeter should now run under JVM 1.3 (but does not build with 1.3)
  • +
+ + + + +

Version 2.1

+

New functionality:

+
    +
  • New Test Script file format - smaller, more compact, more readable
  • +
  • New Sample Result file format - smaller, more compact
  • +
  • XSchema Assertion
  • +
  • XML Tree display
  • +
  • CSV DataSet Config item
  • +
  • New JDBC Connection Pool Config Element
  • +
  • Synchronisation Timer
  • +
  • setProperty function
  • +
  • Save response data on error
  • +
  • Ant JMeter XSLT now optionally shows failed responses and has internal links
  • +
  • Allow JavaScript variable name to be omitted
  • +
  • Changed following Samplers to set sample label from sampler name
  • +
  • All Test elements can be saved as a graphics image to a file
  • +
  • + Bug + 35026 - add RE pattern matching to Proxy
  • +
  • + Bug + 34739 - Enhance constant Throughput timer
  • +
  • + Bug + 25052 - use response encoding to create comparison string in Response Assertion
  • +
  • New optional icons
  • +
  • Allow icons to be defined via property files
  • +
  • New stylesheets for 2.1 format XML test output
  • +
  • Save samplers, config element and listeners as PNG
  • +
  • Enhanced support for WSDL processing
  • +
  • New JMS sampler for topic and queue messages
  • +
  • How-to for JMS samplers
  • +
  • + Bug + 35525 - Added Spanish localisation
  • +
  • + Bug + 30379 - allow server.rmi.port to be overridden
  • +
  • enhanced the monitor listener to save the calculated stats
  • +
  • Functions and variables now work at top level of test plan
  • +
+

Bug fixes:

+
    +
  • + Bug + 34586 - XPath always remained as /
  • +
  • BeanShellInterpreter did not handle null objects properly
  • +
  • Fix Chinese resource bundle names
  • +
  • Save field names if required to CSV files
  • +
  • Ensure XML file is closed
  • +
  • Correct icons now displayed for TestBean components
  • +
  • Allow for missing optional jar(s) in creating menus
  • +
  • Changed Samplers to set sample label from sampler name as was the case for HTTP
  • +
  • Fix various samplers to avoid NPEs when incomplete data is provided
  • +
  • Fix Cookie Manager to use seconds; add debug
  • +
  • + Bug + 35067 - set up filename when using -t option
  • +
  • Don't substitute TestElement.* properties by UDVs in Proxy
  • +
  • + Bug + 35065 - don't save old extensions in File Saver
  • +
  • + Bug + 25413 - don't enable Restart button unnecessarily
  • +
  • + Bug + 35059 - Runtime Controller stopped working
  • +
  • Clear up any left-over connections created by LDAP Extended Sampler
  • +
  • + Bug + 23248 - module controller didn't remember stuff between save and reload
  • +
  • Fix Chinese locales
  • +
  • + Bug + 29920 - change default locale if necessary to ensure default properties are picked up when English is selected.
  • +
  • Bug fixes for Tomcat monitor captions
  • +
  • Fixed webservice sampler so it works with user defined variables
  • +
  • Fixed screen borders for LDAP config GUI elements
  • +
  • + Bug + 31184 - make sure encoding is specified in JDBC sampler
  • +
  • TCP sampler - only share sockets with same host:port details; correct the manual
  • +
  • Extract src attribute for embed tags in JTidy and Html Parsers
  • +
+ + + +

Version 2.0.3

+

New functionality:

+
    +
  • XPath Assertion and XPath Function
  • +
  • Switch Controller
  • +
  • ForEach Controller can now loop through sets of groups
  • +
  • Allow CSVRead delimiter to be changed (see jmeter.properties)
  • +
  • + Bug + 33920 - allow additional property files
  • +
  • + Bug + 33845 - allow direct override of Home dir
  • +
+

Bug fixes:

+
    +
  • Regex Extractor nested constant not put in correct place + Bug + 32395
  • +
  • Start time reset to now if necessary so that delay works OK.
  • +
  • Missing start/end times in scheduler are assumed to be now, not 1970
  • +
  • + Bug + 28661 - 304 responses not appearing in listeners
  • +
  • DOS scripts now handle different disks better
  • +
  • + Bug + 32345 - HTTP Rewriter does not work with HTTP Request default
  • +
  • Catch Runtime Exceptions so an error in one Listener does not affect others
  • +
  • + Bug + 33467 - __threadNum() extracted number wrongly
  • +
  • + Bug + 29186,33299 - fix CLI parsing of "-" in second argument
  • +
  • Fix CLI parse bug: -D arg1=arg2. Log more startup parameters.
  • +
  • Fix JTidy and HTMLParser parsers to handle form src= and link rel=stylesheet
  • +
  • JMeterThread now logs Errors to jmeter.log which were appearing on console
  • +
  • Ensure WhileController condition is dynamically checked
  • +
  • + Bug + 32790 ensure If Controller condition is re-evaluated each time
  • +
  • + Bug + 30266 - document how to display proxy recording responses
  • +
  • + Bug + 33921 - merge should not change file name
  • +
  • Close file now gives chance to save changes
  • +
  • + Bug + 33559 - fixes to Runtime Controller
  • +
+

Other changes:

+
    +
  • To help with variable evaluation, JMeterThread sets "sampling started" a bit earlier (see jmeter.properties)
  • +
  • + Bug + 33796 - delete cookies with null/empty values
  • +
  • Better checking of parameter count in JavaScript function
  • +
  • Thread Group now defaults to 1 loop instead of forever
  • +
  • All Beanshell access is now via a single class; only need BSH jar at run-time
  • +
  • + Bug + 32464 - document Direct Draw settings in jmeter.bat
  • +
  • + Bug + 33919 - increase Counter field sizes
  • +
  • + Bug + 32252 - ForEach was not initialising counters
  • +
+ + + +

Version 2.0.2

+

New functionality:

+
    +
  • While Controller
  • +
  • BeanShell intilisation scripts
  • +
  • Result Saver can optionally save failed results only
  • +
  • Display as HTML has option not to download frames and images etc
  • +
  • Multiple Tree elements can now be enabled/disabled/copied/pasted at once
  • +
  • __split() function added
  • +
  • + Bug + 28699 allow Assertion to regard unsuccessful responses - e.g. 404 - as successful
  • +
  • + Bug + 29075 Regex Extractor can now extract data out of http response header as well as the body
  • +
  • __log() functions can now write to stdout and stderr
  • +
  • URL Modifier can now optionally ignore query parameters
  • +
+

Bug fixes:

+
    +
  • If controller now works after the first false condition + Bug + 31390
  • +
  • Regex GUI was losing track of Header/Body checkbox + Bug + 29853
  • +
  • Display as HTML now handles frames and relative images
  • +
  • Right-click open replaced by merge
  • +
  • Fix some drag and drop problems
  • +
  • Fixed foreach demo example so it works
  • +
  • + Bug + 30741 SSL password prompt now works again
  • +
  • StringFromFile now closes files at end of test; start and end now optional as intended
  • +
  • + Bug + 31342 Fixed text of SOAP Sampler headers
  • +
  • Proxy must now be stopped before it can be removed + Bug + 25145
  • +
  • Link Parser now supports BASE href + Bug + 25490
  • +
  • + Bug + 30917 Classfinder ignores duplicate names
  • +
  • + Bug + 22820 Allow Counter value to be cleared
  • +
  • + Bug + 28230 Fix NPE in HTTP Sampler retrieving embedded resources
  • +
  • Improve handling of StopTest; catch and log some more errors
  • +
  • ForEach Controller no longer runs any samples if first variable is not defined
  • +
  • + Bug + 28663 NPE in remote JDBC execution
  • +
  • + Bug + 30110 Deadlock in stopTest processing
  • +
  • + Bug + 31696 Duration not working correctly when using Scheduler
  • +
  • JMeterContext now uses ThreadLocal - should fix some potential NPE errors
  • +
+

Version 2.0.1

+

Bug fix release. TBA.

+

Version 2.0

+
    +
  • HTML parsing improved; now has choice of 3 parsers, and most embedded elements can now be detected and downloaded.
  • +
  • Redirects can now be delegated to URLConnection by defining the JMeter property HTTPSamper.delegateRedirects=true (default is false)
  • +
  • Stop Thread and Stop Test methods added for Samplers and Assertions etc. Samplers can call setStopThread(true) or setStopTest(true) if they detect an error that needs to stop the thread of the test after the sample has been processed
  • +
  • Thread Group Gui now has an extra pane to specify what happens after a Sampler error: Continue (as now), Stop Thread or Stop Test. + This needs to be extended to a lower level at some stage.
  • +
  • Added Shutdown to Run Menu. This is the same as Stop except that it lets the Threads finish normally (i.e. after the next sample has been completed)
  • +
  • Remote samples can be cached until the end of a test by defining the property hold_samples=true when running the server. +More work is needed to be able to control this from the GUI
  • +
  • Proxy server has option to skip recording browser headers
  • +
  • Proxy restart works better (stop waits for daemon to finish)
  • +
  • Scheduler ignores start if it has already passed
  • +
  • Scheduler now has delay function
  • +
  • added Summariser test element (mainly for non-GUI) testing. This prints summary statistics to System.out and/or the log file every so oftem (3 minutes by default). Multiple summarisers can be used; samples are accumulated by summariser name.
  • +
  • Extra Proxy Server options: +Create all samplers with keep-alive disabled +Add Separator markers between sets of samples +Add Response Assertion to first sampler in each set
  • +
  • Test Plan has a comment field
  • + +
  • Help Page can now be pushed to background
  • +
  • Separate Function help page
  • +
  • New / amended functions
  • +
      +
    • __property() and __P() functions
    • +
    • __log() and __logn() - for writing to the log file
    • +
    • _StringFromFile can now process a sequence of files, e.g. dir/file01.txt, dir/file02.txt etc
    • +
    • _StringFromFile() funtion can now use a variable or function for the file name
    • +
    +
  • New / amended Assertions
  • +
      +
    • Response Assertion now works for URLs, and it handles null data better
    • +
    • Response Assertion can now match on Response Code and Response message as well
    • +
    • HTML Assertion using JTidy to check for well-formed HTML
    • +
    +
  • If Controller (not fully functional yet)
  • +
  • Transaction Controller (aggregates the times of its children)
  • +
  • New Samplers
  • +
      +
    • Basic BSF Sampler (optional)
    • +
    • BeanShell Sampler (optional, needs to be downloaded from www.beanshell.org
    • +
    • Basic TCP Sampler
    • +
    +
  • Optionally start BeanShell server (allows remote access to JMeter variables and methods)
  • +
+

Version 1.9.1

+

TBA

+

Version 1.9

+
    +
  • Sample result log files can now be in CSV or XML format
  • +
  • New Event model for notification of iteration events during test plan run
  • +
  • New Javascript function for executing arbitrary javascript statements
  • +
  • Many GUI improvements
  • +
  • New Pre-processors and Post-processors replace Modifiers and Response-Based Modifiers.
  • +
  • Compatible with jdk1.3
  • +
  • JMeter functions are now fully recursive and universal (can use functions as parameters to functions)
  • +
  • Integrated help window now supports hypertext links
  • +
  • New Random Function
  • +
  • New XML Assertion
  • +
  • New LDAP Sampler (alpha code)
  • +
  • New Ant Task to run JMeter (in extras folder)
  • +
  • New Java Sampler test implementation (to assist developers)
  • +
  • More efficient use of memory, faster loading of .jmx files
  • +
  • New SOAP Sampler (alpha code)
  • +
  • New Median calculation in Graph Results visualizer
  • +
  • Default config element added for developer benefit
  • +
  • Various performance enhancements during test run
  • +
  • New Simple File recorder for minimal GUI overhead during test run
  • +
  • New Function: StringFromFile - grabs values from a file
  • +
  • New Function: CSVRead - grabs multiple values from a file
  • +
  • Functions now longer need to be encoded - special values should be escaped +with "\" if they are literal values
  • +
  • New cut/copy/paste functionality
  • +
  • SSL testing should work with less user-fudging, and in non-gui mode
  • +
  • Mailer Model works in non-gui mode
  • +
  • New Througput Controller
  • +
  • New Module Controller
  • +
  • Tests can now be scheduled to run from a certain time till a certain time
  • +
  • Remote JMeter servers can be started from a non-gui client. Also, in gui mode, all remote servers can be started with a single click
  • +
  • ThreadGroups can now be run either serially or in parallel (default)
  • +
  • New command line options to override properties
  • +
  • New Size Assertion
  • + +
+ +

Version 1.8.1

+
    +
  • Bug Fix Release. Many bugs were fixed.
  • +
  • Removed redundant "Root" node from test tree.
  • +
  • Re-introduced Icons in test tree.
  • +
  • Some re-organization of code to improve build process.
  • +
  • View Results Tree has added option to view results as web document (still buggy at this point).
  • +
  • New Total line in Aggregate Listener (still buggy at this point).
  • +
  • Improvements to ability to change JMeter's Locale settings.
  • +
  • Improvements to SSL Manager.
  • +
+ +

Version 1.8

+
    +
  • Improvement to Aggregate report's calculations.
  • +
  • Simplified application logging.
  • +
  • New Duration Assertion.
  • +
  • Fixed and improved Mailer Visualizer.
  • +
  • Improvements to HTTP Sampler's recovery of resources (sockets and file handles).
  • +
  • Improving JMeter's internal handling of test start/stop.
  • +
  • Fixing and adding options to behavior of Interleave and Random Controllers.
  • +
  • New Counter config element.
  • +
  • New User Parameters config element.
  • +
  • Improved performance of file opener.
  • +
  • Functions and other elements can access global variables.
  • +
  • Help system available within JMeter's GUI.
  • +
  • Test Elements can be disabled.
  • +
  • Language/Locale can be changed while running JMeter (mostly).
  • +
  • View Results Tree can be configured to record only errors.
  • +
  • Various bug fixes.
  • +
+ +

Version 1.7.3

+
    +
  • New Functions that provide more ability to change requests dynamically during test runs.
  • +
  • New language translations in Japanese and German.
  • +
  • Removed annoying Log4J error messages.
  • +
  • Improved support for loading JMeter 1.7 version test plan files (.jmx files).
  • +
  • JMeter now supports proxy servers that require username/password authentication.
  • +
  • Dialog box indicating test stopping doesn't hang JMeter on problems with stopping test.
  • +
  • GUI can run multiple remote JMeter servers (fixes GUI bug that prevented this).
  • +
  • Dialog box to help created function calls in GUI.
  • +
  • New Keep-alive switch in HTTP Requests to indicate JMeter should or should not use Keep-Alive for sockets.
  • +
  • HTTP Post requests can have GET style arguments in Path field. Proxy records them correctly now.
  • +
  • New User-defined test-wide static variables.
  • +
  • View Results Tree now displays more information, including name of request (matching the name +in the test tree) and full request and POST data.
  • +
  • Removed obsolete View Results Visualizer (use View Results Tree instead).
  • +
  • Performance enhancements.
  • +
  • Memory use enhancements.
  • +
  • Graph visualizer GUI improvements.
  • +
  • Updates and fixes to Mailer Visualizer.
  • +
+ +

Version 1.7.2

+
    +
  • JMeter now notifies user when test has stopped running.
  • +
  • HTTP Proxy server records HTTP Requests with re-direct turned off.
  • +
  • HTTP Requests can be instructed to either follow redirects or ignore them.
  • +
  • Various GUI improvements.
  • +
  • New Random Controller.
  • +
  • New SOAP/XML-RPC Sampler.
  • +
+ +

Version 1.7.1

+
    +
  • JMeter's architecture revamped for a more complete separation between GUI code and +test engine code.
  • +
  • Use of Avalon code to save test plans to XML as Configuration Objects
  • +
  • All listeners can save data to file and load same data at later date.
  • +
+ +

Version 1.7Beta

+
    +
  • Better XML support for special characters (Tushar Bhatia)
  • +
  • Non-GUI functioning & Non-GUI test plan execution (Tushar Bhatia)
  • +
  • Removing Swing dependence from base JMeter classes
  • +
  • Internationalization (Takashi Okamoto)
  • +
  • AllTests bug fix (neth6@atozasia.com)
  • +
  • ClassFinder bug fix (neth6@atozasia.com)
  • +
  • New Loop Controller
  • +
  • Proxy Server records HTTP samples from browser + (and documented in the user manual)
  • Multipart Form support
  • +
  • HTTP Header class for Header customization
  • +
  • Extracting HTTP Header information from responses (Jamie Davidson)
  • +
  • Mailer Visualizer re-added to JMeter
  • +
  • JMeter now url encodes parameter names and values
  • +
  • listeners no longer give exceptions if their gui's haven't been initialized
  • +
  • HTTPS and Authorization working together
  • +
  • New Http sampling that automatically parses HTML response + for images to download, and includes the downloading of these + images in total time for request (Neth neth6@atozasia.com)
  • +
  • HTTP responses from server can be parsed for links and forms, + and dynamic data can be extracted and added to test samples + at run-time (documented)
  • +
  • New Ramp-up feature (Jonathan O'Keefe)
  • +
  • New visualizers (Neth)
  • +
  • New Assertions for functional testing
  • +
+ +

Version 1.6.1

+
    +
  • Fixed saving and loading of test scripts (no more extra lines)
  • +
  • Can save and load special characters (such as "&" and "<").
  • +
  • Can save and load timers and listeners.
  • +
  • Minor bug fix for cookies (if you cookie value + contained an "=", then it broke).
  • +
  • URL's can sample ports other than 80, and can test HTTPS, + provided you have the necessary jars (JSSE)
  • +
+ +

Version 1.6 Alpha

+
    +
  • New UI
  • +
  • Separation of GUI and Logic code
  • +
  • New Plug-in framework for new modules
  • +
  • Enhanced performance
  • +
  • Layering of test logic for greater flexibility
  • +
  • Added support for saving of test elements
  • +
  • Added support for distributed testing using a single client
  • + +
+

Version 1.5.1

+
    +
  • Fixed bug that caused cookies not to be read if header name case not as expected.
  • +
  • Clone entries before sending to sampler - prevents relocations from messing up + information across threads
  • +
  • Minor bug fix to convenience dialog for adding paramters to test sample. + Bug prevented entries in dialog from appearing in test sample.
  • +
  • Added xerces.jar to distribution
  • +
  • Added junit.jar to distribution and created a few tests.
  • +
  • Started work on new framework. New files in cvs, but do not effect program yet.
  • +
  • Fixed bug that prevent HTTPJMeterThread from delaying according to chosen timer.
  • +
+

+

Version 1.5

+
    +
  • Abstracted out the concept of the Sampler, SamplerController, and TestSample. + A Sampler represents code that understands a protocol (such as HTTP, + or FTP, RMI, SMTP, etc..). It is the code that actually makes the + connection to whatever is being tested. A SamplerController + represents code that understands how to organize and run a group + of test samples. It is what binds together a Sampler and its test + samples and runs them. A TestSample represents code that understands + how to gather information from the user about a particular test. + For a website, it would represent a URL and any information to be sent + with the URL.
  • +
  • The UI has been updated to make entering test samples more convenient.
  • +
  • Thread groups have been added, allowing a user to setup multiple test to run + concurrently, and to allow sharing of test samples between those tests.
  • +
  • It is now possible to save and load test samples.
  • +
  • ....and many more minor changes/improvements...
  • +
+

+

+Apache JMeter 1.4.1-dev +

    +
  • Cleaned up URLSampler code after tons of patches for better readability. (SM)
  • +
  • Made JMeter send a special "user-agent" identifier. (SM)
  • +
  • Fixed problems with redirection not sending cookies and authentication info and removed + a warning with jikes compilation. Thanks to Wesley Tanaka for the patches (SM)
  • +
  • Fixed a bug in the URLSampler that caused to skip one URL when testing lists of URLs and + a problem with Cookie handling. Thanks to Graham Johnson for the patches (SM)
  • +
  • Fixed a problem with POST actions. Thanks to Stephen Schaub for the patch (SM)
  • +
+

+

+ Apache JMeter 1.4 - Jul 11 1999 +

    +
  • Fixed a problem with POST actions. Thanks to Brendan Burns for the patch (SM)
  • +
  • Added close button to the About box for those window managers who don't provide it. + Thanks to Jan-Henrik Haukeland for pointing it out. (SM)
  • +
  • Added the simple Spline sample visualizer (JPN)
  • +

+

Apache JMeter 1.3 - Apr 16 1999 +

    +
  • Run the Garbage Collector and run finalization before starting to sampling to ensure + same state every time (SM)
  • +
  • Fixed some NullPointerExceptions here and there (SM)
  • +
  • Added HTTP authentication capabilities (RL)
  • +
  • Added windowed sample visualizer (SM)
  • +
  • Fixed stupid bug for command line arguments. Thanks to Jorge Bracer for pointing this out (SM)
  • +

+

Apache JMeter 1.2 - Mar 17 1999 +

    +
  • Integrated cookie capabilities with JMeter (SM)
  • +
  • Added the Cookie manager and Netscape file parser (SD)
  • +
  • Fixed compilation error for JDK 1.1 (SD)

+

Apache JMeter 1.1 - Feb 24 1999 +

    +
  • Created the opportunity to create URL aliasing from the properties file as well as the + ability to associate aliases to URL sequences instead of single URLs (SM) Thanks to + Simon Chatfield for the very nice suggestions + and code examples.
  • +
  • Removed the TextVisualizer and replaced it with the much more useful FileVisualizer (SM)
  • +
  • Added the known bug list (SM)
  • +
  • Removed the Java Apache logo (SM)
  • +
  • Fixed a couple of typos (SM)
  • +
  • Added UNIX makefile (SD)

+

Apache JMeter 1.0.1 - Jan 25 1999 +

    +
  • Removed pending issues doc issues (SM)
  • +
  • Fixed the unix script (SM)
  • +
  • Added the possibility of running the JAR directly using "java -jar + ApacheJMeter.jar" with Java 2 (SM)
  • +
  • Some small updates: fixed Swing location after Java 2(tm) release, license update and + small cleanups (SM)
  • +

+

Apache JMeter 1.0 - Dec 15 1998 +

    +
  • Initial version. (SM)
  • +

+
\ No newline at end of file diff --git a/docs/css/new-style.css b/docs/css/new-style.css new file mode 100644 index 00000000000..3b651833b7a --- /dev/null +++ b/docs/css/new-style.css @@ -0,0 +1,398 @@ +/* + 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. +*/ +.main { + font-family: DejaVu Sans, Helvetica, sans-serif; + width: 60em; +} + +img { + border: 0; + width: auto; + max-width: 95%; + height: auto; +} + +.menu, h1, h2, h3, h4, h5, .go-top, .title { + font-family: "Merriweather"; +} + +h1, h2, h3, h4, h5, .title { + border-bottom: 0.2rem solid orange; +} + +.title>.title { + border-bottom: 0px; +} + +.footer { + background-color: #444; + border-top: 1px solid black; + color: white; + margin-top: 3em; + padding: 2em 0 1em; + text-align: center; + box-shadow: 0 -8px 21px 0 rgba(0, 0, 0, 0.2); +} + +.menu { + border: 1px solid lightgray; + box-shadow: 5px 5px 10px rgba(20, 20, 20, 0.3); + list-style: outside none none; + margin: 0.5em; + padding: 0.5em; +} + +.menu+.menu { + margin-top: 1.5em; +} + +.menu img { + margin-left: 0.3em; + vertical-align: middle; + width: auto; + max-width: 95%; + height: auto; +} + +.banner>iframe { + width: 240px; + height: 70px; +} +.banner>iframe>body { + margin: 0px; + padding: 0px; +} +.banner>iframe img { + width: auto; + max-width: 100% + height: auto; +} + +body { + margin: 0px; + padding: 0px; +} + +.section + pre { + background: none repeat scroll 0 0 lightblue; + border: 1px solid gray; + padding: 0.3rem; + margin: 0.3rem; + font-family: dejavu sans mono, monospace, sans-serif; + overflow: auto; +} + +.code { + background: none repeat scroll 0 0 lightblue; + padding: 0.1em; + font-family: dejavu sans mono, monospace, sans-serif; +} + +.required-Yes>span { + font-weight: bold; +} + +.deprecated, .note { + background: none repeat scroll 0 0 #fee; + border: 1px solid #dbb; + margin: 1em; + padding: 1em; +} + +.component { + background: none repeat scroll 0 0 #fff; + margin: 1em; + padding: 0.4em; +} + +.subsection { + background: none repeat scroll 0 0 white; + margin: 1em; + padding: 1em; +} + +.screenshot { + margin: 2em; + padding: 0; +} + +figure { + margin: 1em 2em; +} + +figure>a>img, .screenshot>a>img { + box-shadow: 10px 10px 10px 0 rgba(50, 50, 50, 0.25); + overflow: auto; + width: auto; + max-width: 100%; + height: auto; +} + +.clear { + clear: both; +} + +.nostyle { + border: 1px solid black; +} + +.title { + font-size: 120%; + font-weight: bold; +} + +.example { + background: none repeat scroll 0 0 lightgray; + border: 1px solid gray; + clear: both; + padding: 1em; + margin: 1em; +} + +.property .name, .property + .description, .property + .required { + display: inline-block; +} + +.property .name { + font-style: italic; + vertical-align: top; + width: 20%; + word-wrap: break-word; +} + +.property .description { + vertical-align: top; + width: 60%; +} + +.property .required { + vertical-align: top; + width: 20%; +} + +.property+.property { + margin-top: 0.5em; +} + +.required.req-false { + font-weight: lighter; +} + +.go-top { + margin: 1em 0; + font-size: 120%; +} + +.properties { + background: none repeat scroll 0 0 lightgoldenrodyellow; + border: 1px solid darksalmon; + margin: 1em; + padding: 1em; +} + +.properties .title { + font-size: 100%; +} + +th { + border-bottom: 1px solid black; + font-family: "Merriweather"; + text-align: left; +} + +td { + vertical-align: top; +} + +tr+tr { + margin-top: 0.2em; +} + +table { + border-bottom: 2px solid; + border-top: 2px solid; +} + +.nav { + display: inline-block; + max-width: 20em; + vertical-align: top; + width: 33%; +} + +.main { + display: inline-block; + margin-left: 2em; + max-width: 60em; + width: 60%; +} + +.header { + clear: both; + display: table; + margin-bottom: 1rem; + width: 100%; + box-shadow: 0px 5px 33px 0px rgba(0, 0, 0, 0.2); + padding: 0.1rem 0em 0.2rem; + border-bottom: 1px solid gray; +} + +.header>div { + display: table-cell; + vertical-align: middle; +} + +.header>div+.header>div { + text-align: center; +} + +.sectionlink { + display: none; +} + +:hover>.sectionlink { + display: inline; + color: orange; +} + +.pagelinks { + list-style: none; +} + +.pagelinks li { + display: inline-block; + margin: 1em; +} + +.pagelinks>li { + border: 1px solid #bbb; + box-shadow: 5px 5px 5px rgba(20, 20, 20, 0.2); + padding: 0.5em 1em; +} + +.pagelinks li { + display: inline-block; + margin: 1em; + font-family: "Merriweather"; +} + +.section-index { + font-family: "Merriweather"; + margin:; + list-style: none; +} + +.section-index>li { + margin: 1em; + padding: 1em; + border: 1px solid #bbb; + box-shadow: 5px 5px 5px rgba(20, 20, 20, 0.2); +} + +.hidden { + display: none; +} + +@media screen and (max-width: 900px) { + .nav { + display: block; + width: 95%; + max-width: 95%; + } + .main { + display: block; + width: 95%; + max-width: 95%; + margin-left: 0.5em; + } + .section-index, pagelinks { + padding-left: 0px; + margin-left: 0px; + } + figure { + margin: 1em 0px; + } + .properties { + margin: 1rem 0px; + } + .property .name, .property + .description, .property + .required { + display: block; + width: 100%; + } + .property .required { + border-bottom: 1px solid #ddd; + } + .property .required::before { + content: 'R: '; + } + .property .name::before { + content: 'N: '; + } + .property .description::before { + content: 'D: '; + } + .header { + display: block; + } + .header span { + display: inline; + } + .pagelinks li { + margin: 0.5rem; + } + .header>div { + display: inline-block; + vertical-align: middle; + width: 50% + } + .header>div+.header>div { + text-align: center; + } + .menu ul { + display: none; + } + .menu li:hover>ul { + display: block; + } + .section-index ul { + display: none; + } + .section-index li:hover>ul { + display: block; + } + .subsection { + padding: 0.3rem; + margin: 0.1rem; + } + .header > .twitter { + width: 100%; + text-align: center; + } + .header > .twitter > div { + display: inline; + width: 50%; + } + .header > .banner { + width: 100%; + text-align: center; + } +} diff --git a/docs/css/style.css b/docs/css/style.css new file mode 100644 index 00000000000..7d3a68293b0 --- /dev/null +++ b/docs/css/style.css @@ -0,0 +1,39 @@ +/* + 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. +*/ + +/*Shows the value of the name attribute when hovered*/ +/* Disabled +a[name]:hover:after{ + content: " #" attr(name); + font-size: 90%; + text-decoration: none; +} +*/ + +/* + * Hide class="sectionlink", except when an enclosing heading + * has the :hover property. + * Used to hide the ¶ marker for generating internal links + */ +.sectionlink { + display: none; +} +:hover > .sectionlink { + display: inline; + /* Green so shows up on section headings too */ + color: rgb(0,255,0); +} diff --git a/docs/demos/AssertionTestPlan.jmx b/docs/demos/AssertionTestPlan.jmx new file mode 100644 index 00000000000..0cd3e3b91c2 --- /dev/null +++ b/docs/demos/AssertionTestPlan.jmx @@ -0,0 +1,128 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836421000 + 1211836421000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + GET + true + false + false + false + + + + false + + + + + + </html> + + 2 + Assertion.response_data + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + assertion.dat + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/docs/demos/AuthManagerTestPlan.jmx b/docs/demos/AuthManagerTestPlan.jmx new file mode 100644 index 00000000000..4b792dcd6b3 --- /dev/null +++ b/docs/demos/AuthManagerTestPlan.jmx @@ -0,0 +1,151 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836473000 + 1211836473000 + false + continue + + + + + + + + http://localhost/secret + kevin + spot + + + + + + + + + + + localhost + + http + + / + + + + + + + + + http + + /secret/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /secret/index2.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /index.html + GET + true + false + false + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + auth-manager.dat + + + + + + diff --git a/docs/demos/BeanShellAssertion.bsh b/docs/demos/BeanShellAssertion.bsh new file mode 100644 index 00000000000..72868132900 --- /dev/null +++ b/docs/demos/BeanShellAssertion.bsh @@ -0,0 +1,53 @@ +/* + * 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. + * + */ + +// Sample BeanShell Assertion script +// Derived from http://www.mail-archive.com/jmeter-user@jakarta.apache.org/msg05597.html + +if (ResponseCode != null && ResponseCode.equals ("200") == false ) +{ + // this is standard stuff + Failure=true ; + FailureMessage ="Response code was not a 200 response code it was " + ResponseCode + "." ; + print ( "the return code is " + ResponseCode); // this goes to stdout + log.warn( "the return code is " + ResponseCode); // this goes to the JMeter log file +} else { + try + { + // non standard stuff where BeanShell assertion will be really powerful . + // in my example I just test the size , but you could extend it further + // to actually test the content against another file. + byte [] arr = (byte[]) ResponseData ; + // print ( arr.length ) ; // use this to determine the size + if (arr != null && arr.length != 25218) + { + Failure= true ; + FailureMessage = "The response data size was not as expected" ; + } + else if ( arr == null ) + { + Failure= true ; + FailureMessage = "The response data size was null" ; + } + } + catch ( Throwable t ) + { + print ( t ) ; + log.warn("Error: ",t); + } +} \ No newline at end of file diff --git a/docs/demos/ForEachTest2.jmx b/docs/demos/ForEachTest2.jmx new file mode 100644 index 00000000000..b19c91fe041 --- /dev/null +++ b/docs/demos/ForEachTest2.jmx @@ -0,0 +1,322 @@ + + + + + false + + + + + false + + + + + 1 + false + continue + 1076438592000 + + false + 2 + + 1 + + + 1076438592000 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Sample 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + a b c d + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + (\w)\s + inputVar + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + For 1 ${returnVar} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + ${returnVar1} + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Sample 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + a b c d + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + (\w)\sx + inputVar + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + For 2 ${returnVar} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + ${returnVar} + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/docs/demos/HeaderManagerTestPlan.jmx b/docs/demos/HeaderManagerTestPlan.jmx new file mode 100644 index 00000000000..71621ee7800 --- /dev/null +++ b/docs/demos/HeaderManagerTestPlan.jmx @@ -0,0 +1,95 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836504000 + 1211836504000 + false + continue + + + + + + + + User-Agent + Mozilla/4.0 (compatible; MSIE 5.5; Windows 98) + + + + + + + + + jakarta.apache.org + + http + + / + GET + true + false + false + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + header-manager.dat + + + + + + diff --git a/docs/demos/InterleaveTestPlan.jmx b/docs/demos/InterleaveTestPlan.jmx new file mode 100644 index 00000000000..1429f0e0498 --- /dev/null +++ b/docs/demos/InterleaveTestPlan.jmx @@ -0,0 +1,160 @@ + + + + + + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 5 + + 0 + 2 + false + 0 + continue + + + + + + 0 + + + + + + + ${server} + + http + + /site/news/index.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + + + + + + + GET + false + true + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/docs/demos/InterleaveTestPlan2.jmx b/docs/demos/InterleaveTestPlan2.jmx new file mode 100644 index 00000000000..1eaabee89f9 --- /dev/null +++ b/docs/demos/InterleaveTestPlan2.jmx @@ -0,0 +1,234 @@ + + + + + + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 8 + + 0 + 1 + false + 0 + continue + + + + + + 1 + + + + + + + + + + + + + + + 1 + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + 1 + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + + + ${server} + + + + + GET + true + false + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/docs/demos/JDBC-Pre-Post-Processor.jmx b/docs/demos/JDBC-Pre-Post-Processor.jmx new file mode 100644 index 00000000000..de46391056f --- /dev/null +++ b/docs/demos/JDBC-Pre-Post-Processor.jmx @@ -0,0 +1,445 @@ + + + + + Execute a series of concurrent valuations + false + true + + + + CalculateFees + 1 + = + + + CalculatePerformanceDetails + 1 + = + + + DriverURL + jdbc:jtds:sqlserver: + = + + + DatabasePort + 1433 + = + + + UseMiddleTierValuationEngine + 0 + = + + + MiddleTierRequestTimeout + 500000 + = + + + PricePropagationMode + 2 + = + + + + + + + + + + PCOQuality + 5 + = + + + ValueDate + 2011-07-21 + = + + + ReportingDate + 2011-07-21 12:30:08.337 + = + + + + + + + + Database + HSPAD_MI_440_SSD + = + + + DatabaseHost + GAIA + = + + + DatabaseUser + sa + = + + + DatabasePassword + sa2008 + = + + + + + + + + Pfo_1 + 1548 + = + + + Pfo_2 + 1611 + = + + + Pfo_3 + 1613 + = + + + CutOff Nr 11249, 2011-07-2 / 2011-07-21 12:30:08.337 / DailyNAV Estimate / Within Price Cut-Off + + + + false + + 5000 + + ${DriverURL}//${DatabaseHost}:${DatabasePort}/${Database} + net.sourceforge.jtds.jdbc.Driver + true + ${DatabasePassword} + 25 + 10000 + 60000 + ${DatabaseUser} + 4096 + Connect to local HSPAD_Demo_CO and set its isolation mode to SNAPSHOT (4096) and disable auto commit. + + + + continue + + false + 3 + + 3 + 0 + 1316530469000 + 1316530469000 + false + + + + + + + WorkBench + Concurrent Valuation Test Plan + PCO Valuation + + + + + + continue + + false + 1 + + 1 + 1 + 1320821253000 + 1320821253000 + false + + + + + + 1 + 0 + + + + + + UPDATE T_SettingGlobal SET UseMiddleTierValuationEngine=?, MiddleTierRequestTimeout=? + ${UseMiddleTierValuationEngine}, ${MiddleTierRequestTimeout} + BIT, INTEGER + Prepared Update Statement + + + + + + + Commit + + + + + + + + + + 1 + 0 + 0 + + + + + Update Statement + DBCC DROPCLEANBUFFERS + + + + + + + + + Update Statement + DBCC FREEPROCCACHE + + + + + + + + + + + + true + + + + + Update Statement + BEGIN TRAN COMMIT TRAN + + + + + false + + + + + Callable Statement + PfoVal_Recalculate ?, ?, 1 + ${Pfo_1}, ${PfoValInstance} + INTEGER, INTEGER + + + true + + + + groovy + + + import groovy.sql.Sql +import org.apache.jmeter.protocol.jdbc.config.DataSourceElement +try { + // build Pfo List + println("Building Portfolio List") + def pfoList = "<PfoList>" + def pfoNr = 1 + def pfo = vars.get("Pfo_" + pfoNr) + while(pfo != null) { + println("Pfo: $pfo"); + pfoList = pfoList + "<Pfo ID='$pfo' EmptyValuation='true' PropagatePrice='true'/>" + pfoNr++ + pfo = vars.get("Pfo_" + pfoNr) + } + pfoList = pfoList + "</PfoList>" + vars.put("PfoListXML", pfoList) +} catch (Exception e) { + println(e.toString()); +} + + + + + CreatePriceCutOff ?, ?, ?, ?, ?, ?, ?, ? + ${__threadNum},${ValueDate},${PCOQuality},${ReportingDate},]NULL[,${PCO},${PfoListXML},${PricePropagationMode} + VARCHAR, DATE, INTEGER,TIMESTAMP,INTEGER,OUT INTEGER,CLOB,INTEGER + Callable Statement + + + + + + + Prepared Select Statement + select Nr from PfoValInstance where Pfo=? AND PriceCutOff=? + ${Pfo_1},${PCO} + INTEGER,INTEGER + PfoValInstance + + + + + + DeletePriceCutOff ? + ${PCO} + INTEGER + Callable Statement + + + + + + + ${JMeterThread.last_sample_ok} + false + + + + + Commit + + + + + + Commit the transaction of the valuation + false + + + + + ${JMeterThread.last_sample_ok}==false + false + + + + + Rollback + + + + + + false + + + + + + + false + + saveConfig + + + false + true + false + + false + false + true + false + false + false + false + false + false + true + false + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + diff --git a/docs/demos/JMSPointToPoint.jmx b/docs/demos/JMSPointToPoint.jmx new file mode 100644 index 00000000000..21e49bccdc5 --- /dev/null +++ b/docs/demos/JMSPointToPoint.jmx @@ -0,0 +1,103 @@ + + + + + + + + false + false + + + + + + 1115386407000 + + + 5 + false + + false + 4 + + 1115386407000 + continue + 5 + + + + + + + + + + = + tcp://localhost:61616 + brokerURL + + + = + example.MyQueue + queue.MyQueue + + + = + example.Q.REQ + queue.Q.REQ + + + = + example.Q.RPL + queue.Q.RPL + + + + false + Q.RPL + 5000 + Q.REQ + + ConnectionFactory + org.activemq.jndi.ActiveMQInitialContextFactory + <msg>test</msg> + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + + + + + + + + + diff --git a/docs/demos/LoopTestPlan.jmx b/docs/demos/LoopTestPlan.jmx new file mode 100644 index 00000000000..5ec16e63a79 --- /dev/null +++ b/docs/demos/LoopTestPlan.jmx @@ -0,0 +1,124 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836533000 + 1211836533000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + + + + + + + + + http + + / + GET + true + false + false + false + + + + false + + + + + true + 5 + + + + + + + + + http + + /site/news.html + GET + true + false + false + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + loop-test.dat + + + + + + diff --git a/docs/demos/OnceOnlyTestPlan.jmx b/docs/demos/OnceOnlyTestPlan.jmx new file mode 100644 index 00000000000..1715a675e85 --- /dev/null +++ b/docs/demos/OnceOnlyTestPlan.jmx @@ -0,0 +1,132 @@ + + + + + + + + = + jakarta.apache.org + server + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 3 + + 0 + 2 + false + 0 + continue + + + + + + + + + + + + + + + + + + + + + + ${server} + + + + + GET + true + false + true + false + + + + false + + + + + + + + + + + + + + GET + true + false + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/docs/demos/ProxyServerTestPlan.jmx b/docs/demos/ProxyServerTestPlan.jmx new file mode 100644 index 00000000000..7fe65112805 --- /dev/null +++ b/docs/demos/ProxyServerTestPlan.jmx @@ -0,0 +1,27 @@ + + + + + + + 8080 + + + true + 0 + false + 0 + false + true + true + false + false + false + + + + + + + + diff --git a/docs/demos/RegEx-User-Parameters.jmx b/docs/demos/RegEx-User-Parameters.jmx new file mode 100644 index 00000000000..fa61974c01a --- /dev/null +++ b/docs/demos/RegEx-User-Parameters.jmx @@ -0,0 +1,183 @@ + + + + + Start HTTP Mirror Server for demo + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1357057761000 + 1357057761000 + false + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Request returning some form + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + <html> <body> <form name="test"> <input name="toto" value="vtoto" /> <input name="tutu" value="vtutu" /> <input name="titi" value="vtiti /> </form> </body> </html> + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + listParams + input name="([^"]+?)" value="([^"]+?)" + $1$ + NV + -1 + + + + + false + true + false + + + + + + + false + + = + true + toto + + + false + + = + true + tutu + + + + localhost + 8081 + + + + + /test + POST + true + false + true + false + false + + + + + listParams + 1 + 2 + + + + + + false + + saveConfig + + + true + true + true + + true + false + true + false + false + false + false + false + false + false + true + false + false + false + false + 0 + true + true + true + true + + + ONLY FOR SCRIPTIN + + + + + + + 8081 + 0 + 25 + + + + + + diff --git a/docs/demos/SimpleTestPlan.jmx b/docs/demos/SimpleTestPlan.jmx new file mode 100644 index 00000000000..a10f97e2a8f --- /dev/null +++ b/docs/demos/SimpleTestPlan.jmx @@ -0,0 +1,166 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836583000 + 1211836583000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + + + + + + + + + + + http + + /ant/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /ant/antnews.html + GET + true + false + false + false + + + + false + + + + + + + + + + + + + http + + /log4j/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /log4j/docs/history.html + GET + true + false + false + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + simple-test.dat + + + + + + diff --git a/docs/demos/URLRewritingExample.jmx b/docs/demos/URLRewritingExample.jmx new file mode 100644 index 00000000000..be60b1eb4e6 --- /dev/null +++ b/docs/demos/URLRewritingExample.jmx @@ -0,0 +1,142 @@ + + + + + + + + + false + false + + + + + 1200525828000 + + + 1 + false + + false + -1 + + 1200525828000 + continue + 0 + + + + + + + my.server.com + + + + / + GET + true + false + true + false + + + + false + + + + + + + false + false + SESSION_ID + false + true + + + + + + + = + user + true + username + true + + + = + password + true + password + true + + + + my.server.com + 80 + http + + /main.jsp + POST + true + false + true + false + + + + false + + + + + + + + my.server.com + + http + + /something_interesting.jsp + GET + true + false + true + false + + + + false + + + + + + + + my.server.com + + http + + /another.jsp + POST + true + false + true + false + + + + false + + + + + + + + diff --git a/docs/demos/forEachTestPlan.jmx b/docs/demos/forEachTestPlan.jmx new file mode 100644 index 00000000000..4dacd017628 --- /dev/null +++ b/docs/demos/forEachTestPlan.jmx @@ -0,0 +1,123 @@ + + + + + false + + + + + false + + + + + 1 + false + continue + 1076438592000 + + false + 1 + + 1 + + + 1076438592000 + + + + + + + localhost + 80 + + + / + GET + true + false + true + false + + + + false + + + + + inputVar + <a href="([^"]+)" + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + localhost + 80 + + + ${returnVar} + GET + true + false + true + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/docs/download_jmeter.cgi b/docs/download_jmeter.cgi new file mode 100755 index 00000000000..ba444668595 --- /dev/null +++ b/docs/download_jmeter.cgi @@ -0,0 +1,7 @@ +#!/bin/sh +# Wrapper script around mirrors.cgi script +# (we must change to that directory in order for python to pick up the +# python includes correctly) +cd /www/www.apache.org/dyn/mirrors +/www/www.apache.org/dyn/mirrors/mirrors.cgi $* + \ No newline at end of file diff --git a/docs/download_jmeter.html b/docs/download_jmeter.html new file mode 100644 index 00000000000..57c636c5035 --- /dev/null +++ b/docs/download_jmeter.html @@ -0,0 +1,133 @@ + +Apache JMeter + - + Download Apache JMeter
Logo ASF
Apache JMeter

Download Apache JMeter

+

+ We recommend you use a mirror to download our release + builds, but you must verify the integrity of + the downloaded files using signatures downloaded from our main + distribution directories. Recent releases (48 hours) may not yet + be available from all the mirrors. +

+ +

+ You are currently using [preferred]. If you + encounter a problem with this mirror, please select another + mirror. If all mirrors are failing, there are backup + mirrors (at the end of the mirrors list) that should be + available. +
+ [if-any logo][end] +

+ +
+

+ Other mirrors: + + +

+
+ +

+ The KEYS link links to the code signing keys used to sign the product. + The PGP link downloads the OpenPGP compatible signature from our main site. + The MD5 link downloads the checksum from the main site. + Please verify the integrity + of the downloaded file. +

+

+ For more information concerning Apache JMeter, see the Apache JMeter site. +

+

+ KEYS +

+

Apache JMeter 2.13 (Requires Java 6 or later)

+ + +

Archives

+

+ Older releases can be obtained from the archives. +

+ +

Verification of downloads

+

+ It is essential that you verify the integrity of the downloaded files using the PGP signature. + Please read Verifying Apache Software Foundation Releases for more information on why you should verify our releases. +

+
\ No newline at end of file diff --git a/docs/extending/jmeter_tutorial.pdf b/docs/extending/jmeter_tutorial.pdf new file mode 100644 index 00000000000..f3c0910b426 Binary files /dev/null and b/docs/extending/jmeter_tutorial.pdf differ diff --git a/docs/images/asf-logo.gif b/docs/images/asf-logo.gif new file mode 100644 index 00000000000..22eb9d7358e Binary files /dev/null and b/docs/images/asf-logo.gif differ diff --git a/docs/images/asf-logo.png b/docs/images/asf-logo.png new file mode 100644 index 00000000000..61b0f836f70 Binary files /dev/null and b/docs/images/asf-logo.png differ diff --git a/docs/images/jakarta-logo.gif b/docs/images/jakarta-logo.gif new file mode 100644 index 00000000000..049cf822952 Binary files /dev/null and b/docs/images/jakarta-logo.gif differ diff --git a/docs/images/logo-small.jpg b/docs/images/logo-small.jpg new file mode 100644 index 00000000000..1590774139a Binary files /dev/null and b/docs/images/logo-small.jpg differ diff --git a/docs/images/logo.jpg b/docs/images/logo.jpg new file mode 100644 index 00000000000..82e2f0e442e Binary files /dev/null and b/docs/images/logo.jpg differ diff --git a/docs/images/screenshots/accesslogsampler.png b/docs/images/screenshots/accesslogsampler.png new file mode 100644 index 00000000000..3da8efc782d Binary files /dev/null and b/docs/images/screenshots/accesslogsampler.png differ diff --git a/docs/images/screenshots/aggregate_graph.png b/docs/images/screenshots/aggregate_graph.png new file mode 100644 index 00000000000..c3a8340de3d Binary files /dev/null and b/docs/images/screenshots/aggregate_graph.png differ diff --git a/docs/images/screenshots/aggregate_graph_settings.png b/docs/images/screenshots/aggregate_graph_settings.png new file mode 100644 index 00000000000..0e9ef47d2a3 Binary files /dev/null and b/docs/images/screenshots/aggregate_graph_settings.png differ diff --git a/docs/images/screenshots/aggregate_report.png b/docs/images/screenshots/aggregate_report.png new file mode 100644 index 00000000000..f186d86d4d7 Binary files /dev/null and b/docs/images/screenshots/aggregate_report.png differ diff --git a/docs/images/screenshots/aggregate_report_grouped.png b/docs/images/screenshots/aggregate_report_grouped.png new file mode 100644 index 00000000000..3ef7a8bfa44 Binary files /dev/null and b/docs/images/screenshots/aggregate_report_grouped.png differ diff --git a/docs/images/screenshots/assertion/HTMLAssertion.png b/docs/images/screenshots/assertion/HTMLAssertion.png new file mode 100644 index 00000000000..927ff8222df Binary files /dev/null and b/docs/images/screenshots/assertion/HTMLAssertion.png differ diff --git a/docs/images/screenshots/assertion/MD5HexAssertion.png b/docs/images/screenshots/assertion/MD5HexAssertion.png new file mode 100644 index 00000000000..f1bde1703a4 Binary files /dev/null and b/docs/images/screenshots/assertion/MD5HexAssertion.png differ diff --git a/docs/images/screenshots/assertion/XMLSchemaAssertion.png b/docs/images/screenshots/assertion/XMLSchemaAssertion.png new file mode 100644 index 00000000000..1a790f070df Binary files /dev/null and b/docs/images/screenshots/assertion/XMLSchemaAssertion.png differ diff --git a/docs/images/screenshots/assertion/assertion.png b/docs/images/screenshots/assertion/assertion.png new file mode 100644 index 00000000000..0a07d7f9397 Binary files /dev/null and b/docs/images/screenshots/assertion/assertion.png differ diff --git a/docs/images/screenshots/assertion/assertionscope.png b/docs/images/screenshots/assertion/assertionscope.png new file mode 100644 index 00000000000..6efb1f9f7eb Binary files /dev/null and b/docs/images/screenshots/assertion/assertionscope.png differ diff --git a/docs/images/screenshots/assertion/assertionscopevar.png b/docs/images/screenshots/assertion/assertionscopevar.png new file mode 100644 index 00000000000..aff8c75d98c Binary files /dev/null and b/docs/images/screenshots/assertion/assertionscopevar.png differ diff --git a/docs/images/screenshots/assertion/compare.png b/docs/images/screenshots/assertion/compare.png new file mode 100644 index 00000000000..03c595432e2 Binary files /dev/null and b/docs/images/screenshots/assertion/compare.png differ diff --git a/docs/images/screenshots/assertion/example1a.png b/docs/images/screenshots/assertion/example1a.png new file mode 100644 index 00000000000..9951367bfb2 Binary files /dev/null and b/docs/images/screenshots/assertion/example1a.png differ diff --git a/docs/images/screenshots/assertion/example1b.png b/docs/images/screenshots/assertion/example1b.png new file mode 100644 index 00000000000..354c3c1dbe1 Binary files /dev/null and b/docs/images/screenshots/assertion/example1b.png differ diff --git a/docs/images/screenshots/assertion/example1c-fail.png b/docs/images/screenshots/assertion/example1c-fail.png new file mode 100644 index 00000000000..84b484e65ae Binary files /dev/null and b/docs/images/screenshots/assertion/example1c-fail.png differ diff --git a/docs/images/screenshots/assertion/example1c-pass.png b/docs/images/screenshots/assertion/example1c-pass.png new file mode 100644 index 00000000000..2488b3c6978 Binary files /dev/null and b/docs/images/screenshots/assertion/example1c-pass.png differ diff --git a/docs/images/screenshots/assertion/smime.png b/docs/images/screenshots/assertion/smime.png new file mode 100644 index 00000000000..65e8ecff708 Binary files /dev/null and b/docs/images/screenshots/assertion/smime.png differ diff --git a/docs/images/screenshots/assertion_results.png b/docs/images/screenshots/assertion_results.png new file mode 100644 index 00000000000..99c32c38810 Binary files /dev/null and b/docs/images/screenshots/assertion_results.png differ diff --git a/docs/images/screenshots/backend_listener.png b/docs/images/screenshots/backend_listener.png new file mode 100644 index 00000000000..91ffdc7fffe Binary files /dev/null and b/docs/images/screenshots/backend_listener.png differ diff --git a/docs/images/screenshots/beanshell_assertion.png b/docs/images/screenshots/beanshell_assertion.png new file mode 100644 index 00000000000..e954082f942 Binary files /dev/null and b/docs/images/screenshots/beanshell_assertion.png differ diff --git a/docs/images/screenshots/beanshell_listener.png b/docs/images/screenshots/beanshell_listener.png new file mode 100644 index 00000000000..43b6a247040 Binary files /dev/null and b/docs/images/screenshots/beanshell_listener.png differ diff --git a/docs/images/screenshots/beanshell_postprocessor.png b/docs/images/screenshots/beanshell_postprocessor.png new file mode 100644 index 00000000000..07f7ff03244 Binary files /dev/null and b/docs/images/screenshots/beanshell_postprocessor.png differ diff --git a/docs/images/screenshots/beanshell_preprocessor.png b/docs/images/screenshots/beanshell_preprocessor.png new file mode 100644 index 00000000000..747b32e9fc6 Binary files /dev/null and b/docs/images/screenshots/beanshell_preprocessor.png differ diff --git a/docs/images/screenshots/beanshellsampler.png b/docs/images/screenshots/beanshellsampler.png new file mode 100644 index 00000000000..5b7697bbe39 Binary files /dev/null and b/docs/images/screenshots/beanshellsampler.png differ diff --git a/docs/images/screenshots/bsf_assertion.png b/docs/images/screenshots/bsf_assertion.png new file mode 100644 index 00000000000..70e76abc13c Binary files /dev/null and b/docs/images/screenshots/bsf_assertion.png differ diff --git a/docs/images/screenshots/bsf_listener.png b/docs/images/screenshots/bsf_listener.png new file mode 100644 index 00000000000..de557671532 Binary files /dev/null and b/docs/images/screenshots/bsf_listener.png differ diff --git a/docs/images/screenshots/bsf_postprocessor.png b/docs/images/screenshots/bsf_postprocessor.png new file mode 100644 index 00000000000..3047a54dc01 Binary files /dev/null and b/docs/images/screenshots/bsf_postprocessor.png differ diff --git a/docs/images/screenshots/bsf_preprocessor.png b/docs/images/screenshots/bsf_preprocessor.png new file mode 100644 index 00000000000..e7b12ea4e94 Binary files /dev/null and b/docs/images/screenshots/bsf_preprocessor.png differ diff --git a/docs/images/screenshots/bsfsampler.png b/docs/images/screenshots/bsfsampler.png new file mode 100644 index 00000000000..83e995a5766 Binary files /dev/null and b/docs/images/screenshots/bsfsampler.png differ diff --git a/docs/images/screenshots/bsh_assertion.png b/docs/images/screenshots/bsh_assertion.png new file mode 100644 index 00000000000..70e76abc13c Binary files /dev/null and b/docs/images/screenshots/bsh_assertion.png differ diff --git a/docs/images/screenshots/changes/2.10/01_css_jquery_tester.png b/docs/images/screenshots/changes/2.10/01_css_jquery_tester.png new file mode 100644 index 00000000000..8cb54272dee Binary files /dev/null and b/docs/images/screenshots/changes/2.10/01_css_jquery_tester.png differ diff --git a/docs/images/screenshots/changes/2.10/02_mongodb_source_config.png b/docs/images/screenshots/changes/2.10/02_mongodb_source_config.png new file mode 100644 index 00000000000..4e5ae817691 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/02_mongodb_source_config.png differ diff --git a/docs/images/screenshots/changes/2.10/03_mongodb_script_alpha.png b/docs/images/screenshots/changes/2.10/03_mongodb_script_alpha.png new file mode 100644 index 00000000000..73001784fb5 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/03_mongodb_script_alpha.png differ diff --git a/docs/images/screenshots/changes/2.10/04_jdbc_request_timeout.png b/docs/images/screenshots/changes/2.10/04_jdbc_request_timeout.png new file mode 100644 index 00000000000..f37743aca7b Binary files /dev/null and b/docs/images/screenshots/changes/2.10/04_jdbc_request_timeout.png differ diff --git a/docs/images/screenshots/changes/2.10/05_urlencode_function.png b/docs/images/screenshots/changes/2.10/05_urlencode_function.png new file mode 100644 index 00000000000..33e10d545a3 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/05_urlencode_function.png differ diff --git a/docs/images/screenshots/changes/2.10/06_http_request_delete_method.png b/docs/images/screenshots/changes/2.10/06_http_request_delete_method.png new file mode 100644 index 00000000000..34f6e11123a Binary files /dev/null and b/docs/images/screenshots/changes/2.10/06_http_request_delete_method.png differ diff --git a/docs/images/screenshots/changes/2.10/07_jmeter_templates_icon.png b/docs/images/screenshots/changes/2.10/07_jmeter_templates_icon.png new file mode 100644 index 00000000000..ae6af99cc0c Binary files /dev/null and b/docs/images/screenshots/changes/2.10/07_jmeter_templates_icon.png differ diff --git a/docs/images/screenshots/changes/2.10/08_jmeter_templates_box.png b/docs/images/screenshots/changes/2.10/08_jmeter_templates_box.png new file mode 100644 index 00000000000..12f058fdd20 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/08_jmeter_templates_box.png differ diff --git a/docs/images/screenshots/changes/2.10/09_save_workbench.png b/docs/images/screenshots/changes/2.10/09_save_workbench.png new file mode 100644 index 00000000000..c651cdfc540 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/09_save_workbench.png differ diff --git a/docs/images/screenshots/changes/2.10/10_color_syntax_bsf_sampler.png b/docs/images/screenshots/changes/2.10/10_color_syntax_bsf_sampler.png new file mode 100644 index 00000000000..971c5500ee9 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/10_color_syntax_bsf_sampler.png differ diff --git a/docs/images/screenshots/changes/2.10/11_color_syntax_jsr223_preprocessor.png b/docs/images/screenshots/changes/2.10/11_color_syntax_jsr223_preprocessor.png new file mode 100644 index 00000000000..d90f2fcd7fd Binary files /dev/null and b/docs/images/screenshots/changes/2.10/11_color_syntax_jsr223_preprocessor.png differ diff --git a/docs/images/screenshots/changes/2.10/12_drap_n-drop_multiple.png b/docs/images/screenshots/changes/2.10/12_drap_n-drop_multiple.png new file mode 100644 index 00000000000..160a4d929db Binary files /dev/null and b/docs/images/screenshots/changes/2.10/12_drap_n-drop_multiple.png differ diff --git a/docs/images/screenshots/changes/2.10/13_response_time_graph_y_scale.png b/docs/images/screenshots/changes/2.10/13_response_time_graph_y_scale.png new file mode 100644 index 00000000000..ed7576d9535 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/13_response_time_graph_y_scale.png differ diff --git a/docs/images/screenshots/changes/2.10/14_mongodb_jsr223.png b/docs/images/screenshots/changes/2.10/14_mongodb_jsr223.png new file mode 100644 index 00000000000..36bceca6b16 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/14_mongodb_jsr223.png differ diff --git a/docs/images/screenshots/changes/2.10/15_kerberos.png b/docs/images/screenshots/changes/2.10/15_kerberos.png new file mode 100644 index 00000000000..0beeb7aa735 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/15_kerberos.png differ diff --git a/docs/images/screenshots/changes/2.10/16_device.png b/docs/images/screenshots/changes/2.10/16_device.png new file mode 100644 index 00000000000..d20ce976cf4 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/16_device.png differ diff --git a/docs/images/screenshots/changes/2.10/17_os_process_timeout.png b/docs/images/screenshots/changes/2.10/17_os_process_timeout.png new file mode 100644 index 00000000000..3857651d466 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/17_os_process_timeout.png differ diff --git a/docs/images/screenshots/changes/2.10/17_threads_gui.png b/docs/images/screenshots/changes/2.10/17_threads_gui.png new file mode 100644 index 00000000000..e178d099683 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/17_threads_gui.png differ diff --git a/docs/images/screenshots/changes/2.10/17_threads_summariser.png b/docs/images/screenshots/changes/2.10/17_threads_summariser.png new file mode 100644 index 00000000000..fa0b94ba8c1 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/17_threads_summariser.png differ diff --git a/docs/images/screenshots/changes/2.10/18_https_test_script_recorder.png b/docs/images/screenshots/changes/2.10/18_https_test_script_recorder.png new file mode 100644 index 00000000000..9ab1bfc5935 Binary files /dev/null and b/docs/images/screenshots/changes/2.10/18_https_test_script_recorder.png differ diff --git a/docs/images/screenshots/changes/2.11/01_jms_properties_typed_values.png b/docs/images/screenshots/changes/2.11/01_jms_properties_typed_values.png new file mode 100644 index 00000000000..0ad62a0df9e Binary files /dev/null and b/docs/images/screenshots/changes/2.11/01_jms_properties_typed_values.png differ diff --git a/docs/images/screenshots/changes/2.11/02_transaction_controller.png b/docs/images/screenshots/changes/2.11/02_transaction_controller.png new file mode 100644 index 00000000000..ec06ed23876 Binary files /dev/null and b/docs/images/screenshots/changes/2.11/02_transaction_controller.png differ diff --git a/docs/images/screenshots/changes/2.11/03_xpath_tester.png b/docs/images/screenshots/changes/2.11/03_xpath_tester.png new file mode 100644 index 00000000000..182cc643ccc Binary files /dev/null and b/docs/images/screenshots/changes/2.11/03_xpath_tester.png differ diff --git a/docs/images/screenshots/changes/2.11/05_save_as_fragement.png b/docs/images/screenshots/changes/2.11/05_save_as_fragement.png new file mode 100644 index 00000000000..5215e142b20 Binary files /dev/null and b/docs/images/screenshots/changes/2.11/05_save_as_fragement.png differ diff --git a/docs/images/screenshots/changes/2.11/06_summariser.png b/docs/images/screenshots/changes/2.11/06_summariser.png new file mode 100644 index 00000000000..8246f31ccd3 Binary files /dev/null and b/docs/images/screenshots/changes/2.11/06_summariser.png differ diff --git a/docs/images/screenshots/changes/2.11/07_keystore_config.png b/docs/images/screenshots/changes/2.11/07_keystore_config.png new file mode 100644 index 00000000000..7afabc09510 Binary files /dev/null and b/docs/images/screenshots/changes/2.11/07_keystore_config.png differ diff --git a/docs/images/screenshots/changes/2.12/01_critical_section_controller.png b/docs/images/screenshots/changes/2.12/01_critical_section_controller.png new file mode 100644 index 00000000000..13dd13b1b34 Binary files /dev/null and b/docs/images/screenshots/changes/2.12/01_critical_section_controller.png differ diff --git a/docs/images/screenshots/changes/2.12/02_dns_cache_manager.png b/docs/images/screenshots/changes/2.12/02_dns_cache_manager.png new file mode 100644 index 00000000000..d9642f694fc Binary files /dev/null and b/docs/images/screenshots/changes/2.12/02_dns_cache_manager.png differ diff --git a/docs/images/screenshots/changes/2.12/03_mail_reader_sampler.png b/docs/images/screenshots/changes/2.12/03_mail_reader_sampler.png new file mode 100644 index 00000000000..f8878a68423 Binary files /dev/null and b/docs/images/screenshots/changes/2.12/03_mail_reader_sampler.png differ diff --git a/docs/images/screenshots/changes/2.12/04_jms_publisher.png b/docs/images/screenshots/changes/2.12/04_jms_publisher.png new file mode 100644 index 00000000000..65f2b18653a Binary files /dev/null and b/docs/images/screenshots/changes/2.12/04_jms_publisher.png differ diff --git a/docs/images/screenshots/changes/2.12/05_jms_point_to_point.png b/docs/images/screenshots/changes/2.12/05_jms_point_to_point.png new file mode 100644 index 00000000000..ff89e680dfc Binary files /dev/null and b/docs/images/screenshots/changes/2.12/05_jms_point_to_point.png differ diff --git a/docs/images/screenshots/changes/2.12/06_smtp_sampler.png b/docs/images/screenshots/changes/2.12/06_smtp_sampler.png new file mode 100644 index 00000000000..f923580db9d Binary files /dev/null and b/docs/images/screenshots/changes/2.12/06_smtp_sampler.png differ diff --git a/docs/images/screenshots/changes/2.12/07_view_results_tree.png b/docs/images/screenshots/changes/2.12/07_view_results_tree.png new file mode 100644 index 00000000000..ceda7289057 Binary files /dev/null and b/docs/images/screenshots/changes/2.12/07_view_results_tree.png differ diff --git a/docs/images/screenshots/changes/2.12/08_response_time_graph.png b/docs/images/screenshots/changes/2.12/08_response_time_graph.png new file mode 100644 index 00000000000..217a42b97ca Binary files /dev/null and b/docs/images/screenshots/changes/2.12/08_response_time_graph.png differ diff --git a/docs/images/screenshots/changes/2.12/09_synchronizing_timer.png b/docs/images/screenshots/changes/2.12/09_synchronizing_timer.png new file mode 100644 index 00000000000..3fdfbb0e5fd Binary files /dev/null and b/docs/images/screenshots/changes/2.12/09_synchronizing_timer.png differ diff --git a/docs/images/screenshots/changes/2.12/10_undo_redo.png b/docs/images/screenshots/changes/2.12/10_undo_redo.png new file mode 100644 index 00000000000..370daa1c378 Binary files /dev/null and b/docs/images/screenshots/changes/2.12/10_undo_redo.png differ diff --git a/docs/images/screenshots/changes/2.12/11_log_viewer.png b/docs/images/screenshots/changes/2.12/11_log_viewer.png new file mode 100644 index 00000000000..c7ddf3aea35 Binary files /dev/null and b/docs/images/screenshots/changes/2.12/11_log_viewer.png differ diff --git a/docs/images/screenshots/changes/2.12/12_cache_resource_mode.png b/docs/images/screenshots/changes/2.12/12_cache_resource_mode.png new file mode 100644 index 00000000000..a281b33c91d Binary files /dev/null and b/docs/images/screenshots/changes/2.12/12_cache_resource_mode.png differ diff --git a/docs/images/screenshots/changes/2.12/13_webdav.png b/docs/images/screenshots/changes/2.12/13_webdav.png new file mode 100644 index 00000000000..6c95e33f03c Binary files /dev/null and b/docs/images/screenshots/changes/2.12/13_webdav.png differ diff --git a/docs/images/screenshots/changes/2.12/14_recorder_filter.png b/docs/images/screenshots/changes/2.12/14_recorder_filter.png new file mode 100644 index 00000000000..b7d293d4665 Binary files /dev/null and b/docs/images/screenshots/changes/2.12/14_recorder_filter.png differ diff --git a/docs/images/screenshots/changes/2.13/aggregate_graph_new_percentile.png b/docs/images/screenshots/changes/2.13/aggregate_graph_new_percentile.png new file mode 100644 index 00000000000..72f6ba9f7bd Binary files /dev/null and b/docs/images/screenshots/changes/2.13/aggregate_graph_new_percentile.png differ diff --git a/docs/images/screenshots/changes/2.13/backend_listener_graphite.png b/docs/images/screenshots/changes/2.13/backend_listener_graphite.png new file mode 100644 index 00000000000..73228c1c2f4 Binary files /dev/null and b/docs/images/screenshots/changes/2.13/backend_listener_graphite.png differ diff --git a/docs/images/screenshots/changes/2.13/connect_time_table.png b/docs/images/screenshots/changes/2.13/connect_time_table.png new file mode 100644 index 00000000000..96967224f22 Binary files /dev/null and b/docs/images/screenshots/changes/2.13/connect_time_table.png differ diff --git a/docs/images/screenshots/changes/2.13/connect_time_tree.png b/docs/images/screenshots/changes/2.13/connect_time_tree.png new file mode 100644 index 00000000000..4c0515c5358 Binary files /dev/null and b/docs/images/screenshots/changes/2.13/connect_time_tree.png differ diff --git a/docs/images/screenshots/changes/2.13/distributed_retry.png b/docs/images/screenshots/changes/2.13/distributed_retry.png new file mode 100644 index 00000000000..c8a31d41f7e Binary files /dev/null and b/docs/images/screenshots/changes/2.13/distributed_retry.png differ diff --git a/docs/images/screenshots/changes/2.13/jdbc_resultset_handler.png b/docs/images/screenshots/changes/2.13/jdbc_resultset_handler.png new file mode 100644 index 00000000000..dd8fad8d1bd Binary files /dev/null and b/docs/images/screenshots/changes/2.13/jdbc_resultset_handler.png differ diff --git a/docs/images/screenshots/changes/2.13/module_controller_tree_view.png b/docs/images/screenshots/changes/2.13/module_controller_tree_view.png new file mode 100644 index 00000000000..5eed7f499b0 Binary files /dev/null and b/docs/images/screenshots/changes/2.13/module_controller_tree_view.png differ diff --git a/docs/images/screenshots/changes/2.13/new_methods_caldav.png b/docs/images/screenshots/changes/2.13/new_methods_caldav.png new file mode 100644 index 00000000000..7d0e5e4c88b Binary files /dev/null and b/docs/images/screenshots/changes/2.13/new_methods_caldav.png differ diff --git a/docs/images/screenshots/changes/2.13/toolbar_22x22.png b/docs/images/screenshots/changes/2.13/toolbar_22x22.png new file mode 100644 index 00000000000..d5aa794e5e6 Binary files /dev/null and b/docs/images/screenshots/changes/2.13/toolbar_22x22.png differ diff --git a/docs/images/screenshots/changes/2.13/toolbar_32x32.png b/docs/images/screenshots/changes/2.13/toolbar_32x32.png new file mode 100644 index 00000000000..410460d93d7 Binary files /dev/null and b/docs/images/screenshots/changes/2.13/toolbar_32x32.png differ diff --git a/docs/images/screenshots/changes/2.13/toolbar_48x48.png b/docs/images/screenshots/changes/2.13/toolbar_48x48.png new file mode 100644 index 00000000000..e8fdbc113f9 Binary files /dev/null and b/docs/images/screenshots/changes/2.13/toolbar_48x48.png differ diff --git a/docs/images/screenshots/changes/2.13/warning_message_proxy.png b/docs/images/screenshots/changes/2.13/warning_message_proxy.png new file mode 100644 index 00000000000..89141393fea Binary files /dev/null and b/docs/images/screenshots/changes/2.13/warning_message_proxy.png differ diff --git a/docs/images/screenshots/changes/2.6/01_toolbar.png b/docs/images/screenshots/changes/2.6/01_toolbar.png new file mode 100644 index 00000000000..b855de1ed2c Binary files /dev/null and b/docs/images/screenshots/changes/2.6/01_toolbar.png differ diff --git a/docs/images/screenshots/changes/2.6/02_ignore_pause_timers.png b/docs/images/screenshots/changes/2.6/02_ignore_pause_timers.png new file mode 100644 index 00000000000..43698e2bfd0 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/02_ignore_pause_timers.png differ diff --git a/docs/images/screenshots/changes/2.6/03_look_and_feel.png b/docs/images/screenshots/changes/2.6/03_look_and_feel.png new file mode 100644 index 00000000000..f57faaa5b71 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/03_look_and_feel.png differ diff --git a/docs/images/screenshots/changes/2.6/04_duplicate_context_menu.png b/docs/images/screenshots/changes/2.6/04_duplicate_context_menu.png new file mode 100644 index 00000000000..61fbdab7196 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/04_duplicate_context_menu.png differ diff --git a/docs/images/screenshots/changes/2.6/05_search_tree.png b/docs/images/screenshots/changes/2.6/05_search_tree.png new file mode 100644 index 00000000000..81a51845b0e Binary files /dev/null and b/docs/images/screenshots/changes/2.6/05_search_tree.png differ diff --git a/docs/images/screenshots/changes/2.6/06_post_data.png b/docs/images/screenshots/changes/2.6/06_post_data.png new file mode 100644 index 00000000000..6e9af4e6887 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/06_post_data.png differ diff --git a/docs/images/screenshots/changes/2.6/07_multiple_selection_params.png b/docs/images/screenshots/changes/2.6/07_multiple_selection_params.png new file mode 100644 index 00000000000..b3e97a09313 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/07_multiple_selection_params.png differ diff --git a/docs/images/screenshots/changes/2.6/08_file_protocol.png b/docs/images/screenshots/changes/2.6/08_file_protocol.png new file mode 100644 index 00000000000..04520ec991c Binary files /dev/null and b/docs/images/screenshots/changes/2.6/08_file_protocol.png differ diff --git a/docs/images/screenshots/changes/2.6/09_file_protocol_embedded.png b/docs/images/screenshots/changes/2.6/09_file_protocol_embedded.png new file mode 100644 index 00000000000..67f582f08b1 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/09_file_protocol_embedded.png differ diff --git a/docs/images/screenshots/changes/2.6/10_child_sampler.png b/docs/images/screenshots/changes/2.6/10_child_sampler.png new file mode 100644 index 00000000000..cff10a8cbce Binary files /dev/null and b/docs/images/screenshots/changes/2.6/10_child_sampler.png differ diff --git a/docs/images/screenshots/changes/2.6/11_jks_keystore.png b/docs/images/screenshots/changes/2.6/11_jks_keystore.png new file mode 100644 index 00000000000..db3375709b3 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/11_jks_keystore.png differ diff --git a/docs/images/screenshots/changes/2.6/12_aggregate_graph_settings.png b/docs/images/screenshots/changes/2.6/12_aggregate_graph_settings.png new file mode 100644 index 00000000000..4bb4d37c82b Binary files /dev/null and b/docs/images/screenshots/changes/2.6/12_aggregate_graph_settings.png differ diff --git a/docs/images/screenshots/changes/2.6/13_aggregate_graph_bar.png b/docs/images/screenshots/changes/2.6/13_aggregate_graph_bar.png new file mode 100644 index 00000000000..def4e8d3c75 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/13_aggregate_graph_bar.png differ diff --git a/docs/images/screenshots/changes/2.6/14_reset_counter.png b/docs/images/screenshots/changes/2.6/14_reset_counter.png new file mode 100644 index 00000000000..1b622b3d9bb Binary files /dev/null and b/docs/images/screenshots/changes/2.6/14_reset_counter.png differ diff --git a/docs/images/screenshots/changes/2.6/15_random_string.png b/docs/images/screenshots/changes/2.6/15_random_string.png new file mode 100644 index 00000000000..a85f5d2df76 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/15_random_string.png differ diff --git a/docs/images/screenshots/changes/2.6/16_udv_comments.png b/docs/images/screenshots/changes/2.6/16_udv_comments.png new file mode 100644 index 00000000000..e014bdb25a3 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/16_udv_comments.png differ diff --git a/docs/images/screenshots/changes/2.6/17_vrt_max_size_display.png b/docs/images/screenshots/changes/2.6/17_vrt_max_size_display.png new file mode 100644 index 00000000000..505302fcc07 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/17_vrt_max_size_display.png differ diff --git a/docs/images/screenshots/changes/2.6/18_change_ctl_type.png b/docs/images/screenshots/changes/2.6/18_change_ctl_type.png new file mode 100644 index 00000000000..bea7c315ebd Binary files /dev/null and b/docs/images/screenshots/changes/2.6/18_change_ctl_type.png differ diff --git a/docs/images/screenshots/changes/2.6/19_jdbc_pre_post_proc.png b/docs/images/screenshots/changes/2.6/19_jdbc_pre_post_proc.png new file mode 100644 index 00000000000..232e2b4a1fe Binary files /dev/null and b/docs/images/screenshots/changes/2.6/19_jdbc_pre_post_proc.png differ diff --git a/docs/images/screenshots/changes/2.6/20_jdbc_trans_isolation.png b/docs/images/screenshots/changes/2.6/20_jdbc_trans_isolation.png new file mode 100644 index 00000000000..7122a593f4a Binary files /dev/null and b/docs/images/screenshots/changes/2.6/20_jdbc_trans_isolation.png differ diff --git a/docs/images/screenshots/changes/2.6/21_poisson_timer.png b/docs/images/screenshots/changes/2.6/21_poisson_timer.png new file mode 100644 index 00000000000..8bba1d1b65a Binary files /dev/null and b/docs/images/screenshots/changes/2.6/21_poisson_timer.png differ diff --git a/docs/images/screenshots/changes/2.6/22_drag_and_drop.png b/docs/images/screenshots/changes/2.6/22_drag_and_drop.png new file mode 100644 index 00000000000..562889169d4 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/22_drag_and_drop.png differ diff --git a/docs/images/screenshots/changes/2.6/23_confirm_remove.png b/docs/images/screenshots/changes/2.6/23_confirm_remove.png new file mode 100644 index 00000000000..8d6a05bdbd1 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/23_confirm_remove.png differ diff --git a/docs/images/screenshots/changes/2.6/24_diskstore.png b/docs/images/screenshots/changes/2.6/24_diskstore.png new file mode 100644 index 00000000000..03024b2a1fb Binary files /dev/null and b/docs/images/screenshots/changes/2.6/24_diskstore.png differ diff --git a/docs/images/screenshots/changes/2.6/25_selector.png b/docs/images/screenshots/changes/2.6/25_selector.png new file mode 100644 index 00000000000..986e06ec6d7 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/25_selector.png differ diff --git a/docs/images/screenshots/changes/2.6/26_ignore_child_failed.png b/docs/images/screenshots/changes/2.6/26_ignore_child_failed.png new file mode 100644 index 00000000000..966fc41e46d Binary files /dev/null and b/docs/images/screenshots/changes/2.6/26_ignore_child_failed.png differ diff --git a/docs/images/screenshots/changes/2.6/27_succes_with_child_failed.png b/docs/images/screenshots/changes/2.6/27_succes_with_child_failed.png new file mode 100644 index 00000000000..097b04b8cb1 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/27_succes_with_child_failed.png differ diff --git a/docs/images/screenshots/changes/2.6/28_loggerpanel.png b/docs/images/screenshots/changes/2.6/28_loggerpanel.png new file mode 100644 index 00000000000..2f889b0609e Binary files /dev/null and b/docs/images/screenshots/changes/2.6/28_loggerpanel.png differ diff --git a/docs/images/screenshots/changes/2.6/28_loggerpanel_option.png b/docs/images/screenshots/changes/2.6/28_loggerpanel_option.png new file mode 100644 index 00000000000..e7afb8954b2 Binary files /dev/null and b/docs/images/screenshots/changes/2.6/28_loggerpanel_option.png differ diff --git a/docs/images/screenshots/changes/2.7/01_os_process_sampler.png b/docs/images/screenshots/changes/2.7/01_os_process_sampler.png new file mode 100644 index 00000000000..920e8cd0a4f Binary files /dev/null and b/docs/images/screenshots/changes/2.7/01_os_process_sampler.png differ diff --git a/docs/images/screenshots/changes/2.7/02_os_process_example_results.png b/docs/images/screenshots/changes/2.7/02_os_process_example_results.png new file mode 100644 index 00000000000..f9727843ab8 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/02_os_process_example_results.png differ diff --git a/docs/images/screenshots/changes/2.7/03_aggregate_graph_with_new_cols.png b/docs/images/screenshots/changes/2.7/03_aggregate_graph_with_new_cols.png new file mode 100644 index 00000000000..94e74eb5df7 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/03_aggregate_graph_with_new_cols.png differ diff --git a/docs/images/screenshots/changes/2.7/04_aggregate_graph_parameters.png b/docs/images/screenshots/changes/2.7/04_aggregate_graph_parameters.png new file mode 100644 index 00000000000..a121e7d2d55 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/04_aggregate_graph_parameters.png differ diff --git a/docs/images/screenshots/changes/2.7/05_jmeter_ant_task_report_success.png b/docs/images/screenshots/changes/2.7/05_jmeter_ant_task_report_success.png new file mode 100644 index 00000000000..8e8b5a5cbcd Binary files /dev/null and b/docs/images/screenshots/changes/2.7/05_jmeter_ant_task_report_success.png differ diff --git a/docs/images/screenshots/changes/2.7/06_jmeter_ant_task_report_errors.png b/docs/images/screenshots/changes/2.7/06_jmeter_ant_task_report_errors.png new file mode 100644 index 00000000000..4f5b7c5ddce Binary files /dev/null and b/docs/images/screenshots/changes/2.7/06_jmeter_ant_task_report_errors.png differ diff --git a/docs/images/screenshots/changes/2.7/07_test_action_next_iter.png b/docs/images/screenshots/changes/2.7/07_test_action_next_iter.png new file mode 100644 index 00000000000..8db01e4ad8f Binary files /dev/null and b/docs/images/screenshots/changes/2.7/07_test_action_next_iter.png differ diff --git a/docs/images/screenshots/changes/2.7/08_param_button_detail.png b/docs/images/screenshots/changes/2.7/08_param_button_detail.png new file mode 100644 index 00000000000..f3498fca5c2 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/08_param_button_detail.png differ diff --git a/docs/images/screenshots/changes/2.7/09_detail_box.png b/docs/images/screenshots/changes/2.7/09_detail_box.png new file mode 100644 index 00000000000..ae7d232c960 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/09_detail_box.png differ diff --git a/docs/images/screenshots/changes/2.7/10_mailer_visualizer_gui.png b/docs/images/screenshots/changes/2.7/10_mailer_visualizer_gui.png new file mode 100644 index 00000000000..2fb226f1b6f Binary files /dev/null and b/docs/images/screenshots/changes/2.7/10_mailer_visualizer_gui.png differ diff --git a/docs/images/screenshots/changes/2.7/11_jms_non_persistent_delivery_mode.png b/docs/images/screenshots/changes/2.7/11_jms_non_persistent_delivery_mode.png new file mode 100644 index 00000000000..04fd6f1a9f8 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/11_jms_non_persistent_delivery_mode.png differ diff --git a/docs/images/screenshots/changes/2.7/12_jms_sending_objects.png b/docs/images/screenshots/changes/2.7/12_jms_sending_objects.png new file mode 100644 index 00000000000..8d82dbdff37 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/12_jms_sending_objects.png differ diff --git a/docs/images/screenshots/changes/2.7/13_jms_properties.png b/docs/images/screenshots/changes/2.7/13_jms_properties.png new file mode 100644 index 00000000000..252535907c7 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/13_jms_properties.png differ diff --git a/docs/images/screenshots/changes/2.7/14_ws_document_cache.png b/docs/images/screenshots/changes/2.7/14_ws_document_cache.png new file mode 100644 index 00000000000..de56edf5d4a Binary files /dev/null and b/docs/images/screenshots/changes/2.7/14_ws_document_cache.png differ diff --git a/docs/images/screenshots/changes/2.7/15_ws_maintain_session.png b/docs/images/screenshots/changes/2.7/15_ws_maintain_session.png new file mode 100644 index 00000000000..84a400539d2 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/15_ws_maintain_session.png differ diff --git a/docs/images/screenshots/changes/2.7/16_log_errors_counter.png b/docs/images/screenshots/changes/2.7/16_log_errors_counter.png new file mode 100644 index 00000000000..e56f555de55 Binary files /dev/null and b/docs/images/screenshots/changes/2.7/16_log_errors_counter.png differ diff --git a/docs/images/screenshots/changes/2.8/01_http_patch_verb.png b/docs/images/screenshots/changes/2.8/01_http_patch_verb.png new file mode 100644 index 00000000000..a868114e2d6 Binary files /dev/null and b/docs/images/screenshots/changes/2.8/01_http_patch_verb.png differ diff --git a/docs/images/screenshots/changes/2.8/02_http_default_hc4.png b/docs/images/screenshots/changes/2.8/02_http_default_hc4.png new file mode 100644 index 00000000000..dccfc8de77e Binary files /dev/null and b/docs/images/screenshots/changes/2.8/02_http_default_hc4.png differ diff --git a/docs/images/screenshots/changes/2.8/03_remove_https_spoofing1.png b/docs/images/screenshots/changes/2.8/03_remove_https_spoofing1.png new file mode 100644 index 00000000000..24c0442db06 Binary files /dev/null and b/docs/images/screenshots/changes/2.8/03_remove_https_spoofing1.png differ diff --git a/docs/images/screenshots/changes/2.8/05_http_defaults_url_filter.png b/docs/images/screenshots/changes/2.8/05_http_defaults_url_filter.png new file mode 100644 index 00000000000..82cb4986e1f Binary files /dev/null and b/docs/images/screenshots/changes/2.8/05_http_defaults_url_filter.png differ diff --git a/docs/images/screenshots/changes/2.8/06_os_sampler_stdout-err-in.png b/docs/images/screenshots/changes/2.8/06_os_sampler_stdout-err-in.png new file mode 100644 index 00000000000..c09a46a603a Binary files /dev/null and b/docs/images/screenshots/changes/2.8/06_os_sampler_stdout-err-in.png differ diff --git a/docs/images/screenshots/changes/2.8/07_aggregate_graph_legend_left_right.png b/docs/images/screenshots/changes/2.8/07_aggregate_graph_legend_left_right.png new file mode 100644 index 00000000000..c14ba44d971 Binary files /dev/null and b/docs/images/screenshots/changes/2.8/07_aggregate_graph_legend_left_right.png differ diff --git a/docs/images/screenshots/changes/2.8/08_resp_time_graph_settings.png b/docs/images/screenshots/changes/2.8/08_resp_time_graph_settings.png new file mode 100644 index 00000000000..c1531568e6a Binary files /dev/null and b/docs/images/screenshots/changes/2.8/08_resp_time_graph_settings.png differ diff --git a/docs/images/screenshots/changes/2.8/09_resp_time_graph.png b/docs/images/screenshots/changes/2.8/09_resp_time_graph.png new file mode 100644 index 00000000000..53bb0b4e7d5 Binary files /dev/null and b/docs/images/screenshots/changes/2.8/09_resp_time_graph.png differ diff --git a/docs/images/screenshots/changes/2.8/10_latency_view_results_table.png b/docs/images/screenshots/changes/2.8/10_latency_view_results_table.png new file mode 100644 index 00000000000..f53bb881095 Binary files /dev/null and b/docs/images/screenshots/changes/2.8/10_latency_view_results_table.png differ diff --git a/docs/images/screenshots/changes/2.8/11_hc4_cookie.png b/docs/images/screenshots/changes/2.8/11_hc4_cookie.png new file mode 100644 index 00000000000..2eb3f861273 Binary files /dev/null and b/docs/images/screenshots/changes/2.8/11_hc4_cookie.png differ diff --git a/docs/images/screenshots/changes/2.8/12_delay_thread_creation.png b/docs/images/screenshots/changes/2.8/12_delay_thread_creation.png new file mode 100644 index 00000000000..9c2aab228d9 Binary files /dev/null and b/docs/images/screenshots/changes/2.8/12_delay_thread_creation.png differ diff --git a/docs/images/screenshots/changes/2.8/13_gnome3_title.png b/docs/images/screenshots/changes/2.8/13_gnome3_title.png new file mode 100644 index 00000000000..04c52e4d56d Binary files /dev/null and b/docs/images/screenshots/changes/2.8/13_gnome3_title.png differ diff --git a/docs/images/screenshots/changes/2.8/14_ctrl_F_shortcut.png b/docs/images/screenshots/changes/2.8/14_ctrl_F_shortcut.png new file mode 100644 index 00000000000..03ff977b8c3 Binary files /dev/null and b/docs/images/screenshots/changes/2.8/14_ctrl_F_shortcut.png differ diff --git a/docs/images/screenshots/changes/2.8/15_add_from_clipboard_filter.png b/docs/images/screenshots/changes/2.8/15_add_from_clipboard_filter.png new file mode 100644 index 00000000000..e414e1ef59a Binary files /dev/null and b/docs/images/screenshots/changes/2.8/15_add_from_clipboard_filter.png differ diff --git a/docs/images/screenshots/changes/2.9/01_css_jquery_extractor.png b/docs/images/screenshots/changes/2.9/01_css_jquery_extractor.png new file mode 100644 index 00000000000..84194037522 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/01_css_jquery_extractor.png differ diff --git a/docs/images/screenshots/changes/2.9/01_css_jquery_extractor_resul.png b/docs/images/screenshots/changes/2.9/01_css_jquery_extractor_resul.png new file mode 100644 index 00000000000..a1de1745273 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/01_css_jquery_extractor_resul.png differ diff --git a/docs/images/screenshots/changes/2.9/02_document_render_view_results_tree.png b/docs/images/screenshots/changes/2.9/02_document_render_view_results_tree.png new file mode 100644 index 00000000000..4f74b59e919 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/02_document_render_view_results_tree.png differ diff --git a/docs/images/screenshots/changes/2.9/03_new_options_tcp_sampler.png b/docs/images/screenshots/changes/2.9/03_new_options_tcp_sampler.png new file mode 100644 index 00000000000..1348cb97850 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/03_new_options_tcp_sampler.png differ diff --git a/docs/images/screenshots/changes/2.9/04_for_each_new_fields.png b/docs/images/screenshots/changes/2.9/04_for_each_new_fields.png new file mode 100644 index 00000000000..743ee1a7cf3 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/04_for_each_new_fields.png differ diff --git a/docs/images/screenshots/changes/2.9/05_result_status_action_handler.png b/docs/images/screenshots/changes/2.9/05_result_status_action_handler.png new file mode 100644 index 00000000000..8f826ec7608 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/05_result_status_action_handler.png differ diff --git a/docs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter1.png b/docs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter1.png new file mode 100644 index 00000000000..79014a6dc05 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter1.png differ diff --git a/docs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter2.png b/docs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter2.png new file mode 100644 index 00000000000..d121899239d Binary files /dev/null and b/docs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter2.png differ diff --git a/docs/images/screenshots/changes/2.9/07_header_panel_add_from_clipboard.png b/docs/images/screenshots/changes/2.9/07_header_panel_add_from_clipboard.png new file mode 100644 index 00000000000..313f3a881cf Binary files /dev/null and b/docs/images/screenshots/changes/2.9/07_header_panel_add_from_clipboard.png differ diff --git a/docs/images/screenshots/changes/2.9/08_module_controller_improvements.png b/docs/images/screenshots/changes/2.9/08_module_controller_improvements.png new file mode 100644 index 00000000000..d1b5837bba1 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/08_module_controller_improvements.png differ diff --git a/docs/images/screenshots/changes/2.9/09_proxy_excludes_suggested.png b/docs/images/screenshots/changes/2.9/09_proxy_excludes_suggested.png new file mode 100644 index 00000000000..9a599032c57 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/09_proxy_excludes_suggested.png differ diff --git a/docs/images/screenshots/changes/2.9/10_http_proxy_dont_force_http_type.png b/docs/images/screenshots/changes/2.9/10_http_proxy_dont_force_http_type.png new file mode 100644 index 00000000000..18aa4a0f47e Binary files /dev/null and b/docs/images/screenshots/changes/2.9/10_http_proxy_dont_force_http_type.png differ diff --git a/docs/images/screenshots/changes/2.9/11_jms_publisher_bytes.png b/docs/images/screenshots/changes/2.9/11_jms_publisher_bytes.png new file mode 100644 index 00000000000..e2fd63f99b7 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/11_jms_publisher_bytes.png differ diff --git a/docs/images/screenshots/changes/2.9/12_jsr223_sampler.png b/docs/images/screenshots/changes/2.9/12_jsr223_sampler.png new file mode 100644 index 00000000000..8d0fd64bd33 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/12_jsr223_sampler.png differ diff --git a/docs/images/screenshots/changes/2.9/13_regex_user_params.png b/docs/images/screenshots/changes/2.9/13_regex_user_params.png new file mode 100644 index 00000000000..c500a6dcfbf Binary files /dev/null and b/docs/images/screenshots/changes/2.9/13_regex_user_params.png differ diff --git a/docs/images/screenshots/changes/2.9/14_xpath_assertion.png b/docs/images/screenshots/changes/2.9/14_xpath_assertion.png new file mode 100644 index 00000000000..149a37fc0d2 Binary files /dev/null and b/docs/images/screenshots/changes/2.9/14_xpath_assertion.png differ diff --git a/docs/images/screenshots/class_diagram.gif b/docs/images/screenshots/class_diagram.gif new file mode 100644 index 00000000000..7b594564f60 Binary files /dev/null and b/docs/images/screenshots/class_diagram.gif differ diff --git a/docs/images/screenshots/comparison_assertion_visualizer.png b/docs/images/screenshots/comparison_assertion_visualizer.png new file mode 100644 index 00000000000..68058a38711 Binary files /dev/null and b/docs/images/screenshots/comparison_assertion_visualizer.png differ diff --git a/docs/images/screenshots/counter.png b/docs/images/screenshots/counter.png new file mode 100644 index 00000000000..6b1e77d7f02 Binary files /dev/null and b/docs/images/screenshots/counter.png differ diff --git a/docs/images/screenshots/css_extractor_attr.png b/docs/images/screenshots/css_extractor_attr.png new file mode 100644 index 00000000000..72b37b9e063 Binary files /dev/null and b/docs/images/screenshots/css_extractor_attr.png differ diff --git a/docs/images/screenshots/css_extractor_noattr.png b/docs/images/screenshots/css_extractor_noattr.png new file mode 100644 index 00000000000..17af38be853 Binary files /dev/null and b/docs/images/screenshots/css_extractor_noattr.png differ diff --git a/docs/images/screenshots/csvdatasetconfig.png b/docs/images/screenshots/csvdatasetconfig.png new file mode 100644 index 00000000000..b05ee5f700a Binary files /dev/null and b/docs/images/screenshots/csvdatasetconfig.png differ diff --git a/docs/images/screenshots/debug_postprocessor.png b/docs/images/screenshots/debug_postprocessor.png new file mode 100644 index 00000000000..20bdfac9e6b Binary files /dev/null and b/docs/images/screenshots/debug_postprocessor.png differ diff --git a/docs/images/screenshots/debug_sampler.png b/docs/images/screenshots/debug_sampler.png new file mode 100644 index 00000000000..b2f8f43a758 Binary files /dev/null and b/docs/images/screenshots/debug_sampler.png differ diff --git a/docs/images/screenshots/distribution_graph.png b/docs/images/screenshots/distribution_graph.png new file mode 100644 index 00000000000..eae11e04476 Binary files /dev/null and b/docs/images/screenshots/distribution_graph.png differ diff --git a/docs/images/screenshots/dns-cache-manager.png b/docs/images/screenshots/dns-cache-manager.png new file mode 100644 index 00000000000..008c6fd30eb Binary files /dev/null and b/docs/images/screenshots/dns-cache-manager.png differ diff --git a/docs/images/screenshots/duration_assertion.png b/docs/images/screenshots/duration_assertion.png new file mode 100644 index 00000000000..ac72297b4cf Binary files /dev/null and b/docs/images/screenshots/duration_assertion.png differ diff --git a/docs/images/screenshots/ftp-config/ftp-request-defaults.png b/docs/images/screenshots/ftp-config/ftp-request-defaults.png new file mode 100644 index 00000000000..a453e9af39d Binary files /dev/null and b/docs/images/screenshots/ftp-config/ftp-request-defaults.png differ diff --git a/docs/images/screenshots/ftptest/ftp-defaults.png b/docs/images/screenshots/ftptest/ftp-defaults.png new file mode 100644 index 00000000000..644664dc141 Binary files /dev/null and b/docs/images/screenshots/ftptest/ftp-defaults.png differ diff --git a/docs/images/screenshots/ftptest/ftp-defaults2.png b/docs/images/screenshots/ftptest/ftp-defaults2.png new file mode 100644 index 00000000000..66f7e14d4ec Binary files /dev/null and b/docs/images/screenshots/ftptest/ftp-defaults2.png differ diff --git a/docs/images/screenshots/ftptest/ftp-request.png b/docs/images/screenshots/ftptest/ftp-request.png new file mode 100644 index 00000000000..14ace11a310 Binary files /dev/null and b/docs/images/screenshots/ftptest/ftp-request.png differ diff --git a/docs/images/screenshots/ftptest/ftp-request2.png b/docs/images/screenshots/ftptest/ftp-request2.png new file mode 100644 index 00000000000..adf298935f3 Binary files /dev/null and b/docs/images/screenshots/ftptest/ftp-request2.png differ diff --git a/docs/images/screenshots/ftptest/ftp-results.png b/docs/images/screenshots/ftptest/ftp-results.png new file mode 100644 index 00000000000..8b489c773f3 Binary files /dev/null and b/docs/images/screenshots/ftptest/ftp-results.png differ diff --git a/docs/images/screenshots/ftptest/threadgroup2.png b/docs/images/screenshots/ftptest/threadgroup2.png new file mode 100644 index 00000000000..543b5d039bc Binary files /dev/null and b/docs/images/screenshots/ftptest/threadgroup2.png differ diff --git a/docs/images/screenshots/function_helper_dialog.png b/docs/images/screenshots/function_helper_dialog.png new file mode 100644 index 00000000000..68cd9f77bd3 Binary files /dev/null and b/docs/images/screenshots/function_helper_dialog.png differ diff --git a/docs/images/screenshots/grafana_dashboard.png b/docs/images/screenshots/grafana_dashboard.png new file mode 100644 index 00000000000..28b3b373574 Binary files /dev/null and b/docs/images/screenshots/grafana_dashboard.png differ diff --git a/docs/images/screenshots/graph_results.png b/docs/images/screenshots/graph_results.png new file mode 100644 index 00000000000..f895b9b7e97 Binary files /dev/null and b/docs/images/screenshots/graph_results.png differ diff --git a/docs/images/screenshots/graphfullresults.png b/docs/images/screenshots/graphfullresults.png new file mode 100644 index 00000000000..462775697e4 Binary files /dev/null and b/docs/images/screenshots/graphfullresults.png differ diff --git a/docs/images/screenshots/html_link_parser.png b/docs/images/screenshots/html_link_parser.png new file mode 100644 index 00000000000..d8886575fc3 Binary files /dev/null and b/docs/images/screenshots/html_link_parser.png differ diff --git a/docs/images/screenshots/http-config/auth-manager-example1a.gif b/docs/images/screenshots/http-config/auth-manager-example1a.gif new file mode 100644 index 00000000000..013474d5171 Binary files /dev/null and b/docs/images/screenshots/http-config/auth-manager-example1a.gif differ diff --git a/docs/images/screenshots/http-config/auth-manager-example1b.png b/docs/images/screenshots/http-config/auth-manager-example1b.png new file mode 100644 index 00000000000..c045ed18f7d Binary files /dev/null and b/docs/images/screenshots/http-config/auth-manager-example1b.png differ diff --git a/docs/images/screenshots/http-config/header-manager-example1a.gif b/docs/images/screenshots/http-config/header-manager-example1a.gif new file mode 100644 index 00000000000..fdf5dc3a51d Binary files /dev/null and b/docs/images/screenshots/http-config/header-manager-example1a.gif differ diff --git a/docs/images/screenshots/http-config/header-manager-example1b.png b/docs/images/screenshots/http-config/header-manager-example1b.png new file mode 100644 index 00000000000..02e5a8387fa Binary files /dev/null and b/docs/images/screenshots/http-config/header-manager-example1b.png differ diff --git a/docs/images/screenshots/http-config/http-auth-manager.png b/docs/images/screenshots/http-config/http-auth-manager.png new file mode 100644 index 00000000000..96989f56da3 Binary files /dev/null and b/docs/images/screenshots/http-config/http-auth-manager.png differ diff --git a/docs/images/screenshots/http-config/http-cache-manager.png b/docs/images/screenshots/http-config/http-cache-manager.png new file mode 100644 index 00000000000..1ad53f2b662 Binary files /dev/null and b/docs/images/screenshots/http-config/http-cache-manager.png differ diff --git a/docs/images/screenshots/http-config/http-config-example.png b/docs/images/screenshots/http-config/http-config-example.png new file mode 100644 index 00000000000..bd292e8242c Binary files /dev/null and b/docs/images/screenshots/http-config/http-config-example.png differ diff --git a/docs/images/screenshots/http-config/http-cookie-manager.png b/docs/images/screenshots/http-config/http-cookie-manager.png new file mode 100644 index 00000000000..4b763a53a18 Binary files /dev/null and b/docs/images/screenshots/http-config/http-cookie-manager.png differ diff --git a/docs/images/screenshots/http-config/http-header-manager.png b/docs/images/screenshots/http-config/http-header-manager.png new file mode 100644 index 00000000000..bb311a816e0 Binary files /dev/null and b/docs/images/screenshots/http-config/http-header-manager.png differ diff --git a/docs/images/screenshots/http-config/http-request-defaults.png b/docs/images/screenshots/http-config/http-request-defaults.png new file mode 100644 index 00000000000..1a6c0e0accf Binary files /dev/null and b/docs/images/screenshots/http-config/http-request-defaults.png differ diff --git a/docs/images/screenshots/http-request-confirm-raw-body.png b/docs/images/screenshots/http-request-confirm-raw-body.png new file mode 100644 index 00000000000..ff4f7c0cb03 Binary files /dev/null and b/docs/images/screenshots/http-request-confirm-raw-body.png differ diff --git a/docs/images/screenshots/http-request-raw-body.png b/docs/images/screenshots/http-request-raw-body.png new file mode 100644 index 00000000000..09cae548f3c Binary files /dev/null and b/docs/images/screenshots/http-request-raw-body.png differ diff --git a/docs/images/screenshots/http-request-raw-single-parameter.png b/docs/images/screenshots/http-request-raw-single-parameter.png new file mode 100644 index 00000000000..f5816e5813b Binary files /dev/null and b/docs/images/screenshots/http-request-raw-single-parameter.png differ diff --git a/docs/images/screenshots/http-request.png b/docs/images/screenshots/http-request.png new file mode 100644 index 00000000000..1fef8cdbb13 Binary files /dev/null and b/docs/images/screenshots/http-request.png differ diff --git a/docs/images/screenshots/icons-22x22.jpg b/docs/images/screenshots/icons-22x22.jpg new file mode 100644 index 00000000000..f15cfb56c5a Binary files /dev/null and b/docs/images/screenshots/icons-22x22.jpg differ diff --git a/docs/images/screenshots/icons-32x32.jpg b/docs/images/screenshots/icons-32x32.jpg new file mode 100644 index 00000000000..0ad10246b8b Binary files /dev/null and b/docs/images/screenshots/icons-32x32.jpg differ diff --git a/docs/images/screenshots/icons-48x48.jpg b/docs/images/screenshots/icons-48x48.jpg new file mode 100644 index 00000000000..be82792ad3c Binary files /dev/null and b/docs/images/screenshots/icons-48x48.jpg differ diff --git a/docs/images/screenshots/ifcontroller.png b/docs/images/screenshots/ifcontroller.png new file mode 100644 index 00000000000..aa1db8aecfb Binary files /dev/null and b/docs/images/screenshots/ifcontroller.png differ diff --git a/docs/images/screenshots/includecontroller.png b/docs/images/screenshots/includecontroller.png new file mode 100644 index 00000000000..1430873d97a Binary files /dev/null and b/docs/images/screenshots/includecontroller.png differ diff --git a/docs/images/screenshots/java_defaults.png b/docs/images/screenshots/java_defaults.png new file mode 100644 index 00000000000..99781413fcc Binary files /dev/null and b/docs/images/screenshots/java_defaults.png differ diff --git a/docs/images/screenshots/java_request.png b/docs/images/screenshots/java_request.png new file mode 100644 index 00000000000..495a95546f5 Binary files /dev/null and b/docs/images/screenshots/java_request.png differ diff --git a/docs/images/screenshots/jdbc-config/jdbc-conn-config.png b/docs/images/screenshots/jdbc-config/jdbc-conn-config.png new file mode 100644 index 00000000000..b5461855c17 Binary files /dev/null and b/docs/images/screenshots/jdbc-config/jdbc-conn-config.png differ diff --git a/docs/images/screenshots/jdbc-config/jdbc-sql-query.png b/docs/images/screenshots/jdbc-config/jdbc-sql-query.png new file mode 100644 index 00000000000..45844059ae8 Binary files /dev/null and b/docs/images/screenshots/jdbc-config/jdbc-sql-query.png differ diff --git a/docs/images/screenshots/jdbc-post-processor.png b/docs/images/screenshots/jdbc-post-processor.png new file mode 100644 index 00000000000..6aa8da66090 Binary files /dev/null and b/docs/images/screenshots/jdbc-post-processor.png differ diff --git a/docs/images/screenshots/jdbc-pre-processor.png b/docs/images/screenshots/jdbc-pre-processor.png new file mode 100644 index 00000000000..3f520e3c62f Binary files /dev/null and b/docs/images/screenshots/jdbc-pre-processor.png differ diff --git a/docs/images/screenshots/jdbctest/JDBCRequest.png b/docs/images/screenshots/jdbctest/JDBCRequest.png new file mode 100644 index 00000000000..b578804f7c8 Binary files /dev/null and b/docs/images/screenshots/jdbctest/JDBCRequest.png differ diff --git a/docs/images/screenshots/jdbctest/JDBCRequest2.png b/docs/images/screenshots/jdbctest/JDBCRequest2.png new file mode 100644 index 00000000000..9181a802a92 Binary files /dev/null and b/docs/images/screenshots/jdbctest/JDBCRequest2.png differ diff --git a/docs/images/screenshots/jdbctest/JDBCRequest3.png b/docs/images/screenshots/jdbctest/JDBCRequest3.png new file mode 100644 index 00000000000..a8b00a9f5d4 Binary files /dev/null and b/docs/images/screenshots/jdbctest/JDBCRequest3.png differ diff --git a/docs/images/screenshots/jdbctest/jdbc-config.png b/docs/images/screenshots/jdbctest/jdbc-config.png new file mode 100644 index 00000000000..75ecf8aa9a5 Binary files /dev/null and b/docs/images/screenshots/jdbctest/jdbc-config.png differ diff --git a/docs/images/screenshots/jdbctest/jdbc-request.png b/docs/images/screenshots/jdbctest/jdbc-request.png new file mode 100644 index 00000000000..b4f5e5aef38 Binary files /dev/null and b/docs/images/screenshots/jdbctest/jdbc-request.png differ diff --git a/docs/images/screenshots/jdbctest/jdbc-results.png b/docs/images/screenshots/jdbctest/jdbc-results.png new file mode 100644 index 00000000000..9d5ca02a1ad Binary files /dev/null and b/docs/images/screenshots/jdbctest/jdbc-results.png differ diff --git a/docs/images/screenshots/jdbctest/threadgroup1.png b/docs/images/screenshots/jdbctest/threadgroup1.png new file mode 100644 index 00000000000..f6f66b0eb5d Binary files /dev/null and b/docs/images/screenshots/jdbctest/threadgroup1.png differ diff --git a/docs/images/screenshots/jdbctest/threadgroup2.png b/docs/images/screenshots/jdbctest/threadgroup2.png new file mode 100644 index 00000000000..fbcdea0f277 Binary files /dev/null and b/docs/images/screenshots/jdbctest/threadgroup2.png differ diff --git a/docs/images/screenshots/jms/JMS_Point-to-Point.png b/docs/images/screenshots/jms/JMS_Point-to-Point.png new file mode 100644 index 00000000000..8674c6fd2df Binary files /dev/null and b/docs/images/screenshots/jms/JMS_Point-to-Point.png differ diff --git a/docs/images/screenshots/jms/jms_config.png b/docs/images/screenshots/jms/jms_config.png new file mode 100644 index 00000000000..c46979c70f8 Binary files /dev/null and b/docs/images/screenshots/jms/jms_config.png differ diff --git a/docs/images/screenshots/jms/jms_messaging.png b/docs/images/screenshots/jms/jms_messaging.png new file mode 100644 index 00000000000..b8d08b148d1 Binary files /dev/null and b/docs/images/screenshots/jms/jms_messaging.png differ diff --git a/docs/images/screenshots/jms/jms_pub.png b/docs/images/screenshots/jms/jms_pub.png new file mode 100644 index 00000000000..8ae48daddc2 Binary files /dev/null and b/docs/images/screenshots/jms/jms_pub.png differ diff --git a/docs/images/screenshots/jms/jms_sub.png b/docs/images/screenshots/jms/jms_sub.png new file mode 100644 index 00000000000..533fd2292fe Binary files /dev/null and b/docs/images/screenshots/jms/jms_sub.png differ diff --git a/docs/images/screenshots/jmspublisher.png b/docs/images/screenshots/jmspublisher.png new file mode 100644 index 00000000000..0476282b4bb Binary files /dev/null and b/docs/images/screenshots/jmspublisher.png differ diff --git a/docs/images/screenshots/jmssubscriber.png b/docs/images/screenshots/jmssubscriber.png new file mode 100644 index 00000000000..916dfded867 Binary files /dev/null and b/docs/images/screenshots/jmssubscriber.png differ diff --git a/docs/images/screenshots/jsr223-sampler.png b/docs/images/screenshots/jsr223-sampler.png new file mode 100644 index 00000000000..41552c4c57d Binary files /dev/null and b/docs/images/screenshots/jsr223-sampler.png differ diff --git a/docs/images/screenshots/junit_sampler.png b/docs/images/screenshots/junit_sampler.png new file mode 100644 index 00000000000..dc441cec7eb Binary files /dev/null and b/docs/images/screenshots/junit_sampler.png differ diff --git a/docs/images/screenshots/keystore_config.png b/docs/images/screenshots/keystore_config.png new file mode 100644 index 00000000000..05fec53c885 Binary files /dev/null and b/docs/images/screenshots/keystore_config.png differ diff --git a/docs/images/screenshots/ldap_defaults.png b/docs/images/screenshots/ldap_defaults.png new file mode 100644 index 00000000000..56dd18740b5 Binary files /dev/null and b/docs/images/screenshots/ldap_defaults.png differ diff --git a/docs/images/screenshots/ldap_request.png b/docs/images/screenshots/ldap_request.png new file mode 100644 index 00000000000..9d791a5d81b Binary files /dev/null and b/docs/images/screenshots/ldap_request.png differ diff --git a/docs/images/screenshots/ldapext_defaults.png b/docs/images/screenshots/ldapext_defaults.png new file mode 100644 index 00000000000..b9877fcf23b Binary files /dev/null and b/docs/images/screenshots/ldapext_defaults.png differ diff --git a/docs/images/screenshots/ldapext_request.png b/docs/images/screenshots/ldapext_request.png new file mode 100644 index 00000000000..29b8e248939 Binary files /dev/null and b/docs/images/screenshots/ldapext_request.png differ diff --git a/docs/images/screenshots/ldaptest/add.png b/docs/images/screenshots/ldaptest/add.png new file mode 100644 index 00000000000..7da9d1e0e43 Binary files /dev/null and b/docs/images/screenshots/ldaptest/add.png differ diff --git a/docs/images/screenshots/ldaptest/delete.png b/docs/images/screenshots/ldaptest/delete.png new file mode 100644 index 00000000000..c7e12b7eefa Binary files /dev/null and b/docs/images/screenshots/ldaptest/delete.png differ diff --git a/docs/images/screenshots/ldaptest/extadd.png b/docs/images/screenshots/ldaptest/extadd.png new file mode 100644 index 00000000000..40c1cd12c63 Binary files /dev/null and b/docs/images/screenshots/ldaptest/extadd.png differ diff --git a/docs/images/screenshots/ldaptest/extcompare.png b/docs/images/screenshots/ldaptest/extcompare.png new file mode 100644 index 00000000000..19912435906 Binary files /dev/null and b/docs/images/screenshots/ldaptest/extcompare.png differ diff --git a/docs/images/screenshots/ldaptest/extdel.png b/docs/images/screenshots/ldaptest/extdel.png new file mode 100644 index 00000000000..685882ad8b5 Binary files /dev/null and b/docs/images/screenshots/ldaptest/extdel.png differ diff --git a/docs/images/screenshots/ldaptest/extmod.png b/docs/images/screenshots/ldaptest/extmod.png new file mode 100644 index 00000000000..1797467a636 Binary files /dev/null and b/docs/images/screenshots/ldaptest/extmod.png differ diff --git a/docs/images/screenshots/ldaptest/extmoddn.png b/docs/images/screenshots/ldaptest/extmoddn.png new file mode 100644 index 00000000000..b7f6529d71e Binary files /dev/null and b/docs/images/screenshots/ldaptest/extmoddn.png differ diff --git a/docs/images/screenshots/ldaptest/extrequestdefaults.png b/docs/images/screenshots/ldaptest/extrequestdefaults.png new file mode 100644 index 00000000000..101a9b01425 Binary files /dev/null and b/docs/images/screenshots/ldaptest/extrequestdefaults.png differ diff --git a/docs/images/screenshots/ldaptest/extsbind.png b/docs/images/screenshots/ldaptest/extsbind.png new file mode 100644 index 00000000000..5879637995e Binary files /dev/null and b/docs/images/screenshots/ldaptest/extsbind.png differ diff --git a/docs/images/screenshots/ldaptest/extsearch.png b/docs/images/screenshots/ldaptest/extsearch.png new file mode 100644 index 00000000000..13b4d51d8ab Binary files /dev/null and b/docs/images/screenshots/ldaptest/extsearch.png differ diff --git a/docs/images/screenshots/ldaptest/extthreadbind.png b/docs/images/screenshots/ldaptest/extthreadbind.png new file mode 100644 index 00000000000..be5abae50b3 Binary files /dev/null and b/docs/images/screenshots/ldaptest/extthreadbind.png differ diff --git a/docs/images/screenshots/ldaptest/extthreadgroup.png b/docs/images/screenshots/ldaptest/extthreadgroup.png new file mode 100644 index 00000000000..1e496d5ee5e Binary files /dev/null and b/docs/images/screenshots/ldaptest/extthreadgroup.png differ diff --git a/docs/images/screenshots/ldaptest/extthreadunbind.png b/docs/images/screenshots/ldaptest/extthreadunbind.png new file mode 100644 index 00000000000..2e7b172d7f0 Binary files /dev/null and b/docs/images/screenshots/ldaptest/extthreadunbind.png differ diff --git a/docs/images/screenshots/ldaptest/extviewtree.png b/docs/images/screenshots/ldaptest/extviewtree.png new file mode 100644 index 00000000000..3427f83b1bd Binary files /dev/null and b/docs/images/screenshots/ldaptest/extviewtree.png differ diff --git a/docs/images/screenshots/ldaptest/login-config-element.png b/docs/images/screenshots/ldaptest/login-config-element.png new file mode 100644 index 00000000000..f39053906c4 Binary files /dev/null and b/docs/images/screenshots/ldaptest/login-config-element.png differ diff --git a/docs/images/screenshots/ldaptest/modify.png b/docs/images/screenshots/ldaptest/modify.png new file mode 100644 index 00000000000..a21a367c5f9 Binary files /dev/null and b/docs/images/screenshots/ldaptest/modify.png differ diff --git a/docs/images/screenshots/ldaptest/requestdefaults.png b/docs/images/screenshots/ldaptest/requestdefaults.png new file mode 100644 index 00000000000..2f4826fb83a Binary files /dev/null and b/docs/images/screenshots/ldaptest/requestdefaults.png differ diff --git a/docs/images/screenshots/ldaptest/responseassertion.png b/docs/images/screenshots/ldaptest/responseassertion.png new file mode 100644 index 00000000000..a3c9117eb14 Binary files /dev/null and b/docs/images/screenshots/ldaptest/responseassertion.png differ diff --git a/docs/images/screenshots/ldaptest/search.png b/docs/images/screenshots/ldaptest/search.png new file mode 100644 index 00000000000..eeb77a8b778 Binary files /dev/null and b/docs/images/screenshots/ldaptest/search.png differ diff --git a/docs/images/screenshots/ldaptest/threadgroup.png b/docs/images/screenshots/ldaptest/threadgroup.png new file mode 100644 index 00000000000..0b0f1266228 Binary files /dev/null and b/docs/images/screenshots/ldaptest/threadgroup.png differ diff --git a/docs/images/screenshots/ldaptest/viewtable.png b/docs/images/screenshots/ldaptest/viewtable.png new file mode 100644 index 00000000000..78028e7899f Binary files /dev/null and b/docs/images/screenshots/ldaptest/viewtable.png differ diff --git a/docs/images/screenshots/log_errors_counter.png b/docs/images/screenshots/log_errors_counter.png new file mode 100644 index 00000000000..83de4757dd5 Binary files /dev/null and b/docs/images/screenshots/log_errors_counter.png differ diff --git a/docs/images/screenshots/logic-controller/critical-section-controller-tp.png b/docs/images/screenshots/logic-controller/critical-section-controller-tp.png new file mode 100644 index 00000000000..6ba1ccde18b Binary files /dev/null and b/docs/images/screenshots/logic-controller/critical-section-controller-tp.png differ diff --git a/docs/images/screenshots/logic-controller/critical-section-controller.png b/docs/images/screenshots/logic-controller/critical-section-controller.png new file mode 100644 index 00000000000..e2c0de5f15b Binary files /dev/null and b/docs/images/screenshots/logic-controller/critical-section-controller.png differ diff --git a/docs/images/screenshots/logic-controller/foreach-controller.png b/docs/images/screenshots/logic-controller/foreach-controller.png new file mode 100644 index 00000000000..ac21768c485 Binary files /dev/null and b/docs/images/screenshots/logic-controller/foreach-controller.png differ diff --git a/docs/images/screenshots/logic-controller/foreach-example.png b/docs/images/screenshots/logic-controller/foreach-example.png new file mode 100644 index 00000000000..0a80fbd8367 Binary files /dev/null and b/docs/images/screenshots/logic-controller/foreach-example.png differ diff --git a/docs/images/screenshots/logic-controller/foreach-example2.png b/docs/images/screenshots/logic-controller/foreach-example2.png new file mode 100644 index 00000000000..b3d2fb7af24 Binary files /dev/null and b/docs/images/screenshots/logic-controller/foreach-example2.png differ diff --git a/docs/images/screenshots/logic-controller/interleave-controller.png b/docs/images/screenshots/logic-controller/interleave-controller.png new file mode 100644 index 00000000000..19d3d9d265b Binary files /dev/null and b/docs/images/screenshots/logic-controller/interleave-controller.png differ diff --git a/docs/images/screenshots/logic-controller/interleave.png b/docs/images/screenshots/logic-controller/interleave.png new file mode 100644 index 00000000000..6f8c9e873c4 Binary files /dev/null and b/docs/images/screenshots/logic-controller/interleave.png differ diff --git a/docs/images/screenshots/logic-controller/interleave2.png b/docs/images/screenshots/logic-controller/interleave2.png new file mode 100644 index 00000000000..b57e908d6a9 Binary files /dev/null and b/docs/images/screenshots/logic-controller/interleave2.png differ diff --git a/docs/images/screenshots/logic-controller/interleave3.png b/docs/images/screenshots/logic-controller/interleave3.png new file mode 100644 index 00000000000..05010350626 Binary files /dev/null and b/docs/images/screenshots/logic-controller/interleave3.png differ diff --git a/docs/images/screenshots/logic-controller/loop-controller.png b/docs/images/screenshots/logic-controller/loop-controller.png new file mode 100644 index 00000000000..bc745b41a57 Binary files /dev/null and b/docs/images/screenshots/logic-controller/loop-controller.png differ diff --git a/docs/images/screenshots/logic-controller/loop-example.png b/docs/images/screenshots/logic-controller/loop-example.png new file mode 100644 index 00000000000..7a9efa8d92f Binary files /dev/null and b/docs/images/screenshots/logic-controller/loop-example.png differ diff --git a/docs/images/screenshots/logic-controller/once-only-controller.png b/docs/images/screenshots/logic-controller/once-only-controller.png new file mode 100644 index 00000000000..827779e9e0b Binary files /dev/null and b/docs/images/screenshots/logic-controller/once-only-controller.png differ diff --git a/docs/images/screenshots/logic-controller/once-only-example.png b/docs/images/screenshots/logic-controller/once-only-example.png new file mode 100644 index 00000000000..d233c0dbcfd Binary files /dev/null and b/docs/images/screenshots/logic-controller/once-only-example.png differ diff --git a/docs/images/screenshots/logic-controller/random-controller.png b/docs/images/screenshots/logic-controller/random-controller.png new file mode 100644 index 00000000000..b155c07dc17 Binary files /dev/null and b/docs/images/screenshots/logic-controller/random-controller.png differ diff --git a/docs/images/screenshots/logic-controller/recording-controller.png b/docs/images/screenshots/logic-controller/recording-controller.png new file mode 100644 index 00000000000..4c856a3a094 Binary files /dev/null and b/docs/images/screenshots/logic-controller/recording-controller.png differ diff --git a/docs/images/screenshots/logic-controller/simple-controller.png b/docs/images/screenshots/logic-controller/simple-controller.png new file mode 100644 index 00000000000..ed64774ca76 Binary files /dev/null and b/docs/images/screenshots/logic-controller/simple-controller.png differ diff --git a/docs/images/screenshots/logic-controller/simple-example.png b/docs/images/screenshots/logic-controller/simple-example.png new file mode 100644 index 00000000000..23abbc1612b Binary files /dev/null and b/docs/images/screenshots/logic-controller/simple-example.png differ diff --git a/docs/images/screenshots/login-config.png b/docs/images/screenshots/login-config.png new file mode 100644 index 00000000000..3a1d42b5315 Binary files /dev/null and b/docs/images/screenshots/login-config.png differ diff --git a/docs/images/screenshots/mailervisualizer.png b/docs/images/screenshots/mailervisualizer.png new file mode 100644 index 00000000000..9d2b293cb4f Binary files /dev/null and b/docs/images/screenshots/mailervisualizer.png differ diff --git a/docs/images/screenshots/mailreader_sampler.png b/docs/images/screenshots/mailreader_sampler.png new file mode 100644 index 00000000000..befee6f4199 Binary files /dev/null and b/docs/images/screenshots/mailreader_sampler.png differ diff --git a/docs/images/screenshots/mirrorserver.png b/docs/images/screenshots/mirrorserver.png new file mode 100644 index 00000000000..628c04148b8 Binary files /dev/null and b/docs/images/screenshots/mirrorserver.png differ diff --git a/docs/images/screenshots/modification.png b/docs/images/screenshots/modification.png new file mode 100644 index 00000000000..69c863491ca Binary files /dev/null and b/docs/images/screenshots/modification.png differ diff --git a/docs/images/screenshots/module_controller.png b/docs/images/screenshots/module_controller.png new file mode 100644 index 00000000000..25f7682fd03 Binary files /dev/null and b/docs/images/screenshots/module_controller.png differ diff --git a/docs/images/screenshots/mongodb-script.png b/docs/images/screenshots/mongodb-script.png new file mode 100644 index 00000000000..6185025a1b3 Binary files /dev/null and b/docs/images/screenshots/mongodb-script.png differ diff --git a/docs/images/screenshots/mongodb-source-config.png b/docs/images/screenshots/mongodb-source-config.png new file mode 100644 index 00000000000..45b1c495ff2 Binary files /dev/null and b/docs/images/screenshots/mongodb-source-config.png differ diff --git a/docs/images/screenshots/monitor_health.png b/docs/images/screenshots/monitor_health.png new file mode 100644 index 00000000000..bf529949f29 Binary files /dev/null and b/docs/images/screenshots/monitor_health.png differ diff --git a/docs/images/screenshots/monitor_screencap.png b/docs/images/screenshots/monitor_screencap.png new file mode 100644 index 00000000000..9a756913da9 Binary files /dev/null and b/docs/images/screenshots/monitor_screencap.png differ diff --git a/docs/images/screenshots/os_process_sampler.png b/docs/images/screenshots/os_process_sampler.png new file mode 100644 index 00000000000..ce7da764533 Binary files /dev/null and b/docs/images/screenshots/os_process_sampler.png differ diff --git a/docs/images/screenshots/parameter_mask.png b/docs/images/screenshots/parameter_mask.png new file mode 100644 index 00000000000..43cff90ec56 Binary files /dev/null and b/docs/images/screenshots/parameter_mask.png differ diff --git a/docs/images/screenshots/property_display.png b/docs/images/screenshots/property_display.png new file mode 100644 index 00000000000..f2bd258f583 Binary files /dev/null and b/docs/images/screenshots/property_display.png differ diff --git a/docs/images/screenshots/proxy_control.png b/docs/images/screenshots/proxy_control.png new file mode 100644 index 00000000000..abe66eac448 Binary files /dev/null and b/docs/images/screenshots/proxy_control.png differ diff --git a/docs/images/screenshots/random_variable.png b/docs/images/screenshots/random_variable.png new file mode 100644 index 00000000000..ce2ce14dd92 Binary files /dev/null and b/docs/images/screenshots/random_variable.png differ diff --git a/docs/images/screenshots/randomordercontroller.png b/docs/images/screenshots/randomordercontroller.png new file mode 100644 index 00000000000..ed977f79af9 Binary files /dev/null and b/docs/images/screenshots/randomordercontroller.png differ diff --git a/docs/images/screenshots/regex_extractor.png b/docs/images/screenshots/regex_extractor.png new file mode 100644 index 00000000000..8ab6d33835a Binary files /dev/null and b/docs/images/screenshots/regex_extractor.png differ diff --git a/docs/images/screenshots/regex_user_params.png b/docs/images/screenshots/regex_user_params.png new file mode 100644 index 00000000000..5149736923a Binary files /dev/null and b/docs/images/screenshots/regex_user_params.png differ diff --git a/docs/images/screenshots/remote/run-menu00.gif b/docs/images/screenshots/remote/run-menu00.gif new file mode 100644 index 00000000000..3a5effe2d21 Binary files /dev/null and b/docs/images/screenshots/remote/run-menu00.gif differ diff --git a/docs/images/screenshots/response_time_graph.png b/docs/images/screenshots/response_time_graph.png new file mode 100644 index 00000000000..667267247dd Binary files /dev/null and b/docs/images/screenshots/response_time_graph.png differ diff --git a/docs/images/screenshots/response_time_graph_settings.png b/docs/images/screenshots/response_time_graph_settings.png new file mode 100644 index 00000000000..cf06ec3c1b1 Binary files /dev/null and b/docs/images/screenshots/response_time_graph_settings.png differ diff --git a/docs/images/screenshots/resultstatusactionhandler.png b/docs/images/screenshots/resultstatusactionhandler.png new file mode 100644 index 00000000000..8a793297435 Binary files /dev/null and b/docs/images/screenshots/resultstatusactionhandler.png differ diff --git a/docs/images/screenshots/runtimecontroller.png b/docs/images/screenshots/runtimecontroller.png new file mode 100644 index 00000000000..2ce8839afe6 Binary files /dev/null and b/docs/images/screenshots/runtimecontroller.png differ diff --git a/docs/images/screenshots/sample_result_config.png b/docs/images/screenshots/sample_result_config.png new file mode 100644 index 00000000000..6ee98d8aa8b Binary files /dev/null and b/docs/images/screenshots/sample_result_config.png differ diff --git a/docs/images/screenshots/save_image.png b/docs/images/screenshots/save_image.png new file mode 100644 index 00000000000..5e53322d8e6 Binary files /dev/null and b/docs/images/screenshots/save_image.png differ diff --git a/docs/images/screenshots/savetofile.png b/docs/images/screenshots/savetofile.png new file mode 100644 index 00000000000..9c260d39f78 Binary files /dev/null and b/docs/images/screenshots/savetofile.png differ diff --git a/docs/images/screenshots/scoping1.png b/docs/images/screenshots/scoping1.png new file mode 100644 index 00000000000..0a58fdc5a17 Binary files /dev/null and b/docs/images/screenshots/scoping1.png differ diff --git a/docs/images/screenshots/scoping2.png b/docs/images/screenshots/scoping2.png new file mode 100644 index 00000000000..4c5a78a3fd0 Binary files /dev/null and b/docs/images/screenshots/scoping2.png differ diff --git a/docs/images/screenshots/scoping3.png b/docs/images/screenshots/scoping3.png new file mode 100644 index 00000000000..4ada01e061a Binary files /dev/null and b/docs/images/screenshots/scoping3.png differ diff --git a/docs/images/screenshots/searching/raw-search-result.png b/docs/images/screenshots/searching/raw-search-result.png new file mode 100644 index 00000000000..786ec5f9996 Binary files /dev/null and b/docs/images/screenshots/searching/raw-search-result.png differ diff --git a/docs/images/screenshots/searching/raw-search.png b/docs/images/screenshots/searching/raw-search.png new file mode 100644 index 00000000000..1fc250837e7 Binary files /dev/null and b/docs/images/screenshots/searching/raw-search.png differ diff --git a/docs/images/screenshots/searching/regexp-search-result.png b/docs/images/screenshots/searching/regexp-search-result.png new file mode 100644 index 00000000000..193597f2883 Binary files /dev/null and b/docs/images/screenshots/searching/regexp-search-result.png differ diff --git a/docs/images/screenshots/searching/regexp-search.png b/docs/images/screenshots/searching/regexp-search.png new file mode 100644 index 00000000000..d9e99de83d9 Binary files /dev/null and b/docs/images/screenshots/searching/regexp-search.png differ diff --git a/docs/images/screenshots/setup_thread_group.png b/docs/images/screenshots/setup_thread_group.png new file mode 100644 index 00000000000..97d3be6e55e Binary files /dev/null and b/docs/images/screenshots/setup_thread_group.png differ diff --git a/docs/images/screenshots/simple_config_element.png b/docs/images/screenshots/simple_config_element.png new file mode 100644 index 00000000000..7d88917c714 Binary files /dev/null and b/docs/images/screenshots/simple_config_element.png differ diff --git a/docs/images/screenshots/simpledatawriter.png b/docs/images/screenshots/simpledatawriter.png new file mode 100644 index 00000000000..060e67eca2e Binary files /dev/null and b/docs/images/screenshots/simpledatawriter.png differ diff --git a/docs/images/screenshots/size_assertion.png b/docs/images/screenshots/size_assertion.png new file mode 100644 index 00000000000..860d4165389 Binary files /dev/null and b/docs/images/screenshots/size_assertion.png differ diff --git a/docs/images/screenshots/smtp_sampler.png b/docs/images/screenshots/smtp_sampler.png new file mode 100644 index 00000000000..9f1821785b4 Binary files /dev/null and b/docs/images/screenshots/smtp_sampler.png differ diff --git a/docs/images/screenshots/soap_sampler.png b/docs/images/screenshots/soap_sampler.png new file mode 100644 index 00000000000..01285630c18 Binary files /dev/null and b/docs/images/screenshots/soap_sampler.png differ diff --git a/docs/images/screenshots/spline_visualizer.png b/docs/images/screenshots/spline_visualizer.png new file mode 100644 index 00000000000..5e19a96f677 Binary files /dev/null and b/docs/images/screenshots/spline_visualizer.png differ diff --git a/docs/images/screenshots/summary.png b/docs/images/screenshots/summary.png new file mode 100644 index 00000000000..03ca2f00e74 Binary files /dev/null and b/docs/images/screenshots/summary.png differ diff --git a/docs/images/screenshots/summary_report.png b/docs/images/screenshots/summary_report.png new file mode 100644 index 00000000000..32925461089 Binary files /dev/null and b/docs/images/screenshots/summary_report.png differ diff --git a/docs/images/screenshots/summary_report_grouped.png b/docs/images/screenshots/summary_report_grouped.png new file mode 100644 index 00000000000..850e2e7372d Binary files /dev/null and b/docs/images/screenshots/summary_report_grouped.png differ diff --git a/docs/images/screenshots/switchcontroller.png b/docs/images/screenshots/switchcontroller.png new file mode 100644 index 00000000000..1762bd2514d Binary files /dev/null and b/docs/images/screenshots/switchcontroller.png differ diff --git a/docs/images/screenshots/table_results.png b/docs/images/screenshots/table_results.png new file mode 100644 index 00000000000..5a95814d9e5 Binary files /dev/null and b/docs/images/screenshots/table_results.png differ diff --git a/docs/images/screenshots/tcpsampler.png b/docs/images/screenshots/tcpsampler.png new file mode 100644 index 00000000000..619baa8d1f0 Binary files /dev/null and b/docs/images/screenshots/tcpsampler.png differ diff --git a/docs/images/screenshots/tcpsamplerconfig.png b/docs/images/screenshots/tcpsamplerconfig.png new file mode 100644 index 00000000000..874dce32828 Binary files /dev/null and b/docs/images/screenshots/tcpsamplerconfig.png differ diff --git a/docs/images/screenshots/tear_down_on_shutdown.png b/docs/images/screenshots/tear_down_on_shutdown.png new file mode 100644 index 00000000000..6708201a1ae Binary files /dev/null and b/docs/images/screenshots/tear_down_on_shutdown.png differ diff --git a/docs/images/screenshots/teardown_thread_group.png b/docs/images/screenshots/teardown_thread_group.png new file mode 100644 index 00000000000..8a5f66f787d Binary files /dev/null and b/docs/images/screenshots/teardown_thread_group.png differ diff --git a/docs/images/screenshots/template_menu.png b/docs/images/screenshots/template_menu.png new file mode 100644 index 00000000000..85952872bfc Binary files /dev/null and b/docs/images/screenshots/template_menu.png differ diff --git a/docs/images/screenshots/template_wizard.png b/docs/images/screenshots/template_wizard.png new file mode 100644 index 00000000000..50a39c7b794 Binary files /dev/null and b/docs/images/screenshots/template_wizard.png differ diff --git a/docs/images/screenshots/test_action.png b/docs/images/screenshots/test_action.png new file mode 100644 index 00000000000..ef250c636d8 Binary files /dev/null and b/docs/images/screenshots/test_action.png differ diff --git a/docs/images/screenshots/test_fragment.png b/docs/images/screenshots/test_fragment.png new file mode 100644 index 00000000000..898c925d923 Binary files /dev/null and b/docs/images/screenshots/test_fragment.png differ diff --git a/docs/images/screenshots/testplan.png b/docs/images/screenshots/testplan.png new file mode 100644 index 00000000000..48f2dceaf43 Binary files /dev/null and b/docs/images/screenshots/testplan.png differ diff --git a/docs/images/screenshots/threadgroup.png b/docs/images/screenshots/threadgroup.png new file mode 100644 index 00000000000..a577ad31b02 Binary files /dev/null and b/docs/images/screenshots/threadgroup.png differ diff --git a/docs/images/screenshots/throughput_controller.png b/docs/images/screenshots/throughput_controller.png new file mode 100644 index 00000000000..5a4a2a1a4a6 Binary files /dev/null and b/docs/images/screenshots/throughput_controller.png differ diff --git a/docs/images/screenshots/timers/beanshell_timer.png b/docs/images/screenshots/timers/beanshell_timer.png new file mode 100644 index 00000000000..89bef46cba5 Binary files /dev/null and b/docs/images/screenshots/timers/beanshell_timer.png differ diff --git a/docs/images/screenshots/timers/bsf_timer.png b/docs/images/screenshots/timers/bsf_timer.png new file mode 100644 index 00000000000..2795881ab95 Binary files /dev/null and b/docs/images/screenshots/timers/bsf_timer.png differ diff --git a/docs/images/screenshots/timers/constant_throughput_timer.png b/docs/images/screenshots/timers/constant_throughput_timer.png new file mode 100644 index 00000000000..3fa5cdd69f3 Binary files /dev/null and b/docs/images/screenshots/timers/constant_throughput_timer.png differ diff --git a/docs/images/screenshots/timers/constant_timer.png b/docs/images/screenshots/timers/constant_timer.png new file mode 100644 index 00000000000..709a33b1f08 Binary files /dev/null and b/docs/images/screenshots/timers/constant_timer.png differ diff --git a/docs/images/screenshots/timers/gauss_random_timer.png b/docs/images/screenshots/timers/gauss_random_timer.png new file mode 100644 index 00000000000..f12b2cb4081 Binary files /dev/null and b/docs/images/screenshots/timers/gauss_random_timer.png differ diff --git a/docs/images/screenshots/timers/poisson_random_timer.png b/docs/images/screenshots/timers/poisson_random_timer.png new file mode 100644 index 00000000000..ba08e90d0d8 Binary files /dev/null and b/docs/images/screenshots/timers/poisson_random_timer.png differ diff --git a/docs/images/screenshots/timers/sync_timer.png b/docs/images/screenshots/timers/sync_timer.png new file mode 100644 index 00000000000..0918539a35a Binary files /dev/null and b/docs/images/screenshots/timers/sync_timer.png differ diff --git a/docs/images/screenshots/timers/uniform_random_timer.png b/docs/images/screenshots/timers/uniform_random_timer.png new file mode 100644 index 00000000000..bb41346b875 Binary files /dev/null and b/docs/images/screenshots/timers/uniform_random_timer.png differ diff --git a/docs/images/screenshots/transactioncontroller.png b/docs/images/screenshots/transactioncontroller.png new file mode 100644 index 00000000000..0448e100aee Binary files /dev/null and b/docs/images/screenshots/transactioncontroller.png differ diff --git a/docs/images/screenshots/url_rewrite_example_a.png b/docs/images/screenshots/url_rewrite_example_a.png new file mode 100644 index 00000000000..aed0fa51d72 Binary files /dev/null and b/docs/images/screenshots/url_rewrite_example_a.png differ diff --git a/docs/images/screenshots/url_rewrite_example_b.gif b/docs/images/screenshots/url_rewrite_example_b.gif new file mode 100644 index 00000000000..b4115e5cfe6 Binary files /dev/null and b/docs/images/screenshots/url_rewrite_example_b.gif differ diff --git a/docs/images/screenshots/url_rewrite_example_b.png b/docs/images/screenshots/url_rewrite_example_b.png new file mode 100644 index 00000000000..f5e12584560 Binary files /dev/null and b/docs/images/screenshots/url_rewrite_example_b.png differ diff --git a/docs/images/screenshots/url_rewriter.png b/docs/images/screenshots/url_rewriter.png new file mode 100644 index 00000000000..ecf6dde2f5b Binary files /dev/null and b/docs/images/screenshots/url_rewriter.png differ diff --git a/docs/images/screenshots/user_defined_variables.png b/docs/images/screenshots/user_defined_variables.png new file mode 100644 index 00000000000..cb5c9f5c32f Binary files /dev/null and b/docs/images/screenshots/user_defined_variables.png differ diff --git a/docs/images/screenshots/user_param_modifier.gif b/docs/images/screenshots/user_param_modifier.gif new file mode 100644 index 00000000000..782ccf79165 Binary files /dev/null and b/docs/images/screenshots/user_param_modifier.gif differ diff --git a/docs/images/screenshots/user_params.png b/docs/images/screenshots/user_params.png new file mode 100644 index 00000000000..1be8b825200 Binary files /dev/null and b/docs/images/screenshots/user_params.png differ diff --git a/docs/images/screenshots/view_results_tree.png b/docs/images/screenshots/view_results_tree.png new file mode 100644 index 00000000000..18c7f7db87a Binary files /dev/null and b/docs/images/screenshots/view_results_tree.png differ diff --git a/docs/images/screenshots/view_results_tree_document.png b/docs/images/screenshots/view_results_tree_document.png new file mode 100644 index 00000000000..d34009fdb48 Binary files /dev/null and b/docs/images/screenshots/view_results_tree_document.png differ diff --git a/docs/images/screenshots/view_results_tree_regex.png b/docs/images/screenshots/view_results_tree_regex.png new file mode 100644 index 00000000000..ede666ef3ea Binary files /dev/null and b/docs/images/screenshots/view_results_tree_regex.png differ diff --git a/docs/images/screenshots/view_results_tree_xml.png b/docs/images/screenshots/view_results_tree_xml.png new file mode 100644 index 00000000000..4e4d682db12 Binary files /dev/null and b/docs/images/screenshots/view_results_tree_xml.png differ diff --git a/docs/images/screenshots/webservice_sampler.png b/docs/images/screenshots/webservice_sampler.png new file mode 100644 index 00000000000..8f85196e020 Binary files /dev/null and b/docs/images/screenshots/webservice_sampler.png differ diff --git a/docs/images/screenshots/webtest/http-defaults1.png b/docs/images/screenshots/webtest/http-defaults1.png new file mode 100644 index 00000000000..9372f0e3168 Binary files /dev/null and b/docs/images/screenshots/webtest/http-defaults1.png differ diff --git a/docs/images/screenshots/webtest/http-defaults2.png b/docs/images/screenshots/webtest/http-defaults2.png new file mode 100644 index 00000000000..9880fa8af05 Binary files /dev/null and b/docs/images/screenshots/webtest/http-defaults2.png differ diff --git a/docs/images/screenshots/webtest/http-request1.png b/docs/images/screenshots/webtest/http-request1.png new file mode 100644 index 00000000000..7d9c3fae0bd Binary files /dev/null and b/docs/images/screenshots/webtest/http-request1.png differ diff --git a/docs/images/screenshots/webtest/http-request2.png b/docs/images/screenshots/webtest/http-request2.png new file mode 100644 index 00000000000..e17f33beb17 Binary files /dev/null and b/docs/images/screenshots/webtest/http-request2.png differ diff --git a/docs/images/screenshots/webtest/http_login.png b/docs/images/screenshots/webtest/http_login.png new file mode 100644 index 00000000000..fb2e7e88bc0 Binary files /dev/null and b/docs/images/screenshots/webtest/http_login.png differ diff --git a/docs/images/screenshots/webtest/threadgroup.png b/docs/images/screenshots/webtest/threadgroup.png new file mode 100644 index 00000000000..6c8d821091d Binary files /dev/null and b/docs/images/screenshots/webtest/threadgroup.png differ diff --git a/docs/images/screenshots/webtest/threadgroup2.png b/docs/images/screenshots/webtest/threadgroup2.png new file mode 100644 index 00000000000..57c72268a84 Binary files /dev/null and b/docs/images/screenshots/webtest/threadgroup2.png differ diff --git a/docs/images/screenshots/whilecontroller.png b/docs/images/screenshots/whilecontroller.png new file mode 100644 index 00000000000..76b5aa49276 Binary files /dev/null and b/docs/images/screenshots/whilecontroller.png differ diff --git a/docs/images/screenshots/workbench.png b/docs/images/screenshots/workbench.png new file mode 100644 index 00000000000..67e4d87f6cf Binary files /dev/null and b/docs/images/screenshots/workbench.png differ diff --git a/docs/images/screenshots/ws_header.png b/docs/images/screenshots/ws_header.png new file mode 100644 index 00000000000..c2c131ee1e6 Binary files /dev/null and b/docs/images/screenshots/ws_header.png differ diff --git a/docs/images/screenshots/ws_http_request.png b/docs/images/screenshots/ws_http_request.png new file mode 100644 index 00000000000..2178bc3fa59 Binary files /dev/null and b/docs/images/screenshots/ws_http_request.png differ diff --git a/docs/images/screenshots/ws_listener.png b/docs/images/screenshots/ws_listener.png new file mode 100644 index 00000000000..0953af64d97 Binary files /dev/null and b/docs/images/screenshots/ws_listener.png differ diff --git a/docs/images/screenshots/ws_template.png b/docs/images/screenshots/ws_template.png new file mode 100644 index 00000000000..f547f373649 Binary files /dev/null and b/docs/images/screenshots/ws_template.png differ diff --git a/docs/images/screenshots/xml_assertion.png b/docs/images/screenshots/xml_assertion.png new file mode 100644 index 00000000000..f7656323358 Binary files /dev/null and b/docs/images/screenshots/xml_assertion.png differ diff --git a/docs/images/screenshots/xpath_assertion.png b/docs/images/screenshots/xpath_assertion.png new file mode 100644 index 00000000000..fe27e8c522f Binary files /dev/null and b/docs/images/screenshots/xpath_assertion.png differ diff --git a/docs/images/screenshots/xpath_extractor.png b/docs/images/screenshots/xpath_extractor.png new file mode 100644 index 00000000000..a9735e37288 Binary files /dev/null and b/docs/images/screenshots/xpath_extractor.png differ diff --git a/docs/images/twitter.png b/docs/images/twitter.png new file mode 100644 index 00000000000..8256d044a0a Binary files /dev/null and b/docs/images/twitter.png differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000000..d88b1bb1c5a --- /dev/null +++ b/docs/index.html @@ -0,0 +1,127 @@ + +Apache JMeter + - + Apache JMeter™
Logo ASF
Apache JMeter

Apache JMeter™

+

+ The Apache JMeter™ application is open source software, + a 100% pure Java application designed + to load test functional behavior and measure performance. It was + originally designed for testing Web Applications but has + since expanded to other test functions. +

+

What can I do with it?

+

+ Apache JMeter may be used to test performance both on static and dynamic + resources (Webservices (SOAP/REST), Web dynamic languages - PHP, Java, ASP.NET, Files, etc. -, Java Objects, Data Bases and + Queries, FTP Servers and more). It can be used to simulate a heavy +load on a server, group of servers, network or object to test its strength or to analyze +overall performance under different load types. You can use it to make a +graphical analysis of performance or to test your server/script/object +behavior under heavy concurrent load. +

+

What does it do?

+

Apache JMeter features include:

+
    +
  • Ability to load and performance test many different server/protocol types: +
      +
    • Web - HTTP, HTTPS
    • +
    • SOAP / REST
    • +
    • FTP
    • +
    • Database via JDBC
    • +
    • LDAP
    • +
    • Message-oriented middleware (MOM) via JMS
    • +
    • Mail - SMTP(S), POP3(S) and IMAP(S)
    • +
    • MongoDB (NoSQL)
    • +
    • Native commands or shell scripts
    • +
    • TCP
    • +
    +
  • +
  • Complete portability and 100% Java purity.
  • +
  • Full multithreading framework allows concurrent sampling by many threads and + simultaneous sampling of different functions by separate thread groups.
  • +
  • Careful GUI design allows faster Test Plan building and debugging.
  • +
  • Caching and offline analysis/replaying of test results.
  • +
  • Highly Extensible core: +
      +
    • Pluggable Samplers allow unlimited testing capabilities.
    • +
    • Several load statistics may be chosen with pluggable timers.
    • +
    • Data analysis and visualization plugins allow great extensibility + as well as personalization.
    • +
    • Functions can be used to provide dynamic input to a test or provide data manipulation.
    • +
    • Scriptable Samplers (BeanShell, BSF-compatible languages and JSR223-compatible languages)
    • +
    +
  • +
+

JMeter is not a browser

+

+JMeter is not a browser. +As far as web-services and remote services are concerned, JMeter looks like a browser (or rather, multiple browsers); +however JMeter does not perform all the actions supported by browsers. +In particular, JMeter does not execute the Javascript found in HTML pages. +Nor does it render the HTML pages as a browser does +(it's possible to view the response as HTML etc, but the timings are not included in any samples, and only one sample in one thread is ever viewed at a time). +

+ +

How do I do it?

+ +

Tutorials (PDF)

+ +

Further Information About JMeter

+ +
\ No newline at end of file diff --git a/docs/issues.html b/docs/issues.html new file mode 100644 index 00000000000..e4548f5f5f3 --- /dev/null +++ b/docs/issues.html @@ -0,0 +1,126 @@ + +Apache JMeter + - + Issues
Logo ASF
Apache JMeter

Issue tracker

+

+JMeter uses Bugzilla for issue tracking, i.e. for reporting bugs and requesting enhancements. +

+

+Before creating a new issue, please check whether the issue has already been reported by searching Bugzilla. +It's also worth checking first on the JMeter user mailing list; others may already have a solution. +

+

Requesting an enhancement

+

+In most cases it is worth starting a discussion on the mailing list first. +Bugzilla is good for tracking progress and supplying patches, but is unwieldy for longer discussions. +

+

+If you have not already done so, you need to register an account first, using the "New Account" link at the top of the +main Bugzilla page: https://issues.apache.org/bugzilla/. +

+

+Make sure you read and understand the information on the account creation page before signing up. +

+

+Once logged in, click "File a bug" and select JMeter from the list +Please set the severity to "enhancement". +

+

+Please make sure that you describe the enhancement in sufficient detail. If necessary provide an example use-case. +

+

+If you are providing a code patch, also provide a test case, and documentation on how to use the new feature (ideally as a documentation patch). +

+

Raising an Issue

+

+First check that the issue has not already been reported. +If reporting a bug, are you sure it really is a bug in JMeter, not just a misunderstanding of how JMeter works? +

+

+If you have not already done so, you need to register an account first, using the "New Account" link at the top of the +main Bugzilla page: https://issues.apache.org/bugzilla/. +

+

+Make sure you read and understand the information on the account creation page before signing up. +

+

+Once logged in, click "File a bug" and select JMeter from the list. +

+

Required Information for bug reporting

+

+Please make sure you provide sufficient information for others to be able to make use of the report effectively. +Use the checklist below to guide you. +

+
    +
  • JMeter version
  • +
  • Java version (output from java -version)
  • +
  • OS version
  • +
  • jmeter.log file (unlikely to contain sensitive information, but check before uploading)
  • +
  • JMX file if relevant (redact any sensitive information first), providing a simplified Test Plan (using DEBUG sampler) will ensure BUG is fixed much more rapidely than without it
  • +
  • JTL file if relevant (may need to redact sensitive information)
  • +
  • For a suspected bug, describe what you did, what happened, and how this differs from what you expected to happen. +Does it happen every time?. +
  • +
  • Add yourself in CC List to be notified when JMeter Team requires more information (in this case bug will be marked as NEEDINFO)
  • +
  • Select accurately the IMPORTANCE level, ENHANCEMENT means it's not a BUG while others mean it's a BUG
  • +
  • If you are providing a patch to fix a bug, please ensure it is in unified diff format. +If using Eclipse, please set the patch root to "Project", not the default "Workspace" which is harder to apply.
  • +
  • New source files can be provided as is; please ensure they have the standard Apache License header (as per other JMeter files). +Please do not use @author tags (credit will be given in the changes file). +
  • +
  • In the case of patches for new features, please also provide documentation patches if at all possible. +Components are documented in xdocs/usermanual/component_reference.xml.
  • +
+

See also the following Bug writing guidelines, +also the terms and conditions noted on the Bugzilla account creation page.

+
\ No newline at end of file diff --git a/docs/jmeter_irc.html b/docs/jmeter_irc.html new file mode 100644 index 00000000000..1c04f2143f3 --- /dev/null +++ b/docs/jmeter_irc.html @@ -0,0 +1,46 @@ + +Apache JMeter + - + JMeter on IRC
Logo ASF
Apache JMeter

JMeter on IRC

+

JMeter developers often hang out on IRC to chat about development issues. +Users are also welcome to stop by and ask questions, offer feature suggestions, +or just chit-chat.

+

IRC Server: irc.us.freenode.net
+Room: #jmeter

+
\ No newline at end of file diff --git a/docs/localising/index.html b/docs/localising/index.html new file mode 100644 index 00000000000..26a00486148 --- /dev/null +++ b/docs/localising/index.html @@ -0,0 +1,147 @@ + +Apache JMeter + - + JMeter Localisation (Translator's Guide)
Logo ASF
Apache JMeter

Introduction

+ +

This document describes the process of creating and maintaining translated texts for JMeter in languages +other than English. English has been tacitly chosen as the project's primary (or "default") language -- despite its +obvious inadequacy for reasonably unambiguous communication -- as a tribute to the Power of the Empire :-)
+The metropolitan language texts are thus maintained by the software developers, while other project contributors +(called "translators" in this document) take care of maintaining the texts in the languages of the +provinces. The process of producing and maintaining the later is called "translation" in this document.

+ +

This document assumes you'll be using i18nEdit as your tool to edit properties files, and instructions will +be specific to this software, but this is not mandatory: the process should mostly work also if you prefer to use +another tool, such as or vi or Emacs. +

+ +

This document describes 6 processes:

+
    +
  1. Obtaining the current texts [translators].
  2. +
  3. Providing the current texts to translators [developers].
  4. +
  5. Downloading and running i18nEdit [everyone].
  6. +
  7. Translating [translators].
  8. +
  9. Submitting your translations to the project [translators].
  10. +
  11. Merging in new translations [committers].
  12. +
+ +

Obtaining the current texts

+ +

If you want to help with JMeter's translation process, start by reading this document. Then +send a message to dev@jmeter.apache.org +stating your intention. The files you need (*.properties and *.metaprop) are included in the source archive. +But if you are having any difficulty, one of the project contributors will be able to grab the current texts +from SVN and send them to you. You'll receive a jar, zip, tar or tgz file that you'll need to unpack in your +local disk.

+

If you are familiar with SVN or you're brave, feel free to anonymously connect to the Apache SVN server +and obtain the JMeter source yourself, as described in +http://jmeter.apache.org/svnindex.html +-- the files necessary to the translation process are all under the jmeter/src directory. +

+

Once you've unpacked or checked out the files, make sure to find file src/i18nedit.properties in there: +you'll need to know where it is to start working with i18nEdit.

+ +

Providing the current texts to translators

+ +

If you have access to JMeter's SVN repository and you want to pack the files necessary for localisation +for sending to a translator, just go to the directory above the project root and issue the following command: +

+
+tar czf jmeter-localisation.tgz `find jmeter/src -name "*.properties" -o -name "*.metaprops"`
+
+

+Of course you could also send the translator the whole jmeter directory, but this will make his life easier. +

+ +

Downloading and running i18nEdit

+ +

The runtime for i18nEdit can be obtained from +http://www.cantamen.com/i18nedit.php. +Download the binary distribution (i18nedit-1.0.0.jar) and save it locally.

+

To run i18nEdit, just make sure to have a reasonably modern Java Runtime Environment in your PATH, change +to the directory where you saved i18nedit-1.0.0.jar, then issue the following command:

+
+java -jar i18nedit-1.0.0.jar
+
+ +Then: +
    +
  1. If you've never run i18nEdit before, choose a language. The rest of this document assumes you chose UK English.
  2. +
  3. Select the "Projects" menu, then "Open project...".
  4. +
  5. Navigate to jmeter/src/, select i18nedit.properties, and press the "Open" button.
  6. +
  7. In the window that opens, select the "Project" menu, then "Project settings". Check that your target language +appears in the list in field "Additional locales (ISO codes)". Otherwise, add it now. Press "Save".
  8. +
+You're now ready to start translating. + +

Translating

+ +

Before you start translating, select the "Project" menu, then "Translation settings". Choose work mode +"Directed translation (source to target)". Enter "en" (without the quotes) in the "Source localization" field. Enter +the ISO code of your target language in the "Target localization field".

+ +

Click on one of the editable fields in the right panel ("Comment" or "Content" for your language). Press F2. +i18nEdit will bring you to the first property that requires your attention, either because a translation does not yet +exist for it or because the English text has changed since the translation was provided. Enter or fix the text if +necessary, then press F2 again to repeat the process.

+ +

i18nEdit's on-line help is excellent: read through it for more information and tips.

+ +

Submitting your translations to the project

+ +

Once you're done translating, just pack up the whole set of files in jmeter/src in a jar, zip, tar, +tgz, or alike and attach them to a JMeter bug report +(follow link to "Known bugs" in JMeter's home page for that).

+ +

Merging in new translations

+ +If you're a committer receiving text files from a translator, follow this steps to merge them into +the project: +
    +
  1. Unpack the files submitted by the translator in a separate directory.
  2. +
  3. Start i18nEdit as described in Downloading and running i18nEdit above.
  4. +
  5. If the translator worked in a new language, make sure it is listed in the Additional locales field in the Project Settings.
  6. +
  7. Open the "Team" menu and select "Merge changes as integrator".
  8. +
  9. Enter the path to the src directory in the files submitted by the translator.
  10. +
  11. Select the translator's target language.
  12. +
  13. Press "Perform merge".
  14. +
  15. Close i18nEdit and commit to SVN as usual (remember to Refresh your project if you're using Eclipse).
  16. +
+ +
\ No newline at end of file diff --git a/docs/mail.html b/docs/mail.html new file mode 100644 index 00000000000..483fd0bb7f4 --- /dev/null +++ b/docs/mail.html @@ -0,0 +1,209 @@ + +Apache JMeter + - + Mailing Lists
Logo ASF
Apache JMeter

Mailing Lists - Guidelines

+ +

A mailing list is an electronic discussion forum that anyone can +subscribe to. When someone sends an email message to the mailing list, +a copy of that message is broadcast to everyone who is subscribed to +that mailing list. Mailing lists provide a simple and effective +communication mechanism. With potentially thousands of subscribers, +there is a common set of etiquette guidelines that you should observe. +Please keep on reading. +

+ +

+Please note that usage of these mailing lists is subject to the +Public Forum Archive Policy. +

+ +

+ + Respect the mailing list type +
+ There are generally two types of lists. +

+ +

+

    +
  • + The "User" lists where you can send questions and comments about + configuration, setup, usage and other "user" types of questions. +
  • +
  • + The "Developer" lists where you can send questions and + comments about the actual software source code and general + "development" types of questions. +
  • +
+

+ +

Some questions are appropriate for posting on both the "user" and +the "developer" lists. In this case, pick one and only one. Do not +cross post.

+ +

Asking a configuration question on the developers list is frowned +upon because developers' time is as precious as yours. By contacting +them directly instead of the user base you are abusing resources. In +fact, it is unlikely that you will get a quicker answer, if at +all.

+ +

+ + Join the lists that are appropriate for your discussion. +
+Please make sure that you are joining the list that is appropriate for the +topic or product that you would like to discuss. For example, +please do not join the Regexp mailing list and ask questions about Tomcat. +Instead, you should join the Tomcat User list and ask your questions +there. +

+ +

+ + Ask smart questions.
+ +Every volunteer project obtains its strength from the people involved +in it. You are welcome to join any of our mailing lists. You can +choose to lurk, or actively participate; it's up to you. The level of +community responsiveness to specific questions is generally directly +proportional to the amount of effort you spend formulating your +question. Eric Raymond and Rick Moen have even written an essay entitled "Asking +Smart Questions" precisely on this topic. Although somewhat +militant, it is definitely worth reading.
+Note: Please do NOT send your Java problems to the two authors. They welcome feedback on the FAQ's contents, but are simply not a Java help resource. Follow the essay's advice and choose your forum carefully. +

+ +

+ + Give feedback when you get a good answer.
+ +If an answer given to you helped you solve your problem then send a mail saying so and don't forget to say THANKS. +If you fixed the problem yourself then contribute to the mailing list by writing how you solved your issue. +Giving feedback is useful to people who faced/will face same problems as you and will be your way +to contribute to the project. Don't forget that people answering your questions are volunteers +doing so on their personal time.
+

+ +

+ + Keep your email short and to the point; use a suitable subject line. +
+If your email is more than about a page of text, chances are that it +won't get read by very many people. It is much better to try to pack a +lot of informative information (see above about asking smart questions) +into as small of an email as possible. If you are replying to a previous +email, it is a good idea to only quote the parts that you are replying +to and to remove the unnecessary bits. This makes it easier for people +to follow a thread as well as making the email archives easier to search +and read. +

+ +

+ + Start a new thread for a new topic +
+When asing a new question, please start a new thread with an appropriate new subject line. +This makes it easier to read, and to find later in the archives. +

+ +

+ + Do your best to ensure that you are not sending HTML or + "Stylelized" email to the list. +
+If you are using Outlook or Outlook Express or Eudora, chances are that +you are sending HTML email by default. There is usually a setting that +will allow you to send "Plain Text" email. If you are using Microsoft +products to send email, there are several bugs in the software that +prevent you from turning off the sending of HTML email. +

+ +

+ +Please don't send attachments or include large chunks of code
+Attachments can be difficult to read and are rarely needed by all recipients. +Some mailing lists are set up to drop them. +If you need to send more than a few lines of code, ask first. +Note that code is often mangled by word-wrapping, so it is better to provide a link to a downloadable file. +If necessary, arrange with the person(s) responding to the posting how best to give access to the data, +should it prove necessary. +

+ +

+ + Watch where you are sending email. +
+The majority of our mailing lists have set the Reply-To to go back to the +list. That means that when you Reply to a message, it will go to the list +and not to the original author directly. The reason is because it helps +facilitate discussion on the list for everyone to benefit from. Be careful +of this as sometimes you may intend to reply to a message directly to someone +instead of the entire list. + +The appropriate contents of the Reply-To header is an age-old debate that +should not be brought up on the mailing lists. You can +examine opposing points of view +condemning +our convention and + +condoning +it. Bringing this up for debate on a mailing list will add nothing +new and is considered off-topic. + +

+ +

+ + Do not cross post messages. +
+In other words, pick a mailing list and send your messages to that mailing +list only. Do not send your messages to multiple mailing lists. The reason is +that people may be subscribed to one list and not to the other. Therefore, +some people will only see part of the conversation. +

+

Conclusion

+

+Now that you have read the guidelines above, here is the page that gives +you a listing of the different mailing lists that you can join. If you +managed to find this without reading the above information, chances +are you will be sent back here. You might as well read it now and save +yourself the embarrassment. +

+
\ No newline at end of file diff --git a/docs/mail2.html b/docs/mail2.html new file mode 100644 index 00000000000..497f083839a --- /dev/null +++ b/docs/mail2.html @@ -0,0 +1,153 @@ + +Apache JMeter + - + Mailing Lists
Logo ASF
Apache JMeter

Mailing Lists - Guidelines

+

+Before subscribing to any of the mailing lists, please make sure that you have +read and understand the guidelines. +

+ +

+Please note that usage of these mailing lists is subject to the +Public Forum Archive Policy. +

+ +

+For details of how to subscribe/unsubscribe please read +Subscribing and Unsubscribing +

+ +

JMeter lists and archives at the ASF

+The following mailing lists are available: +

Apache JMeter User

+

+This is the list where users of Apache JMeter meet and discuss issues. +

+

+Developers are also expected to be lurking on this list to offer support to users of JMeter. +

+

+This list starts part-way through Nov 2011, when JMeter became an independent Apache project. +For earlier postings, please see the Jakarta JMeter User list, below. +

+ +
+

Jakarta JMeter User

+This is the old JMeter user list from when JMeter was a sub-project of Apache Jakarta. + +
+

Apache JMeter Developer

+

+This is the list where participating developers meet and discuss issues, code changes/additions etc. +

+
Please do not send usage questions to this list, see user list above.
+

+This list also collects Wiki update messages. +

+

+This list starts part-way through Nov 2011, when JMeter became an independent Apache project. +For earlier postings, please see below. +

+ +
+

Apache JMeter Commits

+

+SVN commit messages are sent here. +

+Prior to Nov 2011, they were sent to the Jakarta Notifications list, see below. + +
+

Apache JMeter Issues

+

+Bugzilla messages are sent here. +

+

+Prior to Nov 2011, they were sent to the Jakarta Notifications list, see below. +

+ +
+

Jakarta Developer

+

Combined Jakarta developer list, April 2010 to November 2011

+ +
+

Jakarta JMeter Developer

+Historical list, up to April 2010. + +
+

Jakarta Notifications

+Combined Jakarta notifications to November 2011. +Includes Bugzilla, SVN and Wiki commit mails for JMeter. + +
+

Other Archives and Searching

+

+There are several 3rd party sites that archive and provide searching for our mailing lists: +

+

+

+

+
\ No newline at end of file diff --git a/docs/nightly.html b/docs/nightly.html new file mode 100644 index 00000000000..9e822ef042b --- /dev/null +++ b/docs/nightly.html @@ -0,0 +1,97 @@ + +Apache JMeter + - + Nightly builds for developers
Logo ASF
Apache JMeter

Nightly builds for developers

+

+

What are the nightly builds?

+

+ The nightly builds are interim builds that are untested and unsupported. + Use at your own risk! +
+ These unreleased builds may not even load, may have undocumented features, + known defects, and any number of other issues. +
+ They are intended for use by developers and others wishing to help with resolving JMeter bugs. +

+
These builds should not be used in production.
+

Where are the nightly builds?

+

JMeter CI builds are currently run by Jenkins and Buildbot

+

These are located at: +

+

+

What do they consist of?

+

+JMeter is distributed as a set of zip (or tar-gz) archive files. + +The files are called: +

    +
  • apache-jmeter-{version}_bin.zip - JMeter binaries
  • +
  • apache-jmeter-{version}_lib.zip - 3rd party jar files (rarely changes)
  • +
  • apache-jmeter-{version}_src.zip - JMeter source
  • +
  • apache-jmeter-{version}_api.zip - JMeter Javadoc (if available)
  • +
+

Installing JMeter runtime

+Download the _bin and _lib files +
+Unpack the archives into the same directory structure +
+The other archives are not needed to run JMeter. + +

Building JMeter

+Download the _src, _bin and _lib files +
+Unpack all the archives into the same directory structure. +
+

+ +

Warning - please note!

+
+ + The nightly builds may or may not work properly - or at all. + +
+

+ If there is a problem with a particular version, + it may be worth reporting this on the JMeter-dev mailing list and/or trying again in a day or two. +

+
\ No newline at end of file diff --git a/docs/svnindex.html b/docs/svnindex.html new file mode 100644 index 00000000000..47af7d131e3 --- /dev/null +++ b/docs/svnindex.html @@ -0,0 +1,79 @@ + +Apache JMeter + - + Source Repositories
Logo ASF
Apache JMeter

Download the Source

+ +

Most users of the source code probably don't need to have day to +day access to the source code as it changes. For these users we +provide easy to unpack source code downloads via our download page.

+ +

Access the Version Controlled Source Code

+ +

For information on connecting to the ASF Subversion repositories, see the version control +page.

+ + +

Modules available for access are listed below.

+ +

Subversion

+ +

Subversion is an open-source version control system. The root url of the +ASF Subversion repository is http://svn.apache.org/repos/asf/ for non-committers and https://svn.apache.org/repos/asf/ for committers.

+ +

NOTE: +When checking out a subproject using Subversion, ensure that you are checking out a tag, a branch or trunk (the main-line) and not all tags and branches to avoid filling up your hard-disk and wasting bandwidth.

+ + + + + + + + + + + + + + +
Projecthttp (read-only)https (committers)View-SVN
Apache JMeterhttp://svn.apache.org/repos/asf/jmeter/trunkhttps://svn.apache.org/repos/asf/jmeter/trunkhttp://svn.apache.org/viewcvs.cgi/jmeter/
+ +
+ +
\ No newline at end of file diff --git a/docs/usermanual/best-practices.html b/docs/usermanual/best-practices.html new file mode 100644 index 00000000000..f7f9a7a6592 --- /dev/null +++ b/docs/usermanual/best-practices.html @@ -0,0 +1,378 @@ + +Apache JMeter + - + User's Manual: Best Practices
Logo ASF
Apache JMeter

16. Best Practices

+

16.1 Always use latest version of JMeter

+

The performance of JMeter is being constantly improved, so users are highly encouraged to use the most up to date version.
+Ensure you always read changes list to be aware of new improvements and components. +You should absolutely avoid using versions that are older than 3 versions before the last one. +

+

16.2 Use the correct Number of Threads

+

Your hardware capabilities as well as the Test Plan design will both impact the number of threads you can effectively +run with JMeter. The number will also depend on how fast your server is (a faster server + makes JMeter work harder since it returns a response quicker). As with any Load Testing tool, if you don't correctly size + the number of threads, you will face the "Coordinated Omission" problem which can give you wrong or inaccurate results. + If you need large-scale load testing, consider running multiple non-GUI JMeter instances on multiple machines + using distributed mode (or not). When using distributed mode the result file is combined on the Controller node, if + using multiple autonomous instances, the sample result files can be combined for subsequent analysis. +For testing how JMeter performs on a given platform, the JavaTest sampler can be used. +It does not require any network access so can give some idea as to the maximum throughput achievable. +

+

+JMeter versions since 2.8 have an option to delay thread creation until the thread +starts sampling, i.e. after any thread group delay and the ramp-up time for the thread itself. +This allows for a very large total number of threads, provided that not too many are active concurrently. +

+

16.3 Where to Put the Cookie Manager

+

See Building a Web Test +for information.

+

16.4 Where to Put the Authorization Manager

+

See Building an Advanced +Web Test for information.

+

16.5 Using the HTTP(S) Test Script Recorder

+

Refer to HTTP(S) Test Script Recorder for details on setting up the +recorder. The most important thing to do is filter out all requests you aren't +interested in. For instance, there's no point in recording image requests (JMeter can +be instructed to download all images on a page - see HTTP Request). +These will just clutter your test plan. Most likely, there is an extension all your files +share, such as .jsp, .asp, .php, .html or the like. These you should "include" by +entering ".*\.jsp" as an "Include Pattern".

+

Alternatively, you can exclude images by entering ".*\.gif" as an "Exclude Pattern". +Depending on your application, this may or may not be a better way to go. You may +also have to exclude stylesheets, javascript files, and other included files. Test +out your settings to verify you are recording what you want, and then erase and start +fresh.

+ +

The HTTP(S) Test Script Recorder expects to find a ThreadGroup element with a Recording Controller +under it where it will record HTTP Requests to. This conveniently packages all your samples under one +controller, which can be given a name that describes the test case.

+

Now, go through the steps of a Test Case. If you have no pre-defined test cases, use +JMeter to record your actions to define your test cases. Once you have finished a +definite series of steps, save the entire test case in an appropriately named file. Then, wipe +clean and start a new test case. By doing this, you can quickly record a large number of +test case "rough drafts".

+

One of the most useful features of the HTTP(S) Test Script Recorder is that you can abstract out +certain common elements from the recorded samples. By defining some +user-defined variables at the Test Plan level or in +User Defined Variables elements, you can have JMeter automatically +replace values in you recorded samples. For instance, if you are testing an app on +server "xxx.example.com", then you can define a variable called "server" with the value of +"xxx.example.com", and anyplace that value is found in your recorded samples will be replaced +with "${server}". + +

Please note that matching is case-sensitive.
+ +

+

+If JMeter does not record any samples, check that the browser really is using the proxy. +If the browser works OK even if JMeter is not running, then the browser cannot be using the proxy. +Some browsers ignore proxy settings for localhost or 127.0.0.1; try using the local hostname or IP instead. +

+

+The error "unknown_ca" probably means that you are trying to record HTTPS, and the browser has not accepted the +JMeter Proxy server certificate. +

+ + +

16.6 User variables

+

+Some test plans need to use different values for different users/threads. +For example, you might want to test a sequence that requires a unique login for each user. +This is easy to achieve with the facilities provided by JMeter. +

+

For example:

+
    +
  • Create a text file containing the user names and passwords, separated by commas. +Put this in the same directory as your test plan. +
  • +
  • +Add a CSV DataSet configuration element to the test plan. +Name the variables USER and PASS. +
  • +
  • +Replace the login name with ${USER} and the password with ${PASS} on the appropriate +samplers +
  • +
+

The CSV Data Set element will read a new line for each thread. +

+

16.7 Reducing resource requirements

+

+Some suggestions on reducing resource usage. +

+
    +
  • Use non-GUI mode: jmeter -n -t test.jmx -l test.jtl
  • +
  • Use as few Listeners as possible; if using the -l flag as above they can all be deleted or disabled.
  • +
  • Don't use "View Results Tree" or "View Results in Table" listeners during the load test, use them only during scripting phase to debug your scripts.
  • +
  • Rather than using lots of similar samplers, +use the same sampler in a loop, and use variables (CSV Data Set) to vary the sample. +[The Include Controller does not help here, as it adds all the test elements in the file to the test plan.] +
  • +
  • Don't use functional mode
  • +
  • Use CSV output rather than XML
  • +
  • Only save the data that you need
  • +
  • Use as few Assertions as possible
  • +
  • Use the most performing scripting language (see JSR223 section)
  • +
+

+If your test needs large amounts of data - particularly if it needs to be randomised - create the test data in a file +that can be read with CSV Dataset. This avoids wasting resources at run-time. +

+

16.8 BeanShell server

+

+The BeanShell interpreter has a very useful feature - it can act as a server, +which is accessible by telnet or http. +

+
+There is no security. Anyone who can connect to the port can issue any BeanShell commands. +These can provide unrestricted access to the JMeter application and the host. +Do not enable the server unless the ports are protected against access, e.g. by a firewall. +
+

+If you do wish to use the server, define the following in jmeter.properties: +

+
+beanshell.server.port=9000
+beanshell.server.file=../extras/startup.bsh
+
+

+In the above example, the server will be started, and will listen on ports 9000 and 9001. +Port 9000 will be used for http access. Port 9001 will be used for telnet access. +The startup.bsh file will be processed by the server, and can be used to define various functions and set up variables. +The startup file defines methods for setting and printing JMeter and system properties. +This is what you should see in the JMeter console: +

+
+Startup script running
+Startup script completed
+Httpd started on port: 9000
+Sessiond started on port: 9001
+
+

+There is a sample script (extras/remote.bsh) you can use to test the server. +[Have a look at it to see how it works.] +
+When starting it in the JMeter bin directory +(adjust paths as necessary if running from elsewhere) +the output should look like: +

+$ java -jar ../lib/bshclient.jar localhost 9000 ../extras/remote.bsh
+Connecting to BSH server on localhost:9000
+Reading responses from server ...
+BeanShell 2.0b5 - by Pat Niemeyer (pat@pat.net)
+bsh % remote.bsh starting
+user.home = C:\Documents and Settings\User
+user.dir = D:\eclipseworkspaces\main\JMeter_trunk\bin
+log_level.jmeter = INFO
+log_level.jorphan = INFO
+Setting property 'EXAMPLE' to '0'.
+Setting property 'EXAMPLE' to '1'.
+Setting property 'EXAMPLE' to '2'.
+Setting property 'EXAMPLE' to '3'.
+Setting property 'EXAMPLE' to '4'.
+Setting property 'EXAMPLE' to '5'.
+Setting property 'EXAMPLE' to '6'.
+Setting property 'EXAMPLE' to '7'.
+Setting property 'EXAMPLE' to '8'.
+Setting property 'EXAMPLE' to '9'.
+EXAMPLE = 9
+remote.bsh ended
+bsh % ... disconnected from server.
+
+

+

+As a practical example, assume you have a long-running JMeter test running in non-GUI mode, +and you want to vary the throughput at various times during the test. +The test-plan includes a Constant Throughput Timer which is defined in terms of a property, +e.g. ${__P(throughput)}. +The following BeanShell commands could be used to change the test: +

+
+printprop("throughput");
+curr=Integer.decode(args[0]); // Start value
+inc=Integer.decode(args[1]);  // Increment
+end=Integer.decode(args[2]);  // Final value
+secs=Integer.decode(args[3]); // Wait between changes
+while(curr <= end){
+  setprop("throughput",curr.toString()); // Needs to be a string here
+  Thread.sleep(secs*1000);
+  curr += inc;
+}
+printprop("throughput");
+
+

The script can be stored in a file (throughput.bsh, say), and sent to the server using bshclient.jar. +For example: +

+
+java -jar ../lib/bshclient.jar localhost 9000 throughput.bsh 70 5 100 60
+
+

16.9 BeanShell scripting

+

16.9.1 Overview

+

+Each BeanShell test element has its own copy of the interpreter (for each thread). +If the test element is repeatedly called, e.g. within a loop, then the interpreter is retained +between invocations unless the "Reset bsh.Interpreter before each call" option is selected. +For intensive load testing, it is recommended to use a JSR223 scripting language whose ScriptingEngine implements Compilable, +see JSR223 section below for more details. +

+

+Some long-running tests may cause the interpreter to use lots of memory; if this is the case try using the reset option. +

+

+You can test BeanShell scripts outside JMeter by using the command-line interpreter: +

+$ java -cp bsh-xxx.jar[;other jars as needed] bsh.Interperter file.bsh [parameters]
+or
+$ java -cp bsh-xxx.jar bsh.Interperter
+bsh% source("file.bsh");
+bsh% exit(); // or use EOF key (e.g. ^Z or ^D)
+
+

+
+

16.9.2 Sharing Variables

+

+Variables can be defined in startup (initialisation) scripts. +These will be retained across invocations of the test element, unless the reset option is used.\ +

+

+Scripts can also access JMeter variables using the get() and put() methods of the "vars" variable, +for example: vars.get("HOST"); vars.put("MSG","Successful");. +The get() and put() methods only support variables with String values, +but there are also getObject() and putObject() methods which can be used for arbitrary objects. +JMeter variables are local to a thread, but can be used by all test elements (not just Beanshell). +

+

+If you need to share variables between threads, then JMeter properties can be used: +

+import org.apache.jmeter.util.JMeterUtils;
+String value=JMeterUtils.getPropDefault("name","");
+JMeterUtils.setProperty("name", "value");
+
+The sample .bshrc files contain sample definitions of getprop() and setprop() methods. +

+

+Another possible method of sharing variables is to use the "bsh.shared" shared namespace. +For example: +

+if (bsh.shared.myObj == void){
+    // not yet defined, so create it:
+    myObj=new AnyObject();
+}
+bsh.shared.myObj.process();
+
+Rather than creating the object in the test element, it can be created in the startup file +defined by the JMeter property "beanshell.init.file". This is only processed once. +

+
+

16.10 Developing script functions in BeanShell, Javascript or Jexl etc.

+

+It's quite hard to write and test scripts as functions. +However, JMeter has the JSR223, BSF (and BeanShell) samplers which can be used instead. +

+

+Create a simple Test Plan containing the JSR223 or BSF Sampler and Tree View Listener. +Code the script in the sampler script pane, and test it by running the test. +If there are any errors, these will show up in the Tree View. +Also the result of running the script will show up as the response. +

+

+Once the script is working properly, it can be stored as a variable on the Test Plan. +The script variable can then be used to create the function call. +For example, suppose a BeanShell script is stored in the variable RANDOM_NAME. +The function call can then be coded as ${__BeanShell(${RANDOM_NAME})}. +There is no need to escape any commas in the script, +because the function call is parsed before the variable's value is interpolated. +

+

16.11 Parameterising tests

+

+Often it is useful to be able to re-run the same test with different settings. +For example, changing the number of threads or loops, or changing a hostname. +

+

+One way to do this is to define a set of variables on the Test Plan, and then use those variables in the test elements. +For example, one could define the variable LOOPS=10, and refer to that in the Thread Group as ${LOOPS}. +To run the test with 20 loops, just change the value of the LOOPS variable on the Test Plan. +

+

+This quickly becomes tedious if you want to run lots of tests in non-GUI mode. +One solution to this is to define the Test Plan variable in terms of a property, +for example LOOPS=${__P(loops,10))}. +This uses the value of the property "loops", defaulting to 10 if the property is not found. +The "loops" property can then be defined on the JMeter command-line: +jmeter ... -Jloops=12 .... +If there are a lot of properties that need to be changed together, +then one way to achieve this is to use a set of property files. +The appropriate property file can be passed in to JMeter using the -q command-line option. +

+

16.12 JSR223 Elements

+

+For intensive load testing, the recommended scripting language is one whose ScriptingEngine implements the Compilable interface. +Groovy scripting engine implements Compilable. However neither Beanshell nor Javascript do so as of release date of JMeter 2.13, so it is +recommended to avoid them for intensive load testing. +[Note: Beanshell implements the Compilable interface but it has not been coded - the method just throws an Exception. +JMeter has an explicit work-round for this bug.] + +When using JSR 223 elements, always set caching key to a unique value to ensure the script compilation is cached if underlying language supports it. +Ensure the script does not use any variable using ${varName} as caching would take only first value of ${varName}. Instead use : + +vars.get("varName") + +

+

+You can also pass them as Parameters to the script and use them this way. +

+

16.13 Sharing variables between threads and thread groups

+

+Variables are local to a thread; a variable set in one thread cannot be read in another. +This is by design. For variables that can be determined before a test starts, see +Parameterising Tests (above). +If the value is not known until the test starts, there are various options: +

    +
  • Store the variable as a property - properties are global to the JMeter instance
  • +
  • Write variables to a file and re-read them.
  • +
  • Use the bsh.shared namespace - see above
  • +
  • Write your own Java classes
  • +
+

+

16.14 Managing properties

+

When you need to modify jmeter properties, ensure you don't modify jmeter.properties file, instead copy the property from jmeter.properties and modify its value in user.properties file.
+Doing so will ease you migration to the next version of JMeter.
+Note that in the documentation jmeter.properties is frequently mentioned but this should be understood as "Copy from jmeter.properties to user.properties the property you want to modify and do so in the latter file".

+
user.properties file superseeds the properties defined in jmeter.properties
+
\ No newline at end of file diff --git a/docs/usermanual/boss.html b/docs/usermanual/boss.html new file mode 100644 index 00000000000..48361ea2029 --- /dev/null +++ b/docs/usermanual/boss.html @@ -0,0 +1,213 @@ + +Apache JMeter + - + User's Manual: My boss wants me to...
Logo ASF
Apache JMeter

17. Help! My boss wants me to load test our application!

+

This is a fairly open-ended proposition. There are a number of questions to +be asked first, and additionally a number of resources that will be needed. You +will need some hardware to run the benchmarks/load-tests from. A number of +tools will prove useful. There are a number of products to consider. And finally, +why is Java a good choice to implement a load-testing/Benchmarking product. +

+

17.1 Questions to ask

+

What is our anticipated average number of users (normal load) ? +

+

What is our anticipated peak number of users ? +

+

When is a good time to load-test our application (i.e. off-hours or week-ends), +bearing in mind that this may very well crash one or more of our servers ? +

+

Does our application have state ? If so, how does our application manage it +(cookies, session-rewriting, or some other method) ? +

+

What is the testing intended to achieve?

+
+

17.2 Resources

+

The following resources will prove very helpful. Bear in mind that if you +cannot locate these resources, you will become these resources. As you +already have your work cut out for you, it is worth knowing who the following +people are, so that you can ask them for help if you need it. +

+

17.2.1 Network

+

Who knows our network topology ? If you run into any firewall or + proxy issues, this will become very important. As well, a private + testing network (which will therefore have very low network latency) + would be a very nice thing. Knowing who can set one up for you + (if you feel that this is necessary) will be very useful. If the + application doesn't scale as expected, who can add additional + hardware ? +

+
+

17.2.2 Application

+

Who knows how our application functions ? The normal sequence is +

    +
  • test (low-volume - can we benchmark our application?)
  • +
  • benchmark (the average number of users)
  • +
  • load-test (the maximum number of users)
  • +
  • test destructively (what is our hard limit?)
  • +
+ The test process may progress from black-box testing to + white-box testing (the difference is that the first requires + no knowledge of the application [it is treated as a "black box"] + while the second requires some knowledge of the application). + It is not uncommon to discover problems with the application + during this process, so be prepared to defend your work.

+
+
+

17.3 What platform should I use to run the benchmarks/load-tests ?

+

This should be a widely-used piece of hardware, with a standard +(i.e. vanilla) software installation. Remember, if you publish your results, +the first thing your clients will do is hire a graduate student to verify them. +You might as well make it as easy for this person as you possibly can. +

+

For Windows, Windows XP Professional should be a minimum (the others +do not multi-thread past 50-60 connections, and you probably anticipate +more users than that). +

+

Good free platforms include the linuxes, the BSDs, and Solaris Intel. If +you have a little more money, there are commercial linuxes. +This may be worth it if you need the support. +

+

+For non-Windows platforms, investigate "ulimit -n unlimited" with a view to +including it in your user account startup scripts (.bashrc or .cshrc scripts +for the testing account). +

+

As you progress to larger-scale benchmarks/load-tests, this platform +will become the limiting factor. So it's worth using the best hardware and +software that you have available. Remember to include the hardware/software +configuration in your published benchmarks. +

+

When you need a lot of machines or want to test the network latency, Cloud can help you. +JMeter can easily be installed on Cloud instances as it runs on nearly any architecture available in the Cloud. +JMeter is also supported within Commercial Cloud PAAS if you don't want to manage it yourself. +

+

Don't forget JMeter batch (NON-GUI) mode. This mode should be used during load testing for many reasons: +

    +
  • If you have a powerful server that supports Java but perhaps does not have a fast graphics implementation, or where you need to login remotely.
  • +
  • Batch (non-GUI) mode can reduce the network traffic compared with using a remote display or client-server mode.
  • +
  • Java AWT Thread used for GUI mode can alter injection behaviour by blocking sometimes
  • +
+The batch log file can then be loaded into JMeter on a workstation for analysis, or you can +use CSV output and import the data into a spreadsheet.

+
Remember GUI mode is for Script creation and debugging, not for load testing
+
+

17.4 Tools

+

The following tools will all prove useful. It is definitely worthwhile to +become familiar with them. This should include trying them out, and reading the +appropriate documentation (man-pages, info-files, application --help messages, +and any supplied documentation). +

+

17.4.1 ping

+

+ This can be used to establish whether or not you can reach your + target site. Options can be specified so that 'ping' provides the + same type of route reporting as 'traceroute'. +

+
+

17.4.2 nslookup/dig

+

+ While the user will normally use a human-readable internet + address, you may wish to avoid the overhead of DNS lookups when + performing benchmarking/load-testing. These can be used to determine + the unique address (dotted quad) of your target site. +

+
+

17.4.3 traceroute

+

+ If you cannot "ping" your target site, this may be used to determine + the problem (possibly a firewall or a proxy). It can also be used + to estimate the overall network latency (running locally should give + the lowest possible network latency - remember that your users will + be running over a possibly busy internet). Generally, the fewer hops + the better. +

+
+
+

17.5 How can I enhance JMeter ?

+

There a lot of open-source and commercial plugins that can enhance JMeter, let's mention here the main open-source ones: +

+

17.5.1 JMeter-Plugins

+

This non official project is THE companion to core JMeter.
+ It provides many useful extensions, among which: +

    +
  • Active Threads Over Time Graph Listener
  • +
  • Response Times vs Threads Graph Listener
  • +
  • Transaction Throughput vs Threads Graph Listener
  • +
  • GraphGenerator listener to create graphs at end of a load test
  • +
  • Selenium WebDriver Sampler
  • +
  • ....
  • +
+

+
+

17.5.2 JMeter Plugin for Maven

+

This non official plugin allows you to run your automated JMeter tests through Maven.

+
+

17.5.3 JMeter Performance Plugin

+

This non official plugin allows you to capture reports from JMeter and JUnit.
+ Jenkins will generate graphic charts with the trend report of performance and robustness. + It includes the feature of setting the final build status as good, unstable or failed, based on the reported error percentage. +

+
+

17.5.4 JMeter plugin for AWS

+

This non official plugin automates running Apache JMeter on Amazon EC2

+
+
+

17.6 Why Java ?

+

Why not Perl or C ? +

+

Well, Perl might be a very good choice except that the Benchmark package +seems to give fairly fuzzy results. Also, simulating multiple users with +Perl is a tricky proposition (multiple connections can be simulated by forking +many processes from a shell script, but these will not be threads, they will +be processes). However, the Perl community is very large. If you find that +someone has already written something that seems useful, this could be a very +good solution. +

+

C, of course, is a very good choice (check out the Apache ab tool). +But be prepared to write all of the custom networking, threading, and state +management code that you will need to benchmark your application. +

+

Java gives you (for free) the custom networking, threading, and state +management code that you will need to benchmark your application. Java is +aware of HTTP, FTP, and HTTPS - as well as RMI, IIOP, and JDBC (not to mention +cookies, URL-encoding, and URL-rewriting). In addition Java gives you automatic +garbage-collection, and byte-code level security. +

+
+
\ No newline at end of file diff --git a/docs/usermanual/build-adv-web-test-plan.html b/docs/usermanual/build-adv-web-test-plan.html new file mode 100644 index 00000000000..cb808282fe8 --- /dev/null +++ b/docs/usermanual/build-adv-web-test-plan.html @@ -0,0 +1,75 @@ + +Apache JMeter + - + User's Manual: Building an Advanced Web Test Plan
Logo ASF
Apache JMeter

6. Building an Advanced Web Test Plan

+

In this section, you will learn how to create advanced +Test Plans to test a Web site.

+ +

For an example of a basic Test Plan, see +Building a Web Test Plan.

6.1 Handling User Sessions With URL Rewriting

+

If your web application uses URL rewriting rather than cookies to save session information, +then you'll need to do a bit of extra work to test your site.

+

To respond correctly to URL rewriting, JMeter needs to parse the HTML +received from the server and retrieve the unique session ID. Use the appropriate HTTP URL Re-writing Modifier +to accomplish this. Simply enter the name of your session ID parameter into the modifier, and it +will find it and add it to each request. If the request already has a value, it will be replaced. +If "Cache Session Id?" is checked, then the last found session id will be saved, +and will be used if the previous HTTP sample does not contain a session id. +

+ +
URL Rewriting Example
+

Download this example. In Figure 1 is shown a +test plan using URL rewriting. Note that the URL Re-writing modifier is added to the SimpleController, +thus assuring that it will only affect requests under that SimpleController.

+
Figure 1 - Test Tree
Figure 1 - Test Tree
+

In Figure 2, we see the URL Re-writing modifier GUI, which just has a field for the user to specify +the name of the session ID parameter. There is also a checkbox for indicating that the session ID should +be part of the path (separated by a ";"), rather than a request parameter

+
Figure 2 - Request parameters
Figure 2 - Request parameters
+
+

6.2 Using a Header Manager

+

The HTTP Header Manager lets you customize what information +JMeter sends in the HTTP request header. This header includes properties like "User-Agent", +"Pragma", "Referer", etc.

+

The HTTP Header Manager, like the HTTP Cookie Manager, +should probably be added at the Thread Group level, unless for some reason you wish to +specify different headers for the different HTTP Request objects in +your test.

+ +
\ No newline at end of file diff --git a/docs/usermanual/build-db-test-plan.html b/docs/usermanual/build-db-test-plan.html new file mode 100644 index 00000000000..bcf6772634a --- /dev/null +++ b/docs/usermanual/build-db-test-plan.html @@ -0,0 +1,198 @@ + +Apache JMeter + - + User's Manual: Building a Simple Database Test Plan
Logo ASF
Apache JMeter

7. Building a Database Test Plan

+

In this section, you will learn how to create a basic +Test Plan to test a database server. +You will create fifty users that send 2 SQL requests to the database server. +Also, you will tell the users to run their tests 100 times. So, the total number +of requests is (50 users) x (2 requests) x (repeat 100 times) = 10'000 JDBC requests. +To construct the Test Plan, you will use the following elements: +Thread Group, +JDBC Request, Summary Report.

+ +
This example uses the MySQL database driver. +To use this driver, its containing .jar file (ex. mysql-connector-java-X.X.X-bin.jar) must be copied to the JMeter +./lib directory (see JMeter's Classpath +for more details).
+ +

7.1 Adding Users

+

The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group +tells JMeter the number of users you want to simulate, how often the users should +send requests, and the how many requests they should send.

+ +

Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

+ +

You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

+ +

Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 7.1 +below)

+ +

+Figure 7.1. Thread Group with Default Values
+Figure 7.1. Thread Group with Default Values
+ +

Start by providing a more descriptive name for our Thread Group. In the name +field, enter JDBC Users.

+ +
You will need a valid database, database table, and user-level access to that +table. In the example shown here, the database is 'cloud' and the table name is +'vm_instance'.
+ +

Next, increase the number of users to 50.

+ +

In the next field, the Ramp-Up Period, leave the the value of 10 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 10 seconds, JMeter will +finish starting all of your users by the end of the 10 seconds. So, if we have +50 users and a 10 second Ramp-Up Period, then the delay between starting users +would be 200 milliseconds (10 seconds / 50 users = 0.2 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

+ +

Finally, enter a value of 100 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

+ +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element).
+ +

See Figure 7.2 for the completed JDBC Users Thread Group.

+ +

+Figure 7.2. JDBC Users Thread Group
+Figure 7.2. JDBC Users Thread Group
+ +

7.2 Adding JDBC Requests

+

Now that we have defined our users, it is time to define the tasks that they +will be performing. In this section, you will specify the JDBC requests to +perform.

+ +

Begin by selecting the JDBC Users element. Click your right mouse button +to get the wAdd menu, and then select Add --> Config Element --> JDBC Connection Configuration. +Then, select this new element to view its Control Panel (see Figure 7.3).

+ +

Set up the following fields (these assume we will be using a MySQL database called 'cloud'):

+
    +
  • Variable name (here: myDatabase) bound to pool. This needs to uniquely identify the configuration. It is used by the JDBC Sampler to identify the configuration to be used.
  • +
  • Database URL: jdbc:mysql://ipOfTheServer:3306/cloud
  • +
  • JDBC Driver class: com.mysql.jdbc.Driver
  • +
  • Username: the username of database
  • +
  • Password: password for the username
  • +
+

The other fields on the screen can be left as the defaults.

+

JMeter creates a database connection pool with the configuration settings as specified in the Control Panel. +The pool is referred to in JDBC Requests in the 'Variable Name' field. +Several different JDBC Configuration elements can be used, but they must have unique names. +Every JDBC Request must refer to a JDBC Configuration pool. +More than one JDBC Request can refer to the same pool. +

+

+Figure 7.3. JDBC Configuration
+Figure 7.3. JDBC Configuration
+ +

Selecting the JDBC Users element again. Click your right mouse button +to get the Add menu, and then select Add --> Sampler --> JDBC Request. +Then, select this new element to view its Control Panel (see Figure 7.4).

+ +

+Figure 7.4. JDBC Request
+Figure 7.4. JDBC Request
+ +

In our Test Plan, we will make two JDBC requests. The first one is for +select all 'Running' VM instances, and the second is to select 'Expunging' VM instance (obviously you should +change these to examples appropriate for your particular database). These +are illustrated below.

+ +
JMeter sends requests in the order that you add them to the tree.
+ +

Start by editing the following properties (see Figure 7.5): +

    +
  • Change the Name to 'VM Running'.
  • +
  • Enter the Pool Name: 'myDatabase' (same as in the configuration element)
  • +
  • Enter the SQL Query String field.
  • +
  • Enter the Parameter values field with 'Running' value.
  • +
  • Enter the Parameter types with 'VARCHAR'.
  • +
+

+ +

+Figure 7.5. JDBC Request for the first SQL request
+Figure 7.5. JDBC Request for the first SQL request
+ +

Next, add the second JDBC Request and edit the following properties (see +Figure 7.6): +

    +
  • Change the Name to 'VM Expunging'.
  • +
  • Change the value of Parameter values to 'Expunging'.
  • +
+

+ +

+Figure 7.6. JDBC Request for the second request
+Figure 7.6. JDBC Request for the second request
+ +

7.3 Adding a Listener to View/Store the Test Results

+

The final element you need to add to your Test Plan is a +Listener. This element is +responsible for storing all of the results of your JDBC requests in a file +and presenting the results.

+ +

Select the JDBC Users element and add a Summary Report +listener (Add --> Listener --> Summary Report).

+ +

Save the test plan, and run the test with the menu Run --> Start or Ctrl+R

+ +

The listener shows the results.

+ +

+Figure 7.7. Graph results Listener
+Figure 7.7. Graph results Listener
+ +
\ No newline at end of file diff --git a/docs/usermanual/build-ftp-test-plan.html b/docs/usermanual/build-ftp-test-plan.html new file mode 100644 index 00000000000..bb6225c2a82 --- /dev/null +++ b/docs/usermanual/build-ftp-test-plan.html @@ -0,0 +1,194 @@ + +Apache JMeter + - + User's Manual: Building an FTP Test Plan
Logo ASF
Apache JMeter

8. Building an FTP Test Plan

+

In this section, you will learn how to create a basic +Test Plan to test an FTP site. You will +create four users that send requests for two files on a FTP site. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (4 users) x (2 requests) x (repeat 2 times) = 16 FTP requests.

+

To construct the Test Plan, you will use the following elements: +Thread Group, +FTP Request, +FTP Request Defaults, and +View Results in Table.

+ +

8.1 Adding Users

+

The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

+ +

Go ahead and add the Thread Group element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

+ +

You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

+ +

Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 8.1 +below)

+ +

+Figure 8.1. Thread Group with Default Values
+Figure 8.1. Thread Group with Default Values
+ +

Start by providing a more descriptive name for our Thread Group. In the name +field, enter 'FTP Users'.

+ +

Next, increase the number of users to 4.

+ +

In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

+ +

Finally, enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

+ +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element).
+ +

See Figure 8.2 for the completed FTP Users Thread Group.

+ +

+Figure 8.2. FTP Users Thread Group
+Figure 8.2. FTP Users Thread Group
+ +

8.2 Adding Default FTP Request Properties

+

Now that we have defined our users, it is time define the tasks that they +will be performing. In this section, you will specify the default settings +for your FTP requests. And then, in section 8.3, you will add FTP Request +elements which use some of the default settings you specified here.

+ +

Begin by selecting the FTP Users element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> FTP Request +Defaults. Then, select this new element to view its Control Panel (see Figure 8.3). +

+ +

+Figure 8.3. FTP Request Defaults
+Figure 8.3. FTP Request Defaults
+ +

+Like most JMeter elements, the FTP Request Defaults Control +Panel has a name field that you can modify. In this example, leave this field with +the default value.

+ +

Skip to the next field, which is the FTP Server's Server Name/IP. For the +Test Plan that you are building, all FTP requests will be sent to the same +FTP server, ftp.domain.com in this case. Enter this domain name into the field. +This is the only field that we will specify a default, so leave the remaining +fields with their default values.

+ +
The FTP Request Defaults element does not tell JMeter +to send an FTP request. It simply defines the default values that the +FTP Request elements use.
+ +

See Figure 8.4 for the completed FTP Request Defaults element

+ +

+Figure 8.4. FTP Defaults for our Test Plan
+Figure 8.4. FTP Defaults for our Test Plan
+ +

8.3 Adding FTP Requests

+ +

In our Test Plan, we need to make two FTP requests.

+ +
JMeter sends requests in the order that they appear in the tree.
+ +

Start by adding the first FTP Request +to the FTP Users element (Add --> Sampler --> FTP Request). +Then, select the FTP Request element in the tree and edit the following properties +(see Figure 8.5): +

    +
  1. Change the Name to "File1".
  2. +
  3. Change the Remote File field to "/directory/file1.txt".
  4. +
  5. Change the Username field to "anonymous".
  6. +
  7. Change the Password field to "anonymous@test.com".
  8. +
+

+ +
You do not have to set the Server Name field because you already specified +this value in the FTP Request Defaults element.
+ +

+Figure 8.5. FTP Request for file1
+Figure 8.5. FTP Request for file1
+ +

Next, add the second FTP Request and edit the following properties (see +Figure 8.6: +

    +
  1. Change the Name to "File2".
  2. +
  3. Change the Remote File field to "/directory/file2.txt".
  4. +
  5. Change the Username field to "anonymous".
  6. +
  7. Change the Password field to "anonymous@test.com".
  8. +
+

+ +

+Figure 8.6. FTP Request for file2
+Figure 8.6. FTP Request for file2
+ +

8.4 Adding a Listener to View/Store the Test Results

+

The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your FTP requests in a file and presenting +a visual model of the data.

+ +

Select the FTP Users element and add a View Results in Table +listener (Add --> Listener --> View Results in Table).

+

Run your test and view the results.

+ +

+Figure 8.7. View Results in Table Listener
+Figure 8.7. View Results in Table Listener
+ +
\ No newline at end of file diff --git a/docs/usermanual/build-jms-point-to-point-test-plan.html b/docs/usermanual/build-jms-point-to-point-test-plan.html new file mode 100644 index 00000000000..a7a771293ee --- /dev/null +++ b/docs/usermanual/build-jms-point-to-point-test-plan.html @@ -0,0 +1,219 @@ + +Apache JMeter + - + User's Manual: Building a JMS (Java Messaging Service) Point-to-Point Test Plan
Logo ASF
Apache JMeter

11. Building a JMS Point-to-Point Test Plan

+ +
+ Make sure the required jar files are in JMeter's lib directory. If they are not, shutdown JMeter, + copy the jar files over and restart JMeter. + See Getting Started for details. +
+ +

In this section, you will learn how to create a + Test Plan to test a JMS Point-to-Point messaging solution. +The setup of the test is 1 threadgroup with 5 threads sending 4 messages each through a request queue. +A fixed reply queue will be used for monitoring the reply messages. +To construct the Test Plan, you will use the +following elements: + Thread Group, + JMS Point-to-Point, and + Graph Results. +

+ +

General notes on JMS: There are currently two JMS samplers. One uses JMS topics +and the other uses queues. Topic messages are commonly known as pub/sub messaging. +Topic messaging is generally used in cases where a message is published by a producer and +consumed by multiple subscribers. A JMS sampler needs the JMS implementation jar files; +for example, from Apache ActiveMQ. See here for the list +of jars provided by ActiveMQ 3.0.

+ +

11.1 Adding a Thread Group

+

The first step you want to do with every JMeter Test Plan is to add a + Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

+ +

Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

+ +

You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

+ +

Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 11.1 +below)

+ +

+Figure 11.1. Thread Group with Default Values
+Figure 11.1. Thread Group with Default Values
+ +

Start by providing a more descriptive name for our Thread Group. In the name +field, enter Point-to-Point.

+ +

Next, increase the number of users (called threads) to 5.

+ +

In the next field, the Ramp-Up Period, leave set the value to 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

+ +

Clear the checkbox labeled "Forever", and enter a value of 4 in the Loop +Count field. This property tells JMeter how many times to repeat your test. +If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

+ +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element).
+ + +

11.2 Adding JMS Point-to-Point Sampler

+ +

Start by adding the sampler JMS Point-to-Point +to the Point-to-Point element (Add --> Sampler --> JMS Point-to-Point). +Then, select the JMS Point-to-Point sampler element in the tree. + In building the example a configuration will be provided that works with ActiveMQ 3.0. +

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameValueDescription
JMS Resources
QueueuConnectionFactoryConnectionFactory This is the default JNDI entry for the connection factory within active mq.
JNDI Name Request QueueQ.REQThis is equal to the JNDI name defined in the JNDI properties.
JNDI Name Reply QueueQ.RPLThis is equal to the JNDI name defined in the JNDI properties.
Message Properties
Communication StyleRequest ResponseThis means that you need at least a service running outside of JMeter and that will respond to the requests. + This service must listen to the Request Queue and send messages to the queue referenced by the message.getJMSReplyTo()
ContenttestThis is just the content of the message.
JMS PropertiesNothing needed for active mq.
JNDI Properties
InitialContextFactoryorg.apache.activemq.jndi.ActiveMQInitialContextFactoryThe standard InitialContextFactory for Active MQ
Properties
queue.Q.REQexample.AThis defines a JNDI name Q.REQ for the request queue that points to the queue example.A
queue.Q.RPLexample.BThis defines a JNDI name Q.RPL for the reply queue that points to the queue example.B
Provider URL
Provider URLtcp://localhost:61616This defines the URL of the active mq messaging system.
+

+ +

11.3 Adding a Listener to View Store the Test Results

+

The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your JMS requests in a file and presenting +a visual model of the data. +

+ +

Select the Thread Group element and add a + Graph Results listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename. +

+ +

+Figure 11.2. Graph Results Listener
+Figure 11.2. Graph Results Listener
+ +
\ No newline at end of file diff --git a/docs/usermanual/build-jms-topic-test-plan.html b/docs/usermanual/build-jms-topic-test-plan.html new file mode 100644 index 00000000000..437d9437127 --- /dev/null +++ b/docs/usermanual/build-jms-topic-test-plan.html @@ -0,0 +1,204 @@ + +Apache JMeter + - + User's Manual: Building a JMS (Java Messaging Service) Test Plan
Logo ASF
Apache JMeter

12. Building a JMS Topic Test Plan

+
+JMS requires some optional jars to be downloaded. Please refer to Getting Started for full details. +
+

In this section, you will learn how to create a +Test Plan to test JMS Providers. You will +create five subscribers and one publisher. You will create 2 thread groups and set +each one to 10 iterations. The total messages is (6 threads) x (1 message) x +(repeat 10 times) = 60 messages. To construct the Test Plan, you will use the +following elements: +Thread Group, +JMS Publisher, +JMS Subscriber, and +Graph Results.

+ +

General notes on JMS: There are currently two JMS samplers. One uses JMS topics +and the other uses queues. Topic messages are commonly known as pub/sub messaging. +Topic messaging is generally used in cases where a message is published by a producer and +consumed by multiple subscribers. Queue messaging is generally used for transactions +where the sender expects a response. Messaging systems are quite different from +normal HTTP requests. In HTTP, a single user sends a request and gets a response. +Messaging system can work in sychronous and asynchronous mode. A JMS sampler needs +the JMS implementation jar files; for example, from Apache ActiveMQ. +See here for the list of jars provided by ActiveMQ 3.0.

+ +

12.1 Adding Users

+

The first step is add a Thread Group + element. The Thread Group tells JMeter the number of users you want to simulate, + how often the users should send requests, and how many requests they should +send.

+ +

Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

+ +

You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

+ +

Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 12.1 +below)

+ +

+Figure 12.1. Thread Group with Default Values
+Figure 12.1. Thread Group with Default Values
+ +

Start by providing a more descriptive name for our Thread Group. In the name +field, enter Subscribers.

+ +

Next, increase the number of users (called threads) to 5.

+ +

In the next field, the Ramp-Up Period, set the value to 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, JMeter will immediately start all users.

+ +

Clear the checkbox labeled "Forever", and enter a value of 10 in the Loop +Count field. This property tells JMeter how many times to repeat your test. +If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

+ +

Repeat the process and add another thread group. For the second thread +group, enter "Publisher" in the name field, set the number of threads to 1, +and set the iteration to 10. +

+ +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element).
+ + +

12.2 Adding JMS Subscriber and Publisher

+ +

Make sure the required jar files are in JMeter's lib directory. If they are +not, shutdown JMeter, copy the jar files over and restart JMeter.

+ +

Start by adding the sampler JMS Subscriber +to the Subscribers element (Add --> Sampler --> JMS Subscriber). +Then, select the JMS Subscriber element in the tree and edit the following properties: + +

    +
  1. Change the Name field to "Sample Subscriber"
  2. +
  3. If the JMS provider uses the jndi.properties file, check the box
  4. +
  5. Enter the name of the InitialContextFactory class. For example, with ActiveMQ 5.4, the value is "org.apache.activemq.jndi.ActiveMQInitialContextFactory"
  6. +
  7. Enter the provider URL. This is the URL for the JNDI server, if there is one. For example, with ActiveMQ 5.4 on local machine with default port, the value is "tcp://localhost:61616"
  8. +
  9. Enter the name of the connection factory. Please refer to the documentation +of the JMS provider for the information. For ActiveMQ, the default is "ConnectionFactory"
  10. +
  11. Enter the name of the message topic. For ActiveMQ Dynamic Topics (create topics dynamically), example value is "dynamicTopics/MyStaticTopic1" +Note: Setup at startup mean that JMeter starting to listen on the Destination at beginning of test without name change possibility. +Setup on Each sample mean that JMeter (re)starting to listen before run each JMS Subscriber sample, +this last option permit to have Destination name with some JMeter variables
  12. +
  13. If the JMS provider requires authentication, check "required" and enter the +username and password. For example, Orion JMS requires authentication, while ActiveMQ +and MQSeries does not
  14. +
  15. Enter 10 in "Number of samples to aggregate". For performance reasons, the sampler +will aggregate messages, since small messages will arrive very quickly. If the sampler +didn't aggregate the messages, JMeter wouldn't be able to keep up.
  16. +
  17. If you want to read the response, check the box
  18. +
  19. There are two client implementations for subscribers. If the JMS provider +exhibits zombie threads with one client, try the other.
  20. +
+

+ +

+Figure 12.2. JMS Subscriber
+Figure 12.2. JMS Subscriber
+ +

Next add the sampler JMS Publisher +to the Publisher element (Add --> Sampler --> JMS Subscriber). +Then, select the JMS Publisher element in the tree and edit the following properties: +

+ +
    +
  1. Change the Name field to "Sample Publisher".
  2. +
  3. If the JMS provider uses the jndi.properties file, check the box
  4. +
  5. Enter the name of the InitialContextFactory class. For example, with ActiveMQ 5.4, the value is "org.apache.activemq.jndi.ActiveMQInitialContextFactory"
  6. +
  7. Enter the provider URL. This is the URL for the JNDI server, if there is one. For example, with ActiveMQ 5.4 on local machine with default port, the value is "tcp://localhost:61616"
  8. +
  9. Enter the name of the connection factory. Please refer to the documentation +of the JMS provider for the information. For ActiveMQ, the default is "ConnectionFactory"
  10. +
  11. Enter the name of the message topic. For ActiveMQ Dynamic Topics (create topics dynamically), example value is "dynamicTopics/MyStaticTopic1". +Note: Setup at startup mean that JMeter starting connection with the Destination at beginning of test without name change possibility. +Setup on Each sample mean that JMeter (re)starting the connection before run each JMS Publisher sample, +this last option permit to have Destination name with some JMeter variables
  12. +
  13. If the JMS provider requires authentication, check "required" and enter the +username and password. For example, Orion JMS requires authentication, while ActiveMQ +and MQSeries does not
  14. +
  15. Enter 10 in "Number of samples to aggregate". For performance reasons, the sampler +will aggregate messages, since small messages will arrive very quickly. If the sampler +didn't aggregate the messages, JMeter wouldn't be able to keep up.
  16. +
  17. Select the appropriate configuration for getting the message to publish. If you +want the sampler to randomly select the message, place the messages in a directory +and select the directory using browse.
  18. +
  19. Select the message type. If the message is in object format or map message, make sure the +message is generated correctly.
  20. +
+

+

+Figure 12.3. JMS Publisher
+Figure 12.3. JMS Publisher
+ + +

12.3 Adding a Listener to View Store the Test Results

+

The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data.

+ +

Select the Test Plan element and add a Graph Results listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename.

+ +

+Figure 12.4. Graph Results Listener
+Figure 12.4. Graph Results Listener
+ +
\ No newline at end of file diff --git a/docs/usermanual/build-ldap-test-plan.html b/docs/usermanual/build-ldap-test-plan.html new file mode 100644 index 00000000000..5be4079bfc5 --- /dev/null +++ b/docs/usermanual/build-ldap-test-plan.html @@ -0,0 +1,171 @@ + +Apache JMeter + - + User's Manual: Building an LDAP Test Plan
Logo ASF
Apache JMeter

9a. Building an LDAP Test Plan

+

In this section, you will learn how to create a basic Test Plan to test an LDAP server. +You will create four users that send requests for four tests on the LDAP server. Also, you will tell +the users to run their tests 4 times. So, the total number of requests is (4 users) x (4 requests) x +repeat 4 times) = 40 LDAP requests. To construct the Test Plan, you will use the following elements: +Thread Group, +LDAP Request, +LDAP Request Defaults, and +View Results in Table +.

+

This example assumes that the LDAP Server is available at ldap.test.com.

+

9a.1 Adding Users

+

The first step you want to do with every JMeter Test Plan is to add a Thread Group element. +The Thread Group tells JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

+

Go ahead and add the ThreadGroup element by first selecting the Test Plan, clicking your +right mouse button to get the Add menu, and then select Add>ThreadGroup. You should now see the +Thread Group element under Test Plan. If you do not see the element, then "expand" the Test Plan tree by +clicking on the Test Plan element. +


+Figure 9a.1. Thread Group and final test tree
+Figure 9a.1. Thread Group and final test tree
+ +

+

9a.2 Adding Login Config Element

+

Begin by selecting the LDAP Users element. Click your right mouse +button to get the Add menu, and then select Add>Config Element>Login Config Element. +Then, select this new element to view its Control Panel.

+

Like most JMeter elements, the Login Config Element's Control Panel has a name +field that you can modify. In this example, leave this field with the default value.

+ +

+  Figure 9a.2 Login Config Element for our Test Plan
+ Figure 9a.2 Login Config Element for our Test Plan
+ +

Enter Username field to "your LDAP Username",
+ The password field to "your LDAP Passowrd"

+ +

These values will be used by the LDAP Requests.

+

9a.3 Adding LDAP Request Defaults

+

Begin by selecting the LDAP Users element. Click your right mouse button +to get the Add menu, and then select Add>Config Element>LDAP Request Defaults. Then, +select this new element to view its Control Panel.

+

Like most JMeter elements, the LDAP Request Defaults Control Panel has a name +field that you can modify. In this example, leave this field with the default value.

+ + +

+  Figure 9a.3 LDAP Defaults for our Test Plan
+ Figure 9a.3 LDAP Defaults for our Test Plan
+ +
Enter DN field to "your LDAP Root Distinguished Name".
+ Enter LDAP Server's Servername field to "ldap.test.com".
+ The port to 389.
+ These values are default for the LDAP Requests.
+

9a.4 Adding LDAP Requests

+

In our Test Plan, we need to make four LDAP requests.

+
    +
  1. Inbuilt Add Test
  2. +
  3. Inbuilt Search Test
  4. +
  5. Inbuilt Modify Test
  6. +
  7. Inbuilt Delete Test
  8. + +
+

JMeter sends requests in the order that you add them to the tree. +Start by adding the first LDAP Request to the LDAP Users element (Add> +Sampler>LDAP Request). Then, select the LDAP Request element in the tree +and edit the following properties

+
    +
  1. Rename to "Add" this element
  2. +
  3. Select the Add Test radio button in Test Configuration group
  4. +
+

+                  Figure 9a.4.1 LDAP Request for Inbuilt Add test
+ Figure 9a.4.1 LDAP Request for Inbuilt Add test
+ + +

You do not have to set the Server Name field, port field, Username, Password +and DN because you already specified this value in the Login Config Element and +LDAP Request Defaults.

+

Next, add the second LDAP Request and edit the following +properties

+
    +
  1. Rename to "Search" this element
  2. +
  3. Select the Search Test radio button in Test Configuration group
  4. +
+ Next, add the Third LDAP Request and edit the following properties +

+                  Figure 9a.4.2 LDAP Request for Inbuilt Search test
+ Figure 9a.4.2 LDAP Request for Inbuilt Search test
+ +
    +
  1. Rename to "Modify" this element
  2. +
  3. Select the Modify Test radio button in Test Configuration group
  4. +
+ Next, add the fourth LDAP Request and edit the following properties + +

+                  Figure 9a.4.3 LDAP Request for Inbuilt Modify test
+ Figure 9a.4.3 LDAP Request for Inbuilt Modify test
+ +
    +
  1. Rename to "Delete" this element
  2. +
  3. Select the Delete Test radio button in Test Configuration group
  4. +
+

+                  Figure 9a.4.4 LDAP Request for Inbuilt Delete test
+ Figure 9a.4.4 LDAP Request for Inbuilt Delete test
+ +

9a.5 Adding a Response Assertion

+

You can add a Response Assertion element. + This element will check the received response data by verifying if the response text is "successful". + (Add>Assertion>Response Assertion).
Note: A this position in the tree, + the Response Assertion will be executed for each LDAP Request.

+
    +
  1. Select Text Response Radio button in Response Field to Test group
  2. +
  3. Select Substring Radio button in Pattern Matching Rules group
  4. +
  5. Click on Add button and add the string "successful" in Pattern to Test field
  6. +
+

+  Figure 9a.5 LDAP Response Assertion
+ Figure 9a.5 LDAP Response Assertion
+

9a.6 Adding a Listener to View/Store the Test Results

+

The final element you need to add to your Test Plan is a Listener. + This element is responsible for storing all of the results of your LDAP +requests in a file and presenting a visual model of the data. Select the LDAP +Users element and add a View Results in Table (Add>Listener>View Results in Table)

+

+  Figure 9a.6 View Results in Table Listener
+ Figure 9a.6 View Results in Table Listener
+ +
\ No newline at end of file diff --git a/docs/usermanual/build-ldapext-test-plan.html b/docs/usermanual/build-ldapext-test-plan.html new file mode 100644 index 00000000000..42b7dc17627 --- /dev/null +++ b/docs/usermanual/build-ldapext-test-plan.html @@ -0,0 +1,486 @@ + +Apache JMeter + - + User's Manual: Building an Extended LDAP Test Plan
Logo ASF
Apache JMeter

9b. Building an Extended LDAP Test Plan

+

+In this section, you will learn how to create a basic Test Plan to test an LDAP +server.

+

+As the Extended LDAP Sampler is highly configurable, this also means that it takes +some time to build a correct testplan. You can however tune it exactly up to your +needs. +

+ +

+You will create four users that send requests for four tests on the LDAP server. Also, you will tell +the users to run their tests one time. So, the total number of requests is (1 users) x (9 requests) x +repeat 1 time) = 9 LDAP requests. To construct the Test Plan, you will use the following elements:
+Thread Group,
+Adding LDAP Extended Request Defaults,
+Adding LDAP Requests, and
+Adding a Listener to View/Store the Test Results +

+

+This example assumes that the LDAP Server is available at ldap.test.com. +

+

+For the less experienced LDAP users, I build a small +LDAP tutorial which shortly explains +the several LDAP operations that can be used in building a complex testplan. +

+

+Take care when using LDAP special characters in the distinghuished name, in that case (eg, you want to use a + sign in a +distinghuished name) you need to escape the character by adding an "\" sign before that character. +extra exeption: if you want to add a \ character in a distinguished name (in an add or rename operation), you need to use 4 backslashes. +examples: +cn=dolf\+smits to add/search an entry with the name like cn=dolf+smits +cn=dolf \\ smits to search an entry with the name cn=dolf \ smits +cn=c:\\\\log.txt to add an entry with a name like cn=c:\log.txt +

+ + +

9b.1 Adding Users

+

+The first step you want to do with every JMeter Test Plan is to add a Thread Group element. +The Thread Group tells JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

+

+Go ahead and add the Thread Group element by first selecting the Test Plan, clicking your +right mouse button to get the Add menu, and then select Add>Threads (Users)>Thread Group. +You should now see the Thread Group element under Test Plan. If you do not see the element, then "expand" the Test Plan tree by +clicking on the Test Plan element. +

+

+


+Figure 9b.1. Thread Group with Default Values
+Figure 9b.1. Thread Group with Default Values
+ +

+
+

9b.2 Adding LDAP Extended Request Defaults

+

+Begin by selecting the LDAP Ext Users element. Click your right mouse button +to get the Add menu, and then select Add >Config Element>LDAP Extended Request Defaults. Then, +select this new element to view its Control Panel. +

+

+Like most JMeter elements, the LDAP Extended Request Defaults Control Panel has a name +field that you can modify. In this example, leave this field with the default value. +

+


+  Figure 9b.2 LDAP Defaults for our Test Plan

+ Figure 9b.2 LDAP Defaults for our Test Plan
+

+

+ For each of the different operations, some default values can be filled in. + In All cases, when a default is filled in, this is used for the LDAP extended requests. + For each requst, you can override the defaults by filling in the values in the LDAP extended request sampler. + When no value is entered which is necesarry for a test, the test will fail in an unpredictable way! +

+ We will not enter any default values here, as we will build a very small testplan, so we will explain all the different fields when we add the LDAP Extended samplers. +
+

9b.3 Adding LDAP Requests

+

+In our Test Plan, we want to use all 9 LDAP requests. +

+
    +
  1. +Thread bind +
  2. +
  3. +Search Test +
  4. +
  5. +Compare Test +
  6. +
  7. +Single bind/unbind Test +
  8. +
  9. +Add Test +
  10. +
  11. +Modify Test +
  12. +
  13. +Rename entry (moddn) +
  14. +
  15. +Delete Test +
  16. +
  17. +Thread unbind +
  18. +
+

+JMeter sends requests in the order that you add them to the tree. +

+

+Adding a requests always start by:
+Adding the LDAP Extended Request to the LDAP Ext Users element (Add> +Sampler>LDAP Ext Request). Then, select the LDAP Ext Request element in the tree +and edit the following properties.

+ + +

9b.3.1 Adding a Thread bind Request

+

+

    +
  1. +Rename the element: "1. Thread bind" +
  2. +
  3. +Select the "Thread bind" button. +
  4. +
  5. +Enter the hostname value from the LDAP server in the Servername field +
  6. +
  7. +Enter the portnumber from the LDAP server (636 : ldap over SSL) in the port field +
  8. +
  9. +(Optional) Enter the baseDN in the DN field, this baseDN will be used as thestarting point for searches, add, deletes etc.
    +take care that this must be the uppermost shared level for all your request, eg When all information is stored under ou=Users, dc=test, dc=com, you can use this value in the basedn.
    +
  10. +
  11. +(Optional) Enter the distinghuised name from the user you want to use for authentication. +When this field is kept empty, an anonymous bind will be established. +
  12. +
  13. +(Optional) Enter the password for the user you want to authenticate with, an empty password will also lead to an anonymous bind. +
  14. +
  15. +(Optional) Enter a value for the connection timeout with LDAP +
  16. +
  17. +(Optional) Check the box Use Secure LDAP Protocol if you access with LDAP over SSL (ldaps) +
  18. +
+

+

+


+Figure 9b.3.1. Thread Bind example
+Figure 9b.3.1. Thread Bind example
+

+
+ +

9b.3.2 Adding a search Request

+

+

    +
  1. +Rename the element: "2. Search Test" +
  2. +
  3. +Select the "Search Test" button. +
  4. +
  5. +(Optional) enter the searchbase under which you want to perform the search, relative to the basedn, used in the thread bind request.
    +When left empty, the basedn is used as a search base, this files is important if you want to use a "base-entry" or "one-level" search (see below) +
  6. +
  7. +Enter the searchfilter, any decent LDAP serach filter will do, but for now, use something simple, like (sn=Doe) or (cn=*) +
  8. +
  9. +(Optional) Enter the scope in the scope field, it has three options: +
      +
    1. baseobject search
      only the given searchbase is used, only for checking attributes or existence. +
    2. +
    3. onelevel search
      Only search in one level below given searchbase is used +
    4. +
    5. subtree search
      Searches for object at any point below the given basedn +
    +
  10. +
  11. +(Optional) Size limit, specifies the maximun number of returned entries, +
  12. +
  13. +(Optional) Time limit, specifies the maximum number of miliseconds, the SERVER can use for performing the search. it is NOT the maximun time the application will wait.
    +When a very large returnset is returned, from a very fast server, over a very slow line, you may have to wait for ages for the completion of the search request, but this parameter will not influence this. +
  14. +
  15. (Optional) Attributes you want in the search answer. This can be used to limit the size of the answer, especially when an onject has very large attributes (like jpegPhoto). There are three possibilities: +
    1. Leave empty (the default setting must also be empty) This will return all attributes. +
    2. +
    3. Put in one empty value (""), it will request a non-existent attributes, so in reality it returns no attributes +
    4. +
    5. Put in the attributes, seperated by a semi-colon. It will return only the requested attributes +
  16. +
  17. +(Optional) Return object. Checked will return all java-object attributes, it will add these to the requested attributes, as specified above.
    +Unchecked will mean no java-object attributes will be returned. +
  18. +
  19. +(Optional) Dereference aliases. Checked will mean it will follow references, Unchecked says it will not. +
  20. +
  21. +(Optional) Parse the search results?. Checked will mean it gets all results in response data, Unchecked says it will not. +
  22. +
+

+

+


+Figure 9b.3.2. search request example
+Figure 9b.3.2. search request example
+

+ +

9b.3.3 Adding a Compare Request

+

+

    +
  1. +Rename the element: "3. Compare Test" +
  2. +
  3. +Select the "Compare" button. +
  4. +
  5. +enter the entryname form the object on which you want the compare operation to work, relative to the basedn, eg "cn=jdoe,ou=Users" +
  6. +
  7. +Enter the compare filter, this must be in the form "attribute=value", eg "mail=jdoe@test.com" +
  8. +
+

+

+


+Figure 9b.3.3. Compare example
+Figure 9b.3.3. Compare example
+

+
+ +

9b.3.4 Adding a Single bind/unbind

+

+

    +
  1. +Rename the element: "4. Single bind/unbind Test" +
  2. +
  3. +Select the "Single bind/unbind" button. +
  4. +
  5. +Enter the FULL distinghuised name from the user you want to use for authentication.
    +eg. cn=jdoe,ou=Users,dc=test,dc=com +When this field is kept empty, an anonymous bind will be established. +
  6. +
  7. +Enter the password for the user you want to authenticate with, an empty password will also lead to an anonymous bind. +
  8. +
+Take care: This single bind/unbind is in reality two seperate operations but cannot easily be split! +

+


+Figure 9b.3.4. Single bind/unbind example
+Figure 9b.3.4. Single bind/unbind example
+

+
+ +

9b.3.5 Adding an Add Request

+

+

    +
  1. +Rename the element: "5. Add Test" +
  2. +
  3. +Select the "Add" button. +
  4. +
  5. +Enter the distinghuised name for the object to add, relative to the basedn. +
  6. +
  7. +Add a line in the "add test" table, fill in the attribute and value.
    +When you need the same attribute more than once, just add a new line, add the attribute again, and a different value.
    +All necessary attributes and values must be specified to pass the test, see picture!
    +(sometimes the server adds the attribute "objectClass=top", this might give a problem. +
  8. +
+

+

+


+Figure 9b.3.5. Add request example
+Figure 9b.3.5. Add request example
+

+
+ +

9b.3.6 Adding a Modify Request

+

+

    +
  1. +Rename the element: "6. Modify Test" +
  2. +
  3. +Select the "Modify test" button. +
  4. +
  5. +Enter the distinghuised name for the object to modify, relative to the basedn. +
  6. +
  7. +Add a line in the "modify test" table, with the "add" button. +
  8. +
  9. +You need to enter the attribute you want to modify, (optional) a value, and the opcode. The meaning of this opcode: +
    1. add
      this will mean that the attribute value (not optional in this case) willbe added to the attribute.
      +When the attribute is not existing, it will be created and the value added
      +When it is existing, and defined multi-valued, the new value is added.
      +when it is existing, but single valued, it will fail.
    2. +
    3. replace
      +This will overwrite the attribute with the given new value (not optional here)
      +When the attribute is not existing, it will be created and the value added
      +When it is existing, old values are removed, the new value is added.
    4. +
    5. delete
      +When no value is given, all values will be removed
      +When a value is given, only that value will be removed
      + when the given value is not existing, the test will fail +
    +
  10. +
  11. +(Optional) Add more modifications in the "modify test" table.
    +All modifications which are specified must succeed, to let the modification test pass. When one modification fails, +NO modifications at all will be made and the entry will remain unchanged. +
  12. +
+

+

+


+Figure 9b.3.6. Modify example
+Figure 9b.3.6. Modify example
+

+
+ +

9b.3.7 Adding a Rename Request (moddn)

+

+

    +
  1. +Rename the element: "7. Rename entry (moddn)" +
  2. +
  3. +Select the "Rename Entry" button. +
  4. +
  5. +Enter the name of the entry, relative to the baseDN, in the "old entry name-Field".
    +that is, if you want to rename "cn=Little John Doe,ou=Users", and you set the baseDN to "dc=test,dc=com", +you need to enter "cn=John Junior Doe,ou=Users" in the old entry name-field. +
  6. +
  7. +Enter the new name of the entry, relative to the baseDN, in the "new distinghuised name-Field".
    +when you only change the RDN, it will simply rename the entry
    +when you also add a different subtree, eg you change from cn=john doe,ou=Users to cn=john doe,ou=oldusers, it will move the entry. +You can also move a complete subtree (If your LDAP server supports this!), eg ou=Users,ou=retired, to ou=oldusers,ou=users, +this will move the complete subtee, plus all retired people in the subtree to the new place in the tree. +
  8. +
+

+

+


+Figure 9b.3.8. Rename example
+Figure 9b.3.8. Rename example
+

+
+ +

9b.3.8 Adding a Delete Request

+

+

    +
  1. +Rename the element: "8. Delete Test" +
  2. +
  3. +Select the "Delete" button. +
  4. +
  5. +Enter the name of the entry, relative to the baseDN, in the Delete-Field.
    +that is, if you want to remove "cn=John Junior Doe,ou=Users,dc=test,dc=com", and you set the baseDN to "dc=test,dc=com", +you need to enter "cn=John Junior Doe,ou=Users" in the Delete-field. +
  6. +
+

+

+


+Figure 9b.3.7. Delete example
+Figure 9b.3.7. Delete example
+

+
+ +

9b.3.9 Adding an unbind Request

+

+

    +
  1. +Rename the element: 9. Thread unbind" +
  2. +
  3. +Select the "Thread unbind" button. +This will be enough as it just closes the current connection. +The information which is needed is already known by the system +
+

+

+


+Figure 9b.3.9. Unbind example
+Figure 9b.3.9. Unbind example
+

+
+
+ +

9b.4 Adding a Listener to View/Store the Test Results

+

+The final element you need to add to your Test Plan is a Listener. + This element is responsible for storing all of the results of your LDAP +requests in a file and presenting a visual model of the data.Select the Thread group +element and add a View Results Tree (Add>Listener>View Results Tree) +

+

+


+Figure 9b.4. View Result Tree Listener
+Figure 9b.4. View Result Tree Listener
+

+

+In this listener you have three tabs to view, the sampler result, the request and the response data. +

    +
  1. +The sampler result just contains the response time, the returncode and return message +
  2. +
  3. +The request gives a short description of the request that was made, in practice no relevant information +is contained here. +
  4. +
  5. +The response data contains the full details of the sent request, as well the full details of the received answer, +this is given in a (self defined) xml-style. +The full description can be found here. +
  6. +
+

+
+
\ No newline at end of file diff --git a/docs/usermanual/build-monitor-test-plan.html b/docs/usermanual/build-monitor-test-plan.html new file mode 100644 index 00000000000..03001f60813 --- /dev/null +++ b/docs/usermanual/build-monitor-test-plan.html @@ -0,0 +1,158 @@ + +Apache JMeter + - + User's Manual: Building a Monitor Test Plan
Logo ASF
Apache JMeter

13. Building a Monitor Test Plan

+

In this section, you will learn how to create a +Test Plan to monitor webservers. Monitors +are useful for a stress testing and system management. Used with stress +testing, the monitor provides additional information about server performance. +It also makes it easier to see the relationship between server performance +and response time on the client side. As a system administration tool, the +monitor provides an easy way to monitor multiple servers from one console. +The monitor was designed to work with the status servlet in Tomcat 5. In +theory, any servlet container that supports JMX (Java Management Extension) +can port the status servlet to provide the same information.

+

For those who want to use the monitor with other servlet or EJB containers, +Tomcat's status servlet should work with other containers for the memory +statistics without any modifications. To get thread information, you will +need to change the MBeanServer lookup to retrieve the correct MBeans.

+ +

13.1 Adding A Server

+

The first step is to add a Thread Group +element. The Thread Group tells JMeter the number of threads you want. Always use +1, since we are using JMeter as a monitor. This is very important for those not +familiar with server monitors. As a general rule, using multiple threads for a +single server is bad and can create significant stress. +

+ +

Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

+ +

You should now see the Thread Group element under Test Plan. If you do not +see the element, "expand" the Test Plan tree by clicking on the Test Plan element.

+ +

+Figure 13.1. Thread Group with Default Values
+Figure 13.1. Thread Group with Default Values
+ +

Change the loop count to forever (or some large number) so that enough samples are generated.

+ +

13.2 HTTP Auth Manager

+

Add the HTTP Authorization Manager to the Thread Group element +(Add --> Config element --> HTTP Authorization Manager). Enter the username +and password for your webserver. Important note: the monitor only works with +Tomcat5 build 5.0.19 and newer. For instructions on how to setup Tomcat, please +refer to tomcat 5 documentation.

+
    +
  1. leave the base URL blank
  2. +
  3. enter the username
  4. +
  5. enter the password
  6. +
+

13.3 Adding HTTP Request

+ +

Add the HTTP Request to the Thread Group element +(Add --> Sampler --> HTTP Request). Then, select the HTTP Request element +in the tree and edit the following properties): +

    +
  1. Change the Name field to "Server Status".
  2. +
  3. Enter the IP address or Hostname
  4. +
  5. Enter the port number
  6. +
  7. Set the Path field to "/manager/status" if you're using Tomcat.
  8. +
  9. Add a request parameter named "XML" in uppercase. Give it a value of +"true" in lowercase.
  10. +
  11. Check "Use as Monitor" at the bottom of the sampler
  12. +
+

+ +

13.4 Adding Constant Timer

+ +

Add a timer to this thread group (Add --> Timer --> Constant Timer). +Enter 5000 milliseconds in the "Thread Delay" box. In general, using intervals shorter +than 5 seconds will add stress to your server. Find out what is an acceptable interval +before you deploy the monitor in your production environment.

+ +

13.5 Adding a Listener to Store the Results

+

If you want to save the raw results from the server, add a simple data + Listener. If you want to save the + calculated statistics, enter a filename in the listener. If you want to save both + the raw data and statistics, make sure you use different filenames.

+ +

Select the thread group element and add a Simple Data Writer listener +(Add --> Listener --> Simple Data Writer). Next, you need to specify a directory +and filename of the output file. You can either type it into the filename field, or +select the Browse button and browse to a directory and then enter a filename.

+ +

13.6 Adding Monitor Results

+ +

Add the Listener by selecting the +test plan element (Add --> Listener -- > Monitor Results). +
+By default, the Listener will select the results from the first connector in the sample response. +The Connector prefix field can be used to select a different connector. +If specified, the Listener will choose the first connector which matches the prefix. +If no match is found, then the first connector is selected. +

+

There are two tabs in +the monitor results listener. The first is the "Health", which displays the status of +the last sample the monitor received. The second tab is "Performance", which shows a +historical view of the server's performance. +

+ +
+

A quick note about how health is calculated. Typically, a server will crash if +it runs out of memory, or reached the maximum number of threads. In the case of +Tomcat 5, once the threads are maxed out, requests are placed in a queue until a +thread is available. The relative importance of threads vary between containers, so +the current implementation uses 50/50 to be conservative. A container that is more +efficient with thread management might not see any performance degradation, but +the used memory definitely will show an impact.

+
+

The performance graph shows four different lines. The free memory line shows how +much free memory is left in the current allocated block. Tomcat 5 returns the maximum +memory, but it is not graphed. In a well tuned environment, the server should never +reach the maximum memory.

+

Note the graph has captions on both sides of the graph. On the left is percent and +the right is dead/healthy. If the memory line spikes up and down rapidly, it could +indicate memory thrashing. In those situations, it is a good idea to profile the +application with Borland OptimizeIt or JProbe. What you want to see is a regular +pattern for load, memory and threads. Any erratic behavior usually indicates poor +performance or a bug of some sort.

+ +
\ No newline at end of file diff --git a/docs/usermanual/build-test-plan.html b/docs/usermanual/build-test-plan.html new file mode 100644 index 00000000000..93aafc89bd7 --- /dev/null +++ b/docs/usermanual/build-test-plan.html @@ -0,0 +1,159 @@ + +Apache JMeter + - + User's Manual: Building a Test Plan
Logo ASF
Apache JMeter

3. Building a Test Plan

+

A test plan describes a series of steps JMeter will execute when run. A complete +test plan will consist of one or more Thread Groups, logic controllers, sample generating +controllers, listeners, timers, assertions, and configuration elements. +

+ +

3.1 Adding and Removing Elements

+

Adding elements to a test plan can be done by right-clicking on an element in the +tree, and choosing a new element from the "add" list. Alternatively, elements can +be loaded from file and added by choosing the "merge" or "open" option.

+ +

To remove an element, make sure the element is selected, right-click on the element, +and choose the "remove" option.

+
+ +

3.2 Loading and Saving Elements

+

To load an element from file, right click on the existing tree elements to which +you want to add the loaded element, and select the "merge" option. Choose the file where +your elements are saved. JMeter will merge the elements into the tree.

+ +

To save tree elements, right click on an element and choose the "Save Selection As ..." option. +JMeter will save the element selected, plus all child elements beneath it. In this way, +you can save test tree fragments and individual elements for later use.

+ +
The workbench is not automatically saved with the test plan, but it can be saved separately as above.
+
+ +

3.3 Configuring Tree Elements

+

Any element in the test tree will present controls in JMeter's right-hand frame. These +controls allow you to configure the behavior of that particular test element. What can be +configured for an element depends on what type of element it is.

+ +
The Test Tree itself can be manipulated by dragging and dropping components around the test tree.
+
+ +

3.4 Saving the Test Plan

+

Although it is not required, we recommend that you save the Test Plan to a +file before running it. To save the Test Plan, select "Save" or "Save Test Plan As ..." from the +File menu (with the latest release, it is no longer necessary to select the +Test Plan element first).

+ +
JMeter allows you to save the entire Test Plan tree or +only a portion of it. To save only the elements located in a particular "branch" +of the Test Plan tree, select the Test Plan element in the tree from which to start +the "branch", and then click your right mouse button to access the "Save Selection As ..." menu item. +Alternatively, select the appropriate Test Plan element and then select "Save Selection As ..." from +the Edit menu. +
+
+ +

3.5 Running a Test Plan

+

To run your test plan, choose "Start" (Control + r) from the "Run" menu item. +When JMeter is running, it shows a small green box at the right hand end of the section just under the menu bar. +You can also check the "Run" menu. +If "Start" is disabled, and "Stop" is enabled, +then JMeter is running your test plan (or, at least, it thinks it is).

+

+The numbers to the left of the green box are the number of active threads / total number of threads. +These only apply to a locally run test; they do not include any threads started on remote systems when using client-server mode. +

+
+ +

3.6 Stopping a Test

+

+There are two types of stop command available from the menu: +

    +
  • Stop (Control + '.') - stops the threads immediately if possible. +In Versions of JMeter after 2.3.2, many samplers are now Interruptible which means that active samples can be terminated early. +The stop command will check that all threads have stopped within the default timeout, which is 5000 ms = 5 seconds. +[This can be changed using the JMeter property jmeterengine.threadstop.wait] +If the threads have not stopped, then a message is displayed. +The Stop command can be retried, but if it fails, then it is necessary to exit JMeter to clean up. +
  • +
  • Shutdown (Control + ',')- requests the threads to stop at the end of any current work. +Will not interrupt any active samples. +The modal shutdown dialog box will remain active until all threads have stopped.
  • +
+Versions of JMeter after 2.3.2 allow a Stop to be initiated if Shutdown is taking too long. +Close the Shutdown dialog box and select Run/Stop, or just press Control + '.'. +

+

+When running JMeter in non-GUI mode, there is no Menu, and JMeter does not react to keystrokes such as Control + '.'. +So in versions after 2.3.2, JMeter non-GUI mode will listen for commands on a specific port +(default 4445, see the JMeter property jmeterengine.nongui.port). +In versions after 2.4, JMeter supports automatic choice of an alternate port if the default port is being used +(for example by another JMeter instance). In this case, JMeter will try the next higher port, continuing until +it reaches the JMeter property jmeterengine.nongui.maxport) which defaults to 4455. +If maxport is less than or equal to port, port scanning will not take place. +Note that JMeter 2.4 and earlier did not set up the listener for non-GUI clients, only non-GUI standalone tests; +this has been fixed. +
+The chosen port is displayed in the console window. +
+The commands currently supported are: +

    +
  • Shutdown - graceful shutdown
  • +
  • StopTestNow - immediate shutdown
  • +
+These commands can be sent by using the shutdown[.cmd|.sh] or stoptest[.cmd|.sh] script +respectively. The scripts are to be found in the JMeter bin directory. +The commands will only be accepted if the script is run from the same host. +

+
+ +

3.7 Error reporting

+

+JMeter reports warnings and errors to the jmeter.log file, as well as some information on the test run itself. +JMeter shows at the right hand end of its window, the number of warnings/errors found in jmeter.log file next to the warning icon. +Click on the warning icon to show the jmeter.log file at the bottom of JMeter's window. +Just occasionally there may be some errors that JMeter is unable to trap and log; these will appear on the command console. +If a test is not behaving as you expect, please check the log file in case any errors have been reported (e.g. perhaps a syntax error in a function call). +

+

+Sampling errors (e.g. HTTP 404 - file not found) are not normally reported in the log file. +Instead these are stored as attributes of the sample result. +The status of a sample result can be seen in the various different Listeners. +

+
+ +
\ No newline at end of file diff --git a/docs/usermanual/build-web-test-plan.html b/docs/usermanual/build-web-test-plan.html new file mode 100644 index 00000000000..2f36bfb5890 --- /dev/null +++ b/docs/usermanual/build-web-test-plan.html @@ -0,0 +1,227 @@ + +Apache JMeter + - + User's Manual: Building a Web Test Plan
Logo ASF
Apache JMeter

5. Building a Web Test Plan

+

In this section, you will learn how to create a basic +Test Plan to test a Web site. You will +create five users that send requests to two pages on the JMeter Web site. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (5 users) x (2 requests) x (repeat 2 times) = 20 HTTP requests. To +construct the Test Plan, you will use the following elements: +Thread Group, +HTTP Request, +HTTP Request Defaults, and +Graph Results.

+ +

For a more advanced Test Plan, see +Building an Advanced Web Test Plan.

+

5.1 Adding Users

+

The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

+ +

Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

+ +

You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

+ +

Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 5.1 +below)

+ +

+Figure 5.1. Thread Group with Default Values
+Figure 5.1. Thread Group with Default Values
+ +

Start by providing a more descriptive name for our Thread Group. In the name +field, enter JMeter Users.

+ +

Next, increase the number of users (called threads) to 5.

+ +

In the next field, the Ramp-Up Period, leave the the default value of 1 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

+ +

Finally enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. If you enter a loop count value of 1, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

+ +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element).
+ +

See Figure 5.2 for the completed JMeter Users Thread Group.

+ +

+Figure 5.2. JMeter Users Thread Group
+Figure 5.2. JMeter Users Thread Group
+ +

5.2 Adding Default HTTP Request Properties

+

Now that we have defined our users, it is time to define the tasks that they +will be performing. In this section, you will specify the default settings +for your HTTP requests. And then, in section 5.3, you will add HTTP Request +elements which use some of the default settings you specified here.

+ +

Begin by selecting the JMeter Users (Thread Group) element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> HTTP Request +Defaults. Then, select this new element to view its Control Panel (see Figure 5.3). +

+ +

+Figure 5.3. HTTP Request Defaults
+Figure 5.3. HTTP Request Defaults
+ +

+Like most JMeter elements, the HTTP Request Defaults Control +Panel has a name field that you can modify. In this example, leave this field with +the default value.

+ +

Skip to the next field, which is the Web Server's Server Name/IP. For the +Test Plan that you are building, all HTTP requests will be sent to the same +Web server, jmeter.apache.org. Enter this domain name into the field. +This is the only field that we will specify a default, so leave the remaining +fields with their default values.

+ +
The HTTP Request Defaults element does not tell JMeter +to send an HTTP request. It simply defines the default values that the +HTTP Request elements use.
+ +

See Figure 5.4 for the completed HTTP Request Defaults element

+ +

+Figure 5.4. HTTP Defaults for our Test Plan
+Figure 5.4. HTTP Defaults for our Test Plan
+ +

5.3 Adding Cookie Support

+

Nearly all web testing should use cookie support, unless your application +specifically doesn't use cookies. To add cookie support, simply add an +HTTP Cookie Manager to each Thread +Group in your test plan. This will ensure that each thread gets its own +cookies, but shared across all HTTP Request objects.

+ +

To add the HTTP Cookie Manager, simply select the +Thread Group, and choose Add --> +Config Element --> HTTP +Cookie Manager, either from the Edit Menu, or from the right-click pop-up menu.

+

5.4 Adding HTTP Requests

+ +

In our Test Plan, we need to make two HTTP requests. The first one is for the +JMeter home page (http://jmeter.apache.org/), and the second one is for the +Changes page (http://jmeter.apache.org/changes.html).

+ +
JMeter sends requests in the order that they appear in the tree.
+ +

Start by adding the first HTTP Request +to the JMeter Users element (Add --> Sampler --> HTTP Request). +Then, select the HTTP Request element in the tree and edit the following properties +(see Figure 5.5): +

    +
  1. Change the Name field to "Home Page".
  2. +
  3. Set the Path field to "/". Remember that you do not have to set the Server +Name field because you already specified this value in the HTTP Request Defaults +element.
  4. +
+

+ +

+Figure 5.5. HTTP Request for JMeter Home Page
+Figure 5.5. HTTP Request for JMeter Home Page
+ +

Next, add the second HTTP Request and edit the following properties (see +Figure 5.6: +

    +
  1. Change the Name field to "Changes".
  2. +
  3. Set the Path field to "/changes.html".
  4. +
+

+ +

+Figure 5.6. HTTP Request for JMeter Changes Page
+Figure 5.6. HTTP Request for JMeter Changes Page
+ +

5.5 Adding a Listener to View Store the Test Results

+

The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data.

+ +

Select the JMeter Users element and add a Graph Results listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename.

+ +

+Figure 5.7. Graph Results Listener
+Figure 5.7. Graph Results Listener
+ +

5.6 Logging in to a web-site

+

+It's not the case here, but some web-sites require you to login before permitting you to perform certain actions. +In a web-browser, the login will be shown as a form for the user name and password, +and a button to submit the form. +The button generates a POST request, passing the values of the form items as parameters. +

+

+To do this in JMeter, add an HTTP Request, and set the method to POST. +You'll need to know the names of the fields used by the form, and the target page. +These can be found out by inspecting the code of the login page. +[If this is difficult to do, you can use the JMeter Proxy Recorder to record the login sequence.] +Set the path to the target of the submit button. +Click the Add button twice and enter the username and password details. +Sometimes the login form contains additional hidden fields. +These will need to be added as well. +

+

+Figure 5.8. Sample HTTP login request
+Figure 5.8. Sample HTTP login request
+ +
\ No newline at end of file diff --git a/docs/usermanual/build-ws-test-plan.html b/docs/usermanual/build-ws-test-plan.html new file mode 100644 index 00000000000..d58e9b7b6fb --- /dev/null +++ b/docs/usermanual/build-ws-test-plan.html @@ -0,0 +1,161 @@ + +Apache JMeter + - + User's Manual: Building a SOAP WebService Test Plan
Logo ASF
Apache JMeter

10. Building a WebService Test Plan

+

In this section, you will learn how to create a +Test Plan to test a WebService. You will +create five users that send requests to One page. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (5 users) x (1 requests) x (repeat 2 times) = 10 HTTP requests. To +construct the Test Plan, you will use the following elements: +Thread Group, +HTTP Request, and +Aggregate Graph.

+ +

If the sampler appears to be getting an error from the webservice, double check the +SOAP message and make sure the format is correct. In particular, make sure the +xmlns attributes are exactly the same as the WSDL. If the xml namespace is +different, the webservice will likely return an error.

+ +

10.1 Creating WebService Test Plan

+ +

In our Test Plan, we will use a .NET webservice. We won't go into the details of writing a +webservice. If you don't know how to write a webservice, google for +webservice and familiarize yourself with writing webservices for +Java and .NET. It should be noted there is a significant difference +between how .NET and Java implement webservices. The topic is too +broad to cover in the user manual. Please refer to other sources to +get a better idea of the differences.

+ +
JMeter sends requests in the order that they appear in the tree.
+ +

Start by using menu File > "Templates..." and select template "Building a SOAP Webservice Test Plan". +Then, click "Create" button. + +


+Figure 10.1.0. Webservice Template
+Figure 10.1.0. Webservice Template
+Change the following: +
    +
  1. In "HTTP Request Defaults" change "Server Name of IP"
  2. +
  3. In "Soap Request", change "Path:" +
    Figure 10.1.1 Webservice Path
    Figure 10.1.1 Webservice Path
    +
  4. +
+

+ +

Next, select "HTTP Header Manager" and update "SOAPAction" header to match your webservice. +Some webservices may not use SOAPAction in this case remove it.
+Currently, only .NET uses SOAPAction, so it is normal to have a blank SOAPAction for all other webservices. The list includes JWSDP, Weblogic, Axis, The Mind Electric Glue, and gSoap. +

+
Figure 10.1.2 Webservice Headers
Figure 10.1.2 Webservice Headers
+ +

The last step is to paste the SOAP message in the "Body Data" +text area.

+
Figure 10.1.3 Webservice Body
Figure 10.1.3 Webservice Body
+ + +

10.2 Adding Users

+

The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

+ +

Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 10.2 +below)

+ +

+Figure 10.2. Thread Group with Default Values
+Figure 10.2. Thread Group with Default Values
+ +

Start by providing a more descriptive name for our Thread Group. In the name +field, enter JMeter Users.

+ +

Next, increase the number of users (called threads) to 10.

+ +

In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

+ +

Finally, clear the checkbox labeled "Forever", and enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

+ +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element).
+ +

See Figure 10.2 for the completed JMeter Users Thread Group.

+ +

+Figure 10.3. JMeter Users Thread Group
+Figure 10.3. JMeter Users Thread Group
+

10.3 Adding a Listener to View Store the Test Results

+

The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data.

+ +

Select the JMeter Users element and add a Aggregate Graph listener (Add --> Listener +--> Aggregate Graph). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename.

+ +

+Figure 10.4. Graph Results Listener
+Figure 10.4. Graph Results Listener
+ +

10.4 Rest Webservice

+

Testing a REST Webservice is very similar as you only need to modify in HTTP Request +

    +
  • Method: to select the one you want to test
  • +
  • Body Data: which can be JSON, XML or any custom text
  • +
+You may also need to modify "HTTP Header Manager" to select the correct "Content-Type" +

+
\ No newline at end of file diff --git a/docs/usermanual/component_reference.html b/docs/usermanual/component_reference.html new file mode 100644 index 00000000000..112f7a010be --- /dev/null +++ b/docs/usermanual/component_reference.html @@ -0,0 +1,6674 @@ + +Apache JMeter + - + User's Manual: Component Reference
Logo ASF
Apache JMeter

18.0 Introduction

+
+

+ +

+
+ Several test elements use JMeter properties to control their behaviour. + These properties are normally resolved when the class is loaded. + This generally occurs before the test plan starts, so it's not possible to change the settings by using the __setProperty() function. +
+

+

+
+

18.1 Samplers

+
+

+ Samplers perform the actual work of JMeter. + Each sampler (except Test Action) generates one or more sample results. + The sample results have various attributes (success/fail, elapsed time, data size etc) and can be viewed in the various listeners. +

+
+

FTP Request

Screenshot for FTP Request
+
+This controller lets you send an FTP "retrieve file" or "upload file" request to an FTP server. +If you are going to send multiple requests to the same FTP server, consider +using a FTP Request Defaults Configuration +Element so you do not have to enter the same information for each FTP Request Generative +Controller. When downloading a file, it can be stored on disk (Local File) or in the Response Data, or both. +

+Latency is set to the time it takes to login (versions of JMeter after 2.3.1). +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler that is shown in the tree.
No
+
Server Name or IP
Domain name or IP address of the FTP server.
Yes
+
Port
Port to use. If this is >0, then this specific port is used, otherwise JMeter uses the default FTP port.
No
+
Remote File:
File to retrieve or name of destination file to upload.
Yes
+
Local File:
File to upload, or destination for downloads (defaults to remote file name).
Yes, if uploading (*)
+
Local File Contents:
Provides the contents for the upload, overrides the Local File property.
Yes, if uploading (*)
+
get(RETR) / put(STOR)
Whether to retrieve or upload a file.
Yes
+
Use Binary mode ?
Check this to use Binary mode (default Ascii)
Yes
+
Save File in Response ?
+ Whether to store contents of retrieved file in response data. + If the mode is Ascii, then the contents will be visible in the Tree View Listener. +
Yes, if downloading
+
Username
FTP account username.
Usually
+
Password
FTP account password. N.B. This will be visible in the test plan.
Usually
+
+ + +
+ +

HTTP Request

Screenshot for HTTP Request
+ +
+

This sampler lets you send an HTTP/HTTPS request to a web server. It + also lets you control whether or not JMeter parses HTML files for images and + other embedded resources and sends HTTP requests to retrieve them. + The following types of embedded resource are retrieved:

+
    +
  • images
  • +
  • applets
  • +
  • stylesheets
  • +
  • external scripts
  • +
  • frames, iframes
  • +
  • background images (body, table, TD, TR)
  • +
  • background sound
  • +
+

+ The default parser is htmlparser. + This can be changed by using the property "htmlparser.classname" - see jmeter.properties for details. +

+

If you are going to send multiple requests to the same web server, consider + using an HTTP Request Defaults + Configuration Element so you do not have to enter the same information for each + HTTP Request.

+ +

Or, instead of manually adding HTTP Requests, you may want to use + JMeter's HTTP(S) Test Script Recorder to create + them. This can save you time if you have a lot of HTTP requests or requests with many + parameters.

+ +

There are two different test elements used to define the samplers: +

    +
  • AJP/1.3 Sampler - uses the Tomcat mod_jk protocol (allows testing of Tomcat in AJP mode without needing Apache httpd) + The AJP Sampler does not support multiple file upload; only the first file will be used. +
  • +
  • HTTP Request - this has an implementation drop-down box, which selects the HTTP protocol implementation to be used:
  • +
      +
    • Java - uses the HTTP implementation provided by the JVM. + This has some limitations in comparison with the HttpClient implementations - see below.
    • +
    • HTTPClient3.1 - uses Apache Commons HttpClient 3.1. + This is no longer being developed, and support for this may be dropped in a future JMeter release.
    • +
    • HTTPClient4 - uses Apache HttpComponents HttpClient 4.x.
    • +
    • Blank Value - does not set implementation on HTTP Samplers, so relies on HTTP Request Defaults if present or on jmeter.httpsampler property defined in jmeter.properties
    • +
    +
+

+

The Java HTTP implementation has some limitations:

+
    +
  • There is no control over how connections are re-used. + When a connection is released by JMeter, it may or may not be re-used by the same thread.
  • +
  • The API is best suited to single-threaded usage - various settings + are defined via system properties, and therefore apply to all connections.
  • +
  • There is a bug in the handling of HTTPS via a Proxy (the CONNECT is not handled correctly). + See Java bugs 6226610 and 6208335. +
  • +
  • It does not support virtual hosts.
  • +
  • It does not support the following methods: COPY, LOCK, MKCOL, MOVE, PATCH, PROPFIND, PROPPATCH, UNLOCK, REPORT, MKCALENDAR.
  • +
  • It does not support client based certificate testing with Keystore Config.
  • +
+

Note: the FILE protocol is intended for testing purposes only. + It is handled by the same code regardless of which HTTP Sampler is used.

+

If the request requires server or proxy login authorization (i.e. where a browser would create a pop-up dialog box), + you will also have to add an HTTP Authorization Manager Configuration Element. + For normal logins (i.e. where the user enters login information in a form), you will need to work out what the form submit button does, + and create an HTTP request with the appropriate method (usually POST) + and the appropriate parameters from the form definition. + If the page uses HTTP, you can use the JMeter Proxy to capture the login sequence. +

+

+ In versions of JMeter up to 2.2, only a single SSL context was used for all threads and samplers. + This did not generate the proper load for multiple users. + A separate SSL context is now used for each thread. + To revert to the original behaviour, set the JMeter property: +

+https.sessioncontext.shared=true
+
+ By default, the SSL context is retained for the duration of the test. + In versions of JMeter from 2.5.1, the SSL session can be optionally reset for each test iteration. + To enable this, set the JMeter property: +
+https.use.cached.ssl.context=false
+
+ Note: this does not apply to the Java HTTP implementation. +

+

+ JMeter defaults to the SSL protocol level TLS. + If the server needs a different level, e.g. SSLv3, change the JMeter property, for example: +

+https.default.protocol=SSLv3
+
+

+

+ JMeter also allows one to enable additional protocols, by changing the property https.socket.protocols. +

+

If the request uses cookies, then you will also need an + HTTP Cookie Manager. You can + add either of these elements to the Thread Group or the HTTP Request. If you have + more than one HTTP Request that needs authorizations or cookies, then add the + elements to the Thread Group. That way, all HTTP Request controllers will share the + same Authorization Manager and Cookie Manager elements.

+ +

If the request uses a technique called "URL Rewriting" to maintain sessions, + then see section + 6.1 Handling User Sessions With URL Rewriting + for additional configuration steps.

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler that is shown in the tree.
No
+
Server
+ Domain name or IP address of the web server. e.g. www.example.com. [Do not include the http:// prefix.] + Note: in JMeter 2.5 (and later) if the "Host" header is defined in a Header Manager, then this will be used + as the virtual host name. +
Yes, unless provided by HTTP Request Defaults
+
Port
Port the web server is listening to. Default: 80
No
+
Connect Timeout
Connection Timeout. Number of milliseconds to wait for a connection to open.
No
+
Response Timeout
Response Timeout. Number of milliseconds to wait for a response. + Note that this applies to each wait for a response. If the server response is sent in several chunks, the overall + elapsed time may be longer than the timeout. + A Duration Assertion can be used to detect responses that take too long to complete. +
No
+
Server (proxy)
Hostname or IP address of a proxy server to perform request. [Do not include the http:// prefix.]
No
+
Port
Port the proxy server is listening to.
No, unless proxy hostname is specified
+
Username
(Optional) username for proxy server.
No
+
Password
(Optional) password for proxy server. (N.B. this is stored unencrypted in the test plan)
No
+
Implementation
Java, HttpClient3.1, HttpClient4. + If not specified (and not defined by HTTP Request Defaults), the default depends on the value of the JMeter property + jmeter.httpsampler, failing that, the HttpClient4 implementation is used.
No
+
Protocol
HTTP, HTTPS or FILE. Default: HTTP
No
+
Method
GET, POST, HEAD, TRACE, OPTIONS, PUT, DELETE, PATCH (not supported for + JAVA implementation). With HttpClient4, the following methods related to WebDav are also allowed: COPY, LOCK, MKCOL, MOVE, + PROPFIND, PROPPATCH, UNLOCK, REPORT, MKCALENDAR.
Yes
+
Content Encoding
+ Content encoding to be used (for POST, PUT, PATCH and FILE). + This the the character encoding to be used, and is not related to the Content-Encoding HTTP header. +
No
+
Redirect Automatically
+ Sets the underlying http protocol handler to automatically follow redirects, + so they are not seen by JMeter, and thus will not appear as samples. + Should only be used for GET and HEAD requests. + The HttpClient sampler will reject attempts to use it for POST or PUT. + Warning: see below for information on cookie and header handling. +
No
+
Follow Redirects
+ This only has any effect if "Redirect Automatically" is not enabled. + If set, the JMeter sampler will check if the response is a redirect and follow it if so. + The initial redirect and further responses will appear as additional samples. + The URL and data fields of the parent sample will be taken from the final (non-redirected) + sample, but the parent byte count and elapsed time include all samples. + The latency is taken from the initial response (versions of JMeter after 2.3.4 - previously it was zero). + Note that the HttpClient sampler may log the following message:
+ "Redirect requested but followRedirects is disabled"
+ This can be ignored. +
+ In versions after 2.3.4, JMeter will collapse paths of the form '/../segment' in + both absolute and relative redirect URLs. For example http://host/one/../two => http://host/two. + If necessary, this behaviour can be suppressed by setting the JMeter property + httpsampler.redirect.removeslashdotdot=false +
No
+
Use KeepAlive
JMeter sets the Connection: keep-alive header. This does not work properly with the default HTTP implementation, as connection re-use is not under user-control. + It does work with the Apache HttpComponents HttpClient implementations.
No
+
Use multipart/form-data for HTTP POST
+ Use a multipart/form-data or application/x-www-form-urlencoded post request +
No
+
Browser-compatible headers
+ When using multipart/form-data, this suppresses the Content-Type and + Content-Transfer-Encoding headers; only the Content-Disposition header is sent. +
No
+
Path
The path to resource (for example, /servlets/myServlet). If the +resource requires query string parameters, add them below in the +"Send Parameters With the Request" section. + +As a special case, if the path starts with "http://" or "https://" then this is used as the full URL. + +In this case, the server, port and protocol fields are ignored; parameters are also ignored for GET and DELETE methods. +Also please note that the path is not encoded - apart from replacing spaces with %20 - +so unsafe characters may need to be encoded to avoid errors such as URISyntaxException. +
Yes
+
Send Parameters With the Request
The query string will + be generated from the list of parameters you provide. Each parameter has a name and + value, the options to encode the parameter, and an option to include or exclude an equals sign (some applications + don't expect an equals when the value is the empty string). The query string will be generated in the correct fashion, depending on + the choice of "Method" you made (ie if you chose GET or DELETE, the query string will be + appended to the URL, if POST or PUT, then it will be sent separately). Also, if you are + sending a file using a multipart form, the query string will be created using the + multipart form specifications. + See below for some further information on parameter handling. +

+ Additionally, you can specify whether each parameter should be URL encoded. If you are not sure what this + means, it is probably best to select it. If your values contain characters such as the following then encoding is usually required.: +

    +
  • ASCII Control Chars
  • +
  • Non-ASCII characters
  • +
  • Reserved characters:URLs use some characters for special use in defining their syntax. When these characters are not used in their special role inside a URL, they need to be encoded, example : '$', '&', '+', ',' , '/', ':', ';', '=', '?', '@'
  • +
  • Unsafe characters: Some characters present the possibility of being misunderstood within URLs for various reasons. These characters should also always be encoded, example : ' ', '<', '>', '#', '%', ...
  • +
+

+
No
+
File Path:
Name of the file to send. If left blank, JMeter + does not send a file, if filled in, JMeter automatically sends the request as + a multipart form request. +

+ If it is a POST or PUT or PATCH request and there is a single file whose 'Parameter name' attribute (below) is omitted, + then the file is sent as the entire body + of the request, i.e. no wrappers are added. This allows arbitrary bodies to be sent. This functionality is present for POST requests + after version 2.2, and also for PUT requests after version 2.3. + See below for some further information on parameter handling. +

+
No
+
Parameter name:
Value of the "name" web request parameter.
No
+
MIME Type
MIME type (for example, text/plain). + If it is a POST or PUT or PATCH request and either the 'name' attribute (below) are omitted or the request body is + constructed from parameter values only, then the value of this field is used as the value of the + content-type request header. +
No
+
Retrieve All Embedded Resources from HTML Files
Tell JMeter to parse the HTML file +and send HTTP/HTTPS requests for all images, Java applets, JavaScript files, CSSs, etc. referenced in the file. + See below for more details. +
No
+
Use as monitor
For use with the Monitor Results listener.
No
+
Save response as MD5 hash?
+ If this is selected, then the response is not stored in the sample result. + Instead, the 32 character MD5 hash of the data is calculated and stored instead. + This is intended for testing large amounts of data. +
No
+
Embedded URLs must match:
+ If present, this must be a regular expression that is used to match against any embedded URLs found. + So if you only want to download embedded resources from http://example.com/, use the expression: + http://example\.com/.* +
No
+
Use concurrent pool
Use a pool of concurrent connections to get embedded resources.
No
+
Size
Pool size for concurrent connections used to get embedded resources.
No
+
Source address type
+ [Only for HTTP Request with HTTPClient implementation]
+ To distinguish the source address value, select the type of these: +
    +
  • Select IP/Hostname to use a specific IP address or a (local) hostname
  • +
  • Select Device to pick the first available address for that interface which + this may be either IPv4 or IPv6
  • +
  • Select Device IPv4To select the IPv4 address of the device name (like eth0, lo, em0, etc.)
  • +
  • Select Device IPv6To select the IPv6 address of the device name (like eth0, lo, em0, etc.)
  • +
+
No
+
Source address field
+ [Only for HTTP Request with HTTPClient implementation]
+ This property is used to enable IP Spoofing. + It override the default local IP address for this sample. + The JMeter host must have multiple IP addresses (i.e. IP aliases, network interfaces, devices). + The value can be a host name, IP address, or a network interface device such as "eth0" or "lo" or "wlan0".
+ If the property httpclient.localaddress is defined, that is used for all HttpClient requests. +
No
+
+

+N.B. when using Automatic Redirection, cookies are only sent for the initial URL. +This can cause unexpected behaviour for web-sites that redirect to a local server. +E.g. if www.example.com redirects to www.example.co.uk. +In this case the server will probably return cookies for both URLs, but JMeter will only see the cookies for the last +host, i.e. www.example.co.uk. If the next request in the test plan uses www.example.com, +rather than www.example.co.uk, it will not get the correct cookies. +Likewise, Headers are sent for the initial request, and won't be sent for the redirect. +This is generally only a problem for manually created test plans, +as a test plan created using a recorder would continue from the redirected URL. +

+

+Parameter Handling:
+For the POST and PUT method, if there is no file to send, and the name(s) of the parameter(s) are omitted, +then the body is created by concatenating all the value(s) of the parameters. +Note that the values are concatenated without adding any end-of-line characters. +These can be added by using the __char() function in the value fields. +This allows arbitrary bodies to be sent. +The values are encoded if the encoding flag is set (versions of JMeter after 2.3). +See also the MIME Type above how you can control the content-type request header that is sent. +
+For other methods, if the name of the parameter is missing, +then the parameter is ignored. This allows the use of optional parameters defined by variables. +(versions of JMeter after 2.3) +

+
+

Since JMeter 2.6, you have the option to switch to Post Body when a request has only unnamed parameters +(or no parameters at all). +This option is useful in the following cases (amongst others): +

    +
  • GWT RPC HTTP Request
  • +
  • JSON REST HTTP Request
  • +
  • XML REST HTTP Request
  • +
  • SOAP HTTP Request
  • +
+Note that once you leave the Tree node, you cannot switch back to the parameter tab unless you clear the Post Body tab of data. +

+

+In Post Body mode, each line will be sent with CRLF appended, apart from the last line. +To send a CRLF after the last line of data, just ensure that there is an empty line following it. +(This cannot be seen, except by noting whether the cursor can be placed on the subsequent line.) +

+
Figure 1 - HTTP Request with one unnamed parameter
Figure 1 - HTTP Request with one unnamed parameter
+
Figure 2 - Confirm dialog to switch
Figure 2 - Confirm dialog to switch
+
Figure 3 - HTTP Request using Body Data
Figure 3 - HTTP Request using Body Data
+ +

+Method Handling:
+The POST, PUT and PATCH request methods work similarly, except that the PUT and PATCH methods do not support multipart requests +or file upload. +The PUT and PATCH method body must be provided as one of the following: +

    +
  • define the body as a file with empty Parameter name field; in which case the MIME Type is used as the Content-Type
  • +
  • define the body as parameter value(s) with no name
  • +
  • use the Post Body tab
  • +
+If you define any parameters with a name in either the sampler or Http +defaults then nothing is sent. +PUT and PATCH require a Content-Type. +If not using a file, attach a Header Manager to the sampler and define the Content-Type there. +The GET and DELETE request methods work similarly to each other. +

+

Upto and including JMeter 2.1.1, only responses with the content-type "text/html" were scanned for +embedded resources. Other content-types were assumed to be something other than HTML. +JMeter 2.1.2 introduces the a new property HTTPResponse.parsers, which is a list of parser ids, + e.g. htmlParser and wmlParser. For each id found, JMeter checks two further properties:

+
    +
  • id.types - a list of content types
  • +
  • id.className - the parser to be used to extract the embedded resources
  • +
+

See jmeter.properties file for the details of the settings. + If the HTTPResponse.parser property is not set, JMeter reverts to the previous behaviour, + i.e. only text/html responses will be scanned

+Emulating slow connections:
+HttpClient31, HttpClient4 and Java Sampler support emulation of slow connections; see the following entries in jmeter.properties: +
+# Define characters per second > 0 to emulate slow connections
+#httpclient.socket.http.cps=0
+#httpclient.socket.https.cps=0
+
+

Response size calculation
+Optional properties to allow change the method to get response size:
+

  • Gets the real network size in bytes for the body response +
    sampleresult.getbytes.body_real_size=true
  • +
  • Add HTTP headers to full response size +
    sampleresult.getbytes.headers_size=true
+ +
+The Java and HttpClient3 inplementations do not include transport overhead such as +chunk headers in the response body size.
+The HttpClient4 implementation does include the overhead in the response body size, +so the value may be greater than the number of bytes in the response content. +
+ +
Versions of JMeter before 2.5 returns only data response size (uncompressed if request uses gzip/defate mode). +
To return to settings before version 2.5, set the two properties to false.
+

+

+Retry handling
+In version 2.5 of JMeter, the HttpClient4 and Commons HttpClient 3.1 samplers used the default retry count, which was 3. +In later versions, the retry count has been set to 1, which is what the Java implementation appears to do. +The retry count can be overridden by setting the relevant JMeter property, for example: +

+httpclient4.retrycount=3
+httpclient3.retrycount=3
+
+

+

+Note: Certificates does not conform to algorithm constraints
+You may encounter the following error: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints + if you run a HTTPS request on a web site with a SSL certificate (itself or one of SSL certificates in its chain of trust) with a signature + algorithm using MD2 (like md2WithRSAEncryption) or with a SSL certificate with a size lower than 1024 bits. +

+This error is related to increased security in Java 7 version u16 (MD2) and version u40 (Certificate size lower than 1024 bits), and Java 8 too. +

+To allow you to perform your HTTPS request, you can downgrade the security of your Java installation by editing +the Java jdk.certpath.disabledAlgorithms property. Remove the MD2 value or the constraint on size, depending on your case. +

+This property is in this file: +

JAVA_HOME/jre/lib/security/java.security
+See + Bug + 56357 for details. +

+ + +
+ +

JDBC Request

Screenshot for JDBC Request
+ +

This sampler lets you send an JDBC Request (an SQL query) to a database.

+

Before using this you need to set up a +JDBC Connection Configuration Configuration element +

+

+If the Variable Names list is provided, then for each row returned by a Select statement, the variables are set up +with the value of the corresponding column (if a variable name is provided), and the count of rows is also set up. +For example, if the Select statement returns 2 rows of 3 columns, and the variable list is A,,C, +then the following variables will be set up: +

+A_#=2 (number of rows)
+A_1=column 1, row 1
+A_2=column 1, row 2
+C_#=2 (number of rows)
+C_1=column 3, row 1
+C_2=column 3, row 2
+
+If the Select statement returns zero rows, then the A_# and C_# variables would be set to 0, and no other variables would be set. +

+

+Old variables are cleared if necessary - e.g. if the first select retrieves 6 rows and a second select returns only 3 rows, +the additional variables for rows 4, 5 and 6 will be removed. +

+

+Note: The latency time is set from the time it took to acquire a connection. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler that is shown in the tree.
No
+
Variable Name
+ Name of the JMeter variable that the connection pool is bound to. + This must agree with the 'Variable Name' field of a JDBC Connection Configuration. +
Yes
+
Query Type
Set this according to the statement type: +
    +
  • Select Statement
  • +
  • Update Statement - use this for Inserts and Deletes as well
  • +
  • Callable Statement
  • +
  • Prepared Select Statement
  • +
  • Prepared Update Statement - use this for Inserts and Deletes as well
  • +
  • Commit
  • +
  • Rollback
  • +
  • Autocommit(false)
  • +
  • Autocommit(true)
  • +
  • Edit - this should be a variable reference that evaluates to one of the above
  • +
+
+ When "Prepared Select Statement", "Prepared Update Statement" or "Callable Statement" types are selected, a Statement Cache per connection is used by JDBC Request. + It stores by default up to 100 PreparedStatements per connection, this can impact your database (Open Cursors). +
+
Yes
+
SQL Query
+ SQL query. + Do not enter a trailing semi-colon. + There is generally no need to use { and } to enclose Callable statements; + however they mey be used if the database uses a non-standard syntax. + [The JDBC driver automatically converts the statement if necessary when it is enclosed in {}]. + For example: +
    +
  • select * from t_customers where id=23
  • +
  • CALL SYSCS_UTIL.SYSCS_EXPORT_TABLE (null,?, ?, null, null, null) +
      +
    • Parameter values: tablename,filename
    • +
    • Parameter types: VARCHAR,VARCHAR
    • +
    +
  • + The second example assumes you are using Apache Derby. +
+
Yes
+
Parameter values
+ Comma-separated list of parameter values. Use ]NULL[ to indicate a NULL parameter. + (If required, the null string can be changed by defining the property "jdbcsampler.nullmarker".) +
+ The list must be enclosed in double-quotes if any of the values contain a comma or double-quote, + and any embedded double-quotes must be doubled-up, for example: +
"Dbl-Quote: "" and Comma: ,"
+
There must be as many values as there are placeholders in the statement even if your parameters are OUT ones, be sure to set a value even if the value will not be used (for example in a CallableStatement).
+
Yes, if a prepared or callable statement has parameters
+
Parameter types
+ Comma-separated list of SQL parameter types (e.g. INTEGER, DATE, VARCHAR, DOUBLE) or integer values of Constants when for example you use custom database types proposed by driver (-10 for OracleTypes.CURSOR for example).
+ These are defined as fields in the class java.sql.Types, see for example:
+ Javadoc for java.sql.Types.
+ [Note: JMeter will use whatever types are defined by the runtime JVM, + so if you are running on a different JVM, be sure to check the appropriate document]
+ If the callable statement has INOUT or OUT parameters, then these must be indicated by prefixing the + appropriate parameter types, e.g. instead of "INTEGER", use "INOUT INTEGER".
+ If not specified, "IN" is assumed, i.e. "DATE" is the same as "IN DATE". +
+ If the type is not one of the fields found in java.sql.Types, versions of JMeter after 2.3.2 also + accept the corresponding integer number, e.g. since OracleTypes.CURSOR == -10, you can use "INOUT -10". +
+ There must be as many types as there are placeholders in the statement. +
Yes, if a prepared or callable statement has parameters
+
Variable Names
Comma-separated list of variable names to hold values returned by Select statements, Prepared Select Statements or CallableStatement. + Note that when used with CallableStatement, list of variables must be in the same sequence as the OUT parameters returned by the call. + If there are less variable names than OUT parameters only as many results shall be stored in the thread-context variables as variable names were supplied. + If more variable names than OUT parameters exist, the additional variables will be ignored
No
+
Result Variable Name
+ If specified, this will create an Object variable containing a list of row maps. + Each map contains the column name as the key and the column data as the value. Usage:
+ columnValue = vars.getObject("resultObject").get(0).get("Column Name"); +
No
+
Handle ResultSet
Defines how ResultSet returned from callable statements be handled: +
    +
  • Store As String (default) - All variables on Variable Names list are stored as strings, will not iterate through a ResultSets when present on the list.
  • +
  • Store As Object - Variables of ResultSet type on Variables Names list will be stored as Object and can be accessed in subsequent tests/scripts and iterated, will not iterate through the ResultSet
  • +
  • Count Records - Variables of ResultSet types will be iterated through showing the count of records as result. Variables will be stored as Strings.
  • +
+
No
+
+ + +
Versions of JMeter after 2.3.2 use UTF-8 as the character encoding. Previously the platform default was used.
+
Ensure Variable Name is unique accross Test Plan.
+
+ +

Java Request

Screenshot for Java Request
+ +

This sampler lets you control a java class that implements the +org.apache.jmeter.protocol.java.sampler.JavaSamplerClient interface. +By writing your own implementation of this interface, +you can use JMeter to harness multiple threads, input parameter control, and +data collection.

+

The pull-down menu provides the list of all such implementations found by +JMeter in its classpath. The parameters can then be specified in the +table below - as defined by your implementation. Two simple examples (JavaTest and SleepTest) are provided. +

+

+The JavaTest example sampler can be useful for checking test plans, because it allows one to set +values in almost all the fields. These can then be used by Assertions, etc. +The fields allow variables to be used, so the values of these can readily be seen. +

+
+ +
Since JMeter 2.8, if the method teardownTest is not overriden by a subclass of AbstractJavaSamplerClient, its teardownTest method will not be called. +This reduces JMeter memory requirements. +This will not have any impact on existing Test plans. +
+
The Add/Delete buttons don't serve any purpose at present.
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler + that is shown in the tree.
No
+
Classname
The specific implementation of + the JavaSamplerClient interface to be sampled.
Yes
+
Send Parameters with Request
A list of + arguments that will be passed to the sampled class. All arguments + are sent as Strings. See below for specific settings.
No
+
+ +

The following parameters apply to the SleepTest and JavaTest implementations:

+ +

+ Parameters +

Attribute
Description
Required
+
Sleep_time
How long to sleep for (ms)
Yes
+
Sleep_mask
How much "randomness" to add:
+ The sleep time is calculated as follows:
+ totalSleepTime = SleepTime + (System.currentTimeMillis() % SleepMask) +
Yes
+
+ +

The following parameters apply additionaly to the JavaTest implementation:

+ +

+ Parameters +

Attribute
Description
Required
+
Label
The label to use. If provided, overrides Name
No
+
ResponseCode
If provided, sets the SampleResult ResponseCode.
No
+
ResponseMessage
If provided, sets the SampleResult ResponseMessage.
No
+
Status
If provided, sets the SampleResult Status. If this equals "OK" (ignoring case) then the status is set to success, otherwise the sample is marked as failed.
No
+
SamplerData
If provided, sets the SampleResult SamplerData.
No
+
ResultData
If provided, sets the SampleResult ResultData.
No
+
+
+ +

SOAP/XML-RPC Request

Screenshot for SOAP/XML-RPC Request
+ +

This sampler lets you send a SOAP request to a webservice. It can also be +used to send XML-RPC over HTTP. It creates an HTTP POST request, with the specified XML as the +POST content. +To change the "Content-type" from the default of "text/xml", use a HeaderManager. +Note that the sampler will use all the headers from the HeaderManager. +If a SOAP action is specified, that will override any SOAPaction in the HeaderManager. +The primary difference between the soap sampler and +webservice sampler, is the soap sampler uses raw post and does not require conformance to +SOAP 1.1.

+
For versions of JMeter later than 2.2, the sampler no longer uses chunked encoding by default.
+For screen input, it now always uses the size of the data.
+File input uses the file length as determined by Java.
+On some OSes this may not work for all files, in which case add a child Header Manager +with Content-Length set to the actual length of the file.
+Or set Content-Length to -1 to force chunked encoding. +
+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler + that is shown in the tree.
No
+
URL
The URL to direct the SOAP request to.
Yes
+
Send SOAP action
Send a SOAP action header? (overrides the Header Manager)
No
+
Use KeepAlive
If set, sends Connection: keep-alive, else sends Connection: close
No
+
Soap/XML-RPC Data
The Soap XML message, or XML-RPC instructions. + Not used if the filename is provided. +
No
+
Filename
If specified, then the contents of the file are sent, and the Data field is ignored
No
+
+ +
+ +

WebService(SOAP) Request (DEPRECATED)

+ *** This element is deprecated. Use + HTTP_Request + instead *** +
Screenshot for WebService(SOAP) Request (DEPRECATED)
+

This sampler has been tested with IIS Webservice running .NET 1.0 and .NET 1.1. + It has been tested with SUN JWSDP, IBM webservices, Axis and gSoap toolkit for C/C++. + The sampler uses Apache SOAP driver to serialize the message and set the header + with the correct SOAPAction. Right now the sampler doesn't support automatic WSDL + handling, since Apache SOAP currently does not provide support for it. Both IBM + and SUN provide WSDL drivers. There are 3 options for the post data: text area, + external file, or directory. If you want the sampler to randomly select a message, + use the directory. Otherwise, use the text area or a file. The if either the + file or path are set, it will not use the message in the text area. If you need + to test a soap service that uses different encoding, use the file or path. If you + paste the message in to text area, it will not retain the encoding and will result + in errors. Save your message to a file with the proper encoding, and the sampler + will read it as java.io.FileInputStream.

+

An important note on the sampler is it will automatically use the proxy host + and port passed to JMeter from command line, if those fields in the sampler are + left blank. If a sampler has values in the proxy host and port text field, it + will use the ones provided by the user. This behavior may not be what users + expect.

+

By default, the webservice sampler sets SOAPHTTPConnection.setMaintainSession + (true). If you need to maintain the session, add a blank Header Manager. The + sampler uses the Header Manager to store the SOAPHTTPConnection object, since + the version of apache soap does not provide a easy way to get and set the cookies.

+

Note: If you are using CSVDataSet, do not check "Memory Cache". If memory + cache is checked, it will not iterate to the next value. That means all the requests + will use the first value.

+

Make sure you use <soap:Envelope rather than <Envelope. For example:

+
+<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope 
+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
+xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Body>
+<foo xmlns="http://clients-xlmns"/>
+</soap:Body>
+</soap:Envelope>
+
+
The SOAP library that is used does not support SOAP 1.2, only SOAP 1.1. +Also the library does not provide access to the HTTP response code (e.g. 200) or message (e.g. OK). +To get round this, versions of JMeter after 2.3.2 check the returned message length. +If this is zero, then the request is marked as failed. +
+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler + that is shown in the tree.
No
+
WSDL URL
The WSDL URL with the service description. + Versions of JMeter after 2.3.1 support the file: protocol for local WSDL files. +
No
+
Web Methods
Will be populated from the WSDL when the Load WSDL button is pressed. + Select one of the methods and press the Configure button to populate the Protocol, Server, Port, Path and SOAPAction fields. +
No
+
Protocol
HTTP or HTTPS are acceptable protocol.
Yes
+
Server Name or IP
The hostname or IP address.
Yes
+
Port Number
Port Number.
Yes
+
Timeout
Connection timeout.
No
+
Path
Path for the webservice.
Yes
+
SOAPAction
The SOAPAction defined in the webservice description or WSDL.
Yes
+
Soap/XML-RPC Data
The Soap XML message
Yes
+
Soap file
File containing soap message
No
+
Message(s) Folder
Folder containing soap files. Files are choose randomly during test.
No
+
Memory cache
+ When using external files, setting this causes the file to be processed once and caches the result. + This may use a lot of memory if there are many different large files. +
Yes
+
Read SOAP Response
Read the SOAP reponse (consumes performance). Permit to have assertions or post-processors
No
+
Use HTTP Proxy
Check box if http proxy should be used
No
+
Server Name or IP
Proxy hostname
No
+
Port Number
Proxy host port
No
+
+ +
+Webservice Soap Sampler assumes that empty response means failure. +
+
+ +

LDAP Request

Screenshot for LDAP Request
+
This Sampler lets you send a different Ldap request(Add, Modify, Delete and Search) to an LDAP server. +

If you are going to send multiple requests to the same LDAP server, consider + using an LDAP Request Defaults + Configuration Element so you do not have to enter the same information for each + LDAP Request.

The same way the Login Config Element also using for Login and password. +
+ +

There are two ways to create test cases for testing an LDAP Server.

+
  1. Inbuilt Test cases.
  2. +
  3. User defined Test cases.
+ +

There are four test scenarios of testing LDAP. The tests are given below:

+
    +
  1. Add Test
  2. +
    1. Inbuilt test : +

      This will add a pre-defined entry in the LDAP Server and calculate + the execution time. After execution of the test, the created entry will be + deleted from the LDAP + Server.

    2. +
    3. User defined test : +

      This will add the entry in the LDAP Server. User has to enter all the + attributes in the table.The entries are collected from the table to add. The + execution time is calculated. The created entry will not be deleted after the + test.

    + +
  3. Modify Test
  4. +
    1. Inbuilt test : +

      This will create a pre-defined entry first, then will modify the + created entry in the LDAP Server.And calculate the execution time. After + execution + of the test, the created entry will be deleted from the LDAP Server.

    2. +
    3. User defined test +

      This will modify the entry in the LDAP Server. User has to enter all the + attributes in the table. The entries are collected from the table to modify. + The execution time is calculated. The entry will not be deleted from the LDAP + Server.

    + +
  5. Search Test
  6. +
    1. Inbuilt test : +

      This will create the entry first, then will search if the attributes + are available. It calculates the execution time of the search query. At the + end of the execution,created entry will be deleted from the LDAP Server.

    2. +
    3. User defined test +

      This will search the user defined entry(Search filter) in the Search + base (again, defined by the user). The entries should be available in the LDAP + Server. The execution time is calculated.

    + +
  7. Delete Test
  8. +
    1. Inbuilt test : +

      This will create a pre-defined entry first, then it will be deleted + from the LDAP Server. The execution time is calculated.

    2. + +
    3. User defined test +

      This will delete the user-defined entry in the LDAP Server. The entries + should be available in the LDAP Server. The execution time is calculated.

+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler that is shown in the tree.
No
+
Server Name or IP
Domain name or IP address of the LDAP server. + JMeter assumes the LDAP server is listening on the default port(389).
Yes
+
Port
default port(389).
Yes
+
root DN
DN for the server to communicate
Yes
+
Username
LDAP server username.
Usually
+
Password
LDAP server password. (N.B. this is stored unencrypted in the test plan)
Usually
+
Entry DN
the name of the context to create or Modify; may not be empty Example: do you want to add cn=apache,ou=test + you have to add in table name=cn, value=apache +
Yes
+
Delete
the name of the context to Delete; may not be empty
Yes
+
Search base
the name of the context or object to search
Yes
+
Search filter
the filter expression to use for the search; may not be null
Yes
+
add test
this name, value pair to added in the given context object
Yes
+
modify test
this name, value pair to add or modify in the given context object
Yes
+
+ + + +
+ +

LDAP Extended Request

Screenshot for LDAP Extended Request
+
This Sampler can send all 8 different LDAP request to an LDAP server. It is an extended version of the LDAP sampler, + therefore it is harder to configure, but can be made much closer resembling a real LDAP session. +

If you are going to send multiple requests to the same LDAP server, consider + using an LDAP Extended Request Defaults + Configuration Element so you do not have to enter the same information for each + LDAP Request.

+ +

There are nine test operations defined. These operations are given below:

+
    +
  1. Thread bind
  2. +

    Any LDAP request is part of an LDAP session, so the first thing that should be done is starting a session to the LDAP server. + For starting this session a thread bind is used, which is equal to the LDAP "bind" operation. + The user is requested to give a username (Distinguished name) and password, + which will be used to initiate a session. + When no password, or the wrong password is specified, an anonymous session is started. Take care, + omitting the password will not fail this test, a wrong password will. + (N.B. this is stored unencrypted in the test plan)

    +

    + Parameters +

    Attribute
    Description
    Required
    +
    Name
    Descriptive name for this sampler that is shown in the tree.
    No
    +
    Servername
    The name (or IP-address) of the LDAP server.
    Yes
    +
    Port
    The port number that the LDAP server is listening to. If this is omitted + JMeter assumes the LDAP server is listening on the default port(389).
    No
    +
    DN
    The distinguished name of the base object that will be used for any subsequent operation. + It can be used as a starting point for all operations. You cannot start any operation on a higher level than this DN!
    No
    +
    Username
    Full distinguished name of the user as which you want to bind.
    No
    +
    Password
    Password for the above user. If omitted it will result in an anonymous bind. + If is is incorrect, the sampler will return an error and revert to an anonymous bind. (N.B. this is stored unencrypted in the test plan)
    No
    +
    +
    +
  3. Thread unbind
  4. +

    This is simply the operation to end a session. + It is equal to the LDAP "unbind" operation.

    +

    + Parameters +

    Attribute
    Description
    Required
    +
    Name
    Descriptive name for this sampler that is shown in the tree.
    No
    +
    + +
    +
  5. Single bind/unbind
  6. +

    This is a combination of the LDAP "bind" and "unbind" operations. + It can be used for an authentication request/password check for any user. It will open an new session, just to + check the validity of the user/password combination, and end the session again.

    +

    + Parameters +

    Attribute
    Description
    Required
    +
    Name
    Descriptive name for this sampler that is shown in the tree.
    No
    +
    Username
    Full distinguished name of the user as which you want to bind.
    Yes
    +
    Password
    Password for the above user. If omitted it will result in an anonymous bind. + If is is incorrect, the sampler will return an error. (N.B. this is stored unencrypted in the test plan)
    No
    +
    + +
    +
  7. Rename entry
  8. +

    This is the LDAP "moddn" operation. It can be used to rename an entry, but + also for moving an entry or a complete subtree to a different place in + the LDAP tree.

    +

    + Parameters +

    Attribute
    Description
    Required
    +
    Name
    Descriptive name for this sampler that is shown in the tree.
    No
    +
    Old entry name
    The current distinguished name of the object you want to rename or move, + relative to the given DN in the thread bind operation.
    Yes
    +
    New distinguished name
    The new distinguished name of the object you want to rename or move, + relative to the given DN in the thread bind operation.
    Yes
    +
    + +
    +
  9. Add test
  10. +

    This is the ldap "add" operation. It can be used to add any kind of + object to the LDAP server.

    +

    + Parameters +

    Attribute
    Description
    Required
    +
    Name
    Descriptive name for this sampler that is shown in the tree.
    No
    +
    Entry DN
    Distinguished name of the object you want to add, relative to the given DN in the thread bind operation.
    Yes
    +
    Add test
    A list of attributes and their values you want to use for the object. + If you need to add a multiple value attribute, you need to add the same attribute with their respective + values several times to the list.
    Yes
    +
    + +
    +
  11. Delete test
  12. +

    This is the LDAP "delete" operation, it can be used to delete an + object from the LDAP tree

    +

    + Parameters +

    Attribute
    Description
    Required
    +
    Name
    Descriptive name for this sampler that is shown in the tree.
    No
    +
    Delete
    Distinguished name of the object you want to delete, relative to the given DN in the thread bind operation.
    Yes
    +
    + +
    +
  13. Search test
  14. +

    This is the LDAP "search" operation, and will be used for defining searches.

    +

    + Parameters +

    Attribute
    Description
    Required
    +
    Name
    Descriptive name for this sampler that is shown in the tree.
    No
    +
    Search base
    Distinguished name of the subtree you want your + search to look in, relative to the given DN in the thread bind operation.
    No
    +
    Search Filter
    searchfilter, must be specified in LDAP syntax.
    Yes
    +
    Scope
    Use 0 for baseobject-, 1 for onelevel- and 2 for a subtree search. (Default=0)
    No
    +
    Size Limit
    Specify the maximum number of results you want back from the server. (default=0, which means no limit.) When the sampler hits the maximum number of results, it will fail with errorcode 4
    No
    +
    Time Limit
    Specify the maximum amount of (cpu)time (in miliseconds) that the server can spend on your search. Take care, this does not say anything about the responsetime. (default is 0, which means no limit)
    No
    +
    Attributes
    Specify the attributes you want to have returned, seperated by a semicolon. An empty field will return all attributes
    No
    +
    Return object
    Whether the object will be returned (true) or not (false). Default=false
    No
    +
    Dereference aliases
    If true, it will dereference aliases, if false, it will not follow them (default=false)
    No
    +
    + +
    +
  15. Modification test
  16. +

    This is the LDAP "modify" operation. It can be used to modify an object. It + can be used to add, delete or replace values of an attribute.

    +

    + Parameters +

    Attribute
    Description
    Required
    +
    Name
    Descriptive name for this sampler that is shown in the tree.
    No
    +
    Entry name
    Distinguished name of the object you want to modify, relative + to the given DN in the thread bind operation
    Yes
    +
    Modification test
    The attribute-value-opCode triples. The opCode can be any + valid LDAP operationCode (add, delete/remove or replace). If you don't specify a value with a delete operation, + all values of the given attribute will be deleted. If you do specify a value in a delete operation, only + the given value will be deleted. If this value is non-existent, the sampler will fail the test.
    Yes
    +
    + +
    +
  17. Compare
  18. +

    This is the LDAP "compare" operation. It can be used to compare the value + of a given attribute with some already known value. In reality this is mostly + used to check whether a given person is a member of some group. In such a case + you can compare the DN of the user as a given value, with the values in the + attribute "member" of an object of the type groupOfNames. + If the compare operation fails, this test fails with errorcode 49.

    +

    + Parameters +

    Attribute
    Description
    Required
    +
    Name
    Descriptive name for this sampler that is shown in the tree.
    No
    +
    Entry DN
    The current distinguished name of the object of + which you want to compare an attribute, relative to the given DN in the thread bind operation.
    Yes
    +
    Compare filter
    In the form "attribute=value"
    Yes
    +
    +
+ + + +
+ + + + +

Access Log Sampler

Screenshot for Access Log Sampler
+

(Beta Code)

+

AccessLogSampler was designed to read access logs and generate http requests. +For those not familiar with the access log, it is the log the webserver maintains of every +request it accepted. This means every image, css file, javascript file, html file.... +The current implementation is complete, but some features have not been enabled. +There is a filter for the access log parser, but I haven't figured out how to link to the pre-processor. +Once I do, changes to the sampler will be made to enable that functionality.

+

Tomcat uses the common format for access logs. This means any webserver that uses the +common log format can use the AccessLogSampler. Server that use common log format include: +Tomcat, Resin, Weblogic, and SunOne. Common log format looks +like this:

+

127.0.0.1 - - [21/Oct/2003:05:37:21 -0500] "GET /index.jsp?%2Findex.jsp= HTTP/1.1" 200 8343

+
The current implementation of the parser only looks at the text within the quotes that contains one of the HTTP protocol methods (GET, PUT, POST, DELETE...). +Everything else is stripped out and ignored. For example, the response code is completely +ignored by the parser.
+

For the future, it might be nice to filter out entries that +do not have a response code of 200. Extending the sampler should be fairly simple. There +are two interfaces you have to implement:

+
    +
  • org.apache.jmeter.protocol.http.util.accesslog.LogParser
  • +
  • org.apache.jmeter.protocol.http.util.accesslog.Generator
  • +
+

The current implementation of AccessLogSampler uses the generator to create a new +HTTPSampler. The servername, port and get images are set by AccessLogSampler. Next, +the parser is called with integer 1, telling it to parse one entry. After that, +HTTPSampler.sample() is called to make the request. +

+samp = (HTTPSampler) GENERATOR.generateRequest();
+samp.setDomain(this.getDomain());
+samp.setPort(this.getPort());
+samp.setImageParser(this.isImageParser());
+PARSER.parse(1);
+res = samp.sample();
+res.setSampleLabel(samp.toString());
+
+The required methods in LogParser are: +
    +
  • setGenerator(Generator)
  • +
  • parse(int)
  • +
+Classes implementing Generator interface should provide concrete implementation +for all the methods. For an example of how to implement either interface, refer to +StandardGenerator and TCLogParser. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler that is shown in the tree.
No
+
Server
Domain name or IP address of the web server.
Yes
+
Port
Port the web server is listening to.
No (defaults to 80)
+
Log parser class
The log parser class is responsible for parsing the logs.
Yes (default provided)
+
Filter
The filter class is used to filter out certain lines.
No
+
Location of log file
The location of the access log file.
Yes
+
+

+The TCLogParser processes the access log independently for each thread. +The SharedTCLogParser and OrderPreservingLogParser share access to the file, +i.e. each thread gets the next entry in the log. +

+

+The SessionFilter is intended to handle Cookies across threads. +It does not filter out any entries, but modifies the cookie manager so that the cookies for a given IP are +processed by a single thread at a time. If two threads try to process samples from the same client IP address, +then one will be forced to wait until the other has completed. +

+

+The LogFilter is intended to allow access log entries to be filtered by filename and regex, +as well as allowing for the replacement of file extensions. However, it is not currently possible +to configure this via the GUI, so it cannot really be used. +

+
+ +

BeanShell Sampler

Screenshot for BeanShell Sampler
+

This sampler allows you to write a sampler using the BeanShell scripting language. +

+For full details on using BeanShell, please see the BeanShell website. +

+

+The test element supports the ThreadListener and TestListener interface methods. +These must be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

+

+From JMeter version 2.5.1, the BeanShell sampler also supports the Interruptible interface. +The interrupt() method can be defined in the script or the init file. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler that is shown in the tree. + The name is stored in the script variable Label
No
+
Reset bsh.Interpreter before each call
+ If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. +
Yes
+
Parameters
Parameters to pass to the BeanShell script. + This is intended for use with script files; for scripts defined in the GUI, you can use whatever + variable and function references you need within the script itself. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • bsh.args - String array containing parameters, split on white-space
  • +
No
+
Script file
A file containing the BeanShell script to run. + The file name is stored in the script variable FileName
No
+
Script
The BeanShell script to run. + The return value (if not null) is stored as the sampler result.
Yes (unless script file is provided)
+
+

+N.B. Each Sampler instance has its own BeanShell interpeter, +and Samplers are only called from a single thread +

+If the property "beanshell.sampler.init" is defined, it is passed to the Interpreter +as the name of a sourced file. +This can be used to define common methods and variables. +There is a sample init file in the bin directory: BeanShellSampler.bshrc. +

+If a script file is supplied, that will be used, otherwise the script will be used.

+
+JMeter processes function and variable references before passing the script field to the interpreter, +so the references will only be resolved once. +Variable and function references in script files will be passed +verbatim to the interpreter, which is likely to cause a syntax error. +In order to use runtime variables, please use the appropriate props methods, +e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
+BeanShell does not currently support Java 5 syntax such as generics and the enhanced for loop. +
+

Before invoking the script, some variables are set up in the BeanShell interpreter: +

+

The contents of the Parameters field is put into the variable "Parameters". + The string is also split into separate tokens using a single space as the separator, and the resulting list + is stored in the String array bsh.args.

+

The full list of BeanShell variables that is set up is as follows:

+
    +
  • log - the Logger
  • +
  • Label - the Sampler label
  • +
  • FileName - the file name, if any
  • +
  • Parameters - text from the Parameters field
  • +
  • bsh.args - the parameters, split as described above
  • +
  • SampleResult - pointer to the current SampleResult
  • +
  • ResponseCode = 200
  • +
  • ResponseMessage = "OK"
  • +
  • IsSuccess = true
  • +
  • ctx - JMeterContext
  • +
  • vars - JMeterVariables - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.remove("VAR3"); vars.putObject("OBJ1",new Object());
  • +
  • props - JMeterProperties (class java.util.Properties)- e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
+

When the script completes, control is returned to the Sampler, and it copies the contents + of the following script variables into the corresponding variables in the SampleResult:

+
    +
  • ResponseCode - for example 200
  • +
  • ResponseMessage - for example "OK"
  • +
  • IsSuccess - true/false
  • +
+

The SampleResult ResponseData is set from the return value of the script. + Since version 2.1.2, if the script returns null, it can set the response directly, by using the method + SampleResult.setResponseData(data), where data is either a String or a byte array. + The data type defaults to "text", but can be set to binary by using the method + SampleResult.setDataType(SampleResult.BINARY). +

+

The SampleResult variable gives the script full access to all the fields and + methods in the SampleResult. For example, the script has access to the methods + setStopThread(boolean) and setStopTest(boolean). + + Here is a simple (not very useful!) example script:

+ +
+if (bsh.args[0].equalsIgnoreCase("StopThread")) {
+    log.info("Stop Thread detected!");
+    SampleResult.setStopThread(true);
+}
+return "Data from sample with Label "+Label;
+//or, since version 2.1.2
+SampleResult.setResponseData("My data");
+return null;
+
+

Another example:
ensure that the property beanshell.sampler.init=BeanShellSampler.bshrc is defined in jmeter.properties. +The following script will show the values of all the variables in the ResponseData field: +

+
+return getVariables();
+
+

+For details on the methods available for the various classes (JMeterVariables, SampleResult etc) please check the Javadoc or the source code. +Beware however that misuse of any methods can cause subtle faults that may be difficult to find ... +

+
+ + +

BSF Sampler

Screenshot for BSF Sampler
+

This sampler allows you to write a sampler using a BSF scripting language.
+ See the Apache Bean Scripting Framework + website for details of the languages supported. + You may need to download the appropriate jars for the language; they should be put in the JMeter lib directory. +

+
+ The BSF API has been largely superseded by JSR-223, which is included in Java 6 onwards. + Most scripting languages now include support for JSR-223; please use the JSR223 Sampler instead. + The BSF Sampler should only be needed for supporting legacy languages/test scripts. +
+

By default, JMeter supports the following languages:

+
    +
  • javascript
  • +
  • jexl (JMeter version 2.3.2 and later)
  • +
  • xslt
  • +
+
Unlike the BeanShell sampler, the interpreter is not saved between invocations.
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler that is shown in the tree.
No
+
Scripting Language
Name of the BSF scripting language to be used. + N.B. Not all the languages in the drop-down list are supported by default. + The following are supported: jexl, javascript, xslt. + Others may be available if the appropriate jar is installed in the JMeter lib directory. +
Yes
+
Script File
Name of a file to be used as a BSF script, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property
No
+
Parameters
List of parameters to be passed to the script file or the script.
No
+
Script
Script to be passed to BSF language
Yes (unless script file is provided)
+
+

+If a script file is supplied, that will be used, otherwise the script will be used.

+
+JMeter processes function and variable references before passing the script field to the interpreter, +so the references will only be resolved once. +Variable and function references in script files will be passed +verbatim to the interpreter, which is likely to cause a syntax error. +In order to use runtime variables, please use the appropriate props methods, +e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
+

+Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. +

+
    +
  • log - the Logger
  • +
  • Label - the Sampler label
  • +
  • FileName - the file name, if any
  • +
  • Parameters - text from the Parameters field
  • +
  • args - the parameters, split as described above
  • +
  • SampleResult - pointer to the current SampleResult
  • +
  • sampler - pointer to current Sampler
  • +
  • ctx - JMeterContext
  • +
  • vars - JMeterVariables - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.remove("VAR3"); vars.putObject("OBJ1",new Object());
  • +
  • props - JMeterProperties (class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • OUT - System.out - e.g. OUT.println("message")
  • +
+

+The SampleResult ResponseData is set from the return value of the script. +If the script returns null, it can set the response directly, by using the method +SampleResult.setResponseData(data), where data is either a String or a byte array. +The data type defaults to "text", but can be set to binary by using the method +SampleResult.setDataType(SampleResult.BINARY). +

+

+The SampleResult variable gives the script full access to all the fields and +methods in the SampleResult. For example, the script has access to the methods +setStopThread(boolean) and setStopTest(boolean). +

+

+Unlike the BeanShell Sampler, the BSF Sampler does not set the ResponseCode, ResponseMessage and sample status via script variables. +Currently the only way to changes these is via the SampleResult methods: +

    +
  • SampleResult.setSuccessful(true/false)
  • +
  • SampleResult.setResponseCode("code")
  • +
  • SampleResult.setResponseMessage("message")
  • +
+

+
+ +

JSR223 Sampler

Screenshot for JSR223 Sampler
+
+

+The JSR223 Sampler allows JSR223 script code to be used to perform a sample. +

+

+The JSR223 test elements have a feature (compilation) that can significantly increase performance. +To benefit from this feature: +

    +
  • Use Script files instead of inlining them. This will make JMeter compile them if this feature is available on ScriptEngine and cache them.
  • +
  • Or Use Script Text and fill in script cache key property, ensure it is unique across Test Plan as JMeter will use it to cache result of compilation. +
    When using this feature, ensure you script code does not use JMeter variables directly in script code as caching would only cache first replacement. Instead use script parameters.
    +
    To benefit fomr Caching and compilation, language engine used for scripting must implement JSR223 Compilable interface (Groovy is one of these, java, beanshell and javascript are not)
    +
  • +
+Cache size is controlled by the following jmeter property (jmeter.properties): +
    +
  • jsr223.compiled_scripts_cache_size=100
  • +
+For details, see BSF Sampler. +

+
Unlike the BeanShell sampler, the interpreter is not saved between invocations.
+
+Since JMeter 2.8, JSR223 Test Elements using Script file or Script text + cache key are now Compiled if ScriptEngine supports this feature, this enables great performance enhancements. +
+
+
+JMeter processes function and variable references before passing the script field to the interpreter, +so the references will only be resolved once. +Variable and function references in script files will be passed +verbatim to the interpreter, which is likely to cause a syntax error. +In order to use runtime variables, please use the appropriate props methods, +e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
+
+ +

TCP Sampler

Screenshot for TCP Sampler
+
+

+ The TCP Sampler opens a TCP/IP connection to the specified server. + It then sends the text, and waits for a response. +
+ If "Re-use connection" is selected, connections are shared between Samplers in the same thread, + provided that the exact same host name string and port are used. + Different hosts/port combinations will use different connections, as will different threads. + If both of "Re-use connection" and "Close connection" are selected, the socket will be closed after running the sampler. + On the next sampler, another socket will be created. You may want to close a socket at the end of each thread loop. +
+ If an error is detected - or "Re-use connection" is not selected - the socket is closed. + Another socket will be reopened on the next sample. +
+ The following properties can be used to control its operation: +

+
    +
  • tcp.status.prefix - text that precedes a status number
  • +
  • tcp.status.suffix - text that follows a status number
  • +
  • tcp.status.properties - name of property file to convert status codes to messages
  • +
  • tcp.handler - Name of TCP Handler class (default TCPClientImpl) - only used if not specified on the GUI
  • +
+ The class that handles the connection is defined by the GUI, failing that the property tcp.handler. + If not found, the class is then searched for in the package org.apache.jmeter.protocol.tcp.sampler. +

+ Users can provide their own implementation. + The class must extend org.apache.jmeter.protocol.tcp.sampler.TCPClient. +

+

+ The following implementations are currently provided. +

    +
  • TCPClientImpl
  • +
  • BinaryTCPClientImpl
  • +
  • LengthPrefixedBinaryTCPClientImpl
  • +
+ The implementations behave as follows: +

+

TCPClientImpl
+ This implementation is fairly basic. + When reading the response, it reads until the end of line byte, if this is defined + by setting the property tcp.eolByte, otherwise until the end of the input stream. + You can control charset encoding by setting tcp.charset, which will default to Platform default encoding. +

+

BinaryTCPClientImpl
+ This implementation converts the GUI input, which must be a hex-encoded string, into binary, + and performs the reverse when reading the response. + When reading the response, it reads until the end of message byte, if this is defined + by setting the property tcp.BinaryTCPClient.eomByte, otherwise until the end of the input stream. +

+

LengthPrefixedBinaryTCPClientImpl
+ This implementation extends BinaryTCPClientImpl by prefixing the binary message data with a binary length byte. + The length prefix defaults to 2 bytes. + This can be changed by setting the property tcp.binarylength.prefix.length. +

+

Timeout handling + If the timeout is set, the read will be terminated when this expires. + So if you are using an eolByte/eomByte, make sure the timeout is sufficiently long, + otherwise the read will be terminated early. +

+

Response handling +
+ If tcp.status.prefix is defined, then the response message is searched for the text following + that up to the suffix. If any such text is found, it is used to set the response code. + The response message is then fetched from the properties file (if provided). +
+ For example, if the prefix = "[" and the suffix = "]", then the following repsonse: +
+ [J28] XI123,23,GBP,CR +
+ would have the response code J28. +
+ Response codes in the range "400"-"499" and "500"-"599" are currently regarded as failures; + all others are successful. [This needs to be made configurable!] +

+
The login name/password are not used by the supplied TCP implementations.
+
+ Sockets are disconnected at the end of a test run. +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
TCPClient classname
Name of the TCPClient class. Defaults to the property tcp.handler, failing that TCPClientImpl.
No
+
ServerName or IP
Name or IP of TCP server
Yes
+
Port Number
Port to be used
Yes
+
Re-use connection
If selected, the connection is kept open. Otherwise it is closed when the data has been read.
Yes
+
Close connection
If selected, the connection will be closed after running the sampler.
Yes
+
SO_LINGER
Enable/disable SO_LINGER with the specified linger time in seconds when a socket is created. If you set "SO_LINGER" value as 0, you may prevent large numbers of sockets sitting around with a TIME_WAIT status.
No
+
End of line(EOL) byte value
Byte value for end of line, set this to a value outside the range -128 to +127 to skip eol checking. You may set this in jmeter.properties file as well with eolByte property. If you set this in TCP Sampler Config and in jmeter.properties file at the same time, the setting value in the TCP Sampler Config will be used.
No
+
Connect Timeout
Connect Timeout (milliseconds, 0 disables).
No
+
Response Timeout
Response Timeout (milliseconds, 0 disables).
No
+
Set Nodelay
See java.net.Socket.setTcpNoDelay(). + If selected, this will disable Nagle's algorithm, otherwise Nagle's algorithm will be used.
Yes
+
Text to Send
Text to be sent
Yes
+
Login User
User Name - not used by default implementation
No
+
Password
Password - not used by default implementation (N.B. this is stored unencrypted in the test plan)
No
+
+
+ +

JMS Publisher

Screenshot for JMS Publisher
+
BETA CODE - the code is still subject to change
+
+

+ JMS Publisher will publish messages to a given destination (topic/queue). For those not + familiar with JMS, it is the J2EE specification for messaging. There are + numerous JMS servers on the market and several open source options. +

+
+
JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
use JNDI properties file
use jndi.properties. + Note that the file must be on the classpath - e.g. by updating the user.classpath JMeter property. + If this option is not selected, JMeter uses the "JNDI Initial Context Factory" and "Provider URL" fields + to create the connection. +
Yes
+
JNDI Initial Context Factory
Name of the context factory
No
+
Provider URL
The URL for the jms provider
Yes, unless using jndi.properties
+
Destination
The message destination (topic or queue name)
Yes
+
Setup
The destination setup type. With At startup, the destination name is static (i.e. always same name during the test), with Each sample, the destination name is dynamic and is evaluate at each sample (i.e. the destination name may be a variable)
Yes
+
Authentication
Authentication requirement for the JMS provider
Yes
+
User
User Name
No
+
Password
Password (N.B. this is stored unencrypted in the test plan)
No
+
Expiration
+ The expiration time (in milliseconds) of the message before it becomes obsolete. + If you do not specify an expiration time, the default value is 0 (never expires). +
No
+
Priority
+ The priority level of the message. There are ten priority levels from 0 (lowest) to 9 (highest). + If you do not specify a priority level, the default level is 4. +
No
+
Number of samples to aggregate
Number of samples to aggregate
Yes
+
Message source
Where to obtain the message: +
    +
  • From File : means the referenced file will be read and reused by all samples
  • +
  • Random File from folder specified below : means a random file will be selected from folder specified below, this folder must contain either files with extension .dat for Bytes Messages, or files with extension .txt or .obj for Object or Text messages
  • +
  • Text area : The Message to use either for Text or Object message
  • +
+
Yes
+
Message type
Text, Map, Object message or Bytes Message
Yes
+
Use non-persistent delivery mode?
+ Whether to set DeliveryMode.NON_PERSISTENT (defaults to false) +
No
+
JMS Properties
+ The JMS Properties are properties specific for the underlying messaging system. + You can setup the name, the value and the class (type) of value. Default type is String. + For example: for WebSphere 5.1 web services you will need to set the JMS Property targetService to test + webservices through JMS. +
No
+ +
+

+For the MapMessage type, JMeter reads the source as lines of text. +Each line must have 3 fields, delimited by commas. +The fields are: +

    +
  • Name of entry
  • +
  • Object class name, e.g. "String" (assumes java.lang package if not specified)
  • +
  • Object string value
  • +
+For each entry, JMeter adds an Object with the given name. +The value is derived by creating an instance of the class, and using the valueOf(String) method to convert the value if necessary. +For example: +
+name,String,Example
+size,Integer,1234
+
+This is a very simple implementation; it is not intended to support all possible object types. +

+

+

+The Object message is implemented since 2.7 and works as follow: +
    +
  • Put the JAR that contains your object and its dependencies in jmeter_home/lib/ folder
  • +
  • Serialize your object as XML using XStream
  • +
  • Either put result in a file suffixed with .txt or .obj or put XML content direclty in Text Area
  • +
+Note that if message is in a file, replacement of properties will not occur while it will if you use Text Area. +
+ +

+

+The following table shows some values which may be useful when configuring JMS: + + + + + + + + + + + + + + +
Apache ActiveMQValue(s)Comment
Context Factoryorg.apache.activemq.jndi.ActiveMQInitialContextFactory.
Provider URLvm://localhost
Provider URLvm:(broker:(vm://localhost)?persistent=false)Disable persistence
Queue ReferencedynamicQueues/QUEUENAMEDynamically define the QUEUENAME to JNDI
Topic ReferencedynamicTopics/TOPICNAMEDynamically define the TOPICNAME to JNDI
+

+
+ +

JMS Subscriber

Screenshot for JMS Subscriber
+
BETA CODE - the code is still subject to change
+
+

+ JMS Publisher will subscribe to messages in a given destination (topic or queue). For those not + familiar with JMS, it is the J2EE specification for messaging. There are + numerous JMS servers on the market and several open source options. +

+
+
JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
use JNDI properties file
use jndi.properties. + Note that the file must be on the classpath - e.g. by updating the user.classpath JMeter property. + If this option is not selected, JMeter uses the "JNDI Initial Context Factory" and "Provider URL" fields + to create the connection. +
Yes
+
JNDI Initial Context Factory
Name of the context factory
No
+
Provider URL
The URL for the jms provider
No
+
Destination
the message destination (topic or queue name)
Yes
+
Durable Subscription ID
The ID to use for a durable subscription. On first + use the respective queue will automatically be generated by the JMS provider if it does not exist yet.
No
+
Client ID
The Client ID to use when you you use a durable subscription. + Be sure to add a variable like ${__threadNum} when you have more than one Thread.
No
+
JMS Selector
Message Selector as defined by JMS specification to extract only + messages that respect the Selector condition. Syntax uses subpart of SQL 92.
No
+
Setup
The destination setup type. With At startup, the destination name is static (i.e. always same name during the test), with Each sample, the destination name is dynamic and is evaluate at each sample (i.e. the destination name may be a variable)
Yes
+
Authentication
Authentication requirement for the JMS provider
Yes
+
User
User Name
No
+
Password
Password (N.B. this is stored unencrypted in the test plan)
No
+
Number of samples to aggregate
number of samples to aggregate
Yes
+
Read response
should the sampler read the response. If not, only the response length is returned.
Yes
+
Timeout
Specify the timeout to be applied, in milliseconds. 0=none. + This is the overall aggregate timeout, not per sample.
Yes
+
Client
Which client implementation to use. + Both of them create connections which can read messages. However they use a different strategy, as described below: +
    +
  • MessageConsumer.receive() - calls receive() for every requested message. + Retains the connection between samples, but does not fetch messages unless the sampler is active. + This is best suited to Queue subscriptions. +
  • +
  • MessageListener.onMessage() - establishes a Listener that stores all incoming messages on a queue. + The listener remains active after the sampler completes. + This is best suited to Topic subscriptions.
  • +
+
Yes
+
Stop between samples?
+ If selected, then JMeter calls Connection.stop() at the end of each sample (and calls start() before each sample). + This may be useful in some cases where multiple samples/threads have connections to the same queue. + If not selected, JMeter calls Connection.start() at the start of the thread, and does not call stop() until the end of the thread. +
Yes
+
Separator
+ Separator used to separate messages when there is more than one (related to setting Number of samples to aggregate). + Note that \n, \r, \t are accepted. +
No
+
+

+NOTE: JMeter 2.3.4 and earlier used a different strategy for the MessageConsumer.receive() client. +Previously this started a background thread which polled for messages. This thread continued when the sampler +completed, so the net effect was similar to the MessageListener.onMessage() strategy. +

+
+ +

JMS Point-to-Point

Screenshot for JMS Point-to-Point
+
BETA CODE - the code is still subject to change
+
+

+ This sampler sends and optionally receives JMS Messages through point-to-point connections (queues). + It is different from pub/sub messages and is generally used for handling transactions. +

+

+ Request Only will typically used to put load on a JMS System.
+ Request Response will be used when you want to test response time of a JMS service that processes messages sent to the Request Queue as this mode will wait for the response on the Reply queue sent by this service.
+

+

+ Versions of JMeter after 2.3.2 use the properties java.naming.security.[principal|credentials] - if present - + when creating the Queue Connection. If this behaviour is not desired, set the JMeter property + JMSSampler.useSecurity.properties=false +

+
+
JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
QueueConnection Factory
+ The JNDI name of the queue connection factory to use for connecting to the messaging system. +
Yes
+
JNDI Name Request queue
+ This is the JNDI name of the queue to which the messages are sent. +
Yes
+
JNDI Name Reply queue
+ The JNDI name of the receiving queue. If a value is provided here and the communication style is Request Response + this queue will be monitored for responses to the requests sent. +
No
+
JMS Selector
+ Message Selector as defined by JMS specification to extract only + messages that respect the Selector condition. Syntax uses subpart of SQL 92. +
No
+
Communication style
+ The Communication style can be Request Only (also known as Fire and Forget) or Request Response: +
    +
  • Request Only will only send messages and will not monitor replies. As such it can be used to put load on a system.
  • +
  • Request Response will send messages and monitor the replies it receives. Behaviour depends on the value of the JNDI Name Reply Queue. + If JNDI Name Reply Queue has a value, this queue is used to monitor the results. Matching of request and reply is done with + the message id of the request and the correlation id of the reply. If the JNDI Name Reply Queue is empty, then + temporary queues will be used for the communication between the requestor and the server. + This is very different from the fixed reply queue. With temporary queues the sending thread will block until the reply message has been received. + With Request Response mode, you need to have a Server that listens to messages sent to Request Queue and sends replies to + queue referenced by message.getJMSReplyTo().
  • +
+
Yes
+
Use alternate fields for message correlation
+ These check-boxes select the fields which will be used for matching the response message with the original request. +
    +
  • Use Request Message Id - if selected, the request JMSMessageID will be used, + otherwise the request JMSCorrelationID will be used. + In the latter case the correlation id must be specified in the request.
  • +
  • Use Response Message Id - if selected, the response JMSMessageID will be used, + otherwise the response JMSCorrelationID will be used. +
  • +
+ There are two frequently used JMS Correlation patterns: +
    +
  • JMS Correlation ID Pattern - + i.e. match request and response on their correlation Ids + => deselect both checkboxes, and provide a correlation id.
  • +
  • JMS Message ID Pattern - + i.e. match request message id with response correlation id + => select "Use Request Message Id" only. +
  • +
+ In both cases the JMS application is responsible for populating the correlation ID as necessary. +
if the same queue is used to send and receive messages, + then the response message will be the same as the request message. + In which case, either provide a correlation id and clear both checkboxes; + or select both checkboxes to use the message Id for correlation. + This can be useful for checking raw JMS throughput.
+
Yes
+
Timeout
+ The timeout in milliseconds for the reply-messages. If a reply has not been received within the specified + time, the specific testcase failes and the specific reply message received after the timeout is discarded. + Default value is 2000 ms. +
Yes
+
Expiration
+ The expiration time (in milliseconds) of the message before it becomes obsolete. + If you do not specify an expiration time, the default value is 0 (never expires). +
No
+
Priority
+ The priority level of the message. There are ten priority levels from 0 (lowest) to 9 (highest). + If you do not specify a priority level, the default level is 4. +
No
+
Use non-persistent delivery mode?
+ Whether to set DeliveryMode.NON_PERSISTENT. +
Yes
+
Content
+ The content of the message. +
No
+
JMS Properties
+ The JMS Properties are properties specific for the underlying messaging system. + You can setup the name, the value and the class (type) of value. Default type is String. + For example: for WebSphere 5.1 web services you will need to set the JMS Property targetService to test + webservices through JMS. +
No
+
Initial Context Factory
+ The Initial Context Factory is the factory to be used to look up the JMS Resources. +
No
+
JNDI properties
+ The JNDI Properties are the specific properties for the underlying JNDI implementation. +
No
+
Provider URL
+ The URL for the jms provider. +
No
+
+
+ + + +

JUnit Request

Screenshot for JUnit Request
+
+The current implementation supports standard Junit convention and extensions. It also +includes extensions like oneTimeSetUp and oneTimeTearDown. The sampler works like the +JavaSampler with some differences. +
    +
  • rather than use Jmeter's test interface, it scans the jar files for classes extending junit's TestCase class. That includes any class or subclass.
  • +
  • Junit test jar files should be placed in jmeter/lib/junit instead of /lib directory. +In versions of JMeter after 2.3.1, you can also use the "user.classpath" property to specify where to look for TestCase classes.
  • +
  • Junit sampler does not use name/value pairs for configuration like the JavaSampler. The sampler assumes setUp and tearDown will configure the test correctly.
  • +
  • The sampler measures the elapsed time only for the test method and does not include setUp and tearDown.
  • +
  • Each time the test method is called, Jmeter will pass the result to the listeners.
  • +
  • Support for oneTimeSetUp and oneTimeTearDown is done as a method. Since Jmeter is multi-threaded, we cannot call oneTimeSetUp/oneTimeTearDown the same way Maven does it.
  • +
  • The sampler reports unexpected exceptions as errors. +There are some important differences between standard JUnit test runners and JMeter's +implementation. Rather than make a new instance of the class for each test, JMeter +creates 1 instance per sampler and reuses it. +This can be changed with checkbox "Create a new instance per sample".
  • +
+The current implementation of the sampler will try to create an instance using the string constructor first. If the test class does not declare a string constructor, the sampler will look for an empty constructor. Example below: +
Junit Constructors
+Empty Constructor: +
+public class myTestCase {
+  public myTestCase() {}
+}
+
+String Constructor: +
+public class myTestCase {
+  public myTestCase(String text) {
+    super(text);
+  }
+}
+
+
+By default, Jmeter will provide some default values for the success/failure code and message. Users should define a set of unique success and failure codes and use them uniformly across all tests. +
+

General Guidelines

+If you use setUp and tearDown, make sure the methods are declared public. If you do not, the test may not run properly. +
+Here are some general guidelines for writing Junit tests so they work well with Jmeter. Since Jmeter runs multi-threaded, it is important to keep certain things in mind. +
    +
  • Write the setUp and tearDown methods so they are thread safe. This generally means avoid using static memebers.
  • +
  • Make the test methods discrete units of work and not long sequences of actions. By keeping the test method to a descrete operation, it makes it easier to combine test methods to create new test plans.
  • +
  • Avoid making test methods depend on each other. Since Jmeter allows arbitrary sequencing of test methods, the runtime behavior is different than the default Junit behavior.
  • +
  • If a test method is configurable, be careful about where the properties are stored. Reading the properties from the Jar file is recommended.
  • +
  • Each sampler creates an instance of the test class, so write your test so the setup happens in oneTimeSetUp and oneTimeTearDown.
  • +
+
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Search for JUnit4 annotations
Select this to search for JUnit 4 tests (@Test annotations)
Yes
+
Package filter
Comma separated list of packages to show. Example, org.apache.jmeter,junit.framework.
+
Class name
Fully qualified name of the JUnit test class.
Yes
+
Constructor string
String pass to the string constructor. If a string is set, the sampler will use the + string constructor instead of the empty constructor.
+
Test method
The method to test.
Yes
+
Success message
A descriptive message indicating what success means.
+
Success code
An unique code indicating the test was successful.
+
Failure message
A descriptive message indicating what failure means.
+
Failure code
An unique code indicating the test failed.
+
Error message
A description for errors.
+
Error code
Some code for errors. Does not need to be unique.
+
Do not call setUp and tearDown
Set the sampler not to call setUp and tearDown. + By default, setUp and tearDown should be called. Not calling those methods could affect the test and make it inaccurate. + This option should only be used with calling oneTimeSetUp and oneTimeTearDown. If the selected method is oneTimeSetUp or oneTimeTearDown, + this option should be checked.
Yes
+
Append assertion errors
Whether or not to append assertion errors to the response message.
Yes
+
Append runtime exceptions
Whether or not to append runtime exceptions to the response message. Only applies if "Append assertion errors" is not selected.
Yes
+
Create a new Instance per sample
Whether or not to create a new JUnit instance for each sample. Defaults to false, meaning JUnit TestCase is created one and reused.
Yes
+
+

+The following JUnit4 annotations are recognised: +

    +
  • @Test - used to find test methods and classes. The "expected" and "timeout" attributes are supported.
  • +
  • @Before - treated the same as setUp() in JUnit3
  • +
  • @After - treated the same as tearDown() in JUnit3
  • +
  • @BeforeClass, @AfterClass - treated as test methods so they can be run independently as required
  • +
+

+

+Note that JMeter currently runs the test methods directly, rather than leaving it to JUnit. +This is to allow the setUp/tearDown methods to be excluded from the sample time. +

+
+ +

Mail Reader Sampler

Screenshot for Mail Reader Sampler
+
+

+The Mail Reader Sampler can read (and optionally delete) mail messages using POP3(S) or IMAP(S) protocols. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Server Type
The protocol used by the provider: e.g. pop3, pop3s, imap, imaps. +or another string representing the server protocol. +For example file for use with the read-only mail file provider. +The actual provider names for POP3 and IMAP are pop3 and imap +
Yes
+
Server
Hostname or IP address of the server. See below for use with file protocol.
Yes
+
Port
Port to be used to connect to the server (optional)
No
+
Username
User login name
+
Password
User login password (N.B. this is stored unencrypted in the test plan)
+
Folder
The IMAP(S) folder to use. See below for use with file protocol.
Yes, if using IMAP(S)
+
Number of messages to retrieve
Set this to retrieve all or some messages
Yes
+
Fetch headers only
If selected, only the message headers will be retrieved.
Yes
+
Delete messages from the server
If set, messages will be deleted after retrieval
Yes
+
Store the message using MIME
Whether to store the message as MIME. +If so, then the entire raw message is stored in the Response Data; the headers are not stored as they are available in the data. +If not, the message headers are stored as Response Headers. +A few headers are stored (Date, To, From, Subject) in the body. +
Yes
+
Use no security features
Indicates that the connection to the server does not use any security protocol.
+
Use SSL
Indicates that the connection to the server must use the SSL protocol.
+
Use StartTLS
Indicates that the connection to the server should attempt to start the TLS protocol.
+
Enforce StartTLS
If the server does not start the TLS protocol the connection will be terminated.
+
Trust All Certificates
When selected it will accept all certificates independent of the CA.
+
Use local truststore
When selected it will only accept certificates that are locally trusted.
+
Local truststore
Path to file containing the trusted certificates. +Relative paths are resolved against the current directory. +
Failing that, against the directory containing the test script (JMX file). +
+
+

+Messages are stored as subsamples of the main sampler. +In versions of JMeter after 2.3.4, multipart message parts are stored as subsamples of the message. +

+

+Special handling for "file" protocol:
+The file JavaMail provider can be used to read raw messages from files. +The server field is used to specify the path to the parent of the folder. +Individual message files should be stored with the name n.msg, +where n is the message number. +Alternatively, the server field can be the name of a file which contains a single message. +The current implementation is quite basic, and is mainly intended for debugging purposes. +

+
+ +

Test Action

Screenshot for Test Action
+
+The Test Action sampler is a sampler that is intended for use in a conditional controller. +Rather than generate a sample, the test element eithers pauses or stops the selected target. +

This sampler can also be useful in conjunction with the Transaction Controller, as it allows +pauses to be included without needing to generate a sample. +For variable delays, set the pause time to zero, and add a Timer as a child.

+

+The "Stop" action stops the thread or test after completing any samples that are in progress. +The "Stop Now" action stops the test without waiting for samples to complete; it will interrupt any active samples. +If some threads fail to stop within the 5 second time-limit, a message will be displayed in GUI mode. +You can try using the Stop command to see if this will stop the threads, but if not, you should exit JMeter. +In non-GUI mode, JMeter will exit if some threads fail to stop within the 5 second time limit. +[This can be changed using the JMeter property jmeterengine.threadstop.wait] +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Target
Current Thread / All Threads (ignored for Pause)
Yes
+
Action
Pause / Stop / Stop Now / Go to next loop iteration
Yes
+
Duration
How long to pause for (milliseconds)
Yes, if Pause is selected
+
+
+ + +

SMTP Sampler

Screenshot for SMTP Sampler
+
+

+The SMTP Sampler can send mail messages using SMTP/SMTPS protocol. +It is possible to set security propocols for the connection (SSL and TLS), as well as user authentication. +If a security protocol is used a verification on the server certificate will occur.
+Two alternatives to handle this verification are available:
+

    +
  • Trust all certificates. This will ignore certificate chain verification
  • +
  • Use a local truststore. With this option the certificate chain will be validated against the local truststore file.
  • +
+

+
+

+ Parameters +

Attribute
Description
Required
+
Server
Hostname or IP address of the server. See below for use with file protocol.
Yes
+
Port
Port to be used to connect to the server. +Defaults are: SMTP=25, SSL=465, StartTLS=587 +
No
+
Connection timeout
Connection timeout value in milliseconds (socket level). Default is infinite timeout.
No
+
Read timeout
Read timeout value in milliseconds (socket level). Default is infinite timeout.
No
+
Address From
The from address that will appear in the e-mail
Yes
+
Address To
The destination e-mail address (multiple values separated by ";")
Yes, unless CC or BCC is specified
+
Address To CC
Carbon copy destinations e-mail address (multiple values separated by ";")
No
+
Address To BCC
Blind carbon copy destinations e-mail address (multiple values separated by ";")
No
+
Address Reply-To
Alternate Reply-To address (multiple values separated by ";")
No
+
Use Auth
Indicates if the SMTP server requires user authentication
+
Username
User login name
+
Password
User login password (N.B. this is stored unencrypted in the test plan)
+
Use no security features
Indicates that the connection to the SMTP server does not use any security protocol.
+
Use SSL
Indicates that the connection to the SMTP server must use the SSL protocol.
+
Use StartTLS
Indicates that the connection to the SMTP server should attempt to start the TLS protocol.
+
Enforce StartTLS
If the server does not start the TLS protocol the connection will be terminated.
+
Trust All Certificates
When selected it will accept all certificates independent of the CA.
+
Use local truststore
When selected it will only accept certificates that are locally trusted.
+
Local truststore
Path to file containing the trusted certificates. +Relative paths are resolved against the current directory. +
Failing that, against the directory containing the test script (JMX file). +
+
Subject
The e-mail message subject.
+
Suppress Subject Header
If selected, the "Subject:" header is omitted from the mail that is sent. +This is different from sending an empty "Subject:" header, though some e-mail clients may display it identically.
+
Include timestamp in subject
Includes the System.currentTimemilis() in the subject line.
+
Add Header
Additional headers can be defined using this button.
No
+
Message
The message body.
+
Send plain body (i.e. not multipart/mixed)
+If selected, then send the body as a plain message, i.e. not multipart/mixed, if possible. +If the message body is empty and there is a single file, then send the file contents as the message body. +Note: If the message body is not empty, and there is at least one attached file, then the body is sent as multipart/mixed. +
+ No +
+
Attach files
Files to be attached to the message.
+
Send .eml
If set, the .eml file will be sent instead of the entries in the Subject, Message, and Attached files
+
Calculate message size
Calculates the message size and stores it in the sample result.
+
Enable debug logging?
If set, then the "mail.debug" property is set to "true"
+
+
+ +

OS Process Sampler

Screenshot for OS Process Sampler
+
+

+The OS Process Sampler is a sampler that can be used to execute commands on the local machine.
+It should allow execution of any command that can be run from the command line.
+Validation of the return code can be enabled, and the expected return code can be specified.
+

+

+Note that OS shells generally provide command-line parsing. +This varies between OSes, but generally the shell will split parameters on white-space. +Some shells expand wild-card file names; some don't. +The quoting mechanism also varies between OSes. +The sampler deliberately does not do any parsing or quote handling. +The command and its parameters must be provided in the form expected by the executable. +This means that the sampler settings will not be portable between OSes. +

+

+Many OSes have some built-in commands which are not provided as separate executables. +For example the Windows DIR command is part of the command interpreter (CMD.EXE). +These built-ins cannot be run as independent programs, but have to be provided as arguments to the appropriate command interpreter. +

+

+For example, the Windows command-line: DIR C:\TEMP needs to be specified as follows: +

+command:   CMD
+Param 1:   /C
+Param 2:   DIR
+Param 3:   C:\TEMP
+
+

+
+

+ Parameters +

Attribute
Description
Required
+
Command
The program name to execute.
Yes
+
Working directory
Directory from which command will be executed, defaults to folder referenced by "user.dir" System property
No
+
Command Parameters
Parameters passed to the program name.
No
+
Environment Parameters
Key/Value pairs added to environment when running command.
No
+
Standard input (stdin)
Name of file from which input is to be taken (STDIN).
No
+
Standard output (stdout
Name of output file for standard output (STDOUT). +If omitted, output is captured and returned as the response data.
No
+
Standard error (stderr)
Name of output file for standard error (STDERR). +If omitted, output is captured and returned as the response data.
No
+
Check Return Code
If checked, sampler will compare return code with Expected Return Code.
No
+
Expected Return Code
Expected return code for System Call, required if "Check Return Code" is checked.
No
+
Timeout
Timeout for command in milliseconds, defaults to 0, which means NO timeout. +If the timeout expires before the command finishes, JMeter will attempt to kill the OS process. +
No
+
+
+ +

MongoDB Script

Screenshot for MongoDB Script
+

This sampler lets you send an Request to a MongoDB.

+

Before using this you need to set up a +MongoDB Source Config Configuration element +

+
This Element currently uses com.mongodb.DB#eval which takes a global write lock causing a performance impact on the database, see db.eval(). +So it is better to avoid using this element for load testing and use JSR223+Groovy scripting using MongoDBHolder instead. +MongoDB Script is more suitable for functionnal testing or test setup (setup/teardown threads)
+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this sampler that is shown in the tree.
No
+
MongoDB Source
+ Name of the JMeter variable that the MongoDB connection is bound to. + This must agree with the 'MongoDB Source' field of a MongoDB Source Config. +
Yes
+
Database Name
Database Name, will be used in your script +
Yes
+
Username
+
No
+
Password
+
No
+
Script
+ Mongo script as it would be used in MongoDB shell +
Yes
+
+ + +
Ensure Variable Name is unique accross Test Plan.
+
+ +^ + +

18.2 Logic Controllers

+
+
Logic Controllers determine the order in which Samplers are processed. +
+ +

Simple Controller

Screenshot for Simple Controller
+
+

The Simple Logic Controller lets you organize your Samplers and other +Logic Controllers. Unlike other Logic Controllers, this controller provides no functionality beyond that of a +storage device.

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
+ +
Using the Simple Controller
+

Download this example (see Figure 6). +In this example, we created a Test Plan that sends two Ant HTTP requests and two +Log4J HTTP requests. We grouped the Ant and Log4J requests by placing them inside +Simple Logic Controllers. Remember, the Simple Logic Controller has no effect on how JMeter +processes the controller(s) you add to it. So, in this example, JMeter sends the requests in the +following order: Ant Home Page, Ant News Page, Log4J Home Page, Log4J History Page. +Note, the File Reporter +is configured to store the results in a file named "simple-test.dat" in the current directory.

+
Figure 6 Simple Controller Example
Figure 6 Simple Controller Example
+ +
+
+ +

Loop Controller

Screenshot for Loop Controller
+

If you add Generative or Logic Controllers to a Loop Controller, JMeter will +loop through them a certain number of times, in addition to the loop value you +specified for the Thread Group. For example, if you add one HTTP Request to a +Loop Controller with a loop count of two, and configure the Thread Group loop +count to three, JMeter will send a total of 2 * 3 = 6 HTTP Requests. +

+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
Loop Count
+ The number of times the subelements of this controller will be iterated each time + through a test run. +

Special Case: The Loop Controller embedded in the Thread Group + element behaves slightly differently. Unless set to forever, it stops the test after + the given number of iterations have been done.

+
When using a function in this field, be aware it may be evaluated multiple times. + Example using __Random will evaluate it to a different value for each child samplers of Loop Controller and result into unwanted behaviour.
Yes, unless "Forever" is checked
+
+ +
Looping Example
+ +

Download this example (see Figure 4). +In this example, we created a Test Plan that sends a particular HTTP Request +only once and sends another HTTP Request five times.

+ +
Figure 4 - Loop Controller Example
Figure 4 - Loop Controller Example
+ +

We configured the Thread Group for a single thread and a loop count value of +one. Instead of letting the Thread Group control the looping, we used a Loop +Controller. You can see that we added one HTTP Request to the Thread Group and +another HTTP Request to a Loop Controller. We configured the Loop Controller +with a loop count value of five.

+

JMeter will send the requests in the following order: Home Page, News Page, +News Page, News Page, News Page, and News Page. Note, the File Reporter +is configured to store the results in a file named "loop-test.dat" in the current directory.

+ +
+ +
+ +

Once Only Controller

Screenshot for Once Only Controller
+
+

The Once Only Logic Controller tells JMeter to process the controller(s) inside it only once per Thread, and pass over any requests under it +during further iterations through the test plan.

+ +

The Once Only Controller will now execute always during the first iteration of any looping parent controller. +Thus, if the Once Only Controller is placed under a Loop Controller specified to loop 5 times, then the Once Only Controller will execute only on the first iteration through the Loop Controller +(ie, every 5 times). +Note this means the Once Only Controller will still behave as previously expected if put under a Thread Group (runs only once per test per Thread), +but now the user has more flexibility in the use of the Once Only Controller.

+ +

For testing that requires a login, consider placing the login request in this controller since each thread only needs +to login once to establish a session.

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
+ +
Once Only Example
+

Download this example (see Figure 5). +In this example, we created a Test Plan that has two threads that send HTTP request. +Each thread sends one request to the Home Page, followed by three requests to the Bug Page. +Although we configured the Thread Group to iterate three times, each JMeter thread only +sends one request to the Home Page because this request lives inside a Once Only Controller.

+
Figure 5. Once Only Controller Example
Figure 5. Once Only Controller Example
+

Each JMeter thread will send the requests in the following order: Home Page, Bug Page, +Bug Page, Bug Page. Note, the File Reporter is configured to store the results in a file named "loop-test.dat" in the current directory.

+ +
+
The behaviour of the Once Only controller under anything other than the +Thread Group or a Loop Controller is not currently defined. Odd things may happen.
+
+ +

Interleave Controller

Screenshot for Interleave Controller
+

If you add Generative or Logic Controllers to an Interleave Controller, JMeter will alternate among each of the +other controllers for each loop iteration.

+
+

+ Parameters +

Attribute
Description
Required
+
name
Descriptive name for this controller that is shown in the tree.
No
+
ignore sub-controller blocks
If checked, the interleave controller will treat sub-controllers like single request elements and only allow one request per controller at a time.
No
+
+ + +
Simple Interleave Example
+ +

Download this example (see Figure 1). In this example, +we configured the Thread Group to have two threads and a loop count of five, for a total of ten +requests per thread. See the table below for the sequence JMeter sends the HTTP Requests.

+ +
Figure 1 - Interleave Controller Example 1
Figure 1 - Interleave Controller Example 1
+ + + + + + + + + + + + + +
Loop IterationEach JMeter Thread Sends These HTTP Requests
1News Page
1Log Page
2FAQ Page
2Log Page
3Gump Page
3Log Page
4Because there are no more requests in the controller,
JMeter starts over and sends the first HTTP Request, which is the News Page.
4Log Page
5FAQ Page
5Log Page
+ + +
+ +
Useful Interleave Example
+ +

Download another example (see Figure 2). In this +example, we configured the Thread Group +to have a single thread and a loop count of eight. Notice that the Test Plan has an outer Interleave Controller with +two Interleave Controllers inside of it.

+ +

+        Figure 2 - Interleave Controller Example 2
+
+ Figure 2 - Interleave Controller Example 2 +
+ +

The outer Interleave Controller alternates between the +two inner ones. Then, each inner Interleave Controller alternates between each of the HTTP Requests. Each JMeter +thread will send the requests in the following order: Home Page, Interleaved, Bug Page, Interleaved, CVS Page, Interleaved, and FAQ Page, Interleaved. +Note, the File Reporter is configured to store the results in a file named "interleave-test2.dat" in the current directory.

+ +

+        Figure 3 - Interleave Controller Example 3
+
+ Figure 3 - Interleave Controller Example 3 +
+

If the two interleave controllers under the main interleave controller were instead simple controllers, then the order would be: Home Page, CVS Page, Interleaved, Bug Page, FAQ Page, Interleaved. However, if "ignore sub-controller blocks" was checked on the main interleave controller, then the order would be: Home Page, Interleaved, Bug Page, Interleaved, CVS Page, Interleaved, and FAQ Page, Interleaved.

+
+
+ +

Random Controller

Screenshot for Random Controller
+
+

The Random Logic Controller acts similarly to the Interleave Controller, except that +instead of going in order through its sub-controllers and samplers, it picks one +at random at each pass.

+
Interactions between multiple controllers can yield complex behavior. +This is particularly true of the Random Controller. Experiment before you assume +what results any given interaction will give
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
+ +
+ + + +

Random Order Controller

Screenshot for Random Order Controller
+
+

The Random Order Controller is much like a Simple Controller in that it will execute each child + element at most once, but the order of execution of the nodes will be random.

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
+
+ +

Throughput Controller

Screenshot for Throughput Controller
+
+

+This controller is badly named, as it does not control throughput. +Please refer to the Constant Throughput Timer for an element that can be used to adjust the throughput. +

+

The Throughput Controller allows the user to control how often it is executed. There are two modes - percent execution and total executions. Percent executions causes the controller to execute a certain percentage of the iterations through the test plan. Total +executions causes the controller to stop executing after a certain number of executions have occurred. Like the Once Only Controller, this +setting is reset when a parent Loop Controller restarts. +

+
+
The Throughput Controller can yield very complex behavior when combined with other controllers - in particular with interleave or random controllers as parents (also very useful).
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
Execution Style
Whether the controller will run in percent executions or total executions mode.
Yes
+
Throughput
A number. for percent execution mode, a number from 0-100 that indicates the percentage of times the controller will execute. "50" means the controller will execute during half the iterations throught the test plan. for total execution mode, the number indicates the total number of times the controller will execute.
Yes
+
Per User
If checked, per user will cause the controller to calculate whether it should execute on a per user (per thread) basis. if unchecked, then the calculation will be global for all users. for example, if using total execution mode, and uncheck "per user", then the number given for throughput will be the total number of executions made. if "per user" is checked, then the total number of executions would be the number of users times the number given for throughput.
No
+
+ +
+ +

Runtime Controller

Screenshot for Runtime Controller
+
+

The Runtime Controller controls how long its children are allowed to run. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree, and used to name the transaction.
Yes
+
Runtime (seconds)
Desired runtime in seconds
Yes
+
+
+ +

If Controller

Screenshot for If Controller
+
+

The If Controller allows the user to control whether the test elements below it (its children) are run or not.

+

+ Prior to JMeter 2.3RC3, the condition was evaluated for every runnable element contained in the controller. + This sometimes caused unexpected behaviour, so 2.3RC3 was changed to evaluate the condition only once on initial entry. + However, the original behaviour is also useful, so versions of JMeter after 2.3RC4 have an additional + option to select the original behaviour. +

+

+ Versions of JMeter after 2.3.2 allow the script to be processed as a variable expression, rather than requiring Javascript. + It was always possible to use functions and variables in the Javascript condition, so long as they evaluated to "true" or "false"; + now this can be done without the overhead of using Javascript as well. For example, previously one could use the condition: + ${__jexl(${VAR} == 23)} and this would be evaluated as true/false, the result would then be passed to Javascript + which would then return true/false. If the Variable Expression option is selected, then the expression is evaluated + and compared with "true", without needing to use Javascript. + Also, variable expressions can return any value, whereas the + Javascript condition must return "true"/"false" or an error is logged. +

+
+ No variables are made available to the script when the condition is interpreted as Javascript. + If you need access to such variables, then select "Interpret Condition as Variable Expression?" and use + a __javaScript() function call. You can then use the objects "vars", "log", "ctx" etc. in the script. +
+
+ To test if a variable is undefined (or null) do the following, suppose var is named myVar, expression will be: +
+ "${myVar}" == "\${myVar}" +
+ Or use: +
+ "${myVar}" != "\${myVar}" +
+ to test if a variable is defined and is not null. +
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
Condition (default Javascript)
By default the condition is interpreted as Javascript code that returns "true" or "false", + but this can be overriden (see below)
Yes
+
Interpret Condition as Variable Expression?
If this is selected, then the condition must be an expression that evaluates to "true" (case is ignored). + For example, ${FOUND} or ${__jexl(${VAR} > 100)}. + Unlike the Javascript case, the condition is only checked to see if it matches "true" (case is ignored). +
Yes
+
Evaluate for all children
+ Should condition be evaluated for all children? + If not checked, then the condition is only evaluated on entry. +
Yes
+
+

Examples (Javascript): +

    +
  • ${COUNT} < 10
  • +
  • "${VAR}" == "abcd"
  • +
  • ${JMeterThread.last_sample_ok} (check if last sample succeeded)
  • +
+ If there is an error interpreting the code, the condition is assumed to be false, and a message is logged in jmeter.log. +

+

Examples (Variable Expression): +

    +
  • ${__jexl(${COUNT} < 10)}
  • +
  • ${RESULT}
  • +
+

+
+ + + + + + + + +

While Controller

Screenshot for While Controller
+
+

+The While Controller runs its children until the condition is "false". +

+ +

Possible condition values:

+
    +
  • blank - exit loop when last sample in loop fails
  • +
  • LAST - exit loop when last sample in loop fails. +If the last sample just before the loop failed, don't enter loop.
  • +
  • Otherwise - exit (or don't enter) the loop when the condition is equal to the string "false"
  • +
+
+The condition can be any variable or function that eventually evaluates to the string "false". +This allows the use of JavaScript, BeanShell, properties or variables as needed. +
+
+
+Note that the is evaluated twice, once before starting sampling children and once at end of children sampling, so putting +non idempotent functions in Condition (like __counter) can introduce issues. +
+
+For example: +
    +
  • ${VAR} - where VAR is set to false by some other test element
  • +
  • ${__javaScript(${C}==10)}
  • +
  • ${__javaScript("${VAR2}"=="abcd")}
  • +
  • ${_P(property)} - where property is set to "false" somewhere else
  • +
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree, and used to name the transaction.
Yes
+
Condition
blank, LAST, or variable/function
Yes
+
+
+ +

Switch Controller

Screenshot for Switch Controller
+
+

+The Switch Controller acts like the Interleave Controller +in that it runs one of the subordinate elements on each iteration, but rather than +run them in sequence, the controller runs the element defined by the switch value. +

+

+Note: In versions of JMeter after 2.3.1, the switch value can also be a name. +

+

If the switch value is out of range, it will run the zeroth element, +which therefore acts as the default for the numeric case. +It also runs the zeroth element if the value is the empty string.

+

+If the value is non-numeric (and non-empty), then the Switch Controller looks for the +element with the same name (case is significant). +If none of the names match, then the element named "default" (case not significant) is selected. +If there is no default, then no element is selected, and the controller will not run anything. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree, and used to name the transaction.
Yes
+
Switch Value
The number (or name) of the subordinate element to be invoked. Elements are numbered from 0.
Yes
+
+
+ +

ForEach Controller

Screenshot for ForEach Controller
+

A ForEach controller loops through the values of a set of related variables. +When you add samplers (or controllers) to a ForEach controller, every sample sample (or controller) +is executed one or more times, where during every loop the variable has a new value. +The input should consist of several variables, each extended with an underscore and a number. +Each such variable must have a value. +So for example when the input variable has the name inputVar, the following variables should have been defined: +

    +
  • inputVar_1 = wendy
  • +
  • inputVar_2 = charles
  • +
  • inputVar_3 = peter
  • +
  • inputVar_4 = john
  • +
+

Note: the "_" separator is now optional.

+When the return variable is given as "returnVar", the collection of samplers and controllers under the ForEach controller will be executed 4 consecutive times, +with the return variable having the respective above values, which can then be used in the samplers. +

+

+It is especially suited for running with the regular expression post-processor. +This can "create" the necessary input variables out of the result data of a previous request. +By omitting the "_" separator, the ForEach Controller can be used to loop through the groups by using +the input variable refName_g, and can also loop through all the groups in all the matches +by using an input variable of the form refName_${C}_g, where C is a counter variable. +

+
The ForEach Controller does not run any samples if inputVar_1 is null. +This would be the case if the Regular Expression returned no matches.
+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
Input variable prefix
Prefix for the variable names to be used as input.
Yes
+
Start index for loop
Start index (exclusive) for loop over variables (first element is at start index + 1)
No
+
End index for loop
End index (inclusive) for loop over variables
No
+
Output variable
+ The name of the variable which can be used in the loop for replacement in the samplers
Yes
+
Use Separator
If not checked, the "_" separator is omitted.
Yes
+
+ +
ForEach Example
+ +

Download this example (see Figure 7). +In this example, we created a Test Plan that sends a particular HTTP Request +only once and sends another HTTP Request to every link that can be found on the page.

+ +
Figure 7 - ForEach Controller Example
Figure 7 - ForEach Controller Example
+ +

We configured the Thread Group for a single thread and a loop count value of +one. You can see that we added one HTTP Request to the Thread Group and +another HTTP Request to the ForEach Controller.

+

After the first HTTP request, a regular expression extractor is added, which extracts all the html links +out of the return page and puts them in the inputVar variable

+

In the ForEach loop, a HTTP sampler is added which requests all the links that were extracted from the first returned HTML page. +

+
ForEach Example
+

Here is another example you can download. +This has two Regular Expressions and ForEach Controllers. +The first RE matches, but the second does not match, +so no samples are run by the second ForEach Controller

+
Figure 8 - ForEach Controller Example 2
Figure 8 - ForEach Controller Example 2
+

The Thread Group has a single thread and a loop count of two. +

+Sample 1 uses the JavaTest Sampler to return the string "a b c d". +

The Regex Extractor uses the expression (\w)\s which matches a letter followed by a space, +and returns the letter (not the space). Any matches are prefixed with the string "inputVar". +

The ForEach Controller extracts all variables with the prefix "inputVar_", and executes its +sample, passing the value in the variable "returnVar". In this case it will set the variable to the values "a" "b" and "c" in turn. +

The For 1 Sampler is another Java Sampler which uses the return variable "returnVar" as part of the sample Label +and as the sampler Data. +

Sample 2, Regex 2 and For 2 are almost identical, except that the Regex has been changed to "(\w)\sx", +which clearly won't match. Thus the For 2 Sampler will not be run. +

+
+
+ +

Module Controller

Screenshot for Module Controller
+
+

+The Module Controller provides a mechanism for substituting test plan fragments into the current test plan at run-time. +

+

+A test plan fragment consists of a Controller and all the test elements (samplers etc) contained in it. +The fragment can be located in any Thread Group, or on the WorkBench. +If the fragment is located in a Thread Group, then its Controller can be disabled to prevent the fragment being run +except by the Module Controller. +Or you can store the fragments in a dummy Thread Group, and disable the entire Thread Group. +

+

+There can be multiple fragments, each with a different series of +samplers under them. The module controller can then be used to easily switch between these multiple test cases simply by choosing +the appropriate controller in its drop down box. This provides convenience for running many alternate test plans quickly and easily. +

+

+A fragment name is made up of the Controller name and all its parent names. +For example: +

+Test Plan / Protocol: JDBC / Control / Interleave Controller (Module1)
+
+Any fragments used by the Module Controller must have a unique name, +as the name is used to find the target controller when a test plan is reloaded. +For this reason it is best to ensure that the Controller name is changed from the default +- as shown in the example above - +otherwise a duplicate may be accidentally created when new elements are added to the test plan. +

+
+
The Module Controller should not be used with remote testing or non-gui testing in conjunction with Workbench components since the Workbench test elements are not part of test plan .jmx files. Any such test will fail.
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
Module to Run
The module controller provides a list of all controllers loaded into the gui. Select + the one you want to substitute in at runtime.
Yes
+
+
+ +

Include Controller

Screenshot for Include Controller
+
+

+The include controller is designed to use an external jmx file. To use it, create a Test Fragment +underneath the Test Plan and add any desired samplers, controllers etc. below it. +Then save the Test Plan. The file is now ready to be included as part of other Test Plans. +

+

+For convenience, a Thread Group can also be added in the external JMX file for debugging purposes. +A Module Controller can be used to reference the Test Fragment. The Thread Group will be ignored during the +include process. +

+

+If the test uses a Cookie Manager or User Defined Variables, these should be placed in the top-level +test plan, not the included file, otherwise they are not guaranteed to work. +

+
+This element does not support variables/functions in the filename field.
+However, if the property includecontroller.prefix is defined, +the contents are used to prefix the pathname. +
+
+When using IncludeController and including the same JMX file, ensure you name the IncludeController differently to avoid facing known issue + Bug + 50898. +
+

+If the file cannot be found at the location given by prefix+filename, then the controller +attempts to open the fileName relative to the JMX launch directory (versions of JMeter after 2.3.4). +

+
+

+ Parameters +

Attribute
Description
Required
+
Filename
The file to include.
Yes
+
+
+ +

Transaction Controller

Screenshot for Transaction Controller
+
+

+ The Transaction Controller generates an additional + sample which measures the overall time taken to perform the nested test elements. +

+
+ Note: when the check box "Include duration of timer and pre-post processors in generated sample" is checked, + the time includes all processing within the controller scope, not just the samples. +
+

+ For JMeter versions after 2.3, there are two modes of operation +

    +
  • additional sample is added after the nested samples
  • +
  • additional sample is added as a parent of the nested samples
  • +
+

+

+ The generated sample time includes all the times for the nested samplers, and any timers etc. + Depending on the clock resolution, it may be slightly longer than the sum of the individual samplers plus timers. + The clock might tick after the controller recorded the start time but before the first sample starts. + Similarly at the end. +

+

The generated sample is only regarded as successful if all its sub-samples are successful.

+

+ In parent mode, the individual samples can still be seen in the Tree View Listener, + but no longer appear as separate entries in other Listeners. + Also, the sub-samples do not appear in CSV log files, but they can be saved to XML files. +

+
+ In parent mode, Assertions (etc) can be added to the Transaction Controller. + However by default they will be applied to both the individual samples and the overall transaction sample. + To limit the scope of the Assertions, use a Simple Controller to contain the samples, and add the Assertions + to the Simple Controller. + Parent mode controllers do not currently properly support nested transaction controllers of either type. +
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree, and used to name the transaction.
Yes
+
Generate Parent Sample
+ If checked, then the sample is generated as a parent of the other samples, + otherwise the sample is generated as an independent sample. +
Yes
+
Include duration of timer and pre-post processors in generated sample
+ Whether to include timer, pre- and post-processing delays in the generated sample. + Default is false (since JMeter 2.11, in previous versions the default value is true). +
Yes
+
+
+ +

Recording Controller

Screenshot for Recording Controller
+
+

The Recording Controller is a place holder indicating where the proxy server should +record samples to. During test run, it has no effect, similar to the Simple Controller. But during +recording using the HTTP(S) Test Script Recorder, all recorded samples will by default +be saved under the Recording Controller.

+ +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this controller that is shown in the tree.
No
+
+ +
+ +

Critical Section Controller

Screenshot for Critical Section Controller
+
+

The Critical Section Controller ensures that its children elements (samplers/controllers, etc) will be executed +by only one thread as a named lock will be taken before executing children of controller.

+
+

+ The figure below shows an example of using Critical Section Controller, in the figure below 2 Critical Section Controllers ensure + that: +

    +
  • DS2-${__threadNum} is executed only by one thread at a time
  • +
  • DS4-${__threadNum} is executed only by one thread at a time
  • +
+
Test Plan using Critical Section Controller
Test Plan using Critical Section Controller
+

+ +

+ Parameters +

Attribute
Description
Required
+
Lock Name
Lock that will be taken by controller, ensure you use different lock names for unrelated sections
Yes
+
+
+Critical Section Controller takes locks only within one JVM, so if using Distributed testing ensure your use case does not rely on all threads of all JVMs blocking. +
+ +
+ +^ + +

18.3 Listeners

+
+
+Most of the listeners perform several roles in addition to "listening" +to the test results. +They also provide means to view, save, and read saved test results. +

Note that Listeners are processed at the end of the scope in which they are found.

+

+The saving and reading of test results is generic. The various +listeners have a panel whereby one can specify the file to +which the results will be written (or read from). +By default, the results are stored as XML +files, typically with a ".jtl" extension. +Storing as CSV is the most efficient option, but is less detailed than XML (the other available option). +

+

+Listeners do not process sample data in non-GUI mode, but the raw data will be saved if an output +file has been configured. +In order to analyse the data generated by a non-GUI test run, you need to load the file into the appropriate +Listener. +

+
+To read existing results and display them, use the file panel Browse button to open the file. +
+

+Versions of JMeter up to 2.3.2 used to clear any current data before loading the new file.
+This is no longer done, thus allowing files to be merged. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. +

+

Results can be read from XML or CSV format files. +When reading from CSV results files, the header (if present) is used to determine which fields are present. +In order to interpret a header-less CSV file correctly, the appropriate properties must be set in jmeter.properties. +

+
+The file name can contain function and/or variable references. +However variable references do not work in client-server mode (functions work OK). +
+

Listeners can use a lot of memory if there are a lot of samples. +Most of the listeners currently keep a copy of every sample in their scope, apart from: +

+
    +
  • Simple Data Writer
  • +
  • BeanShell/BSF Listener
  • +
  • Mailer Visualizer
  • +
  • Monitor Results
  • +
  • Summary Report
  • +
+

+The following Listeners no longer need to keep copies of every single sample. +Instead, samples with the same elapsed time are aggregated. +Less memory is now needed, especially if most samples only take a second or two at most. +

+
    +
  • Aggregate Report
  • +
  • Aggregate Graph
  • +
  • Distribution Graph
  • +
+

To minimise the amount of memory needed, use the Simple Data Writer, and use the CSV format.

+

+

+Versions of JMeter after 2.3.1 allow JMeter variables to be saved to the output files. +This can only be specified using a property. +See the Listener Sample Variables for details +
+For full details on setting up the default items to be saved +see the Listener Default Configuration documentation. +For details of the contents of the output files, +see the CSV log format or +the XML log format. +

+
The entries in jmeter.properties are used to define the defaults; +these can be overriden for individual listeners by using the Configure button, +as shown below. +The settings in jmeter.properties also apply to the listener that is added +by using the -l command-line flag. +
+

+ The figure below shows an example of the result file configuration panel +

Result file configuration panel
Result file configuration panel
+

+

+ Parameters +

Attribute
Description
Required
+
Filename
Name of the file containing sample results. + The file name can be specified using either a relative or an absolute path name. + Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). + Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). + If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), + then the path is assumed to be relative to the JMX file location. +
No
+
Browse...
File Browse Button
No
+
Errors
Select this to write/read only results with errors
No
+
Successes
Select this to write/read only results without errors. + If neither Errors nor Successes is selected, then all results are processed.
No
+
Configure
Configure Button, see below
No
+
+
+ +

Sample Result Save Configuration

Screenshot for Sample Result Save Configuration
+
+

+Listeners can be configured to save different items to the result log files (JTL) by using the Config popup as shown below. +The defaults are defined as described in the Listener Default Configuration documentation. +Items with (CSV) after the name only apply to the CSV format; items with (XML) only apply to XML format. +CSV format cannot currently be used to save any items that include line-breaks. +

+

+Note that cookies, method and the query string are saved as part of the "Sampler Data" option. +

+
+
+ +

Graph Results

Screenshot for Graph Results
+
+
+Graph Results MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. +
+

The Graph Results listener generates a simple graph that plots all sample times. Along +the bottom of the graph, the current sample (black), the current average of all samples(blue), the +current standard deviation (red), and the current throughput rate (green) are displayed in milliseconds.

+

The throughput number represents the actual number of requests/minute the server handled. This calculation +includes any delays you added to your test and JMeter's own internal processing time. The advantage +of doing the calculation like this is that this number represents something +real - your server in fact handled that many requests per minute, and you can increase the number of threads +and/or decrease the delays to discover your server's maximum throughput. Whereas if you made calculations +that factored out delays and JMeter's processing, it would be unclear what you could conclude from that +number.

+

The following table briefly describes the items on the graph. +Further details on the precise meaning of the statistical terms can be found on the web + - e.g. Wikipedia - or by consulting a book on statistics. +

+
    +
  • Data - plot the actual data values
  • +
  • Average - plot the Average
  • +
  • Median - plot the Median (midway value)
  • +
  • Deviation - plot the Standard Deviation (a measure of the variation)
  • +
  • Throughput - plot the number of samples per unit of time
  • +
+

The individual figures at the bottom of the display are the current values. + "Latest Sample" is the current elapsed sample time, shown on the graph as "Data".

+

The value displayed on the top left of graph is the max of 90th percentile of response time.

+
+ +

Spline Visualizer

Screenshot for Spline Visualizer
+
+Spline Visualizer MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. +
+
+

+The Spline Visualizer provides a view of all sample times from the start +of the test till the end, regardless of how many samples have been taken. The spline +has 10 points, each representing 10% of the samples, and connected using spline +logic to show a single continuous line. +

+

+The graph is automatically scaled to fit within the window. +This needs to be borne in mind when comparing graphs. +

+
+
+ +

Assertion Results

Screenshot for Assertion Results
+
+
+Assertion Results MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. +
+

The Assertion Results visualizer shows the Label of each sample taken. +It also reports failures of any Assertions that +are part of the test plan.

+ + +
+ +

View Results Tree

Screenshot for View Results Tree
+
+
+View Results Tree MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. +
+The View Results Tree shows a tree of all sample responses, allowing you to view the +response for any sample. In addition to showing the response, you can see the time it took to get +this response, and some response codes. +Note that the Request panel only shows the headers added by JMeter. +It does not show any headers (such as Host) that may be added by the HTTP protocol implementation. +

+There are several ways to view the response, selectable by a drop-down box at the bottom of the left hand panel.

+ + + + + + + + + + + + + + + + + + + + + +
RendererDescription
CSS/JQuery TesterThe CSS/JQuery Tester only works for text responses. It shows the plain text in the upper panel. +The "Test" button allows the user to apply the CSS/JQuery to the upper panel and the results +will be displayed in the lower panel.
+The engine of CSS/JQuery expression can be JSoup or Jodd, syntax of these 2 implementation differs slightly.
+For example, the Selector a[class=sectionlink] with attribute href applied to the current JMeter functions page gives the following output: +
+
+Match count: 74
+Match[1]=#functions
+Match[2]=#what_can_do
+Match[3]=#where
+Match[4]=#how
+Match[5]=#function_helper
+Match[6]=#functions
+Match[7]=#__regexFunction
+Match[8]=#__regexFunction_parms
+Match[9]=#__counter
+... and so on ...
+
+
DocumentThe Document view will show the extract text from various type of documents like Microsoft Office +(Word, Excel, PowerPoint 97-2003, 2007-2010 (openxml), Apache OpenOffice (writer, calc, impress), HTML, +gzip, jar/zip files (list of content), and some meta-data on "multimedia" files like mp3, mp4, flv, etc. The complete list of +support format is available on Apache Tika format page. +

+Note: A requirement to the Document view is to download the +Apache Tika binary package (tika-app-x.x.jar) and put this in JMETER_HOME/lib directory. +

+If the document is larger than 10 MB, then it won't be displayed. +To change this limit, set the JMeter property document.max_size (unit is byte) or set to 0 to remove the limit. +
HTMLThe HTML view attempts to render the response as +HTML. The rendered HTML is likely to compare poorly to the view one +would get in any web browser; however, it does provide a quick +approximation that is helpful for initial result evaluation.
+Images, style-sheets, etc. aren't downloaded. +
HTML (download resources)If the HTML (download resources) view option is selected, the renderer +may download images, style-sheets, etc. referenced by the HTML code. +
JSONThe JSON view will show the response in tree style (also handles JSON embedded in JavaScript). +
Regexp TesterThe Regexp Tester view only works for text responses. It shows the plain text in the upper panel. +The "Test" button allows the user to apply the Regular Expression to the upper panel and the results +will be displayed in the lower panel.
+The engine of regular expression is the same that the Regular Expression Extractor.
+For example, the RE (JMeter\w*).* applied to the current JMeter home page gives the following output: +
+
+Match count: 26
+Match[1][0]=JMeter - Apache JMeter</title>
+Match[1][1]=JMeter
+Match[2][0]=JMeter" title="JMeter" border="0"/></a>
+Match[2][1]=JMeter
+Match[3][0]=JMeterCommitters">Contributors</a>
+Match[3][1]=JMeterCommitters
+... and so on ...
+
+
+The first number in [] is the match number; the second number is the group. +Group [0] is whatever matched the whole RE. +Group [1] is whatever matched the 1st group, i.e. (JMeter\w*) in this case. +See Figure 9b (below). +
Text +The default Text view shows all of the text contained in the response. +Note that this will only work if the response content-type is considered to be text. +If the content-type begins with any of the following, it is considered as binary, +otherwise it is considered to be text. +
+image/
+audio/
+video/
+
+
XMLThe XML view will show response in tree style. +Any DTD nodes or Prolog nodes will not show up in tree; however, response may contain those nodes. +
XPath TesterThe XPath Tester only works for text responses. It shows the plain text in the upper panel. +The "Test" button allows the user to apply the XPath query to the upper panel and the results +will be displayed in the lower panel.
+
+

Scroll automatically? option permit to have last node display in tree selection

+

+With Search option, most of the views also allow the displayed data to be searched; the result of the search will be high-lighted +in the display above. For example the Control panel screenshot below shows one result of searching for "Java". +Note that the search operates on the visible text, so you may get different results when searching +the Text and HTML views. +
Note: The regular expression uses the Java engine (not ORO engine like the Regular Expression Extractor or Regexp Tester view). +

+

+If there is no content-type provided, then the content +will not be displayed in the any of the Response Data panels. +You can use Save Responses to a file to save the data in this case. +Note that the response data will still be available in the sample result, +so can still be accessed using Post-Processors. +

+

If the response data is larger than 200K, then it won't be displayed. +To change this limit, set the JMeter property view.results.tree.max_size. +You can also use save the entire response to a file using +Save Responses to a file. +

+

+Additional renderers can be created. +The class must implement the interface org.apache.jmeter.visualizers.ResultRenderer +and/or extend the abstract class org.apache.jmeter.visualizers.SamplerResultTab, and the +compiled code must be available to JMeter (e.g. by adding it to the lib/ext directory). +

+
+

+ The Control Panel (above) shows an example of an HTML display.
+ Figure 9 (below) shows an example of an XML display.
+ Figure 9a (below) shows an example of an Regexp tester display.
+ Figure 9b (below) shows an example of an Document display.
+

+
Figure 9 Sample XML display
Figure 9 Sample XML display
+
Figure 9a Sample Regexp Test display
Figure 9a Sample Regexp Test display
+
Figure 9b Sample Document (here PDF) display
Figure 9b Sample Document (here PDF) display
+
+

+
+ +

Aggregate Report

Screenshot for Aggregate Report
+
The aggregate report creates a table row for each differently named request in your +test. For each request, it totals the response information and provides request count, min, max, +average, error rate, approximate throughput (request/second) and Kilobytes per second throughput. +Once the test is done, the throughput is the actual through for the duration of the entire test. +

+The thoughput is calculated from the point of view of the sampler target +(e.g. the remote server in the case of HTTP samples). +JMeter takes into account the total time over which the requests have been generated. +If other samplers and timers are in the same thread, these will increase the total time, +and therefore reduce the throughput value. +So two identical samplers with different names will have half the throughput of two samplers with the same name. +It is important to choose the sampler names correctly to get the best results from +the Aggregate Report. +

+

+Calculation of the Median and 90% Line (90th percentile) values requires additional memory. +JMeter now combines samples with the same elapsed time, so far less memory is used. +However, for samples that take more than a few seconds, the probability is that fewer samples will have identical times, +in which case more memory will be needed. +Note you can use this listener afterwards to reload a CSV or XML results file which is the recommended way to avoid performance impacts. +See the Summary Report for a similar Listener that does not store individual samples and so needs constant memory. +

+
+Starting with JMeter 2.12, you can configure the 3 percentile values you want to compute, this can be done by setting properties: +
    +
  • aggregate_rpt_pct1: defaults to 90th percentile
  • +
  • aggregate_rpt_pct2: defaults to 95th percentile
  • +
  • aggregate_rpt_pct3: defaults to 99th percentile
  • +
+
+
    +
  • Label - The label of the sample. +If "Include group name in label?" is selected, then the name of the thread group is added as a prefix. +This allows identical labels from different thread groups to be collated separately if required. +
  • +
  • # Samples - The number of samples with the same label
  • +
  • Average - The average time of a set of results
  • +
  • Median - The median is the time in the middle of a set of results. +50% of the samples took no more than this time; the remainder took at least as long.
  • +
  • 90% Line - 90% of the samples took no more than this time. +The remaining samples took at least as long as this. (90th percentile)
  • +
  • 95% Line - 95% of the samples took no more than this time. +The remaining samples took at least as long as this. (95th percentile)
  • +
  • 99% Line - 99% of the samples took no more than this time. +The remaining samples took at least as long as this. (99th percentile)
  • +
  • Min - The shortest time for the samples with the same label
  • +
  • Max - The longest time for the samples with the same label
  • +
  • Error % - Percent of requests with errors
  • +
  • Throughput - the Throughput is measured in requests per second/minute/hour. +The time unit is chosen so that the displayed rate is at least 1.0. +When the throughput is saved to a CSV file, it is expressed in requests/second, +i.e. 30.0 requests/minute is saved as 0.5. +
  • +
  • Kb/sec - The throughput measured in Kilobytes per second
  • +
+

Times are in milliseconds.

+
+
+

+ The figure below shows an example of selecting the "Include group name" checkbox. +

Sample "Include group name" display
Sample "Include group name" display
+

+
+
+ +

View Results in Table

Screenshot for View Results in Table
+
This visualizer creates a row for every sample result. +Like the View Results Tree, this visualizer uses a lot of memory. +

+By default, it only displays the main (parent) samples; it does not display the sub-samples (child samples). +Versions of JMeter after 2.5.1 have a "Child Samples?" check-box. +If this is selected, then the sub-samples are displayed instead of the main samples. +

+
+
+ +

Simple Data Writer

Screenshot for Simple Data Writer
+
This listener can record results to a file +but not to the UI. It is meant to provide an efficient means of +recording data by eliminating GUI overhead. +When running in non-GUI mode, the -l flag can be used to create a data file. +The fields to save are defined by JMeter properties. +See the jmeter.properties file for details. +
+
+ +

Monitor Results

Screenshot for Monitor Results
+
+

Monitor Results is a new Visualizer for displaying server +status. It is designed for Tomcat 5, but any servlet container +can port the status servlet and use this monitor. There are two primary +tabs for the monitor. The first is the "Health" tab, which will show the +status of one or more servers. The second tab labled "Performance" shows +the performance for one server for the last 1000 samples. The equations +used for the load calculation is included in the Visualizer.

+

Currently, the primary limitation of the monitor is system memory. A +quick benchmark of memory usage indicates a buffer of 1000 data points for +100 servers would take roughly 10Mb of RAM. On a 1.4Ghz centrino +laptop with 1Gb of ram, the monitor should be able to handle several +hundred servers.

+

As a general rule, monitoring production systems should take care to +set an appropriate interval. Intervals shorter than 5 seconds are too +aggressive and have a potential of impacting the server. With a buffer of +1000 data points at 5 second intervals, the monitor would check the server +status 12 times a minute or 720 times a hour. This means the buffer shows +the performance history of each machine for the last hour.

+
+The monitor requires Tomcat 5 or above. +Use a browser to check that you can access the Tomcat status servlet OK. +
+

+For a detailed description of how to use the monitor, please refer to +Building a Monitor Test Plan +

+
+
+ +

Distribution Graph (alpha)

Screenshot for Distribution Graph (alpha)
+
+
+Distribution Graph MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. +
+ +

The distribution graph will display a bar for every unique response time. Since the +granularity of System.currentTimeMillis() is 10 milliseconds, the 90% threshold should be +within the width of the graph. The graph will draw two threshold lines: 50% and 90%. +What this means is 50% of the response times finished between 0 and the line. The same +is true of 90% line. Several tests with Tomcat were performed using 30 threads for 600K +requests. The graph was able to display the distribution without any problems and both +the 50% and 90% line were within the width of the graph. A performant application will +generally produce results that clump together. A poorly written application that has +memory leaks may result in wild fluctuations. In those situations, the threshold lines +may be beyond the width of the graph. The recommended solution to this specific problem +is fix the webapp so it performs well. If your test plan produces distribution graphs +with no apparent clumping or pattern, it may indicate a memory leak. The only way to +know for sure is to use a profiling tool.

+
+
+ +

Aggregate Graph

Screenshot for Aggregate Graph
+
The aggregate graph is similar to the aggregate report. The primary +difference is the aggregate graph provides an easy way to generate bar graphs and save +the graph as a PNG file.
+
+

+ The figure below shows an example of settings to draw this graph. +

Aggregate graph settings
Aggregate graph settings
+

+
+

Please note: All this parameters aren't saved in JMeter jmx script.

+

+ Parameters +

Attribute
Description
Required
+
Column settings
    +
  • Columns to display: Choose the column(s) to display in graph.
  • +
  • Rectangles color: Clic on right color rectangle open a popup dialog to choose a custom color for column.
  • +
  • Foreground color Allow to change the value text color.
  • +
  • Value font: Allow to define font settings for the text.
  • +
  • Draw outlines bar? To draw or not the border line on bar chart
  • +
  • Show number grouping? Show or not the number grouping in Y Axis labels.
  • +
  • Value labels vertical? Change orientation for value label. (Default is horizontal)
  • +
  • Column label selection: Filter by result label. A regular expression can be used, example: .*Transaction.* +
    Before display the graph, click on Apply filter button to refresh internal data.
Yes
+
Title
Define the graph's title on the head of chart. Empty value is the default value : "Aggregate Graph". + The button Synchronize with name define the title with the label of the listener. And define font settings for graph title
No
+
Graph size
Compute the graph size by the width and height depending of the current JMeter's window size. + Use Width and Height fields to define a custom size. The unit is pixel.
No
+
X Axis settings
Define the max length of X Axis label (in pixel).
No
+
Y Axis settings
Define a custom maximum value for Y Axis.
No
+
Legend
Define the placement and font settings for chart legend
Yes
+
+
+ +

Response Time Graph

Screenshot for Response Time Graph
+
+The Response Time Graph draws a line chart showing the evolution of response time during the test, for each labelled request. +If many samples exist for the same timestamp, the mean value is displayed. +
+
+

+ The figure below shows an example of settings to draw this graph. +

Response time graph settings
Response time graph settings
+

+
+

Please note: All this parameters are saved in JMeter .jmx file.

+

+ Parameters +

Attribute
Description
Required
+
Interval (ms)
The time in milli-seconds for X axis interval. Samples are grouped according to this value. + Before display the graph, click on Apply interval button to refresh internal data.
Yes
+
Sampler label selection
Filter by result label. A regular expression can be used, ex..*Transaction.*. + Before display the graph, click on Apply filter button to refresh internal data.
No
+
Title
Define the graph's title on the head of chart. Empty value is the default value : "Response Time Graph". + The button Synchronize with name define the title with the label of the listener. And define font settings for graph title
No
+
Line settings
Define the width of the line. Define the type of each value point. Choose none to have a line without mark
Yes
+
Graph size
Compute the graph size by the width and height depending of the current JMeter's window size. + Use Width and Height fields to define a custom size. The unit is pixel.
No
+
X Axis settings
Customize the date format of X axis label. + The syntax is the Java SimpleDateFormat API.
No
+
Y Axis settings
Define a custom maximum value for Y Axis in milli-seconds. Define the increment for the scale (in ms) Show or not the number grouping in Y Axis labels.
No
+
Legend
Define the placement and font settings for chart legend
Yes
+
+
+ +

Mailer Visualizer

Screenshot for Mailer Visualizer
+

The mailer visualizer can be set up to send email if a test run receives too many +failed responses from the server.

+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
From
Email address to send messages from.
Yes
+
Addressee(s)
Email address to send messages to, comma-separated.
Yes
+
Success Subject
Email subject line for success messages.
No
+
Success Limit
Once this number of successful responses is exceeded + after previously reaching the failure limit, a success email + is sent. The mailer will thus only send out messages in a sequence of failed-succeeded-failed-succeeded, etc.
Yes
+
Failure Subject
Email subject line for fail messages.
No
+
Failure Limit
Once this number of failed responses is exceeded, a failure + email is sent - i.e. set the count to 0 to send an e-mail on the first failure.
Yes
+ +
Host
IP address or host name of SMTP server (email redirector) + server.
No
+
Port
Port of SMTP server (defaults to 25).
No
+
Login
Login used to authenticate.
No
+
Password
Password used to authenticate.
No
+
Connection security
Type of encryption for SMTP authentication (SSL, TLS or none).
No
+ +
Test Mail
Press this button to send a test mail
No
+
Failures
A field that keeps a running total of number + of failures so far received.
No
+
+
+ +

BeanShell Listener

Screenshot for BeanShell Listener
+
+

+The BeanShell Listener allows the use of BeanShell for processing samples for saving etc. +

+

+For full details on using BeanShell, please see the BeanShell website. +

+

+The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label
+
Reset bsh.Interpreter before each call
+ If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. +
Yes
+
Parameters
Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • bsh.args - String array containing parameters, split on white-space
  • +
No
+
Script file
A file containing the BeanShell script to run. + The file name is stored in the script variable FileName
No
+
Script
The BeanShell script to run. The return value is ignored.
Yes (unless script file is provided)
+
+

Before invoking the script, some variables are set up in the BeanShell interpreter:

+
    +
  • log - (Logger) - can be used to write to the log file
  • +
  • ctx - (JMeterContext) - gives access to the context
  • +
  • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
  • +
  • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • sampleResult, prev - (SampleResult) - gives access to the previous SampleResult
  • +
  • sampleEvent (SampleEvent) gives access to the current sample event
  • +
+

For details of all the methods available on each of the above variables, please check the Javadoc

+

If the property beanshell.listener.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

+
+ +

Summary Report

Screenshot for Summary Report
+
The summary report creates a table row for each differently named request in your +test. This is similar to the Aggregate Report , except that it uses less memory. +

+The thoughput is calculated from the point of view of the sampler target +(e.g. the remote server in the case of HTTP samples). +JMeter takes into account the total time over which the requests have been generated. +If other samplers and timers are in the same thread, these will increase the total time, +and therefore reduce the throughput value. +So two identical samplers with different names will have half the throughput of two samplers with the same name. +It is important to choose the sampler labels correctly to get the best results from +the Report. +

+
    +
  • Label - The label of the sample. +If "Include group name in label?" is selected, then the name of the thread group is added as a prefix. +This allows identical labels from different thread groups to be collated separately if required. +
  • +
  • # Samples - The number of samples with the same label
  • +
  • Average - The average elapsed time of a set of results
  • +
  • Min - The lowest elapsed time for the samples with the same label
  • +
  • Max - The longest elapsed time for the samples with the same label
  • +
  • Std. Dev. - the Standard Deviation of the sample elapsed time
  • +
  • Error % - Percent of requests with errors
  • +
  • Throughput - the Throughput is measured in requests per second/minute/hour. +The time unit is chosen so that the displayed rate is at least 1.0. +When the throughput is saved to a CSV file, it is expressed in requests/second, +i.e. 30.0 requests/minute is saved as 0.5. +
  • +
  • Kb/sec - The throughput measured in Kilobytes per second
  • +
  • Avg. Bytes - average size of the sample response in bytes. (in JMeter 2.2 it wrongly showed the value in kB)
  • +
+

Times are in milliseconds.

+
+
+

+ The figure below shows an example of selecting the "Include group name" checkbox. +

Sample "Include group name" display
Sample "Include group name" display
+

+
+
+ +

Save Responses to a file

Screenshot for Save Responses to a file
+
+

+ This test element can be placed anywhere in the test plan. + For each sample in its scope, it will create a file of the response Data. + The primary use for this is in creating functional tests, but it can also + be useful where the response is too large to be displayed in the + View Results Tree Listener. + The file name is created from the specified prefix, plus a number (unless this is disabled, see below). + The file extension is created from the document type, if known. + If not known, the file extension is set to 'unknown'. + If numbering is disabled, and adding a suffix is disabled, then the file prefix is + taken as the entire file name. This allows a fixed file name to be generated if required. + The generated file name is stored in the sample response, and can be saved + in the test log output file if required. +

+

+ The current sample is saved first, followed by any sub-samples (child samples). + If a variable name is provided, then the names of the files are saved in the order + that the sub-samples appear. See below. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Filename Prefix
Prefix for the generated file names; this can include a directory name. + Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). + Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). + If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), + then the path is assumed to be relative to the JMX file location. +
Yes
+
Variable Name
+ Name of a variable in which to save the generated file name (so it can be used later in the test plan). + If there are sub-samples then a numeric suffix is added to the variable name. + E.g. if the variable name is FILENAME, then the parent sample file name is saved in the variable FILENAME, + and the filenames for the child samplers are saved in FILENAME1, FILENAME2 etc. +
No
+
Save Failed Responses only
If selected, then only failed responses are saved
Yes
+
Save Successful Responses only
If selected, then only successful responses are saved
Yes
+
Don't add number to prefix
If selected, then no number is added to the prefix. If you select this option, make sure that the prefix is unique or the file may be overwritten.
Yes
+
Don't add suffix
If selected, then no suffix is added. If you select this option, make sure that the prefix is unique or the file may be overwritten.
Yes
+
+
+ +

BSF Listener

Screenshot for BSF Listener
+
+

+The BSF Listener allows BSF script code to be applied to sample results. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Language
The BSF language to be used
Yes
+
Parameters
Parameters to pass to the script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • args - String array containing parameters, split on white-space
  • +
No
+
Script file
A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property
No
+
Script
The script to run.
Yes (unless script file is provided)
+
+

+The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

+

+Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. +

+
    +
  • log - (Logger) - can be used to write to the log file
  • +
  • Label - the String Label
  • +
  • Filename - the script file name (if any)
  • +
  • Parameters - the parameters (as a String)
  • +
  • args[] - the parameters as a String array (split on whitespace)
  • +
  • ctx - (JMeterContext) - gives access to the context
  • +
  • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
  • +
  • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • sampleResult, prev - (SampleResult) - gives access to the SampleResult
  • +
  • sampleEvent - (SampleEvent) - gives access to the SampleEvent
  • +
  • sampler - (Sampler)- gives access to the last sampler
  • +
  • OUT - System.out - e.g. OUT.println("message")
  • +
+

For details of all the methods available on each of the above variables, please check the Javadoc

+
+ +

JSR223 Listener

+
+

+The JSR223 Listener allows JSR223 script code to be applied to sample results. +For details, see BSF Listener. +

+
+
+ +

Generate Summary Results

Screenshot for Generate Summary Results
+
This test element can be placed anywhere in the test plan. +Generates a summary of the test run so far to the log file and/or +standard output. Both running and differential totals are shown. +Output is generated every n seconds (default 30 seconds) on the appropriate +time boundary, so that multiple test runs on the same time will be synchronised. +See jmeter.properties file for the summariser configuration items: +
+# Define the following property to automatically start a summariser with that name
+# (applies to non-GUI mode only)
+#summariser.name=summary
+#
+# interval between summaries (in seconds) default 3 minutes
+#summariser.interval=30
+#
+# Write messages to log file
+#summariser.log=true
+#
+# Write messages to System.out
+#summariser.out=true
+
+This element is mainly intended for batch (non-GUI) runs. +The output looks like the following: +
+label +   171 in  20.3s =    8.4/s Avg:  1129 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label +   263 in  31.3s =    8.4/s Avg:  1138 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label =   434 in  50.4s =    8.6/s Avg:  1135 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label +   263 in  31.0s =    8.5/s Avg:  1138 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label =   697 in  80.3s =    8.7/s Avg:  1136 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label +   109 in  12.4s =    8.8/s Avg:  1092 Min:    47 Max:  1250 Err:     0 (0.00%)
+label =   806 in  91.6s =    8.8/s Avg:  1130 Min:    47 Max:  1250 Err:     0 (0.00%)
+
+The "label" is the the name of the element. +The "+" means that the line is a delta line, i.e. shows the changes since the last output.
+The "=" means that the line is a totals line, i.e. it shows the running total.
+Entries in the jmeter log file also include time-stamps. +The example "806 in 91.6s = 8.8/s" means that there were 806 samples recorded in 91.6 seconds, +and that works out at 8.8 samples per second.
+The Avg (Average), Min(imum) and Max(imum) times are in milliseconds.
+"Err" means number of errors (also shown as percentage).
+The last two lines will appear at the end of a test. +They will not be synchronised to the appropriate time boundary. +Note that the initial and final deltas may be for less than the interval (in the example above this is 30 seconds). +The first delta will generally be lower, as JMeter synchronizes to the interval boundary. +The last delta will be lower, as the test will generally not finish on an exact interval boundary. +

+The label is used to group sample results together. +So if you have multiple Thread Groups and want to summarize across them all, then use the same label + - or add the summariser to the Test Plan (so all thread groups are in scope). +Different summary groupings can be implemented +by using suitable labels and adding the summarisers to appropriate parts of the test plan. +

+
+In Non-GUI mode by default a Generate Summary Results listener named "summariser" is configured, if you have already added one to your Test Plan, ensure you name it differently +otherwise results will be accumulated under this label (summary) leading to wrong results (sum of total samples + samples located under the Parent of Generate Summary Results listener). +This is not a bug but a design choice allowing to summarize across thread groups. +
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree. + It appears as the "label" in the output. Details for all elements with the same label will be added together. +
Yes
+
+
+ +

Comparison Assertion Visualizer

Screenshot for Comparison Assertion Visualizer
+
+The Comparison Assertion Visualizer shows the results of any Compare Assertion elements. +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree. +
Yes
+
+
+ +

Backend Listener

Screenshot for Backend Listener
+
+The backend listener is an Asynchronous listener that enables you to plug custom implementations of BackendListenerClient. +By default, a Graphite implementation is provided. +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
Yes
+
Backend Listener implementation
Class of the BackendListenerClient implementation.
Yes
+
Async Queue size
Size of the queue that holds the SampleResults while they are processed asynchronously.
Yes
+
Parameters
Parameters of the BackendListenerClient implementation.
Yes
+
+ + +

The following parameters apply to the GraphiteBackendListenerClient implementation:

+ +

+ Parameters +

Attribute
Description
Required
+
graphiteMetricsSender
org.apache.jmeter.visualizers.backend.graphite.TextGraphiteMetricsSender or org.apache.jmeter.visualizers.backend.graphite.PickleGraphiteMetricsSender
Yes
+
graphiteHost
Graphite or InfluxDB (with Graphite plugin enabled) server host
Yes
+
graphitePort
Graphite or InfluxDB (with Graphite plugin enabled) server port, defaults to 2003. Note PickleGraphiteMetricsSender (port 2004) can only talk to Graphite server.
Yes
+
rootMetricsPrefix
Prefix of metrics sent to backend. Defaults to "jmeter."
Yes
+
summaryOnly
Only send a summary with no detail. Defaults to true.
Yes
+
samplersList
Semicolon separated list of samplers for which you want to report metrics to backend.
Yes
+
percentiles
The percentiles you want to send to backend. List must be semicolon separated.
Yes
+
+

Read this for more details.

+
Grafana dashboard
Grafana dashboard
+
+ +^ + +

18.4 Configuration Elements

+
+
+ Configuration elements can be used to set up defaults and variables for later use by samplers. + Note that these elements are processed at the start of the scope in which they are found, + i.e. before any samplers in the same scope. +
+
+ +

CSV Data Set Config

Screenshot for CSV Data Set Config
+
+

+ CSV Data Set Config is used to read lines from a file, and split them into variables. + It is easier to use than the __CSVRead() and _StringFromFile() functions. + It is well suited to handling large numbers of variables, and is also useful for tesing with + "random" and unique values. + Generating unique random values at run-time is expensive in terms of CPU and memory, so just create the data + in advance of the test. If necessary, the "random" data from the file can be used in conjunction with + a run-time parameter to create different sets of values from each run - e.g. using concatenation - which is + much cheaper than generating everything at run-time. +

+

+ Versions of JMeter after 2.3.1 allow values to be quoted; this allows the value to contain a delimiter. + Previously it was necessary to choose a delimiter that was not used in any values. + If "allow quoted data" is enabled, a value may be enclosed in double-quotes. + These are removed. To include double-quotes within a quoted field, use two double-quotes. + For example:

+1,"2,3","4""5" =>
+1
+2,3
+4"5
+
+

+

+ Versions of JMeter after 2.3.4 support CSV files which have a header line defining the column names. + To enable this, leave the "Variable Names" field empty. The correct delimiter must be provided. +

+

+ Versions of JMeter after 2.7 support CSV files with quoted data that includes new-lines. +

+

+ By default, the file is only opened once, and each thread will use a different line from the file. + However the order in which lines are passed to threads depends on the order in which they execute, + which may vary between iterations. + Lines are read at the start of each test iteration. + The file name and mode are resolved in the first iteration. +

+

+ See the description of the Share mode below for additional options (JMeter 2.3.2+). + If you want each thread to have its own set of values, then you will need to create a set of files, + one for each thread. For example test1.csv, test2.csv,... testn.csv. Use the filename + test${__threadNum}.csv and set the "Sharing mode" to "Current thread". +

+
CSV Dataset variables are defined at the start of each test iteration. + As this is after configuration processing is completed, + they cannot be used for some configuration items - such as JDBC Config - + that process their contents at configuration time (see + Bug + 40394) + However the variables do work in the HTTP Auth Manager, as the username etc are processed at run-time. +
+

+ As a special case, the string "\t" (without quotes) in the delimiter field is treated as a Tab. +

+

+ When the end of file (EOF) is reached, and the recycle option is true, reading starts again with the first line of the file. +

+

+ If the recycle option is false, and stopThread is false, then all the variables are set to <EOF> when the end of file is reached. + This value can be changed by setting the JMeter property csvdataset.eofstring. +

+

+ If the Recycle option is false, and Stop Thread is true, then reaching EOF will cause the thread to be stopped. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Filename
Name of the file to be read. + Relative file names are resolved with respect to the path of the active test plan. + For distributed testing, the CSV file must be stored on the server host system in the correct relative directory to where the jmeter server is started. + Absolute file names are also supported, but note that they are unlikely to work in remote mode, + unless the remote server has the same directory structure. + If the same physical file is referenced in two different ways - e.g. csvdata.txt and ./csvdata.txt - + then these are treated as different files. + If the OS does not distinguish between upper and lower case, csvData.TXT would also be opened separately. +
Yes
+
File Encoding
The encoding to be used to read the file, if not the platform default.
No
+
Variable Names
List of variable names (comma-delimited). + Versions of JMeter after 2.3.4 support CSV header lines: + if the variable name field empty, then the first line of the file is read and interpreted as the list of column names. + The names must be separated by the delimiter character. They can be quoted using double-quotes. +
Yes
+
Delimiter
Delimiter to be used to split the records in the file. + If there are fewer values on the line than there are variables the remaining variables are not updated - + so they will retain their previous value (if any).
Yes
+
Allow quoted data?
Should the CSV file allow values to be quoted? + If enabled, then values can be enclosed in " - double-quote - allowing values to contain a delimeter. +
Yes
+
Recycle on EOF?
Should the file be re-read from the beginning on reaching EOF? (default is true)
Yes
+
Stop thread on EOF?
Should the thread be stopped on EOF, if Recycle is false? (default is false)
Yes
+
Sharing mode
+
    +
  • All threads - (the default) the file is shared between all the threads.
  • +
  • Current thread group - each file is opened once for each thread group in which the element appears
  • +
  • Current thread - each file is opened separately for each thread
  • +
  • Identifier - all threads sharing the same identifier share the same file. + So for example if you have 4 thread groups, you could use a common id for two or more of the groups + to share the file between them. + Or you could use the thread number to share the file between the same thread numbers in different thread groups. +
  • +
+
Yes
+
+
+ +

FTP Request Defaults

Screenshot for FTP Request Defaults
+
+
+ +

DNS Cache Manager

Screenshot for DNS Cache Manager
+
DNS Cache Manager is designed for using in the root of Thread Group or Test Plan. Do not place it as child element of particular HTTP Sampler +
+
DNS Cache Manager working only with the HTTP request using HTTPClient4 implementation.
+

The DNS Cache Manager element allows to test applications, which have several servers behind load balancers (CDN, etc), + when user receives content from different IP's. By default JMeter uses JVM DNS cache. That's why + only one server from the cluster receives load. DNS Cache Manager resolves name for each thread separately each iteration and + saves results of resolving to its internal DNS Cache, which independent from both JVM and OS DNS caches. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Clear cache each Iteration
If selected, DNS cache of every Thread is cleared each time new iteration is started.
No
+
Use system DNS resolver
System DNS resolver will be used. For correct work edit + $JAVA_HOME/jre/lib/security/java.security and add
networkaddress.cache.ttl=0
+
N/A
+
Use custom DNS resolver
Custom DNS resolver(from dnsjava library) will be used.
N/A
+
Hostname or IP address
List of DNS servers to use. If empty, network configuration DNS will used.
No
+
Add Button
Add an entry to the DNS servers table.
N/A
+
Delete Button
Delete the currently selected table entry.
N/A
+
+
+ +

HTTP Authorization Manager

Screenshot for HTTP Authorization Manager
+
If there is more than one Authorization Manager in the scope of a Sampler, +there is currently no way to specify which one is to be used.
+ +
+

The Authorization Manager lets you specify one or more user logins for web pages that are +restricted using server authentication. You see this type of authentication when you use +your browser to access a restricted page, and your browser displays a login dialog box. JMeter +transmits the login information when it encounters this type of page.

+

+The Authorisation headers may not be shown in the Tree View Listener "Request" tab. +The Java implementation does pre-emptive authentication, but it does not +return the Authorization header when JMeter fetches the headers. +The Commons HttpClient (3.1) implementation defaults to pre-emptive and the header will be shown. +The HttpComponents implementation does not do pre-emptive auth +(it is supported by the library but JMeter does not enable it) +

+

+In versions of JMeter after 2.2, the HttpClient sampler defaults to pre-emptive authentication +if the setting has not been defined. To disable this, set the values as below, in which case +authentication will only be performed in response to a challenge. +

+jmeter.properties:
+httpclient.parameters.file=httpclient.parameters
+
+httpclient.parameters:
+http.authentication.preemptive$Boolean=false
+
+Note: the above settings only apply to the HttpClient sampler (and the SOAP samplers, which use Httpclient). +

+
+When looking for a match against a URL, JMeter checks each entry in turn, and stops when it finds the first match. +Thus the most specific URLs should appear first in the list, followed by less specific ones. +Duplicate URLs will be ignored. +If you want to use different usernames/passwords for different threads, you can use variables. +These can be set up using a CSV Data Set Config Element (for example). +
+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Clear auth on each iteration ?
Used by Kerberos authentication. If checked, authentication will be done on each iteration of Main Thread Group loop even if it has already been done in a previous one. + This is usually useful if each main thread group iteration represents behaviour of one Virtual User. +
Yes
+
Base URL
A partial or complete URL that matches one or more HTTP Request URLs. As an example, +say you specify a Base URL of "http://jmeter.apache.org/restricted/" with a username of "jmeter" and +a password of "jmeter". If you send an HTTP request to the URL +"http://jmeter.apache.org/restricted/ant/myPage.html", the Authorization Manager sends the login +information for the user named, "jmeter".
Yes
+
Username
The username to authorize.
Yes
+
Password
The password for the user. (N.B. this is stored unencrypted in the test plan)
Yes
+
Domain
The domain to use for NTLM.
No
+
Realm
The realm to use for NTLM.
No
+
Mechanism
Type of authentication to perform. JMeter can perform different types of authentications based on used Http Samplers: + + + + + +
SamplerAuthentications
JavaBASIC
HttpClient 3.1BASIC, DIGEST
HttpClient 4BASIC, DIGEST and Kerberos
+
No
+
+
+The Realm only applies to the HttpClient sampler. +In JMeter 2.2, the domain and realm did not have separate columns, and were encoded as part of +the user name in the form: [domain\]username[@realm]. +This was an experimental feature and has been removed. +
+
+Kerberos Configuration: +

To configure Kerberos you need to setup at least two JVM system properties: +

    +
  • -Djava.security.krb5.conf=krb5.conf
  • +
  • -Djava.security.auth.login.config=jaas.conf
  • +
+You can also configure those two properties in the file bin/system.properties. +Look at the two sample configuration files (krb5.conf and jaas.conf) located in the jmeter bin folder +for references to more documentation, and tweak them to match your Kerberos configuration. +

+

+When generating a SPN for Kerberos SPNEGO authentication IE and Firefox will omit the port number +from the url. Chrome has an option (--enable-auth-negotiate-port) to include the port +number if it differs from the standard ones (80 and 443). That behavior +can be emulated by setting the following jmeter property as below. +

+

+In jmeter.properties or user.properties, set: +

    +
  • kerberos.spnego.strip_port=false
  • +
+

+
+Controls: +
    +
  • Add Button - Add an entry to the authorization table.
  • +
  • Delete Button - Delete the currently selected table entry.
  • +
  • Load Button - Load a previously saved authorization table and add the entries to the existing +authorization table entries.
  • +
  • Save As Button - Save the current authorization table to a file.
  • +
+ +
When you save the Test Plan, JMeter automatically saves all of the authorization +table entries - including any passwords, which are not encrypted.
+ +
Authorization Example
+ +

Download this example. In this example, we created a Test Plan on a local server that sends three HTTP requests, two requiring a login and the +other is open to everyone. See figure 10 to see the makeup of our Test Plan. On our server, we have a restricted +directory named, "secret", which contains two files, "index.html" and "index2.html". We created a login id named, "kevin", +which has a password of "spot". So, in our Authorization Manager, we created an entry for the restricted directory and +a username and password (see figure 11). The two HTTP requests named "SecretPage1" and "SecretPage2" make requests +to "/secret/index.html" and "/secret/index2.html". The other HTTP request, named "NoSecretPage" makes a request to +"/index.html".

+ +
Figure 10 - Test Plan
Figure 10 - Test Plan
+
Figure 11 - Authorization Manager Control Panel
Figure 11 - Authorization Manager Control Panel
+ +

When we run the Test Plan, JMeter looks in the Authorization table for the URL it is requesting. If the Base URL matches +the URL, then JMeter passes this information along with the request.

+ +
You can download the Test Plan, but since it is built as a test for our local server, you will not +be able to run it. However, you can use it as a reference in constructing your own Test Plan.
+
+ +
+ +

HTTP Cache Manager

Screenshot for HTTP Cache Manager
+
+

+The HTTP Cache Manager is used to add caching functionality to HTTP requests within its scope to simulate browser cache feature. +Each Virtual User thread has its own Cache. By default, Cache Manager will store up to 5000 items in cache per Virtual User thread, using LRU algorithm. +Use property "maxSize" to modify this value. Note that the more you increase this value the more HTTP Cache Manager will consume memory, so be sure to adapt -Xmx option accordingly. +

+

+If a sample is successful (i.e. has response code 2xx) then the Last-Modified and Etag (and Expired if relevant) values are saved for the URL. +Before executing the next sample, the sampler checks to see if there is an entry in the cache, +and if so, the If-Last-Modified and If-None-Match conditional headers are set for the request. +

+

+Additionally, if the "Use Cache-Control/Expires header" option is selected, then the Cache-Control/Expires value is checked against the current time. +If the request is a GET request, and the timestamp is in the future, then the sampler returns immediately, +without requesting the URL from the remote server. This is intended to emulate browser behaviour. +Note that if Cache-Control header is "no-cache", the response will be stored in cache as pre-expired, +so will generate a conditional GET request. +If Cache-Control has any other value, +the "max-age" expiry option is processed to compute entry lifetime, if missing then expire header will be used, if also missing entry will be cached +as specified in RFC 2616 section 13.2.4. using Last-Modified time and response Date. +

+

+If the requested document has not changed since it was cached, then the response body will be empty. +Likewise if the Expires date is in the future. +This may cause problems for Assertions. +

+ +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Clear cache each iteration
+ If selected, then the cache is cleared at the start of the thread. +
Yes
+
Use Cache Control/Expires header when processing GET requests
See description above.
Yes
+
Max Number of elements in cache
See description above.
Yes
+
+
+ +
Screenshot for HTTP Cookie Manager
+ +
If there is more than one Cookie Manager in the scope of a Sampler, +there is currently no way to specify which one is to be used. +Also, a cookie stored in one cookie manager is not available to any other manager, +so use multiple Cookie Managers with care.
+ +

The Cookie Manager element has two functions:
+First, it stores and sends cookies just like a web browser. If you have an HTTP Request and +the response contains a cookie, the Cookie Manager automatically stores that cookie and will +use it for all future requests to that particular web site. Each JMeter thread has its own +"cookie storage area". So, if you are testing a web site that uses a cookie for storing +session information, each JMeter thread will have its own session. +Note that such cookies do not appear on the Cookie Manager display, but they can be seen using +the View Results Tree Listener. +

+

+JMeter version 2.3.2 and earlier did not check that received cookies were valid for the URL. +This meant that cross-domain cookies were stored, and might be used later. +This has been fixed in later versions. +To revert to the earlier behaviour, define the JMeter property "CookieManager.check.cookies=false". +

+

+Received Cookies can be stored as JMeter thread variables +(versions of JMeter after 2.3.2 no longer do this by default). +To save cookies as variables, define the property "CookieManager.save.cookies=true". +Also, cookies names are prefixed with "COOKIE_" before they are stored (this avoids accidental corruption of local variables) +To revert to the original behaviour, define the property "CookieManager.name.prefix= " (one or more spaces). +If enabled, the value of a cookie with the name TEST can be referred to as ${COOKIE_TEST}. +

+

Second, you can manually add a cookie to the Cookie Manager. However, if you do this, +the cookie will be shared by all JMeter threads.

+

Note that such Cookies are created with an Expiration time far in the future

+

+Since version 2.0.3, cookies with null values are ignored by default. +This can be changed by setting the JMeter property: CookieManager.delete_null_cookies=false. +Note that this also applies to manually defined cookies - any such cookies will be removed from the display when it is updated. +Note also that the cookie name must be unique - if a second cookie is defined with the same name, it will replace the first. +

+
+
Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Clear Cookies each Iteration
If selected, all server-defined cookies are cleared each time the main Thread Group loop is executed. + In JMeter versions after 2.3, any cookies defined in the GUI are not cleared.
Yes
+
Cookie Policy
The cookie policy that will be used to manage the cookies. + "compatibility" is the default, and should work in most cases. + See http://hc.apache.org/httpclient-3.x/cookies.html and + http://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/cookie/CookiePolicy.html + [Note: "ignoreCookies" is equivalent to omitting the CookieManager.] +
Yes
+
Implementation
HC3CookieHandler (HttpClient 3.1 API) or HC4CookieHandler (HttpClient 4 API). + Default is HC3CookieHandler. +
+ [Note: If you have a website to test with IPv6 address, choose HC4CookieHandler (IPv6 compliant)]
Yes
+
User-Defined Cookies
This + gives you the opportunity to use hardcoded cookies that will be used by all threads during the test execution. +
+ The "domain" is the hostname of the server (without http://); the port is currently ignored. +
No (discouraged, unless you know what you're doing)
+
Add Button
Add an entry to the cookie table.
N/A
+
Delete Button
Delete the currently selected table entry.
N/A
+
Load Button
Load a previously saved cookie table and add the entries to the existing +cookie table entries.
N/A
+
Save As Button
+ Save the current cookie table to a file (does not save any cookies extracted from HTTP Responses). +
N/A
+
+ +
+ +

HTTP Request Defaults

Screenshot for HTTP Request Defaults
+

This element lets you set default values that your HTTP Request controllers use. For example, if you are +creating a Test Plan with 25 HTTP Request controllers and all of the requests are being sent to the same server, +you could add a single HTTP Request Defaults element with the "Server Name or IP" field filled in. Then, when +you add the 25 HTTP Request controllers, leave the "Server Name or IP" field empty. The controllers will inherit +this field value from the HTTP Request Defaults element.

+
+In JMeter 2.2 and earlier, port 80 was treated specially - it was ignored if the sampler used the https protocol. +JMeter 2.3 and later treat all port values equally; a sampler that does not specify a port will use the HTTP Request Defaults port, if one is provided. +
+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Server
Domain name or IP address of the web server. e.g. www.example.com. [Do not include the http:// prefix.
No
+
Port
Port the web server is listening to.
No
+
Connect Timeout
Connection Timeout. Number of milliseconds to wait for a connection to open.
No
+
Response Timeout
Response Timeout. Number of milliseconds to wait for a response.
No
+
Implementation
Java, HttpClient3.1, HttpClient4. + If not specified the default depends on the value of the JMeter property + jmeter.httpsampler, failing that, the Java implementation is used.
No
+
Protocol
HTTP or HTTPS.
No
+
Method
HTTP GET or HTTP POST.
No
+
Path
The path to resource (for example, /servlets/myServlet). If the + resource requires query string parameters, add them below in the "Send Parameters With the Request" section. + Note that the path is the default for the full path, not a prefix to be applied to paths + specified on the HTTP Request screens. +
No
+
Send Parameters With the Request
The query string will + be generated from the list of parameters you provide. Each parameter has a name and + value. The query string will be generated in the correct fashion, depending on + the choice of "Method" you made (ie if you chose GET, the query string will be + appended to the URL, if POST, then it will be sent separately). Also, if you are + sending a file using a multipart form, the query string will be created using the + multipart form specifications.
No
+
Server (proxy)
Hostname or IP address of a proxy server to perform request. [Do not include the http:// prefix.]
No
+
Port
Port the proxy server is listening to.
No, unless proxy hostname is specified
+
Username
(Optional) username for proxy server.
No
+
Password
(Optional) password for proxy server. (N.B. this is stored unencrypted in the test plan)
No
+
Retrieve All Embedded Resources from HTML Files
Tell JMeter to parse the HTML file +and send HTTP/HTTPS requests for all images, Java applets, JavaScript files, CSSs, etc. referenced in the file. +
No
+
Use concurrent pool
Use a pool of concurrent connections to get embedded resources.
No
+
Size
Pool size for concurrent connections used to get embedded resources.
No
+
Embedded URLs must match:
+ If present, this must be a regular expression that is used to match against any embedded URLs found. + So if you only want to download embedded resources from http://example.com/, use the expression: + http://example\.com/.* +
No
+
+
+Note: radio buttons only have two states - on or off. +This makes it impossible to override settings consistently +- does off mean off, or does it mean use the current default? +JMeter uses the latter (otherwise defaults would not work at all). +So if the button is off, then a later element can set it on, +but if the button is on, a later element cannot set it off. +
+
+ +

HTTP Header Manager

Screenshot for HTTP Header Manager
+
+

The Header Manager lets you add or override HTTP request headers.

+

+Versions of JMeter up to 2.3.2 supported only one Header Manager per sampler; +if there were more in scope, then only the last one would be used. +

+

+JMeter now supports multiple Header Managers. The header entries are merged to form the list for the sampler. +If an entry to be merged matches an existing header name, it replaces the previous entry, +unless the entry value is empty, in which case any existing entry is removed. +This allows one to set up a default set of headers, and apply adjustments to particular samplers. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Name (Header)
Name of the request header. + Two common request headers you may want to experiment with +are "User-Agent" and "Referer".
No (You should have at least one, however)
+
Value
Request header value.
No (You should have at least one, however)
+
Add Button
Add an entry to the header table.
N/A
+
Delete Button
Delete the currently selected table entry.
N/A
+
Load Button
Load a previously saved header table and add the entries to the existing +header table entries.
N/A
+
Save As Button
Save the current header table to a file.
N/A
+
+ +
Header Manager example
+ +

Download this example. In this example, we created a Test Plan +that tells JMeter to override the default "User-Agent" request header and use a particular Internet Explorer agent string +instead. (see figures 12 and 13).

+ +
Figure 12 - Test Plan
Figure 12 - Test Plan
+
Figure 13 - Header Manager Control Panel
Figure 13 - Header Manager Control Panel
+
+ +
+ +

Java Request Defaults

Screenshot for Java Request Defaults
+

The Java Request Defaults component lets you set default values for Java testing. See the Java Request.

+
+ +
+ +

JDBC Connection Configuration

Screenshot for JDBC Connection Configuration
+
Creates a database connection (used by JDBC RequestSampler) + from the supplied JDBC Connection settings. The connection may be optionally pooled between threads. + Otherwise each thread gets its own connection. + The connection configuration name is used by the JDBC Sampler to select the appropriate + connection. +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for the connection configuration that is shown in the tree.
No
+
Variable Name
The name of the variable the connection is tied to. + Multiple connections can be used, each tied to a different variable, allowing JDBC Samplers + to select the appropriate connection. + Each name must be different. If there are two configuration elements using the same name, + only one will be saved. JMeter versions after 2.3 log a message if a duplicate name is detected. +
Yes
+
Max Number of Connections
+ Maximum number of connections allowed in the pool. + In most cases, set this to zero (0). + This means that each thread will get its own pool with a single connection in it, i.e. + the connections are not shared betweeen threads. +
+ If you really want to use shared pooling (why?), then set the max count to the same as the number of threads + to ensure threads don't wait on each other. +
Yes
+
Pool timeout
Pool throws an error if the timeout period is exceeded in the + process of trying to retrieve a connection
Yes
+
Idle Cleanup Interval (ms)
This is used to specify how long idle connections will be maintained in the pool before being closed. For a complete explanation on how this works, see ResourceLimitingPool.trim() (Defaults to "60000", 1 minute)
Yes
+
Auto Commit
Turn auto commit on or off for the connections.
Yes
+
Keep-alive
The keep-alive is used enable a mechanism to monitor the health of connections. If a connection has not been used for Max Connection Age (ms) then before returning the connection from a call to getConnection(), the connection is first used to ping the database to make sure that it is still alive. + Setting the age allows the 5 second age to be overridden. Validation Query will be used to test it.
Yes
+
Max Connection Age (ms)
Controls the age mentionned in "Keep-Alive" property. It means connections not used for more than Max Connection Age will be tested
Yes
+
Validation Query
A simple query used to determine if the database is still + responding.
Yes
+
Database URL
JDBC Connection string for the database.
Yes
+
JDBC Driver class
Fully qualified name of driver class. (Must be in + JMeter's classpath - easiest to copy .jar file into JMeter's /lib directory).
Yes
+
Username
Name of user to connect as.
No
+
Password
Password to connect with. (N.B. this is stored unencrypted in the test plan)
No
+
+

Different databases and JDBC drivers require different JDBC settings. +The Database URL and JDBC Driver class are defined by the provider of the JDBC implementation.

+

Some possible settings are shown below. Please check the exact details in the JDBC driver documentation.

+ +

+If JMeter reports No suitable driver, then this could mean either: +

    +
  • The driver class was not found. In this case, there will be a log message such as DataSourceElement: Could not load driver: {classname} java.lang.ClassNotFoundException: {classname}
  • +
  • The driver class was found, but the class does not support the connection string. This could be because of a syntax error in the connection string, or because the the wrong classname was used.
  • +
+If the database server is not running or is not accessible, then JMeter will report a java.net.ConnectException. +

+ + + + + + + + +
DatabaseDriver classDatabase URL
MySQLcom.mysql.jdbc.Driverjdbc:mysql://host[:port]/dbname
PostgreSQLorg.postgresql.Driverjdbc:postgresql:{dbname}
Oracleoracle.jdbc.OracleDriverjdbc:oracle:thin:@//host:port/service +OR
jdbc:oracle:thin:@(description=(address=(host={mc-name})(protocol=tcp)(port={port-no}))(connect_data=(sid={sid})))
Ingres (2006)ingres.jdbc.IngresDriverjdbc:ingres://host:port/db[;attr=value]
SQL Server (MS JDBC driver)com.microsoft.sqlserver.jdbc.SQLServerDriverjdbc:sqlserver://host:port;DatabaseName=dbname
Apache Derbyorg.apache.derby.jdbc.ClientDriverjdbc:derby://server[:port]/databaseName[;URLAttributes=value[;...]]
+
The above may not be correct - please check the relevant JDBC driver documentation.
+
+ + +

Keystore Configuration

Screenshot for Keystore Configuration
+

The Keystore Config Element lets you configure how Keystore will be loaded and which keys it will use. +This component is typically used in HTTPS scenarios where you don't want to take into account keystore initialization into account in response time.

+

To use this element, you need to setup first a Java Key Store with the client certificates you want to test, to do that: +

    +
  1. Create your certificates either with Java keytool utility or through your PKI
  2. +
  3. If created by PKI, import your keys in Java Key Store by converting them to a format acceptable by JKS
  4. +
  5. Then reference the keystore file through the 2 JVM properties (or add them in system.properties): +
      +
    • -Djavax.net.ssl.keyStore=path_to_keystore
    • +
    • -Djavax.net.ssl.keyStorePassword=password_of_keystore
    • +
    +
  6. +
+

+
+ +preload.shortDescription=Preload Keystore before test. Setting is to true is usually the best option. +startIndex.displayName=Alias Start index (0-based) +startIndex.shortDescription=First index of Alias in Keystore +endIndex.displayName=Alias End index (0-based) +endIndex.shortDescription=Last index of Alias in Keystore. When using Variable name ensure it is large enough so that all keys are loaded at startup. +clientCertAliasVarName.displayName=Variable name holding certificate alias +clientCertAliasVarName.shortDescription=Variable name that will contain the alias to use for Cert authentication. Var content can come from CSV Data Set. + +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Preload
Wether or not to preload Keystore. Setting is to true is usually the best option.
Yes
+
Variable name holding certificate alias
Variable name that will contain the alias to use for authentication by client certificate. Variable value will be filled from CSV Data Set for example. In the screenshot, "certificat_ssl" will also be a variable in CSV Data Set.
False
+
Alias Start Index
The index of the first key to use in Keystore, 0-based.
Yes
+
Alias End Index
The index of the last key to use in Keystore, 0-based. When using "Variable name holding certificate alias" ensure it is large enough so that all keys are loaded at startup.
Yes
+
+
+To make JMeter use more than one certificate you need to ensure that: +
    +
  • https.use.cached.ssl.context=false is set in jmeter.properties or user.properties
  • +
  • You use either HTTPClient 3.1 or 4 implementations for HTTP Request
  • +
+
+
+ +

Login Config Element

Screenshot for Login Config Element
+

The Login Config Element lets you add or override username and password settings in samplers that use username and password as part of their setup.

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Username
The default username to use.
No
+
Password
The default password to use. (N.B. this is stored unencrypted in the test plan)
No
+
+ +
+ +

LDAP Request Defaults

Screenshot for LDAP Request Defaults
+

The LDAP Request Defaults component lets you set default values for LDAP testing. See the LDAP Request.

+
+ +
+ +

LDAP Extended Request Defaults

Screenshot for LDAP Extended Request Defaults
+

The LDAP Extended Request Defaults component lets you set default values for extended LDAP testing. See the LDAP Extended Request.

+
+ +
+ +

TCP Sampler Config

Screenshot for TCP Sampler Config
+
+

+ The TCP Sampler Config provides default data for the TCP Sampler +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
TCPClient classname
Name of the TCPClient class. Defaults to the property tcp.handler, failing that TCPClientImpl.
No
+
ServerName or IP
Name or IP of TCP server
+
Port Number
Port to be used
+
Re-use connection
If selected, the connection is kept open. Otherwise it is closed when the data has been read.
Yes
+
Close connection
If selected, the connection will be closed after running the sampler.
Yes
+
SO_LINGER
Enable/disable SO_LINGER with the specified linger time in seconds when a socket is created. If you set "SO_LINGER" value as 0, you may prevent large numbers of sockets sitting around with a TIME_WAIT status.
No
+
End of line(EOL) byte value
Byte value for end of line, set this to a value outside the range -128 to +127 to skip eol checking. You may set this in jmeter.properties file as well with eolByte property. If you set this in TCP Sampler Config and in jmeter.properties file at the same time, the setting value in the TCP Sampler Config will be used.
No
+
Connect Timeout
Connect Timeout (milliseconds, 0 disables).
No
+
Response Timeout
Response Timeout (milliseconds, 0 disables).
No
+
Set Nodelay
Should the nodelay property be set?
+
Text to Send
Text to be sent
+
+
+ +

User Defined Variables

Screenshot for User Defined Variables
+

The User Defined Variables element lets you define an initial set of variables, just as in the Test Plan. + +Note that all the UDV elements in a test plan - no matter where they are - are processed at the start. + +So you cannot reference variables which are defined as part of a test run, e.g. in a Post-Processor. +

+

+ +UDVs should not be used with functions that generate different results each time they are called. +Only the result of the first function call will be saved in the variable. + +However, UDVs can be used with functions such as __P(), for example: +

+HOST      ${__P(host,localhost)} 
+
+which would define the variable "HOST" to have the value of the JMeter property "host", defaulting to "localhost" if not defined. +

+

+For defining variables during a test run, see User Parameters. +UDVs are processed in the order they appear in the Plan, from top to bottom. +

+

+For simplicity, it is suggested that UDVs are placed only at the start of a Thread Group +(or perhaps under the Test Plan itself). +

+

+Once the Test Plan and all UDVs have been processed, the resulting set of variables is +copied to each thread to provide the initial set of variables. +

+

+If a runtime element such as a User Parameters Pre-Processor or Regular Expression Extractor defines a variable +with the same name as one of the UDV variables, then this will replace the initial value, and all other test +elements in the thread will see the updated value. +

+
+
+If you have more than one Thread Group, make sure you use different names for different values, as UDVs are shared between Thread Groups. +Also, the variables are not available for use until after the element has been processed, +so you cannot reference variables that are defined in the same element. +You can reference variables defined in earlier UDVs or on the Test Plan. +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
User Defined Variables
Variable name/value pairs. The string under the "Name" + column is what you'll need to place inside the brackets in ${...} constructs to use the variables later on. The + whole ${...} will then be replaced by the string in the "Value" column.
+
+
+ +

Random Variable

Screenshot for Random Variable
+
+

+The Random Variable Config Element is used to generate random numeric strings and store them in variable for use later. +It's simpler than using User Defined Variables together with the __Random() function. +

+

+The output variable is constructed by using the random number generator, +and then the resulting number is formatted using the format string. +The number is calculated using the formula minimum+Random.nextInt(maximum-minimum+1). +Random.nextInt() requires a positive integer. +This means that maximum-minimum - i.e. the range - must be less than 2147483647, +however the minimum and maximum values can be any long values so long as the range is OK. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
Yes
+
Variable Name
The name of the variable in which to store the random string.
Yes
+
Format String
The java.text.DecimalFormat format string to be used. + For example "000" which will generate numbers with at least 3 digits, + or "USER_000" which will generate output of the form USER_nnn. + If not specified, the default is to generate the number using Long.toString()
No
+
Minimum Value
The minimum value (long) of the generated random number.
Yes
+
Maximum Value
The maximum value (long) of the generated random number.
Yes
+
Random Seed
The seed for the random number generator. Default is the current time in milliseconds. + If you use the same seed value with Per Thread set to true, you will get the same value for earch Thread as per + Random class. +
No
+
Per Thread(User)?
If False, the generator is shared between all threads in the thread group. + If True, then each thread has its own random generator.
Yes
+
+ +
+ +

Counter

Screenshot for Counter
+

Allows the user to create a counter that can be referenced anywhere +in the Thread Group. The counter config lets the user configure a starting point, a maximum, +and the increment. The counter will loop from the start to the max, and then start over +with the start, continuing on like that until the test is ended.

+

From version 2.1.2, the counter now uses a long to store the value, so the range is from -2^63 to 2^63-1.

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Start
The starting number for the counter. The counter will equal this + number during the first iteration.
Yes
+
Increment
How much to increment the counter by after each + iteration.
Yes
+
Maximum
If the counter exceeds the maximum, then it is reset to the Start value. + For versions after 2.2 the default is Long.MAX_VALUE (previously it was 0). +
No
+
Format
Optional format, e.g. 000 will format as 001, 002 etc. + This is passed to DecimalFormat, so any valid formats can be used. + If there is a problem interpreting the format, then it is ignored. + [The default format is generated using Long.toString()] +
No
+
Reference Name
This controls how you refer to this value in other elements. Syntax is + as in user-defined values: $(reference_name}.
Yes
+
Track Counter Independently for each User
In other words, is this a global counter, or does each user get their + own counter? If unchecked, the counter is global (ie, user #1 will get value "1", and user #2 will get value "2" on + the first iteration). If checked, each user has an independent counter.
No
+
Reset counter on each Thread Group Iteration
This option is only available when counter is tracked per User, if checked, + counter will be reset to Start value on each Thread Group iteration. This can be useful when Counter is inside a Loop Controller.
No
+
+
+ +

Simple Config Element

Screenshot for Simple Config Element
+

The Simple Config Element lets you add or override arbitrary values in samplers. You can choose the name of the value +and the value itself. Although some adventurous users might find a use for this element, it's here primarily for developers as a basic +GUI that they can use while developing new JMeter components.

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
Yes
+
Parameter Name
The name of each parameter. These values are internal to JMeter's workings and + are not generally documented. Only those familiar with the code will know these values.
Yes
+
Parameter Value
The value to apply to that parameter.
Yes
+
+ +
+ + +

MongoDB Source Config

Screenshot for MongoDB Source Config
+
Creates a MongoDB connection (used by MongoDB ScriptSampler) + from the supplied Connection settings. Each thread gets its own connection. + The connection configuration name is used by the JDBC Sampler to select the appropriate + connection. + You can then access com.mongodb.DB object in Beanshell or JSR223 Test Elements through the element MongoDBHolder + using this code
+ +
+import com.mongodb.DB;
+import org.apache.jmeter.protocol.mongodb.config.MongoDBHolder;
+DB db = MongoDBHolder.getDBFromSource("value of property MongoDB Source",
+            "value of property Database Name");
+...
+    
+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for the connection configuration that is shown in the tree.
No
+
Server Address List
Mongo DB Servers
Yes
+
MongoDB Source
The name of the variable the connection is tied to. +
Each name must be different. If there are two configuration elements using the same name, only one will be saved.
+
Yes
+ +
Keep Trying
+ If true, the driver will keep trying to connect to the same server in case that the socket cannot be established.
+ There is maximum amount of time to keep retrying, which is 15s by default.
This can be useful to avoid some exceptions being thrown when a server is down temporarily by blocking the operations. +
It also can be useful to smooth the transition to a new master (so that a new master is elected within the retry time).
+ Note that when using this flag\: +
- for a replica set, the driver will trying to connect to the old master for that time, instead of failing over to the new one right away +
- this does not prevent exception from being thrown in read/write operations on the socket, which must be handled by application.
+ Even if this flag is false, the driver already has mechanisms to automatically recreate broken connections and retry the read operations.
+ Default is false. +
No
+
Maximum connections per host
No
+
Connection timeout
+ The connection timeout in milliseconds.
It is used solely when establishing a new connection Socket.connect(java.net.SocketAddress, int)
Default is 0 and means no timeout. +
No
+
Maximum retry time
+ The maximum amount of time in MS to spend retrying to open connection to the same server.
Default is 0, which means to use the default 15s if autoConnectRetry is on. +
No
+
Maximum wait time
+ The maximum wait time in ms that a thread may wait for a connection to become available.
Default is 120,000. +
No
+
Socket timeout
+ The socket timeout in milliseconds It is used for I/O socket read and write operations Socket.setSoTimeout(int)
Default is 0 and means no timeout. +
No
+
Socket keep alive
+ This flag controls the socket keep alive feature that keeps a connection alive through firewalls Socket.setKeepAlive(boolean)
+ Default is false. +
No
+
ThreadsAllowedToBlockForConnectionMultiplier
+ This multiplier, multiplied with the connectionsPerHost setting, gives the maximum number of threads that may be waiting for a connection to become available from the pool.
+ All further threads will get an exception right away.
+ For example if connectionsPerHost is 10 and threadsAllowedToBlockForConnectionMultiplier is 5, then up to 50 threads can wait for a connection.
+ Default is 5. +
No
+
Write Concern : Safe
+ If true the driver will use a WriteConcern of WriteConcern.SAFE for all operations.
+ If w, wtimeout, fsync or j are specified, this setting is ignored.
+ Default is false. +
No
+
Write Concern : Fsync
+ The fsync value of the global WriteConcern.
+ Default is false. +
No
+
Write Concern : Wait for Journal
+ The j value of the global WriteConcern.
+ Default is false. +
No
+
Write Concern : Wait for servers
+ The w value of the global WriteConcern.
Default is 0. +
No
+
Write Concern : Wait timeout
+ The wtimeout value of the global WriteConcern.
Default is 0. +
No
+
Write Concern : Continue on error
+ If batch inserts should continue after the first error +
No
+
+
+ +^ + +

18.5 Assertions

+
+

+ Assertions are used to perform additional checks on samplers, and are processed after every sampler + in the same scope. + To ensure that an Assertion is applied only to a particular sampler, add it as a child of the sampler. +

+

+ Note: Unless documented otherwise, Assertions are not applied to sub-samples (child samples) - + only to the parent sample. + In the case of BSF and BeanShell Assertions, the script can retrieve sub-samples using the method + prev.getSubResults() which returns an array of SampleResults. + The array will be empty if there are none. +

+

+ Versions of JMeter after 2.3.2 include the option to apply certain assertions + to either the main sample, the sub-samples or both. + The default is to apply the assertion to the main sample only. + If the Assertion supports this option, then there will be an entry on the GUI which looks like the following: +

Assertion Scope
Assertion Scope
+ or the following +
Assertion Scope
Assertion Scope
+ If a sub-sampler fails and the main sample is successful, + then the main sample will be set to failed status and an Assertion Result will be added. + If the JMeter variable option is used, it is assumed to relate to the main sample, and + any failure will be applied to the main sample only. +

+
+ The variable JMeterThread.last_sample_ok is updated to + "true" or "false" after all assertions for a sampler have been run. +
+
+

Response Assertion

Screenshot for Response Assertion
+ +

The response assertion control panel lets you add pattern strings to be compared against various + fields of the response. + The pattern strings are: +

    +
  • Contains, Matches: Perl5-style regular expressions
  • +
  • Equals, Substring: plain text, case-sensitive
  • +
+

+

+ A summary of the pattern matching characters can be found at ORO Perl5 regular expressions. +

+

You can also choose whether the strings will be expected +to match the entire response, or if the response is only expected to contain the +pattern. You can attach multiple assertions to any controller for additional flexibility.

+

Note that the pattern string should not include the enclosing delimiters, + i.e. use Price: \d+ not /Price: \d+/. +

+

+ By default, the pattern is in multi-line mode, which means that the "." meta-character does not match newline. + In multi-line mode, "^" and "$" match the start or end of any line anywhere within the string + - not just the start and end of the entire string. Note that \s does match new-line. + Case is also significant. To override these settings, one can use the extended regular expression syntax. + For example: +

+
+
(?i)
ignore case
+
(?s)
treat target as single line, i.e. "." matches new-line
+
(?is)
both the above
+
+These can be used anywhere within the expression and remain in effect until overriden. e.g. +
+
(?i)apple(?-i) Pie
matches "ApPLe Pie", but not "ApPLe pIe"
+
(?s)Apple.+?Pie
matches Apple followed by Pie, which may be on a subsequent line.
+
Apple(?s).+?Pie
same as above, but it's probably clearer to use the (?s) at the start.
+
+ +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Apply to:
+ This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
    +
  • Main sample only - assertion only applies to the main sample
  • +
  • Sub-samples only - assertion only applies to the sub-samples
  • +
  • Main sample and sub-samples - assertion applies to both.
  • +
  • JMeter Variable - assertion is to be applied to the contents of the named variable
  • +
+
Yes
+
Response Field to Test
Instructs JMeter which field of the Response to test. +
    +
  • Text Response - the response text from the server, i.e. the body, excluding any HTTP headers.
  • +
  • Document (text) - the extract text from various type of documents via Apache Tika (see View Results Tree Document view section).
  • +
  • URL sampled
  • +
  • Response Code - e.g. 200
  • +
  • Response Message - e.g. OK
  • +
  • Response Headers, including Set-Cookie headers (if any)
  • +
+
Yes
+
Ignore status
Instructs JMeter to set the status to success initially. +

+ The overall success of the sample is determined by combining the result of the + assertion with the existing Response status. + When the Ignore Status checkbox is selected, the Response status is forced + to successful before evaluating the Assertion. +

+ HTTP Responses with statuses in the 4xx and 5xx ranges are normally + regarded as unsuccessful. + The "Ignore status" checkbox can be used to set the status successful before performing further checks. + Note that this will have the effect of clearing any previous assertion failures, + so make sure that this is only set on the first assertion. +
Yes
+
Pattern Matching Rules
Indicates how the text being tested + is checked against the pattern. +
    +
  • Contains - true if the text contains the regular expression pattern
  • +
  • Matches - true if the whole text matches the regular expression pattern
  • +
  • Equals - true if the whole text equals the pattern string (case-sensitive)
  • +
  • Substring - true if the text contains the pattern string (case-sensitive)
  • +
+ Equals and Substring patterns are plain strings, not regular expressions. + NOT may also be selected to invert the result of the check.
Yes
+
Patterns to Test
A list of patterns to + be tested. + Each pattern is tested separately. + If a pattern fails, then further patterns are not checked. + There is no difference between setting up + one Assertion with multiple patterns and setting up multiple Assertions with one + pattern each (assuming the other options are the same). + However, when the Ignore Status checkbox is selected, this has the effect of cancelling any + previous assertion failures - so make sure that the Ignore Status checkbox is only used on + the first Assertion. +
Yes
+
+

+ The pattern is a Perl5-style regular expression, but without the enclosing brackets. +

+
Assertion Examples
+
+
Figure 14 - Test Plan
Figure 14 - Test Plan
+
Figure 15 - Assertion Control Panel with Pattern
Figure 15 - Assertion Control Panel with Pattern
+
Figure 16 - Assertion Listener Results (Pass)
Figure 16 - Assertion Listener Results (Pass)
+
Figure 17 - Assertion Listener Results (Fail)
Figure 17 - Assertion Listener Results (Fail)
+
+
+ + +
+ +

Duration Assertion

Screenshot for Duration Assertion
+

The Duration Assertion tests that each response was received within a given amount +of time. Any response that takes longer than the given number of milliseconds (specified by the +user) is marked as a failed response.

+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Duration in Milliseconds
The maximum number of milliseconds + each response is allowed before being marked as failed.
Yes
+
+
+ +

Size Assertion

Screenshot for Size Assertion
+

The Size Assertion tests that each response contains the right number of bytes in it. You can specify that +the size be equal to, greater than, less than, or not equal to a given number of bytes.

+
Since JMeter 2.3RC3, an empty response is treated as being 0 bytes rather than reported as an error.
+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Apply to:
+ This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
    +
  • Main sample only - assertion only applies to the main sample
  • +
  • Sub-samples only - assertion only applies to the sub-samples
  • +
  • Main sample and sub-samples - assertion applies to both.
  • +
  • JMeter Variable - assertion is to be applied to the contents of the named variable
  • +
+
Yes
+
Size in bytes
The number of bytes to use in testing the size of the response (or value of the JMeter variable).
Yes
+
Type of Comparison
Whether to test that the response is equal to, greater than, less than, + or not equal to, the number of bytes specified.
Yes
+ +
+
+ +

XML Assertion

Screenshot for XML Assertion
+

The XML Assertion tests that the response data consists of a formally correct XML document. It does not +validate the XML based on a DTD or schema or do any further validation.

+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+ +
+
+ +

BeanShell Assertion

Screenshot for BeanShell Assertion
+

The BeanShell Assertion allows the user to perform assertion checking using a BeanShell script. +

+

+For full details on using BeanShell, please see the BeanShell website. +

+Note that a different Interpreter is used for each independent occurence of the assertion +in each thread in a test script, but the same Interpreter is used for subsequent invocations. +This means that variables persist across calls to the assertion. +

+

+All Assertions are called from the same thread as the sampler. +

+

+If the property "beanshell.assertion.init" is defined, it is passed to the Interpreter +as the name of a sourced file. This can be used to define common methods and variables. +There is a sample init file in the bin directory: BeanShellAssertion.bshrc +

+

+The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label
+
Reset bsh.Interpreter before each call
+ If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. +
Yes
+
Parameters
Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • bsh.args - String array containing parameters, split on white-space
  • +
No
+
Script file
A file containing the BeanShell script to run. This overrides the script. + The file name is stored in the script variable FileName
No
+
Script
The BeanShell script to run. The return value is ignored.
Yes (unless script file is provided)
+
+

There's a sample script you can try.

+

+Before invoking the script, some variables are set up in the BeanShell interpreter. +These are strings unless otherwise noted: +

    +
  • log - the Logger Object. (e.g.) log.warn("Message"[,Throwable])
  • +
  • SampleResult - the SampleResult Object; read-write
  • +
  • Response - the response Object; read-write
  • +
  • Failure - boolean; read-write; used to set the Assertion status
  • +
  • FailureMessage - String; read-write; used to set the Assertion message
  • +
  • ResponseData - the response body (byte [])
  • +
  • ResponseCode - e.g. 200
  • +
  • ResponseMessage - e.g. OK
  • +
  • ResponseHeaders - contains the HTTP headers
  • +
  • RequestHeaders - contains the HTTP headers sent to the server
  • +
  • SampleLabel
  • +
  • SamplerData - data that was sent to the server
  • +
  • ctx - JMeterContext
  • +
  • vars - JMeterVariables - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.putObject("OBJ1",new Object());
  • +
  • props - JMeterProperties (class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
+

+

The following methods of the Response object may be useful: +

    +
  • setStopThread(boolean)
  • +
  • setStopTest(boolean)
  • +
  • String getSampleLabel()
  • +
  • setSampleLabel(String)
  • +

+
+ +

MD5Hex Assertion

Screenshot for MD5Hex Assertion
+

The MD5Hex Assertion allows the user to check the MD5 hash of the response data.

+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
MD5 sum
32 hex digits representing the MD5 hash (case not significant)
Yes
+ +
+
+ +

HTML Assertion

Screenshot for HTML Assertion
+

The HTML Assertion allows the user to check the HTML syntax of the response data using JTidy.

+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
doctype
omit/auto/strict/loose
Yes
+
Format
HTML, XHTML or XML
Yes
+
Errors only
Only take note of errors?
Yes
+
Error threshold
Number of errors allowed before classing the response as failed
Yes
+
Warning threshold
Number of warnings allowed before classing the response as failed
Yes
+
Filename
Name of file to which report is written
No
+ +
+
+

XPath Assertion

Screenshot for XPath Assertion
+

The XPath Assertion tests a document for well formedness, has the option +of validating against a DTD, or putting the document through JTidy and testing for an +XPath. If that XPath exists, the Assertion is true. Using "/" will match any well-formed +document, and is the default XPath Expression. +The assertion also supports boolean expressions, such as "count(//*error)=2". +See http://www.w3.org/TR/xpath for more information +on XPath. +

+Some sample expressions: +
    +
  • //title[text()='Text to match'] - matches <text>Text to match</text> anywhere in the response
  • +
  • /title[text()='Text to match'] - matches <text>Text to match</text> at root level in the response
  • +
+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Use Tidy (tolerant parser)
Use Tidy, i.e. be tolerant of XML/HTML errors
Yes
+
Quiet
Sets the Tidy Quiet flag
If Tidy is selected
+
Report Errors
If a Tidy error occurs, then set the Assertion accordingly
If Tidy is selected
+
Show warnings
Sets the Tidy showWarnings option
If Tidy is selected
+
Use Namespaces
Should namespaces be honoured?
If Tidy is not selected
+
Validate XML
Check the document against its schema.
If Tidy is not selected
+
Ignore Whitespace
Ignore Element Whitespace.
If Tidy is not selected
+
Fetch External DTDs
If selected, external DTDs are fetched.
If Tidy is not selected
+
XPath Assertion
XPath to match in the document.
Yes
+
True if nothing matches
True if a XPath expression is not matched
No
+
+
+The non-tolerant parser can be quite slow, as it may need to download the DTD etc. +
+
+As a work-round for namespace limitations of the Xalan XPath parser implementation on which JMeter is based, +you can provide a Properties file which contains mappings for the namespace prefixes: +
    +
  • prefix1=Full Namespace 1
  • +
  • prefix2=Full Namespace 2
  • +
  • ...
  • +
+ +You reference this file in jmeter.properties file using the property: +
    +
  • xpath.namespace.config
  • +
+
+
+

XML Schema Assertion

Screenshot for XML Schema Assertion
+

The XML Schema Assertion allows the user to validate a response against an XML Schema.

+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
File Name
Specify XML Schema File Name
Yes
+
+
+ +

BSF Assertion

Screenshot for BSF Assertion
+
+

+The BSF Assertion allows BSF script code to be used to check the status of the previous sample. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Language
The BSF language to be used
Yes
+
Parameters
Parameters to pass to the script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • args - String array containing parameters, split on white-space
  • +
No
+
Script file
A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property
No
+
Script
The script to run.
Yes (unless script file is provided)
+
+

+The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

+

The following variables are set up for use by the script:

+
    +
  • log - (Logger) - can be used to write to the log file
  • +
  • Label - the String Label
  • +
  • Filename - the script file name (if any)
  • +
  • Parameters - the parameters (as a String)
  • +
  • args[] - the parameters as a String array (split on whitespace)
  • +
  • ctx - (JMeterContext) - gives access to the context
  • +
  • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
  • +
  • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • SampleResult, prev - (SampleResult) - gives access to the previous SampleResult (if any)
  • +
  • sampler - (Sampler)- gives access to the current sampler
  • +
  • OUT - System.out - e.g. OUT.println("message")
  • +
  • AssertionResult - the assertion result
  • +
+

+The script can check various aspects of the SampleResult. +If an error is detected, the script should use AssertionResult.setFailureMessage("message") and AssertionResult.setFailure(true). +

+

For futher details of all the methods available on each of the above variables, please check the Javadoc

+
+ +

JSR223 Assertion

+
+

+The JSR223 Assertion allows JSR223 script code to be used to check the status of the previous sample. +For details, see BSF Assertion. +

+
+
+ +

Compare Assertion

Screenshot for Compare Assertion
+
+
+Compare Assertion MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. +
+ +The Compare Assertion can be used to compare sample results within its scope. +Either the contents or the elapsed time can be compared, and the contents can be filtered before comparison. +The assertion comparisons can be seen in the Comparison Assertion Visualizer. +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Compare Content
Whether or not to compare the content (response data)
Yes
+
Compare Time
If the value is >=0, then check if the response time difference is no greater than the value. + I.e. if the value is 0, then the response times must be exactly equal.
Yes
+
Comparison Filters
Filters can be used to remove strings from the content comparison. + For example, if the page has a time-stamp, it might be matched with: "Time: \d\d:\d\d:\d\d" and replaced with a dummy fixed time "Time: HH:MM:SS". +
No
+
+
+ +

SMIME Assertion

Screenshot for SMIME Assertion
+
+The SMIME Assertion can be used to evaluate the sample results from the Mail Reader Sampler. +This assertion verifies if the body of a mime message is signed or not. The signature can also be verified against a specific signer certificate. +As this is a functionality that is not necessarily needed by most users, additional jars need to be downloaded and added to JMETER_HOME/lib :
+
    +
  • bcmail-xxx.jar (BouncyCastle SMIME/CMS)
  • +
  • bcprov-xxx.jar (BouncyCastle Provider)
  • +
+These need to be downloaded from BouncyCastle. +

+If using the Mail Reader Sampler, +please ensure that you select "Store the message using MIME (raw)" otherwise the Assertion won't be able to process the message correctly. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Verify Signature
If selected, the asertion will verify if it is a valid signature according to the parameters defined in the Signer Certificate box.
Yes
+
Message not signed
Whether or not to expect a signature in the message
Yes
+
Signer Cerificate
"No Check" means that it wil not perform signature verification. "Check values" is used to verify the signature against the inputs provided. And "Certificate file" will perform the verification against a specific certificate file.
Yes
+
Message Position
+ The Mail sampler can retrieve multiple messages in a single sample. + Use this field to specify which message will be checked. + Messages are numbered from 0, so 0 means the first message. + Negative numbers count from the LAST message; -1 means LAST, -2 means penultimate etc. +
Yes
+
+
+ +^ + +

18.6 Timers

+
+
+

+ Note that timers are processed before each sampler in the scope in which they are found; + if there are several timers in the same scope, all the timers will be processed before + each sampler. +
+ Timers are only processed in conjunction with a sampler. + A timer which is not in the same scope as a sampler will not be processed at all. +
+ To apply a timer to a single sampler, add the timer as a child element of the sampler. + The timer will be applied before the sampler is executed. + To apply a timer after a sampler, either add it to the next sampler, or add it as the + child of a Test Action Sampler. +

+
+

Constant Timer

Screenshot for Constant Timer
+
+

If you want to have each thread pause for the same amount of time between +requests, use this timer.

+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this timer that is shown in the tree.
No
+
Thread Delay
Number of milliseconds to pause.
Yes
+
+
+ +

Gaussian Random Timer

Screenshot for Gaussian Random Timer
+ +

This timer pauses each thread request for a random amount of time, with most +of the time intervals ocurring near a particular value. +The total delay is the sum of the Gaussian distributed value (with mean 0.0 and standard deviation 1.0) times +the deviation value you specify, and the offset value. +Another way to explain it, in Gaussian Random Timer, the variation around constant offset has a gaussian curve distribution. + +

+ + +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this timer that is shown in the tree
No
+
Deviation
Deviation in milliseconds.
Yes
+
Constant Delay Offset
Number of milliseconds to pause in addition +to the random delay.
Yes
+
+ +
+ +

Uniform Random Timer

Screenshot for Uniform Random Timer
+ +

This timer pauses each thread request for a random amount of time, with +each time interval having the same probability of occurring. The total delay +is the sum of the random value and the offset value.

+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this timer that is shown in the tree.
No
+
Random Delay Maximum
Maxium random number of milliseconds to +pause.
Yes
+
Constant Delay Offset
Number of milliseconds to pause in addition +to the random delay.
Yes
+
+ +
+ +

Constant Throughput Timer

Screenshot for Constant Throughput Timer
+ +

This timer introduces variable pauses, calculated to keep the total throughput (in terms of samples per minute) as close as possible to a give figure. Of course the throughput will be lower if the server is not capable of handling it, or if other timers or time-consuming test elements prevent it.

+

+N.B. although the Timer is called the Constant Throughput timer, the throughput value does not need to be constant. +It can be defined in terms of a variable or function call, and the value can be changed during a test. +The value can be changed in various ways: +

+
    +
  • using a counter variable
  • +
  • using a JavaScript or BeanShell function to provide a changing value
  • +
  • using the remote BeanShell server to change a JMeter property
  • +
+

See Best Practices for further details. +Note that the throughput value should not be changed too often during a test +- it will take a while for the new value to take effect. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this timer that is shown in the tree.
No
+
Target Throughput
Throughput we want the timer to try to generate.
Yes
+
Calculate Throughput based on
+
    +
  • this thread only - each thread will try to maintain the target throughput. The overall throughput will be proportional to the number of active threads.
  • +
  • all active threads in current thread group - the target throughput is divided amongst all the active threads in the group. + Each thread will delay as needed, based on when it last ran.
  • +
  • all active threads - the target throughput is divided amongst all the active threads in all Thread Groups. + Each thread will delay as needed, based on when it last ran. + In this case, each other Thread Group will need a Constant Throughput timer with the same settings.
  • +
  • all active threads in current thread group (shared) - as above, but each thread is delayed based on when any thread in the group last ran.
  • +
  • all active threads (shared) - as above; each thread is delayed based on when any thread last ran.
  • +
+
Yes
+

The shared and non-shared algorithms both aim to generate the desired thoughput, and will produce similar results. + The shared algorithm should generate a more accurate overall transaction rate. + The non-shared algortihm should generate a more even spread of transactions across threads.

+
+
+ +

Synchronizing Timer

Screenshot for Synchronizing Timer
+ +
+

+The purpose of the SyncTimer is to block threads until X number of threads have been blocked, and +then they are all released at once. A SyncTimer can thus create large instant loads at various +points of the test plan. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this timer that is shown in the tree.
No
+
Number of Simultaneous Users to Group by
Number of threads to release at once. Setting it to 0 is equivalent to setting it to Number of threads in Thread Group.
Yes
+
Timeout in milliseconds
If set to 0, Timer will wait for the number of threads to reach the value in "Number of Simultaneous Users to Group", if superior to 0, then timer will wait at max "Timeout in milliseconds" if number of Threads does not reach if ater the timeout interval the number of users waiting is not reached, timer will stop waiting. Defaults to 0
No
+
+
+If timeout in milliseconds is set to 0 and number of threads never reaches "Number of Simultaneous Users to Group by" then Test will pause infinitely. +Only a forced stop will stop it. Setting Timeout in milliseconds is an option to consider in this case. +
+
+Synchronizing timer blocks only within one JVM, so if using Distributed testing ensure you never set "Number of Simultaneous Users to Group by" to a value superior to the number of users +of its containing Thread group considering 1 injector only. +
+ +
+ +

BeanShell Timer

Screenshot for BeanShell Timer
+
+

+The BeanShell Timer can be used to generate a delay. +

+

+For full details on using BeanShell, please see the BeanShell website. +

+

+The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label
No
+
Reset bsh.Interpreter before each call
+ If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. +
Yes
+
Parameters
Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • bsh.args - String array containing parameters, split on white-space
  • +
+
No
+
Script file
+ A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The return value is used as the number of milliseconds to wait. +
No
+
Script
+ The BeanShell script. The return value is used as the number of milliseconds to wait. +
Yes (unless script file is provided)
+
+

Before invoking the script, some variables are set up in the BeanShell interpreter:

+
    +
  • log - (Logger) - can be used to write to the log file
  • +
  • ctx - (JMeterContext) - gives access to the context
  • +
  • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
  • +
  • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • prev - (SampleResult) - gives access to the previous SampleResult (if any)
  • +
+

For details of all the methods available on each of the above variables, please check the Javadoc

+

If the property beanshell.timer.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

+
+ + +

BSF Timer

Screenshot for BSF Timer
+
+

+The BSF Timer can be used to generate a delay using a BSF scripting language. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
ScriptLanguage
+ The scripting language to be used. +
Yes
+
Parameters
Parameters to pass to the script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • args - String array containing parameters, split on white-space
  • +
+
No
+
Script file
+ A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property + The return value is converted to a long integer and used as the number of milliseconds to wait. +
No
+
Script
+ The script. The return value is used as the number of milliseconds to wait. +
Yes (unless script file is provided)
+
+

Before invoking the script, some variables are set up in the script interpreter:

+
    +
  • log - (Logger) - can be used to write to the log file
  • +
  • ctx - (JMeterContext) - gives access to the context
  • +
  • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
  • +
  • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • sampler - the current Sampler
  • +
  • Label - the name of the Timer
  • +
  • Filename - the file name (if any)
  • +
  • OUT - System.out
  • +
+

For details of all the methods available on each of the above variables, please check the Javadoc

+
+ +

JSR223 Timer

+
+

+The JSR223 Timer can be used to generate a delay using a JSR223 scripting language, +For details, see BSF Timer. +

+
+
+ +

Poisson Random Timer

Screenshot for Poisson Random Timer
+ +

This timer pauses each thread request for a random amount of time, with most +of the time intervals ocurring near a particular value. The total delay is the +sum of the Poisson distributed value, and the offset value.

+ + +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this timer that is shown in the tree
No
+
Lambda
Lambda value in milliseconds.
Yes
+
Constant Delay Offset
Number of milliseconds to pause in addition +to the random delay.
Yes
+
+ +
+ +^ + +

18.7 Pre Processors

+
+
+ Preprocessors are used to modify the Samplers in their scope. +
+
+
Screenshot for HTML Link Parser
+
+

This modifier parses HTML response from the server and extracts +links and forms. A URL test sample that passes through this modifier will be examined to +see if it "matches" any of the links or forms extracted +from the immediately previous response. It would then replace the values in the URL +test sample with appropriate values from the matching link or form. Perl-type regular +expressions are used to find matches.

+
+
+Matches are performed using protocol, host, path and parameter names. +The target sampler cannot contain parameters that are not in the response links. +
+
+If using distributed testing, ensure you switch mode (see jmeter.properties) so that it's not a stripping one, see + Bug + 56376 +
+ +
Spidering Example
+

Consider a simple example: let's say you wanted JMeter to "spider" through your site, +hitting link after link parsed from the HTML returned from your server (this is not +actually the most useful thing to do, but it serves as a good example). You would create +a Simple Controller, and add the "HTML Link Parser" to it. Then, create an +HTTP Request, and set the domain to ".*", and the path likewise. This will +cause your test sample to match with any link found on the returned pages. If you wanted to +restrict the spidering to a particular domain, then change the domain value +to the one you want. Then, only links to that domain will be followed. +

+
+ +
Poll Example
+

A more useful example: given a web polling application, you might have a page with +several poll options as radio buttons for the user to select. Let's say the values +of the poll options are very dynamic - maybe user generated. If you wanted JMeter to +test the poll, you could either create test samples with hardcoded values chosen, or you +could let the HTML Link Parser parse the form, and insert a random poll option into +your URL test sample. To do this, follow the above example, except, when configuring +your Web Test controller's URL options, be sure to choose "POST" as the +method. Put in hard-coded values for the domain, path, and any additional form parameters. +Then, for the actual radio button parameter, put in the name (let's say it's called "poll_choice"), +and then ".*" for the value of that parameter. When the modifier examines +this URL test sample, it will find that it "matches" the poll form (and +it shouldn't match any other form, given that you've specified all the other aspects of +the URL test sample), and it will replace your form parameters with the matching +parameters from the form. Since the regular expression ".*" will match with +anything, the modifier will probably have a list of radio buttons to choose from. It +will choose at random, and replace the value in your URL test sample. Each time through +the test, a new random value will be chosen.

+ +
Figure 18 - Online Poll Example
Figure 18 - Online Poll Example
+ +
One important thing to remember is that you must create a test sample immediately +prior that will return an HTML page with the links and forms that are relevant to +your dynamic test sample.
+
+ +
+ +

HTTP URL Re-writing Modifier

Screenshot for HTTP URL Re-writing Modifier
+

This modifier works similarly to the HTML Link Parser, except it has a specific purpose for which +it is easier to use than the HTML Link Parser, and more efficient. For web applications that +use URL Re-writing to store session ids instead of cookies, this element can be attached at the +ThreadGroup level, much like the HTTP Cookie Manager. Simply give it the name +of the session id parameter, and it will find it on the page and add the argument to every +request of that ThreadGroup.

+

Alternatively, this modifier can be attached to select requests and it will modify only them. +Clever users will even determine that this modifier can be used to grab values that elude the +HTML Link Parser.

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name given to this element in the test tree.
No
+
Session Argument Name
The name of the parameter to grab from + previous response. This modifier will find the parameter anywhere it exists on the page, and + grab the value assigned to it, whether it's in an HREF or a form.
Yes
+
Path Extension
Some web apps rewrite URLs by appending + a semi-colon plus the session id parameter. Check this box if that is so.
No
+
Do not use equals in path extension
Some web apps rewrite URLs without using an "=" sign between the parameter name and value (such as Intershop Enfinity).
No
+
Do not use questionmark in path extension
Prevents the query string to end up in the path extension (such as Intershop Enfinity).
No
+
Cache Session Id?
+ Should the value of the session Id be saved for later use when the session Id is not present? +
Yes
+
URL Encode
+ URL Encode value when writing parameter +
No
+
+ +
+If using distributed testing, ensure you switch mode (see jmeter.properties) so that it's not a stripping one, see + Bug + 56376. +
+ +
+ +

HTML Parameter Mask (DEPRECATED)

+ *** This element is deprecated. Use + Counter + instead *** +
Screenshot for HTML Parameter Mask (DEPRECATED)
+

The HTML Parameter Mask is used to generate unique values for HTML arguments. By +specifying the name of the parameter, a value prefix and suffix, and counter parameters, this +modifier will generate values of the form "name=prefixcountersuffix". Any HTTP +Request that it modifies, it will replace any parameter with the same name or add the appropriate +parameter to the requests list of arguments.

+
The value of the argument in your HTTP Request must be a '*' in order for the HTML Parameter Mask +Modifier to replace it.
+

As an example, the username for a login script could be modified to send a series of values +such as:
+user_1
+user_2
+user_3
+user_4, etc.

+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name given to this element in the test tree.
No
+
Name (second appearing)
The name of the parameter to + modify or add to the HTTP Request.
Yes
+
ID Prefix
A string value to prefix to every generated value.
No
+
Lower Bound
A number value to start the counter at.
Yes
+
Upper Bound
A number value to end the counter, at which point it restarts + with the Lower Bound value.
Yes
+
Increment
Value to increment the counter by each time through.
Yes
+
ID Suffix
A string value to add as suffix to every generated vaue.
No
+
+
+ +

User Parameters

Screenshot for User Parameters
+

Allows the user to specify values for User Variables specific to individual threads.

+

User Variables can also be specified in the Test Plan but not specific to individual threads. This panel allows +you to specify a series of values for any User Variable. For each thread, the variable will be assigned one of the values from the series +in sequence. If there are more threads than values, the values get re-used. For example, this can be used to assign a distinct +user id to be used by each thread. User variables can be referenced in any field of any jMeter Component.

+ +

The variable is specified by clicking the Add Variable button in the bottom of the panel and filling in the Variable name in the 'Name:' column. +To add a new value to the series, click the 'Add User' button and fill in the desired value in the newly added column.

+ +

Values can be accessed in any test component in the same thread group, using the function syntax: ${variable}.

+

See also the CSV Data Set Config element, which is more suitable for large numbers of parameters

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Update Once Per Iteration
A flag to indicate whether the User Paramters element + should update its variables only once per iteration. if you embed functions into the UP, then you may need greater + control over how often the values of the variables are updated. Keep this box checked to ensure the values are + updated each time through the UP's parent controller. Uncheck the box, and the UP will update the parameters for + every sample request made within its scope.
Yes
+ +
+
+ +

BeanShell PreProcessor

Screenshot for BeanShell PreProcessor
+
+

+The BeanShell PreProcessor allows arbitrary code to be applied before taking a sample. +

+

+For full details on using BeanShell, please see the BeanShell website. +

+

+The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label
No
+
Reset bsh.Interpreter before each call
+ If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. +
Yes
+
Parameters
Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • bsh.args - String array containing parameters, split on white-space
  • +
No
+
Script file
A file containing the BeanShell script to run. + The file name is stored in the script variable FileName
No
+
Script
The BeanShell script. The return value is ignored.
Yes (unless script file is provided)
+
+

Before invoking the script, some variables are set up in the BeanShell interpreter:

+
    +
  • log - (Logger) - can be used to write to the log file
  • +
  • ctx - (JMeterContext) - gives access to the context
  • +
  • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
  • +
  • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • prev - (SampleResult) - gives access to the previous SampleResult (if any)
  • +
  • sampler - (Sampler)- gives access to the current sampler
  • +
+

For details of all the methods available on each of the above variables, please check the Javadoc

+

If the property beanshell.preprocessor.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

+
+ +

BSF PreProcessor

Screenshot for BSF PreProcessor
+
+

+The BSF PreProcessor allows BSF script code to be applied before taking a sample. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Language
The BSF language to be used
Yes
+
Parameters
Parameters to pass to the script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • args - String array containing parameters, split on white-space
  • +
No
+
Script file
A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property
No
+
Script
The script to run.
Yes (unless script file is provided)
+
+

+The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

+

The following BSF variables are set up for use by the script:

+
    +
  • log - (Logger) - can be used to write to the log file
  • +
  • Label - the String Label
  • +
  • Filename - the script file name (if any)
  • +
  • Parameters - the parameters (as a String)
  • +
  • args[] - the parameters as a String array (split on whitespace)
  • +
  • ctx - (JMeterContext) - gives access to the context
  • +
  • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
  • +
  • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • sampler - (Sampler)- gives access to the current sampler
  • +
  • OUT - System.out - e.g. OUT.println("message")
  • +
+

For details of all the methods available on each of the above variables, please check the Javadoc

+
+ +

JSR223 PreProcessor

+
+

+The JSR223 PreProcessor allows JSR223 script code to be applied before taking a sample. +For details, see BSF PreProcessor. +

+
+
+ +

JDBC PreProcessor

+
+

+The JDBC PreProcessor enables you to run some SQL statement just before a sample runs. +This can be useful if your JDBC Sample requires some data to be in DataBase and you cannot compute this in a setup Thread group. +For details, see JDBC Request. +

+

+See the following Test plan: +

+ +

+In the linked test plan,"Create Price Cut-Off" JDBC PreProcessor calls a stored procedure to create a Price Cut-Off in Database, +this one will be used by "Calculate Price cut off". + +

Create Price Cut-Off Preprocessor
Create Price Cut-Off Preprocessor
+ +

+
+
+ +

RegEx User Parameters

Screenshot for RegEx User Parameters
+

Allows to specify dynamic values for HTTP parameters extracted from another HTTP Request using regular expressions. + RegEx User Parameters are specific to individual threads.

+

This component allows you to specify reference name of a regular expression that extracts names and values of HTTP request parameters. + Regular expression group numbers must be specified for parameter's name and also for parameter's value. + Replacement will only occur for parameters in the Sampler that uses this RegEx User Parameters which name matches

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Regular Expression Reference Name
Name of a reference to a regular expression
Yes
+
Parameter names regexp group number
Group number of regular expression used to extract parameter names
Yes
+
Parameter values regex group number
Group number of regular expression used to extract parameter values
Yes
+
+ +

Example:

+

Suppose we have a request which returns a form with 3 input parameters and we want to extract the value of 2 of them to inject them in next request

+

1. - Create Post Processor Regular Expression for first HTTP Request

+
    +
  • refName - set name of a regular expression Ex. (listParams)
  • +
  • regular expression - expression that will extract input names and input values attributes +
    + Ex: input name="([^"]+?)" value="([^"]+?)"
  • +
  • template would be empty
  • +
  • match nr - -1 (in order to iterate through all the possible matches)
  • +
+ +

2. - Create Pre Processor RegEx User Parameters for second HTTP Request

+
    +
  • refName - set the same reference name of a regular expression, would be listParams in our example
  • +
  • parameter names group number - group number of regular expression for parameter names, would be 1 in our example
  • +
  • parameter values group number - group number of regular expression for parameter values, would be 2 in our example
  • +
+ +

See also the Regular Expression Extractor element, which is used to extract parametes names and values

+
+ + +^ + +

18.8 Post-Processors

+
+

+ As the name suggests, Post-Processors are applied after samplers. Note that they are + applied to all the samplers in the same scope, so to ensure that a post-processor + is applied only to a particular sampler, add it as a child of the sampler. +

+

+ Note: Unless documented otherwise, Post-Processors are not applied to sub-samples (child samples) - + only to the parent sample. + In the case of BSF and BeanShell post-processors, the script can retrieve sub-samples using the method + prev.getSubResults() which returns an array of SampleResults. + The array will be empty if there are none. +

+

+ Post-Processors are run before Assertions, so they do not have access to any Assertion Results, nor will + the sample status reflect the results of any Assertions. If you require access to Assertion Results, try + using a Listener instead. Also note that the variable JMeterThread.last_sample_ok is set to "true" or "false" + after all Assertions have been run. +

+
+

Regular Expression Extractor

Screenshot for Regular Expression Extractor
+

Allows the user to extract values from a server response using a Perl-type regular expression. As a post-processor, +this element will execute after each Sample request in its scope, applying the regular expression, extracting the requested values, +generate the template string, and store the result into the given variable name.

+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Apply to:
+ This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
    +
  • Main sample only - only applies to the main sample
  • +
  • Sub-samples only - only applies to the sub-samples
  • +
  • Main sample and sub-samples - applies to both.
  • +
  • JMeter Variable - assertion is to be applied to the contents of the named variable
  • +
+ Matching is applied to all qualifying samples in turn. + For example if there is a main sample and 3 sub-samples, each of which contains a single match for the regex, + (i.e. 4 matches in total). + For match number = 3, Sub-samples only, the extractor will match the 3rd sub-sample. + For match number = 3, Main sample and sub-samples, the extractor will match the 2nd sub-sample (1st match is main sample). + For match number = 0 or negative, all qualifying samples will be processed. + For match number > 0, matching will stop as soon as enough matches have been found. +
Yes
+
Field to check
+ The following fields can be checked: +
    +
  • Body - the body of the response, e.g. the content of a web-page (excluding headers)
  • +
  • Body (unescaped) - the body of the response, with all Html escape codes replaced. + Note that Html escapes are processed without regard to context, so some incorrect substitutions + may be made. +
    Note that this option highly impacts performances, so use it only when absolutely necessary and be aware of its impacts
    +
  • +
  • Body as a Document - the extract text from various type of documents via Apache Tika (see View Results Tree Document view section). +
    Note that the Body as a Document option can impact performances, so ensure it is Ok for your test
    +
  • +
  • Request Headers - may not be present for non-HTTP samples
  • +
  • Response Headers - may not be present for non-HTTP samples
  • +
  • URL
  • +
  • Response Code - e.g. 200
  • +
  • Response Message - e.g. OK
  • +
+ Headers can be useful for HTTP samples; it may not be present for other sample types. +
Yes
+
Reference Name
The name of the JMeter variable in which to store the result. Also note that each group is stored as [refname]_g#, where [refname] is the string you entered as the reference name, and # is the group number, where group 0 is the entire match, group 1 is the match from the first set of parentheses, etc.
Yes
+
Regular Expression
The regular expression used to parse the response data. + This must contain at least one set of parentheses "()" to capture a portion of the string, unless using the group $0$. + Do not enclose the expression in / / - unless of course you want to match these characters as well. +
Yes
+
Template
The template used to create a string from the matches found. This is an arbitrary string + with special elements to refer to groups within the regular expression. The syntax to refer to a group is: '$1$' to refer to + group 1, '$2$' to refer to group 2, etc. $0$ refers to whatever the entire expression matches.
Yes
+
Match No.
Indicates which match to use. The regular expression may match multiple times. +
    +
  • Use a value of zero to indicate JMeter should choose a match at random.
  • +
  • A positive number N means to select the nth match.
  • +
  • Negative numbers are used in conjunction with the ForEach controller - see below.
  • +
+
Yes
+
Default Value
+ If the regular expression does not match, then the reference variable will be set to the default value. + This is particularly useful for debugging tests. If no default is provided, then it is difficult to tell + whether the regular expression did not match, or the RE element was not processed or maybe the wrong variable + is being used. +

+ However, if you have several test elements that set the same variable, + you may wish to leave the variable unchanged if the expression does not match. + In this case, remove the default value once debugging is complete. +

+
No, but recommended
+
+

+ If the match number is set to a non-negative number, and a match occurs, the variables are set as follows: +

    +
  • refName - the value of the template
  • +
  • refName_gn, where n=0,1,2 - the groups for the match
  • +
  • refName_g - the number of groups in the Regex (excluding 0)
  • +
+ If no match occurs, then the refName variable is set to the default (unless this is absent). + Also, the following variables are removed: +
    +
  • refName_g0
  • +
  • refName_g1
  • +
  • refName_g
  • +
+

+

+ If the match number is set to a negative number, then all the possible matches in the sampler data are processed. + The variables are set as follows: +

    +
  • refName_matchNr - the number of matches found; could be 0
  • +
  • refName_n, where n = 1,2,3 etc - the strings as generated by the template
  • +
  • refName_n_gm, where m=0,1,2 - the groups for match n
  • +
  • refName - always set to the default value
  • +
  • refName_gn - not set
  • +
+ Note that the refName variable is always set to the default value in this case, + and the associated group variables are not set. +

See also Response Assertion for some examples of how to specify modifiers, + and for further information on JMeter regular expressions.

+

+
+ +

CSS/JQuery Extractor

Screenshot for CSS/JQuery Extractor
+

Allows the user to extract values from a server response using a CSS/JQuery selector like syntax. As a post-processor, +this element will execute after each Sample request in its scope, applying the CSS/JQuery expression, extracting the requested nodes, +extracting the node as text or attribute value and store the result into the given variable name.

+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Apply to:
+ This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
    +
  • Main sample only - only applies to the main sample
  • +
  • Sub-samples only - only applies to the sub-samples
  • +
  • Main sample and sub-samples - applies to both.
  • +
  • JMeter Variable - assertion is to be applied to the contents of the named variable
  • +
+ Matching is applied to all qualifying samples in turn. + For example if there is a main sample and 3 sub-samples, each of which contains a single match for the regex, + (i.e. 4 matches in total). + For match number = 3, Sub-samples only, the extractor will match the 3rd sub-sample. + For match number = 3, Main sample and sub-samples, the extractor will match the 2nd sub-sample (1st match is main sample). + For match number = 0 or negative, all qualifying samples will be processed. + For match number > 0, matching will stop as soon as enough matches have been found. +
Yes
+
CSS/JQuery extractor Implementation
+ As of JMeter 2.9, 2 implementations for CSS/JQuery based syntax are supported: + + If selector is set to empty, default implementation(JSoup) will be used. +
False
+
Reference Name
The name of the JMeter variable in which to store the result.
Yes
+
CSS/JQuery expression
The CSS/JQuery selector used to select nodes from the response data. + Selector, selectors combination and pseudo-selectors are supported, examples: +
    +
  • E[foo] an E element with a "foo" attribute
  • +
  • ancestor child:child elements that descend from ancestor, e.g. .body p finds p elements anywhere under a block with class "body"
  • +
  • :lt(n): find elements whose sibling index (i.e. its position in the DOM tree relative to its parent) is less than n; e.g. td:lt(3)
  • +
  • :contains(text): find elements that contain the given text. The search is case-insensitive; e.g. p:contains(jsoup)
  • +
  • ...
  • +
+ For more details on syntax, see: + +
Yes
+
Attribute
+ Name of attribute (as per HTML syntax) to extract from nodes that matched the selector. If empty, then the combined text of this element and all its children will be returned.
+ This is the equivalent Element#attr(name) function for JSoup if an atttribute is set.
+
CSS Extractor with attribute value set
CSS Extractor with attribute value set

+ If empty this is the equivalent of Element#text() function for JSoup if not value is set for attribute. +
CSS Extractor with no attribute set
CSS Extractor with no attribute set
+
false
+
Match No.
Indicates which match to use. The CSS/JQuery selector may match multiple times. +
    +
  • Use a value of zero to indicate JMeter should choose a match at random.
  • +
  • A positive number N means to select the nth match.
  • +
  • Negative numbers are used in conjunction with the ForEach controller - see below.
  • +
+
Yes
+
Default Value
+ If the expression does not match, then the reference variable will be set to the default value. + This is particularly useful for debugging tests. If no default is provided, then it is difficult to tell + whether the expression did not match, or the CSS/JQuery element was not processed or maybe the wrong variable + is being used. +

+ However, if you have several test elements that set the same variable, + you may wish to leave the variable unchanged if the expression does not match. + In this case, remove the default value once debugging is complete. +

+
No, but recommended
+
+

+ If the match number is set to a non-negative number, and a match occurs, the variables are set as follows: +

    +
  • refName - the value of the template
  • +
+ If no match occurs, then the refName variable is set to the default (unless this is absent). +

+

+ If the match number is set to a negative number, then all the possible matches in the sampler data are processed. + The variables are set as follows: +

    +
  • refName_matchNr - the number of matches found; could be 0
  • +
  • refName_n, where n = 1,2,3 etc - the strings as generated by the template
  • +
  • refName - always set to the default value
  • +
+ Note that the refName variable is always set to the default value in this case. +

+
+ +

XPath Extractor

Screenshot for XPath Extractor
+
This test element allows the user to extract value(s) from + structured response - XML or (X)HTML - using XPath + query language. +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Apply to:
+ This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
    +
  • Main sample only - only applies to the main sample
  • +
  • Sub-samples only - only applies to the sub-samples
  • +
  • Main sample and sub-samples - applies to both.
  • +
  • JMeter Variable - assertion is to be applied to the contents of the named variable
  • +
+ XPath matching is applied to all qualifying samples in turn, and all the matching results will be returned. +
Yes
+
Use Tidy (tolerant parser)
If checked use Tidy to parse HTML response into XHTML. +
    +
  • "Use Tidy" should be checked on for HTML response. Such response is converted to valid XHTML (XML compatible HTML) using Tidy
  • +
  • "Use Tidy" should be unchecked for both XHTML or XML response (for example RSS)
  • +
+
Yes
+
Quiet
Sets the Tidy Quiet flag
If Tidy is selected
+
Report Errors
If a Tidy error occurs, then set the Assertion accordingly
If Tidy is selected
+
Show warnings
Sets the Tidy showWarnings option
If Tidy is selected
+
Use Namespaces
+ If checked, then the XML parser will use namespace resolution. + Note that currently only namespaces declared on the root element will be recognised. + A later version of JMeter may support user-definition of additional workspace names. + Meanwhile, a work-round is to replace: +
+ //mynamespace:tagname +
+ by +
+ //*[local-name()='tagname' and namespace-uri()='uri-for-namespace'] +
+ where "uri-for-namespace" is the uri for the "mynamespace" namespace. + + (not applicable if Tidy is selected) +
If Tidy is not selected
+
Validate XML
Check the document against its schema.
If Tidy is not selected
+
Ignore Whitespace
Ignore Element Whitespace.
If Tidy is not selected
+
Fetch External DTDs
If selected, external DTDs are fetched.
If Tidy is not selected
+
Return entire XPath fragment instead of text content?
+ If selected, the fragment will be returned rather than the text content.
+ For example //title would return "<title>Apache JMeter</title>" rather than "Apache JMeter".
+ In this case, //title/text() would return "Apache JMeter". +
Yes
+
Reference Name
The name of the JMeter variable in which to store the result.
Yes
+
XPath Query
Element query in XPath language. Can return more than one match.
Yes
+
Default Value
Default value returned when no match found. + It is also returned if the node has no value and the fragment option is not selected.
+
+

To allow for use in a ForEach Controller, the following variables are set on return:

+
    +
  • refName - set to first (or only) match; if no match, then set to default
  • +
  • refName_matchNr - set to number of matches (may be 0)
  • +
  • refName_n - n=1,2,3 etc. Set to the 1st, 2nd 3rd match etc. +
  • +
+

Note: The next refName_n variable is set to null - e.g. if there are 2 matches, then refName_3 is set to null, + and if there are no matches, then refName_1 is set to null. +

+

XPath is query language targeted primarily for XSLT transformations. However it is usefull as generic query language for structured data too. See + XPath Reference or XPath specification for more information. Here are few examples: +

+
+
/html/head/title
+
extracts title element from HTML response
+
/book/page[2]
+
extracts 2nd page from a book
+
/book/page
+
extracts all pages from a book
+
//form[@name='countryForm']//select[@name='country']/option[text()='Czech Republic'])/@value
+
extracts value attribute of option element that match text 'Czech Republic' + inside of select element with name attribute 'country' inside of + form with name attribute 'countryForm'
+
+
When "Use Tidy" is checked on - resulting XML document may slightly differ from original HTML response: +
    +
  • All elements and attribute names are converted to lowercase
  • +
  • Tidy attempts to correct improperly nested elements. For example - original (incorrect) ul/font/li becomes correct ul/li/font
  • +
+ See Tidy homepage for more information. +
+ +
+ +

Result Status Action Handler

Screenshot for Result Status Action Handler
+
This test element allows the user to stop the thread or the whole test if the relevant sampler failed. +
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Action to be taken after a Sampler error
+ Determines what happens if a sampler error occurs, either because the sample itself failed or an assertion failed. + The possible choices are: +
    +
  • Continue - ignore the error and continue with the test
  • +
  • Start next thread loop - does not execute samplers following the sampler in error for the current iteration and restarts the loop on next iteration
  • +
  • Stop Thread - current thread exits
  • +
  • Stop Test - the entire test is stopped at the end of any current samples.
  • +
  • Stop Test Now - the entire test is stopped abruptly. Any current samplers are interrupted if possible.
  • +
+
+ No +
+
+
+ +

BeanShell PostProcessor

Screenshot for BeanShell PostProcessor
+
+

+The BeanShell PreProcessor allows arbitrary code to be applied after taking a sample. +

+

For JMeter versions after 2.2 the BeanShell Post-Processor no longer ignores samples with zero-length result data

+

+For full details on using BeanShell, please see the BeanShell website. +

+

+The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label
No
+
Reset bsh.Interpreter before each call
+ If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. +
Yes
+
Parameters
Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • bsh.args - String array containing parameters, split on white-space
  • +
No
+
Script file
A file containing the BeanShell script to run. + The file name is stored in the script variable FileName
No
+
Script
The BeanShell script. The return value is ignored.
Yes (unless script file is provided)
+
+

The following BeanShell variables are set up for use by the script:

+
    +
  • log - (Logger) - can be used to write to the log file
  • +
  • ctx - (JMeterContext) - gives access to the context
  • +
  • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
  • +
  • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • prev - (SampleResult) - gives access to the previous SampleResult
  • +
  • data - (byte [])- gives access to the current sample data
  • +
+

For details of all the methods available on each of the above variables, please check the Javadoc

+

If the property beanshell.postprocessor.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

+
+ +

BSF PostProcessor

Screenshot for BSF PostProcessor
+
+

+The BSF PostProcessor allows BSF script code to be applied after taking a sample. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Language
The BSF language to be used
Yes
+
Parameters
Parameters to pass to the script. + The parameters are stored in the following variables: +
    +
  • Parameters - string containing the parameters as a single variable
  • +
  • args - String array containing parameters, split on white-space
  • +
No
+
Script file
A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property
No
+
Script
The script to run.
Yes (unless script file is provided)
+
+

+The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

+

+Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. +

+
    +
  • log - (Logger) - can be used to write to the log file
  • +
  • Label - the String Label
  • +
  • Filename - the script file name (if any)
  • +
  • Parameters - the parameters (as a String)
  • +
  • args[] - the parameters as a String array (split on whitespace)
  • +
  • ctx - (JMeterContext) - gives access to the context
  • +
  • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
  • +
  • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
  • +
  • prev - (SampleResult) - gives access to the previous SampleResult (if any)
  • +
  • sampler - (Sampler)- gives access to the current sampler
  • +
  • OUT - System.out - e.g. OUT.println("message")
  • +
+

For details of all the methods available on each of the above variables, please check the Javadoc

+
+ +

JSR223 PostProcessor

+
+

+The JSR223 PostProcessor allows JSR223 script code to be applied after taking a sample. +For details, see the BSF PostProcessor. +

+
+
+ +

JDBC PostProcessor

+
+

+The JDBC PostProcessor enables you to run some SQL statement just after a sample has run. +This can be useful if your JDBC Sample changes some data and you want to reset state to what it was before the JDBC sample run. +

+
+
+ +

+In the linked test plan,"JDBC PostProcessor" JDBC PostProcessor calls a stored procedure to delete from Database the Price Cut-Off that was created by PreProcessor. +

JDBC PostProcessor
JDBC PostProcessor
+

+

18.9 Miscellaneous Features

+
+
+
+

Test Plan

Screenshot for Test Plan
+
+

+The Test Plan is where the overall settings for a test are specified. +

+

+Static variables can be defined for values that are repeated throughout a test, such as server names. +For example the variable SERVER could be defined as www.example.com, and the rest of the test plan +could refer to it as ${SERVER}. This simplifies changing the name later. +

+

+If the same variable name is reused on one of more +User Defined Variables Configuration elements, +the value is set to the last definition in the test plan (reading from top to bottom). +Such variables should be used for items that may change between test runs, +but which remain the same during a test run. +

+

+Note that the Test Plan cannot refer to variables it defines. +If you need to construct other variables from the Test Plan variables, +use a User Defined Variables test element. +

+

+Selecting Functional Testing instructs JMeter to save the additional sample information +- Response Data and Sampler Data - to all result files. +This increases the resources needed to run a test, and may adversely impact JMeter performance. +If more data is required for a particular sampler only, then add a Listener to it, and configure the fields as required. +[The option does not affect CSV result files, which cannot currently store such information.] +

+

Also, an option exists here to instruct JMeter to run the Thread Group serially rather than in parallel.

+

Run tearDown Thread Groups after shutdown of main threads: +if selected, the tearDown groups (if any) will be run after graceful shutdown of the main threads. +The tearDown threads won't be run if the test is forcibly stopped. +

+

+Test plan now provides an easy way to add classpath setting to a specific test plan. +The feature is additive, meaning that you can add jar files or directories, +but removing an entry requires restarting JMeter. +Note that this cannot be used to add JMeter GUI plugins, because they are processed earlier. +However it can be useful for utility jars such as JDBC drivers. The jars are only added to +the search path for the JMeter loader, not for the system class loader. +

+

+JMeter properties also provides an entry for loading additional classpaths. +In jmeter.properties, edit "user.classpath" or "plugin_dependency_paths" to include additional libraries. +See JMeter's Classpath and +Configuring JMeter for details. +

+
+
+ +

Thread Group

Screenshot for Thread Group
+
+

A Thread Group defines a pool of users that will execute a particular test case against your server. In the Thread Group GUI, you can control the number of users simulated (num of threads), the ramp up time (how long it takes to start all the threads), the number of times to perform the test, and optionally, a start and stop time for the test.

+

+See also tearDown Thread Group and setUp Thread Group. +

+

+When using the scheduler, JMeter runs the thread group until either the number of loops is reached or the duration/end-time is reached - whichever occurs first. +Note that the condition is only checked between samples; when the end condition is reached, that thread will stop. +JMeter does not interrupt samplers which are waiting for a response, so the end time may be delayed arbitrarily. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
+
Action to be taken after a Sampler error
+ Determines what happens if a sampler error occurs, either because the sample itself failed or an assertion failed. + The possible choices are: +
    +
  • Continue - ignore the error and continue with the test
  • +
  • Start Next Loop - ignore the error, start next loop and continue with the test
  • +
  • Stop Thread - current thread exits
  • +
  • Stop Test - the entire test is stopped at the end of any current samples.
  • +
  • Stop Test Now - the entire test is stopped abruptly. Any current samplers are interrupted if possible.
  • +
+
+ No +
+
Number of Threads
Number of users to simulate.
Yes
+
Ramp-up Period
How long JMeter should take to get all the threads started. If there are 10 threads and a ramp-up time of 100 seconds, then each thread will begin 10 seconds after the previous thread started, for a total time of 100 seconds to get the test fully up to speed.
Yes
+
Loop Count
Number of times to perform the test case. Alternatively, "forever" can be selected causing the test to run until manually stopped.
Yes, unless forever is selected
+
Delay Thread creation until needed
+ If selected, threads are created only when the appropriate proportion of the ramp-up time has elapsed. + This is most appropriate for tests with a ramp-up time that is significantly longer than the time to execute a single thread. + I.e. where earlier threads finish before later ones start. +
+ If not selected, all threads are created when the test starts (they then pause for the appropriate proportion of the ramp-up time). + This is the original default, and is appropriate for tests where threads are active throughout most of the test. +
Yes
+
Scheduler
If selected, enables the scheduler
Yes
+
Start Time
If the scheduler checkbox is selected, one can choose an absolute start time. When you start your test, JMeter will wait until the specified start time to begin testing. + Note: the Startup Delay field over-rides this - see below. +
No
+
End Time
If the scheduler checkbox is selected, one can choose an absolute end time. When you start your test, JMeter will wait until the specified start time to begin testing, and it will stop at the specified end time. + Note: the Duration field over-rides this - see below. +
No
+
Duration (seconds)
+ If the scheduler checkbox is selected, one can choose a relative end time. + JMeter will use this to calculate the End Time, and ignore the End Time value. +
No
+
Startup delay (seconds)
+ If the scheduler checkbox is selected, one can choose a relative startup delay. + JMeter will use this to calculate the Start Time, and ignore the Start Time value. +
No
+
+
+ +

WorkBench

Screenshot for WorkBench
+
+

The WorkBench simply provides a place to temporarily store test elements while not in use, for copy/paste purposes, or any other purpose you desire. +When you save your test plan, WorkBench items are not saved with it by default unless you check "Save Workbench" option. +Your WorkBench can be saved independently, if you like (right-click on WorkBench and choose Save).

+

Certain test elements are only available on the WorkBench:

+ +

+ Parameters +

Attribute
Description
Required
+
Save WorkBench
+ Allow to save the WorkBench's elements into the JMX file. +
No
+
+
+
+ +

SSL Manager

+

+ The SSL Manager is a way to select a client certificate so that you can test + applications that use Public Key Infrastructure (PKI). + It is only needed if you have not set up the appropriate System properties. +

+ +Choosing a Client Certificate +

+ You may either use a Java Key Store (JKS) format key store, or a Public Key + Certificate Standard #12 (PKCS12) file for your client certificates. There + is a feature of the JSSE libraries that require you to have at least a six character + password on your key (at least for the keytool utility that comes with your + JDK). +

+

+ To select the client certificate, choose Options->SSL Manager from the menu bar. + You will be presented with a file finder that looks for PKCS12 files by default. + Your PKCS12 file must have the extension '.p12' for SSL Manager to recognize it + as a PKCS12 file. Any other file will be treated like an average JKS key store. + If JSSE is correctly installed, you will be prompted for the password. The text + box does not hide the characters you type at this point--so make sure no one is + looking over your shoulder. The current implementation assumes that the password + for the keystore is also the password for the private key of the client you want + to authenticate as. +

+

Or you can set the appropriate System properties - see the system.properties file.

+

+ The next time you run your test, the SSL Manager will examine your key store to + see if it has at least one key available to it. If there is only one key, SSL + Manager will select it for you. If there is more than one key, it currently selects the first key. + There is currently no way to select other entries in the keystore, so the desired key must be the first. +

+Things to Look Out For +

+ You must have your Certificate Authority (CA) certificate installed properly + if it is not signed by one of the five CA certificates that ships with your + JDK. One method to install it is to import your CA certificate into a JKS + file, and name the JKS file "jssecacerts". Place the file in your JRE's + lib/security folder. This file will be read before the "cacerts" file in + the same directory. Keep in mind that as long as the "jssecacerts" file + exists, the certificates installed in "cacerts" will not be used. This may + cause problems for you. If you don't mind importing your CA certificate into + the "cacerts" file, then you can authenticate against all of the CA certificates + installed. +

+
+ +

HTTP(S) Test Script Recorder + (was: + HTTP Proxy Server + ) +

Screenshot for HTTP(S) Test Script Recorder
+

The HTTP(S) Test Script Recorder allows JMeter to intercept and record your actions while you browse your web application +with your normal browser. JMeter will create test sample objects and store them +directly into your test plan as you go (so you can view samples interactively while you make them).
+Ensure you read this wiki page to setup correctly JMeter. +

+ +

To use the recorder, add the HTTP(S) Test Script Recorder element to the workbench. +Select the WorkBench element in the tree, and right-click on this element to get the +Add menu (Add --> Non-Test Elements --> HTTP(S) Test Script Recorder).

+

+The recorder is implemented as an HTTP(S) proxy server. +You need to set up your browser use the proxy for all HTTP and HTTPS requests. +[Do not use JMeter as the proxy for any other request types - FTP, etc. - as JMeter cannot handle them.] +

+

+Ideally use private browsing mode when recording the session. +This should ensure that the browser starts with no stored cookies, and prevents certain changes from being saved. +For example, Firefox does not allow certificate overrides to be saved permanently. +

+

HTTPS recording and certificates

+

+HTTPS connections use certificates to authenticate the connection between the browser and the web server. +When connecting via HTTPS, the server presents the certificate to the browser. +To authenticate the certificate, the browser checks that the server certificate is signed +by a Certificate Authority (CA) that is linked to one of its in-built root CAs. +[Browsers also check that the certificate is for the correct host or domain, and that it is valid and not expired.] +If any of the browser checks fail, it will prompt the user who can then decided whether to allow the connection to proceed. +

+

+JMeter needs to use its own certificate to enable it to intercept the HTTPS connection from +the browser. Effectively JMeter has to pretend to be the target server. +

+

+For versions of JMeter from 2.10, JMeter will generate its own certificate(s). +These are generated with a validity period defined by the property proxy.cert.validity, default 7 days, and random passwords. +If JMeter detects that it is running under Java 7 or later, it will generate certificates for each target server as necessary (dynamic mode) +unless the following property is defined: proxy.cert.dynamic_keys=false. +When using dynamic mode, the certificate will be for the correct host name, and will be signed by a JMeter-generated CA certificate. +By default, this CA certificate won't be trusted by the browser, however it can be installed as a trusted certificate. +Once this is done, the generated server certificates will be accepted by the browser. +This has the advantage that even embedded HTTPS resources can be intercepted, and there is no need to override the browser checks for each new server. +(Browsers don't prompt for embedded resources. So with earlier versions, embedded resources would only be downloaded for servers that were already 'known' to the browser) +

+

Unless a keystore is provided (and you define the property proxy.cert.alias), +JMeter needs to use the keytool application to create the keystore entries. +Versions of JMeter after 2.10 include code to check that keytool is available by looking in various standard places. +If JMeter is unable to find the keytool application, it will report an error. +If necessary, the systen property keytool.directory can be used to tell JMeter where to find keytool. +This should be defined in the file system.properties. +

+

+The JMeter certificates are generated (if necessary) when the Start button is pressed. +Certificate generation can take some while, during which time the GUI will be unresponsive. +The cursor is changed to an hour-glass whilst this is happening. +When certificate generation is complete, the GUI will display a pop-up dialogue containing the details of the certificate for the root CA. +This certificate needs to be installed by the browser in order for it to accept the host certificates generated by JMeter; see below for details. +

+

+If necessary, you can force JMeter to regenerate the keystore (and the exported certificates - ApacheJMeterTemporaryRootCA[.usr|.crt]) by deleting the keystore file proxyserver.jks from the JMeter directory. +

+

+With versions of JMeter up to 2.9, it used a single certificate for all target servers. +[Likewise if JMeter is not being run under Java 7 or later] +This certificate is not one of the certificates that browsers normally trust, and will not be for the +correct host.
+As a consequence: +

    +
  • The browser should display a dialogue asking if you want to accept the certificate or not. For example: +
    +1) The server's name "www.example.com" does not match the certificate's name
    +   "JMeter Proxy (DO NOT TRUST)". Somebody may be trying to eavesdrop on you.
    +2) The certificate for "JMeter Proxy (DO NOT TRUST)" is signed by the unknown Certificate Authority
    +   "JMeter Proxy (DO NOT TRUST)". It is not possible to verify that this is a valid certificate.
    +
    +You will need to accept the certificate in order to allow the JMeter Proxy to intercept the SSL traffic in order to +record it. +However, do not accept this certificate permanently; it should only be accepted temporarily. +Browsers only prompt this dialogue for the certificate of the main url, not for the resources loaded in the page, such as images, css or javascript files hosted on a secured external CDN. +If you have such resources (gmail has for example), you'll have to first browse manually to these other domains in order to accept JMeter's certificate for them. +Check in jmeter.log for secure domains that you need to register certificate for. +
  • +
  • If the browser has already registered a validated certificate for this domain, the browser will detect JMeter as a security breach and will refuse to load the page. If so, you have to remove the trusted certificate from your browser's keystore. +
  • +
+

+

+Versions of JMeter from 2.10 onwards still support this method, and will continue to do so if the you define the following property: +proxy.cert.alias +The following properties can be used to change the certificate that is used: +

    +
  • proxy.cert.directory - the directory in which to find the certificate (default = JMeter bin/)
  • +
  • proxy.cert.file - name of the keystore file (default "proxyserver.jks")
  • +
  • proxy.cert.keystorepass - keystore password (default "password") [Ignored if using JMeter certificate]
  • +
  • proxy.cert.keypassword - certificate key password (default "password") [Ignored if using JMeter certificate]
  • +
  • proxy.cert.type - the certificate type (default "JKS") [Ignored if using JMeter certificate]
  • +
  • proxy.cert.factory - the factory (default "SunX509") [Ignored if using JMeter certificate]
  • +
  • proxy.cert.alias - the alias for the key to be used. If this is defined, JMeter does not attempt to generate its own certificate(s).
  • +
  • proxy.ssl.protocol - the protocol to be used (default "SSLv3")
  • +
+

+
+If your browser currently uses a proxy (e.g. a company intranet may route all external requests via a proxy), +then you need to tell JMeter to use that proxy before starting JMeter, +using the command-line options -H and -P. +This setting will also be needed when running the generated test plan. +
+ +

Installing the JMeter CA certificate for HTTPS recording

+

+As mentioned above, when run under Java 7, JMeter can generate certificates for each server. +For this to work smoothly, the root CA signing certificate used by JMeter needs to be trusted by the browser. +The first time that the recorder is started, it will generate the certificates if necessary. +The root CA certificate is exported into a file with the name ApacheJMeterTemporaryRootCA in the current launch directory. +When the certificates have been set up, JMeter will show a dialog with the current certificate details. +At this point, the certificate can be imported into the browser, as per the instructions below. +

+

+Note that once the root CA certificate has been installed as a trusted CA, the browser will trust any certificates signed by it. +Until such time as the certificate expires or the certificate is removed from the browser, it will not warn the user that the certificate is being relied upon. +So anyone that can get hold of the keystore and password can use the certificate to generate certificates which will be accepted +by any browsers that trust the JMeter root CA certificate. +For this reason, the password for the keystore and private keys are randomly generated and a short validity period used. +The passwords are stored in the local preferences area. +Please ensure that only trusted users have access to the host with the keystore. +

+
Installing the certificate in Firefox
+

+Choose the following options: +

    +
  • Tools / Options
  • +
  • Advanced / Certificates
  • +
  • View Certificates
  • +
  • Authorities
  • +
  • Import ...
  • +
  • Browse to the JMeter launch directory, and click on the file ApacheJMeterTemporaryRootCA.crt, press Open
  • +
  • Click View and check that the certificate details agree with the ones displayed by the JMeter Test Script Recorder
  • +
  • If OK, select "Trust this CA to identify web sites", and press OK
  • +
  • Close dialogs by pressing OK as necessary
  • +
+

+
Installing the certificate in Chrome or Internet Explorer
+

+Both Chrome and Internet Explorer use the same trust store for certificates. +

    +
  • Browse to the JMeter launch directory, and click on the file ApacheJMeterTemporaryRootCA.crt, and open it
  • +
  • Click on the "Details" tab and check that the certificate details agree with the ones displayed by the JMeter Test Script Recorder
  • +
  • If OK, go back to the "General" tab, and click on "Install Certificate ..." and follow the Wizard prompts
  • +
+

+
Installing the certificate in Opera
+

+

    +
  • Tools / Preferences / Advanced / Security
  • +
  • Manage Certificates...
  • +
  • Select "Intermediate" tab, click "Import..."
  • +
  • Browse to the JMeter launch directory, and click on the file ApacheJMeterTemporaryRootCA.usr, and open it
  • +
  • +
+

+
+ +

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
Port
The port that the HTTP(S) Test Script Recorder listens to. 8080 is the default, but you can change it.
Yes
+
HTTPS Domains
List of domain (or host) names for HTTPS. Use this to pre-generate certificates for all servers you wish to record. +
+ For example, *.apache.org,*.incubator.apache.org +
+ Note that wildcard domains only apply to one level, + i.e. podling.incubator.apache.org matches *.incubator.apache.org but not *.apache.org +
No
+
Target Controller
The controller where the proxy will store the generated samples. By default, it will look for a Recording Controller and store them there wherever it is.
Yes
+
Grouping
Whether to group samplers for requests from a single "click" (requests received without significant time separation), and how to represent that grouping in the recording: +
    +
  • Do not group samplers: store all recorded samplers sequentially, without any grouping.
  • +
  • Add separators between groups: add a controller named "--------------" to create a visual separation between the groups. Otherwise the samplers are all stored sequentially.
  • +
  • Put each group in a new controller: create a new Simple Controller for each group, and store all samplers for that group in it.
  • +
  • Store 1st sampler of each group only: only the first request in each group will be recorded. The "Follow Redirects" and "Retrieve All Embedded Resources..." flags will be turned on in those samplers.
  • +
  • Put each group in a new transaction controller: create a new Transaction Controller for each group, and store all samplers for that group in it.
  • +
+ The property proxy.pause determines the minimum gap that JMeter needs between requests + to treat them as separate "clicks". The default is 1000 (milliseconds) i.e. 1 second. + If you are using grouping, please ensure that you leave the required gap between clicks. +
Yes
+ +
Capture HTTP Headers
Should headers be added to the plan? + If specified, a Header Manager will be added to each HTTP Sampler. + The Proxy server always removes Cookie and Authorization headers from the generated Header Managers. + By default it also removes If-Modified-Since and If-None-Match headers. + These are used to determine if the browser cache items are up to date; + when recording one normally wants to download all the content. + To change which additional headers are removed, define the JMeter property proxy.headers.remove + as a comma-separated list of headers. +
Yes
+
Add Assertions
Add a blank assertion to each sampler?
Yes
+
Regex Matching
Use Regex Matching when replacing variables? If checked replacement will use word boundaries, ie it will only replace word matching values of variable, not part of a word. A word boundary follows Perl5 definition and is equivalent to \b. More information below in the paragraph about "User Defined Variable replacement".
Yes
+
Type
Which type of sampler to generate (the Java default or HTTPClient)
Yes
+
Redirect Automatically
Set Redirect Automatically in the generated samplers?
Yes
+
Follow Redirects
Set Follow Redirects in the generated samplers?
+ Note: see "Recording and redirects" section below for important information. +
Yes
+
Use Keep-Alive
Set Use Keep-Alive in the generated samplers?
Yes
+
Retrieve all Embedded Resources
Set Retrieve all Embedded Resources in the generated samplers?
Yes
+
Content Type filter
+ Filter the requests based on the content-type - e.g. "text/html [;charset=utf-8 ]". + The fields are regular expressions which are checked to see if they are contained in the content-type. + [Does not have to match the entire field]. + The include filter is checked first, then the exclude filter. + Samples which are filtered out will not be stored. + Note: this filtering is applied to the content type of the response +
No
+
Patterns to Include
Regular expressions that are matched against the full URL that is sampled. Allows filtering of requests that are recorded. All requests pass through, but only + those that meet the requirements of the Include/Exclude fields are recorded. If both Include and Exclude are + left empty, then everything is recorded (which can result in dozens of samples recorded for each page, as images, stylesheets, + etc are recorded). If there is at least one entry in the Include field, then only requests that match one or more Include patterns are + recorded.
No
+
Patterns to Exclude
Regular expressions that are matched against the URL that is sampled. + Any requests that match one or more Exclude pattern are not recorded.
No
+
Notify Child Listeners of filtered samplers
Notify Child Listeners of filtered samplers + Any response that match one or more Exclude pattern is not delivered to Child Listeners (View Results Tree).
No
+
Start Button
Start the proxy server. JMeter writes the following message to the console once the proxy server has started up and is ready to take requests: "Proxy up and running!".
N/A
+
Stop Button
Stop the proxy server.
N/A
+
Restart Button
Stops and restarts the proxy server. This is + useful when you change/add/delete an include/exclude filter expression.
N/A
+
+ +

Recording and redirects

+

+During recording, the browser will follow a redirect response and generate an additional request. +The Proxy will record both the original request and the redirected request +(subject to whatever exclusions are configured). +The generated samples have "Follow Redirects" selected by default, because that is generally better. +[Redirects may depend on the original request, so repeating the originally recorded sample may not always work.] +

+

+Now if JMeter is set to follow the redirect during replay, it will issue the original request, +and then replay the redirect request that was recorded. +To avoid this duplicate replay, JMeter tries to detect when a sample is the result of a previous +redirect. If the current response is a redirect, JMeter will save the redirect URL. +When the next request is received, it is compared with the saved redirect URL and if there is a match, +JMeter will disable the generated sample. It also adds comments to the redirect chain. +This assumes that all the requests in a redirect chain will follow each other without any intervening requests. +To disable the redirect detection, set the property proxy.redirect.disabling=false +

+ +

Includes and Excludes

+

The include and exclude patterns are treated as regular expressions (using Jakarta ORO). +They will be matched against the host name, port (actual or implied) path and query (if any) of each browser request. +If the URL you are browsing is
+"http://jmeter.apache.org/jmeter/index.html?username=xxxx",
+then the regular expression will be tested against the string:
+"jmeter.apache.org:80/jmeter/index.html?username=xxxx".
+Thus, if you want to include all .html files, your regular expression might look like:
+".*\.html(\?.*)?" - or ".*\.html" +if you know that there is no query string or you only want html pages without query strings. +

+

+If there are any include patterns, then the URL must match at least one of the patterns +, otherwise it will not be recorded. +If there are any exclude patterns, then the URL must not match any of the patterns +, otherwise it will not be recorded. +Using a combination of includes and excludes, +you should be able to record what you are interested in and skip what you are not. +

+ +

+N.B. the string that is matched by the regular expression must be the same as the whole host+path string.
Thus "\.html" will not match j.a.o/index.html +

+ +

Capturing binary POST data

+

+Versions of JMeter from 2.3.2 are able to capture binary POST data. +To configure which content-types are treated as binary, update the JMeter property proxy.binary.types. +The default settings are as follows: +

+# These content-types will be handled by saving the request in a file:
+proxy.binary.types=application/x-amf,application/x-java-serialized-object
+# The files will be saved in this directory:
+proxy.binary.directory=user.dir
+# The files will be created with this file filesuffix:
+proxy.binary.filesuffix=.binary
+
+

+ +

Adding timers

+

It is also possible to have the proxy add timers to the recorded script. To +do this, create a timer directly within the HTTP(S) Test Script Recorder component. +The proxy will place a copy of this timer into each sample it records, or into +the first sample of each group if you're using grouping. This copy will then be +scanned for occurences of variable ${T} in its properties, and any such +occurences will be replaced by the time gap from the previous sampler +recorded (in milliseconds).

+ +

When you are ready to begin, hit "start".

+
You will need to edit the proxy settings of your browser to point at the +appropriate server and port, where the server is the machine JMeter is running on, and +the port # is from the Proxy Control Panel shown above.
+ +

Where Do Samples Get Recorded?

+

JMeter places the recorded samples in the Target Controller you choose. If you choose the default option +"Use Recording Controller", they will be stored in the first Recording Controller found in the test object tree (so be +sure to add a Recording Controller before you start recording).

+ +

+If the Proxy does not seem to record any samples, this could be because the browser is not actually using the proxy. +To check if this is the case, try stopping the proxy. +If the browser still downloads pages, then it was not sending requests via the proxy. +Double-check the browser options. +If you are trying to record from a server running on the same host, +then check that the browser is not set to "Bypass proxy server for local addresses" +(this example is from IE7, but there will be similar options for other browsers). +If JMeter does not record browser URLs such as http://localhost/ or http://127.0.0.1/, +try using the non-loopback hostname or IP address, e.g. http://myhost/ or http://192.168.0.2/. +

+ +

Handling of HTTP Request Defaults

+

If the HTTP(S) Test Script Recorder finds enabled HTTP Request Defaults directly within the +controller where samples are being stored, or directly within any of its parent controllers, the recorded samples +will have empty fields for the default values you specified. You may further control this behaviour by placing an +HTTP Request Defaults element directly within the HTTP(S) Test Script Recorder, whose non-blank values will override +those in the other HTTP Request Defaults. See Best +Practices with the HTTP(S) Test Script Recorder for more info.

+ +

User Defined Variable replacement

+

Similarly, if the HTTP(S) Test Script Recorder finds User Defined Variables (UDV) directly within the +controller where samples are being stored, or directly within any of its parent controllers, the recorded samples +will have any occurences of the values of those variables replaced by the corresponding variable. Again, you can +place User Defined Variables directly within the HTTP(S) Test Script Recorder to override the values to be replaced. See + Best Practices with the Test Script Recorder for more info.

+ +
Please note that matching is case-sensitive.
+ +

Replacement by Variables: by default, the Proxy server looks for all occurences of UDV values. +If you define the variable WEB with the value www, for example, +the string www will be replaced by ${WEB} wherever it is found. +To avoid this happening everywhere, set the "Regex Matching" check-box. +This tells the proxy server to treat values as Regexes (using the perl5 compatible regex matchers provided by ORO).

+ +

If "Regex Matching" is selected every variable will be compiled into a perl compatible regex enclosed in +\b( and )\b. That way each match will start and end at a word boundary.

+ +
Note that the boundary characters are not part of the matching group, e.g. n.* to match name out +of You can call me 'name'.
+ +

If you don't want your regex to be enclosed with those boundary matchers, you have to enclose your +regex within parens, e.g ('.*?') to match 'name' out of You can call me 'name'.

+ +
+The variables will be checked in random order. So ensure, that the potential matches don't overlap. +Overlapping matchers would be .* (which matches anything) and www (which +matches www only). Non-overlapping matchers would be a+ (matches a sequence +of a's) and b+ (matches a sequence of b's). +
+ +

If you want to match a whole string only, enclose it in (^ and $), e.g. (^thus$). +The parens are neccessary, since the normally added boundary characters will prevent ^ and +$ to match.

+ +

If you want to match /images at the start of a string only, use the value (^/images). +Jakarta ORO also supports zero-width look-ahead, so one can match /images/... +but retain the trailing / in the output by using (^/images(?=/))".

+ +
+Note that the current version of Jakara ORO does not support look-behind - i.e. (?<=...) or (?<!...). +
+ +

Look out for overlapping matchers. For example the value .* as a regex in a variable named +regex will partly match a previous replaced variable, which will result in something like +${{regex}, which is most probably not the desired result.

+ +

If there are any problems interpreting any variables as patterns, these are reported in jmeter.log, +so be sure to check this if UDVs are not working as expected.

+ +

When you are done recording your test samples, stop the proxy server (hit the "stop" button). Remember to reset +your browser's proxy settings. Now, you may want to sort and re-order the test script, add timers, listeners, a +cookie manager, etc.

+ +

How can I record the server's responses too?

+

Just place a View Results Tree listener as a child of the HTTP(S) Test Script Recorder and the responses will be displayed. +You can also add a Save Responses to a file Post-Processor which will save the responses to files. +

+ +

Associating requests with responses

+

+If you define the property proxy.number.requests=true +JMeter will add a number to each sampler and each response. +Note that there may be more responses than samplers if excludes or includes have been used. +Responses that have been excluded will have labels enclosed in [ and ], for example [23 /favicon.ico] +

+

Cookie Manager

+

+If the server you are testing against uses cookies, remember to add an HTTP Cookie Manager to the test plan +when you have finished recording it. +During recording, the browser handles any cookies, but JMeter needs a Cookie Manager +to do the cookie handling during a test run. +The JMeter Proxy server passes on all cookies sent by the browser during recording, but does not save them to the test +plan because they are likely to change between runs. +

+

Authorization Manager

+

+The HTTP(S) Test Script Recorder grabs "Authentication" header, tries to compute the Auth Policy. If Authorization Manager was added to target +controller manually, HTTP(S) Test Script Recorder will find it and add authorization(matching ones will be removed). Otherwise +Authorization Manager will be added to target controller with authorization object. +You may have to fix automatically computed values after recording. + +

+

Uploading files

+

+Some browsers (e.g. Firefox and Opera) don't include the full name of a file when uploading files. +This can cause the JMeter proxy server to fail. +One solution is to ensure that any files to be uploaded are in the JMeter working directory, +either by copying the files there or by starting JMeter in the directory containing the files. +

+

Recording HTTP Based Non Textual Protocols not natively available in JMeter

+

+You may have to record an HTTP protocol that is not handled by default by JMeter (Custom Binary Protocol, Adobe Flex, Microsoft Silverlight... ). +Although JMeter does not provide a native proxy implementation to record these protocols, you have the ability to +record these protocols by implementing a custom SamplerCreator. This Sampler Creator will translate the binary format into a HTTPSamplerBase subclass +that can be added to the JMeter Test Case. +For more details see "Extending JMeter". +

+
+ +

HTTP Mirror Server

Screenshot for HTTP Mirror Server
+
+

+The HTTP Mirrror Server is a very simple HTTP server - it simply mirrors the data sent to it. +This is useful for checking the content of HTTP requests. +

+

+It uses default port 8081 since 2.6. +

+
+

+ Parameters +

Attribute
Description
Required
+
Port
Port on which Mirror server listens, defaults to 8081.
Yes
+
Max Number of threads
If set to a value > 0, number of threads serving requests will be limited to the configured number, if set to a value <=0 + a new thread will be created to serve each incoming request. Defaults to 0
No
+
Max Queue size
Size of queue used for holding tasks before they are executed by Thread Pool, when Thread pool is exceeded, incoming requests will + be held in this queue and discarded when this queue is full. This parameter is only used if Max Number of Threads is greater than 0. Defaults to 25
No
+
+
+Note that you can get more control over the responses by adding an HTTP Header Manager with the following name/value pairs: +
+

+ Parameters +

Attribute
Description
Required
+
X-Sleep
Time to sleep in ms before sending response
No
+
X-SetCookie
Cookies to be set on response
No
+
X-ResponseStatus
Response status, see HTTP Status responses, example 200 OK, 500 Internal Server Error ....
No
+
X-ResponseLength
Size of response, this trims the response to the requested size if that is less than the total size
No
+
X-SetHeaders
Pipe separated list of headers, example:
+ headerA=valueA|headerB=valueB would set headerA to valueA and headerB to valueB. +
No
+
+

+You can also use the following query parameters: +

+

+ Parameters +

Attribute
Description
Required
+
redirect
Generates a 302 (Temporary Redirect) with the provided location. + e.g. ?redirect=/path +
No
+
status
Overrides the default status return. e.g. ?status=404 Not Found
No
+
v
Verbose flag, writes some details to standard output. e.g. first line and redirect location if specified
No
+
+
+ +

Property Display

Screenshot for Property Display
+
+

+The Property Display shows the values of System or JMeter properties. +Values can be changed by entering new text in the Value column. +It is available only on the WorkBench. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
+
+ +

Debug Sampler

Screenshot for Debug Sampler
+
+

+The Debug Sampler generates a sample containing the values of all JMeter variables and/or properties. +

+

+The values can be seen in the View Results Tree Listener Response Data pane. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
JMeter Properties
Include JMeter properties ?
Yes
+
JMeter Variables
Include JMeter variables ?
Yes
+
System Properties
Include System properties ?
Yes
+
+
+ +

Debug PostProcessor

Screenshot for Debug PostProcessor
+
+

+The Debug PostProcessor creates a subSample with the details of the previous Sampler properties, +JMeter variables, properties and/or System Properties. +

+

+The values can be seen in the View Results Tree Listener Response Data pane. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
No
+
JMeter Properties
Whether to show JMeter properties (default false).
Yes
+
JMeter Variables
Whether to show JMeter variables (default false).
Yes
+
Sampler Properties
Whether to show Sampler properties (default true).
Yes
+
System Properties
Whether to show System properties (default false).
Yes
+
+
+ +

Test Fragment

Screenshot for Test Fragment
+
+

+The Test Fragment is used in conjunction with the Include Controller and Module Controller. +

+
+

+ Parameters +

Attribute
Description
Required
+
Name
Descriptive name for this element that is shown in the tree.
Yes
+
+
+When using Test Fragment with Module Controller, ensure you disable the Test Fragment to avoid the execution of Test Fragment itself. +This is done by default since JMeter 2.13. +
+
+ +

setUp Thread Group

Screenshot for setUp Thread Group
+
+

+ A special type of ThreadGroup that can be utilized to perform Pre-Test Actions. The behavior of these threads + is exactly like a normal Thread Group element. The difference is that these type of threads + execute before the test proceeds to the executing of regular Thread Groups. +

+
+
+ +

tearDown Thread Group

Screenshot for tearDown Thread Group
+
+

+ A special type of ThreadGroup that can be utilized to perform Post-Test Actions. The behavior of these threads + is exactly like a normal Thread Group element. The difference is that these type of threads + execute after the test has finished executing its regular Thread Groups. +

+
+
+Note that by default it won't run if Test is gracefully shutdown, if you want to make it run in this case, +ensure you check option "Run tearDown Thread Groups after shutdown of main threads" on Test Plan element. +If Test Plan is stopped, tearDown will not run even if option is checked. +
+
Figure 1 - Run tearDown Thread Groups after shutdown of main threads
Figure 1 - Run tearDown Thread Groups after shutdown of main threads
+
+ +^ + +
\ No newline at end of file diff --git a/docs/usermanual/functions.html b/docs/usermanual/functions.html new file mode 100644 index 00000000000..083b6383321 --- /dev/null +++ b/docs/usermanual/functions.html @@ -0,0 +1,1391 @@ + +Apache JMeter + - + User's Manual: Functions and Variables
Logo ASF
Apache JMeter

19. Functions and Variables

+

+JMeter functions are special values that can populate fields of any Sampler or other +element in a test tree. A function call looks like this:

+ +

${__functionName(var1,var2,var3)}

+ +

+Where "__functionName" matches the name of a function. +
+Parentheses surround the parameters sent to the function, for example ${__time(YMD)} +The actual parameters vary from function to function. +Functions that require no parameters can leave off the parentheses, for example ${__threadNum}. +

+ +

+If a function parameter contains a comma, then be sure to escape this with "\", otherwise JMeter will treat it as a parameter delimiter. +For example: +

+${__time(EEE\, d MMM yyyy)}
+
+If the comma is not escaped - e.g. ${__javaScript(Math.max(2,5))} - you will get an error such as: +
+ERROR - jmeter.functions.JavaScript: Error processing Javascript: [Math.max(2]
+    org.mozilla.javascript.EvaluatorException: missing ) after argument list (<cmd>#1)
+ 
+ This is because the string "Math.max(2,5)" is treated as being two parameters to the __javascript function:
+ Math.max(2 and 5)
+ Other error messages are possible. +

+

Variables are referenced as follows: +

+${VARIABLE}
+
+

+

+ +If an undefined function or variable is referenced, JMeter does not report/log an error - the reference is returned unchanged. +For example if UNDEF is not defined as a variable, then the value of ${UNDEF} is ${UNDEF}. + +Variables, functions (and properties) are all case-sensitive. + +Versions of JMeter after 2.3.1 trim spaces from variable names before use, so for example +${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY '. + +

+
+Properties are not the same as variables. +Variables are local to a thread; properties are common to all threads, +and need to be referenced using the __P or __property function. +
+
+When using \ before a variable for a windows path for example C:\test\${test}, ensure you escape the \ otherwise JMeter will not interpret the variable, example: +C:\\test\\${test}. +
+Alternatively, just use / instead for the path separator - e.g. C:/test/${test} - Windows JVMs will convert the separators as necessary. +
+

List of functions, loosely grouped into types.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Type of functionNameCommentSince
Information threadNumget thread number1.X
Information samplerNameget the sampler name (label)2.5
Information machineIPget the local machine IP address2.6
Information machineNameget the local machine name1.X
Information timereturn current time in various formats2.2
Information loglog (or display) a message (and return the value)2.2
Information lognlog (or display) a message (empty return value)2.2
Input StringFromFileread a line from a file1.9
Input FileToStringread an entire file2.4
Input CSVReadread from CSV delimited file1.9
Input XPathUse an XPath expression to read from a file2.0.3
Calculation countergenerate an incrementing number1.X
Calculation intSumadd int numbers1.8.1
Calculation longSumadd long numbers2.3.2
Calculation Randomgenerate a random number1.9
Calculation RandomStringgenerate a random string2.6
Calculation UUIDgenerate a random type 4 UUID2.9
Scripting BeanShellrun a BeanShell script1.X
Scripting javaScriptprocess JavaScript (Mozilla Rhino)1.9
Scripting jexl, jexl2evaluate a Commons Jexl expressionjexl(2.2), jexl2(2.6)
Properties property read a property2.0
Properties Pread a property (shorthand method)2.0
Properties setPropertyset a JMeter property2.1
Variables splitSplit a string into variables2.0.2
Variables Vevaluate a variable name2.3RC3
Variables evalevaluate a variable expression2.3.1
Variables evalVarevaluate an expression stored in a variable2.3.1
String regexFunctionparse previous response using a regular expression1.X
String escapeOroRegexpCharsquote meta chars used by ORO regular expression2.9
String chargenerate Unicode char values from a list of numbers2.3.3
String unescapeProcess strings containing Java escapes (e.g. \n & \t)2.3.3
String unescapeHtmlDecode HTML-encoded strings2.3.3
String escapeHtmlEncode strings using HTML encoding2.3.3
String urldecodeDecode a application/x-www-form-urlencoded string2.10
String urlencodeEncode a string to a application/x-www-form-urlencoded string2.10
String TestPlanNameReturn name of current test plan2.6
+

+

19.1 What can functions do

+

There are two kinds of functions: user-defined static values (or variables), and built-in functions.
+User-defined static values allow the user to define variables to be replaced with their static value when +a test tree is compiled and submitted to be run. This replacement happens once at the beginning of the test +run. This could be used to replace the DOMAIN field of all HTTP requests, for example - making it a simple +matter to change a test to target a different server with the same test. +

+

+Note that variables cannot currently be nested; i.e ${Var${N}} does not work. +The __V (variable) function (versions after 2.2) can be used to do this: ${__V(Var${N})}. +In earlier JMeter versions one can use ${__BeanShell(vars.get("Var${N}")}. +

+

This type of replacement is possible without functions, but was less convenient and less intuitive. +It required users to create default config elements that would fill in blank values of Samplers. +Variables allow one to replace only part of any given value, not just filling in blank values.

+

+With built-in functions users can compute new values at run-time based on previous response data, which +thread the function is in, the time, and many other sources. These values are generated fresh for every +request throughout the course of the test.

+
Functions are shared between threads. +Each occurrence of a function call in a test plan is handled by a separate function instance.
+
+ +

19.2 Where can functions and variables be used?

+

+Functions and variables can be written into any field of any test component (apart from the TestPlan - see below). +Some fields do not allow random strings +because they are expecting numbers, and thus will not accept a function. However, most fields will allow +functions. +

+

+Functions which are used on the Test Plan have some restrictions. +JMeter thread variables will have not been fully set up when the functions are processed, +so variable names passed as parameters will not be set up, and variable references will not work, +so split() and regex() and the variable evaluation functions won't work. +The threadNum() function won't work (and does not make sense at test plan level). +The following functions should work OK on the test plan: +

    +
  • intSum
  • +
  • longSum
  • +
  • machineName
  • +
  • BeanShell
  • +
  • javaScript
  • +
  • jexl
  • +
  • random
  • +
  • time
  • +
  • property functions
  • +
  • log functions
  • +
+

+

+Configuration elements are processed by a separate thread. +Therefore functions such as __threadNum do not work properly in elements such as User Defined Variables. +Also note that variables defined in a UDV element are not available until the element has been processed. +

+
When using variable/function references in SQL code (etc), +remember to include any necessary quotes for text strings, +i.e. use
+SELECT item from table where name='${VAR}' +
not +
+SELECT item from table where name=${VAR} +
(unless VAR itself contains the quotes) +
+
+ +

19.3 How to reference variables and functions

+

Referencing a variable in a test element is done by bracketing the variable name with '${' and '}'.

+

Functions are referenced in the same manner, but by convention, the names of +functions begin with "__" to avoid conflict with user value names*. Some functions take arguments to +configure them, and these go in parentheses, comma-delimited. If the function takes no arguments, the parentheses can +be omitted.

+ +

Argument values that themselves contain commas should be escaped as necessary. +If you need to include a comma in your parameter value, escape it like so: '\,'. +This applies for example to the scripting functions - Javascript, Beanshell, Jexl - where it is necessary to escape any commas +that may be needed in script method calls - e.g. +

+
+    ${__BeanShell(vars.put("name"\,"value"))}
+
+

+Alternatively, you can define your script as a variable, e.g. on the Test Plan: +

SCRIPT          vars.put("name","value")
+The script can then be referenced as follows: +
${__BeanShell(${SCRIPT})}
+There is no need to escape commas in the SCRIPT variable because the function call is parsed before the variable is replaced with its value. +This works well in conjunction with the BSF or BeanShell Samplers, as these can be used to test Javascript, Jexl and BeanShell scripts. +

+

+Functions can reference variables and other functions, for example +${__XPath(${__P(xpath.file),${XPATH})} +will use the property "xpath.file" as the file name +and the contents of the variable XPATH as the expression to search for. +

+

+JMeter provides a tool to help you construct +function calls for various built-in functions, which you can then copy-paste. +It will not automatically escape values for you, since functions can be parameters to other functions, and you should only escape values you intend as literal. +

+
+If a string contains a backslash('\') and also contains a function or variable reference, the backslash will be removed if +it appears before '$' or ',' or '\'. +This behaviour is necessary to allow for nested functions that include commas or the string ${. +Backslashes before '$' or ',' or '\' are not removed if the string does not contain a function or variable reference. +
+

+The value of a variable or function can be reported using the __logn() function. +The __logn() function reference can be used anywhere in the test plan after the variable has been defined. +Alternatively, the Java Request sampler can be used to create a sample containing variable references; +the output will be shown in the appropriate Listener. +For versions of JMeter later than 2.3, there is a Debug Sampler +that can be used to display the values of variables etc in the Tree View Listener. +

+
*If you define a user-defined static variable with the same name as a built-in function, your static +variable will override the built-in function.
+
+ +

19.4 The Function Helper Dialog

+

The Function Helper dialog is available from JMeter's Tools menu.

+
Function Helper Dialog
Function Helper Dialog
+

Using the Function Helper, you can select a function from the pull down, and assign +values for its arguments. The left column in the table provides a brief description of the +argument, and the right column is where you write in the value for that argument. Different +functions take different arguments.

+

Once you have done this, click the "generate" button, and the appropriate string is generated +for you to copy-paste into your test plan wherever you like.

+
+ +

19.5 Functions

+ +

__regexFunction

+

The Regex Function is used to parse the previous response (or the value of a variable) using any regular +expression (provided by user). The function returns the template string with variable values filled +in.

+

The __regexFunction can also store values for future use. In the sixth parameter, you can specify +a reference name. After this function executes, the same values can be retrieved at later times +using the syntax for user-defined values. For instance, if you enter "refName" as the sixth +parameter you will be able to use: +

    +
  • ${refName} to refer to the computed result of the second parameter ("Template for the +replacement string") parsed by this function
  • +
  • ${refName_g0} to refer to the entire match parsed by this function.
  • +
  • ${refName_g1} to refer to the first group parsed by this function.
  • +
  • ${refName_g#} to refer to the nth group parsed by this function.
  • +
  • ${refName_matchNr} to refer to the number of groups found by this function.
  • +
+
If using distributed testing, ensure you switch mode (see jmeter.properties) so that it's not a stripping one, see + Bug + 56376
+

+
+ +

+ Parameters +

Attribute
Description
Required
+
First argument
The first argument is the regular expression + to be applied to the response data. It will grab all matches. Any parts of this expression + that you wish to use in your template string, be sure to surround in parentheses. Example: + <a href="(.*)">. This will grab the value of the link and store it as the first group (there is + only 1 group). Another example: <input type="hidden" name="(.*)" value="(.*)">. This will + grab the name as the first group, and the value as the second group. These values can be used + in your template string
Yes
+
Second argument
This is the template string that will replace + the function at run-time. To refer to a group captured in the regular expression, use the syntax: + $[group_number]$. Ie: $1$, or $2$. Your template can be any string.
Yes
+
Third argument
The third argument tells JMeter which match + to use. Your regular expression might find numerous matches. You have four choices: +
  • An integer - Tells JMeter to use that match. '1' for the first found match, '2' for the + second, and so on
  • +
  • RAND - Tells JMeter to choose a match at random.
  • +
  • ALL - Tells JMeter to use all matches, and create a template string for each one and then + append them all together. This option is little used.
  • +
  • A float number between 0 and 1 - tells JMeter to find the Xth match using the formula: + (number_of_matches_found * float_number) rounded to nearest integer.
  • +
No, default=1
+
Fourth argument
If 'ALL' was selected for the above argument + value, then this argument will be inserted between each appended copy of the template value.
No
+
Fifth argument
Default value returned if no match is found
No
+
Sixth argument
A reference name for reusing the values parsed by this function.
+ Stored values are ${refName} (the replacement template string) and ${refName_g#} where "#" is the + group number from the regular expression ("0" can be used to refer to the entire match).
No
+
Seventh argument
Input variable name. + If specified, then the value of the variable is used as the input instead of using the previous sample result. +
No
+
+
+ +

__counter

+

The counter generates a new number each time it is called, starting with 1 +and incrementing by +1 each time. The counter can be configured to keep each simulated user's values +separate, or to use the same counter for all users. If each user's values is incremented separately, +that is like counting the number of iterations through the test plan. A global counter is like +counting how many times that request was run. +

+

The counter uses an integer variable to hold the count, which therefore has a maximum of 2,147,483,647.

+

The counter function instances are now completely independent. +[JMeter 2.1.1 and earlier used a fixed thread variable to keep track of the per-user count, +so multiple counter functions operated on the same value.] +The global counter - "FALSE" - is separately maintained by each counter instance. +

+

+ +Multiple __counter function calls in the same iteration won't increment the value further. + +
+If you want to have a count that increments for each sample, use the function in a Pre-Processor such as User Parameters. +

+
+

+ Parameters +

Attribute
Description
Required
+
First argument
TRUE if you wish each simulated user's counter + to be kept independent and separate from the other users. FALSE for a global counter.
Yes
+ +
Second argument
A reference name for reusing the value created by this function.
+ Stored values are of the form ${refName}. This allows you to keep one counter and refer to its value in + multiple places. [For JMeter 2.1.1 and earlier this parameter was required.]
No
+
+
+ +

__threadNum

+

The thread number function simply returns the number of the thread currently +being executed. These numbers are independent of ThreadGroup, meaning thread #1 in one threadgroup +is indistinguishable from thread #1 in another threadgroup, from the point of view of this function.

+ +

There are no arguments for this function.

+
+
+This function does not work in any Configuration elements (e.g. User Defined Variables) as these are run from a separate thread. +Nor does it make sense to use it on the Test Plan. +
+
+ +

__intSum

+
+

+The intSum function can be used to compute the sum of two or more integer values. +

+
+JMeter Versions 2.3.1 and earlier required the reference name to be present. +The reference name is now optional, but it must not be a valid integer. +
+
+ +

+ Parameters +

Attribute
Description
Required
+
First argument
The first int value.
Yes
+
Second argument
The second int value.
Yes
+
nth argument
The nth int value.
No
+
last argument
A reference name for reusing the value computed by this function. + If specified, the reference name must contain at least one non-numeric character otherwise it will + be treated as another int value to be added. +
No
+
+
+ +

__longSum

+

The longSum function can be used to compute the sum of two or more long values. +

+ +

+ Parameters +

Attribute
Description
Required
+
First argument
The first long value.
Yes
+
Second argument
The second long value.
Yes
+
nth argument
The nth long value.
No
+
last argument
A reference name for reusing the value computed by this function. + If specified, the reference name must contain at least one non-numeric character otherwise it will + be treated as another long value to be added. +
No
+
+
+ + + +

__StringFromFile

+ +
+

+ The StringFromFile function can be used to read strings from a text file. + This is useful for running tests that require lots of variable data. + For example when testing a banking application, 100s or 1000s of different account numbers might be required. +

+

+ See also the + CSV Data Set Config test element + which may be easier to use. However, that does not currently support multiple input files. +

+ +

+ Each time it is called it reads the next line from the file. + All threads share the same instance, so different threads will get different lines. + When the end of the file is reached, it will start reading again from the beginning, + unless the maximum loop count has been reached. + If there are multiple references to the function in a test script, each will open the file independently, + even if the file names are the same. + [If the value is to be used again elsewhere, use different variable names for each function call.] +

+
+ Function instances are shared between threads, and the file is (re-)opened by whatever thread + happens to need the next line of input, so using the threadNumber as part of the file name + will result in unpredictable behaviour. +
+

If an error occurs opening or reading the file, then the function returns the string "**ERR**"

+
+ +

+ Parameters +

Attribute
Description
Required
+
File Name
Path to the file name. + (The path can be relative to the JMeter launch directory) + If using optional sequence numbers, the path name should be suitable for passing to DecimalFormat. + See below for examples. +
Yes
+
Variable Name
+A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. +Defaults to "StringFromFile_". +
No
+
Start sequence number
Initial Sequence number (if omitted, the End sequence number is treated as a loop count)
No
+
End sequence number
Final sequence number (if omitted, seqence numbers can increase without limit)
No
+
+

The file name parameter is resolved when the file is opened or re-opened.

+

The reference name parameter (if supplied) is resolved every time the function is executed.

+

Using sequence numbers:

+

When using the optional sequence numbers, the path name is used as the format string for java.text.DecimalFormat. + The current sequence number is passed in as the only parameter. + If the optional start number is not specified, the path name is used as is. + Useful formatting sequences are: +

+

+ +# - insert the number, with no leading zeros or spaces
+000 - insert the number packed out to 3 digits with leading zeros if necessary +

+Examples:
+ pin#'.'dat -> pin1.dat, ... pin9.dat, pin10.dat, ... pin9999.dat
+ pin000'.'dat -> pin001.dat ... pin099.dat ... pin999.dat ... pin9999.dat
+ pin'.'dat# -> pin.dat1, ... pin.dat9 ... pin.dat999 +

+ If more digits are required than there are formatting characters, the number will be + expanded as necessary.
+ To prevent a formatting character from being interpreted, + enclose it in single quotes. Note that "." is a formatting character, + and must be enclosed in single quotes + (though #. and 000. work as expected in locales where the decimal point is also ".") +
+ In other locales (e.g. fr), the decimal point is "," - which means that "#." + becomes "nnn,".
+ See the documentation for DecimalFormat for full details.
+ If the path name does not contain any special formatting characters, + the current sequence number will be appended to the name, otherwise + the number will be inserted aaccording to the fomatting instructions.
+ If the start sequence number is omitted, and the end sequence number is specified, + the sequence number is interpreted as a loop count, and the file will be used at most "end" times. + In this case the filename is not formatted. + +
+ ${_StringFromFile(PIN#'.'DAT,,1,2)} - reads PIN1.DAT, PIN2.DAT
+ ${_StringFromFile(PIN.DAT,,,2)} - reads PIN.DAT twice
+
+ Note that the "." in PIN.DAT above should not be quoted. + In this case the start number is omitted, so the file name is used exactly as is. +

+
+ +

__machineName

+

The machineName function returns the local host name

+ +

+ Parameters +

Attribute
Description
Required
+
Variable Name
A reference name for reusing the value + computed by this function.
No
+
+
+ +

__machineIP

+

The machineIP function returns the local IP address

+ +

+ Parameters +

Attribute
Description
Required
+
Variable Name
A reference name for reusing the value + computed by this function.
No
+
+
+ +

__javaScript

+
+

+The javaScript function executes a piece of JavaScript (not Java!) code and returns its value +

+

+The JMeter Javascript function calls a standalone JavaScript interpreter. +Javascript is used as a scripting language, so you can do calculations etc.

+

+For details of the language, please see Mozilla Rhino Overview +

+

+The following variables are made available to the script: +

+
    +
  • log - the logger for the function
  • +
  • ctx - JMeterContext object
  • +
  • vars - JMeterVariables object
  • +
  • threadName - String containing the current thread name (in 2.3.2 it was misspelt as "theadName")
  • +
  • sampler - current Sampler object (if any)
  • +
  • sampleResult - previous SampleResult object (if any)
  • +
  • props - JMeter Properties object
  • +
+

+Rhinoscript allows access to static methods via its Packages object. +See the Scripting Java documentation. +For example one can access the JMeterContextService static methods thus: +Packages.org.apache.jmeter.threads.JMeterContextService.getTotalThreads() +

+
+JMeter is not a browser, and does not interpret the JavaScript in downloaded pages. +
+
+ +

+ Parameters +

Attribute
Description
Required
+
Expression
The JavaScript expression to be executed. For example: +
    +
  • new Date() - return the current date and time
  • +
  • Math.floor(Math.random()*(${maxRandom}+1)) + - a random number between 0 and the variable maxRandom
  • +
  • ${minRandom}+Math.floor(Math.random()*(${maxRandom}-${minRandom}+1)) + - a random number between the variables minRandom and maxRandom
  • +
  • "${VAR}"=="abcd"
  • +
+
Yes
+
Variable Name
A reference name for reusing the value + computed by this function.
No
+
+
Remember to include any necessary quotes for text strings and JMeter variables. Also, if +the expression has commas, please make sure to escape them. For example in: +
+${__javaScript('${sp}'.slice(7\,99999))} +
+the comma after 7 is escaped.
+
+ +

__Random

+

The random function returns a random number that lies between the given min and max values.

+ +

+ Parameters +

Attribute
Description
Required
+
Minimum value
A number
Yes
+
Maximum value
A bigger number
Yes
+
Variable Name
A reference name for reusing the value + computed by this function.
No
+
+
+ +

__RandomString

+

The RandomString function returns a random String of length using characters in chars to use.

+ +

+ Parameters +

Attribute
Description
Required
+
Length
A number length of generated String
Yes
+
Characters to use
Chars used to generate String
No
+
Variable Name
A reference name for reusing the value + computed by this function.
No
+
+
+ +

__UUID

+
+

The UUID function returns a pseudo random type 4 Universally Unique IDentifier (UUID).

+
+

+ Parameters +

Attribute
Description
Required
+
+
+ +

__CSVRead

+

The CSVRead function returns a string from a CSV file (c.f. StringFromFile)

+

NOTE: versions up to 1.9.1 only supported a single file. + JMeter versions since 1.9.1 support multiple file names. +

+

In most cases, the newer + CSV Data Set Config element + is easier to use.

+

+ When a filename is first encountered, the file is opened and read into an internal + array. If a blank line is detected, this is treated as end of file - this allows + trailing comments to be used (N.B. this feature was introduced in versions after 1.9.1) +

+

All subsequent references to the same file name use the same internal array. + N.B. the filename case is significant to the function, even if the OS doesn't care, + so CSVRead(abc.txt,0) and CSVRead(aBc.txt,0) would refer to different internal arrays. +

+

+ The *ALIAS feature allows the same file to be opened more than once, + and also allows for shorter file names. +

+

+ Each thread has its own internal pointer to its current row in the file array. + When a thread first refers to the file it will be allocated the next free row in + the array, so each thread will access a different row from all other threads. + [Unless there are more threads than there are rows in the array.] +

+

+ Note: the function splits the line at every comma by default. + If you want to enter columns containing commas, then you will need + to change the delimiter to a character that does not appear in any + column data, by setting the property: csvread.delimiter +

+
+ +

+ Parameters +

Attribute
Description
Required
+
File Name
The file (or *ALIAS) to read from
Yes
+
Column number
+ The column number in the file. + 0 = first column, 1 = second etc. + "next" - go to next line of file. + *ALIAS - open a file and assign it to the alias +
Yes
+
+

For example, you could set up some variables as follows: +

    +
  • COL1a ${__CSVRead(random.txt,0)}
  • +
  • COL2a ${__CSVRead(random.txt,1)}${__CSVRead(random.txt,next)}
  • +
  • COL1b ${__CSVRead(random.txt,0)}
  • +
  • COL2b ${__CSVRead(random.txt,1)}${__CSVRead(random.txt,next)}
  • +
+This would read two columns from one line, and two columns from the next available line. +If all the variables are defined on the same User Parameters Pre-Processor, then the lines +will be consecutive. Otherwise, a different thread may grab the next line. +

+
+The function is not suitable for use with large files, as the entire file is stored in memory. +For larger files, use CSV Data Set Config element +or StringFromFile. +
+
+ +

__property

+

The property function returns the value of a JMeter property. + If the property value cannot be found, and no default has been supplied, it returns the property name. + When supplying a default value, there is no need to provide a function name - the parameter can be set to null, and it will be ignored. +

For example:

+

    +
  • ${__property(user.dir)} - return value of user.dir
  • +
  • ${__property(user.dir,UDIR)} - return value of user.dir and save in UDIR
  • +
  • ${__property(abcd,ABCD,atod)} - return value of property abcd (or "atod" if not defined) and save in ABCD
  • +
  • ${__property(abcd,,atod)} - return value of property abcd (or "atod" if not defined) but don't save it
  • +
+

+
+ +

+ Parameters +

Attribute
Description
Required
+
Property Name
The property name to be retrieved.
Yes
+
Variable Name
A reference name for reusing the value + computed by this function.
No
+
Default Value
The default value for the property.
No
+
+
+ +

__P

+

This is a simplified property function which is + intended for use with properties defined on the command line. + Unlike the __property function, there is no option to save the value in a variable, + and if no default value is supplied, it is assumed to be 1. + The value of 1 was chosen because it is valid for common test variables such + as loops, thread count, ramp up etc. +

For example:

+ +Define the property value: +
+jmeter -Jgroup1.threads=7 -Jhostname1=www.realhost.edu +
+ +Fetch the values: +
+${__P(group1.threads)} - return the value of group1.threads +
+${__P(group1.loops)} - return the value of group1.loops +
+${__P(hostname,www.dummy.org)} - return value of property hostname or www.dummy.org if not defined +
+
+In the examples above, the first function call would return 7, +the second would return 1 and the last would return www.dummy.org +(unless those properties were defined elsewhere!) +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Property Name
The property name to be retrieved.
Yes
+
Default Value
The default value for the property. + If omitted, the default is set to "1".
No
+
+
+ +

__log

+
+

+ The log function logs a message, and returns its input string +

+
+ +

+ Parameters +

Attribute
Description
Required
+
String to be logged
A string
Yes
+
Log Level
OUT, ERR, DEBUG, INFO (default), WARN or ERROR
No
+
Throwable text
If non-empty, creates a Throwable to pass to the logger
No
+
Comment
If present, it is displayed in the string. + Useful for identifying what is being logged.
No
+
+

The OUT and ERR log level names are used to direct the output to System.out and System.err respectively. + In this case, the output is always printed - it does not depend on the current log setting. +

+
+For example:
+     ${__log(Message)} - written to the log file as   "...thread Name : Message"
+     ${__log(Message,OUT)} - written to console window
+     ${__log(${VAR},,,VAR=)} - written to log file as "...thread Name VAR=value"
+
+
+ +

__logn

+
+

+ The logn function logs a message, and returns the empty string +

+
+ +

+ Parameters +

Attribute
Description
Required
+
String to be logged
A string
Yes
+
Log Level
OUT, ERR, DEBUG, INFO (default), WARN or ERROR
No
+
Throwable text
If non-empty, creates a Throwable to pass to the logger
No
+
+

The OUT and ERR log level names are used to direct the output to System.out and System.err respectively. + In this case, the output is always printed - it does not depend on the current log setting. +

+
+For example:
+     ${__logn(VAR1=${VAR1},OUT)} - write the value of the variable to the console window
+
+
+ +

__BeanShell

+
+

+ The BeanShell function evaluates the script passed to it, and returns the result. +

+

+For full details on using BeanShell, please see the BeanShell web-site at http://www.beanshell.org/ + +

+

+Note that a different Interpreter is used for each independent occurence of the function +in a test script, but the same Interpreter is used for subsequent invocations. +This means that variables persist across calls to the function. +

+

+A single instance of a function may be called from multiple threads. +However the function execute() method is synchronised. +

+

+If the property "beanshell.function.init" is defined, it is passed to the Interpreter +as the name of a sourced file. This can be used to define common methods and variables. There is a +sample init file in the bin directory: BeanShellFunction.bshrc. +

+

+The following variables are set before the script is executed: +

    +
  • log - the logger for the BeanShell function (*)
  • +
  • ctx - the current JMeter context variable
  • +
  • vars - the current JMeter variables
  • +
  • props - JMeter Properties object
  • +
  • threadName - the threadName (String)
  • +
  • Sampler the current Sampler, if any
  • +
  • SampleResult - the current SampleResult, if any
  • +
+(*) means that this is set before the init file, if any, is processed. +Other variables vary from invocation to invocation. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
BeanShell script
A beanshell script (not a file name)
Yes
+
Name of variable
A reference name for reusing the value + computed by this function.
No
+ +
+

+Example: +

+${__BeanShell(123*456)} - returns 56088
+${__BeanShell(source("function.bsh"))} - processes the script in function.bsh
+
+

+
+Remember to include any necessary quotes for text strings and JMeter variables that represent text strings. +
+
+ +

__split

+
+

+ The split function splits the string passed to it according to the delimiter, + and returns the original string. If any delimiters are adjacent, "?" is returned as the value. + The split strings are returned in the variables ${VAR_1}, ${VAR_2} etc. + The count of variables is returned in ${VAR_n}. + From JMeter 2.1.2 onwards, a trailing delimiter is treated as a missing variable, and "?" is returned. + Also, to allow it to work better with the ForEach controller, + __split now deletes the first unused variable in case it was set by a previous split. +

+

+ Example: + +
+ Define VAR="a||c|" in the test plan. +
+ ${__split(${VAR},VAR,|)} +
+ This will return the contents of VAR, i.e. "a||c|" and set the following variables: +
+ VAR_n=4 (3 in JMeter 2.1.1 and earlier) +
+ VAR_1=a +
+ VAR_2=? +
+ VAR_3=c +
+ VAR_4=? (null in JMeter 2.1.1 and earlier) +
+ VAR_5=null (in JMeter 2.1.2 and later) +
+
+ +

+ Parameters +

Attribute
Description
Required
+
String to split
A delimited string, e.g. "a|b|c"
Yes
+
Name of variable
A reference name for reusing the value + computed by this function.
Yes
+
Delimiter
The delimiter character, e.g. |. +If omitted, , is used. Note that , would need to be specified as \,. +
No
+ +
+
+

__XPath

+
+

+ The XPath function reads an XML file and matches the XPath. + Each time the function is called, the next match will be returned. + At end of file, it will wrap around to the start. + If no nodes matched, then the function will return the empty string, + and a warning message will be written to the JMeter log file. +

Note that the entire NodeList is held in memory.
+

+

+ Example: + +
+ +
+ ${__XPath(/path/to/build.xml, //target/@name)} +
+ This will match all targets in build.xml and return the contents of the next name attribute +
+
+ +

+ Parameters +

Attribute
Description
Required
+
XML file to parse
a XML file to parse
Yes
+
XPath
a XPath expression to match nodes in the XML file
Yes
+
+
+ +

__setProperty

+
+

The setProperty function sets the value of a JMeter property. + The default return value from the function is the empty string, + so the function call can be used anywhere functions are valid.

+

The original value can be returned by setting the optional 3rd parameter to "true".

+

Properties are global to JMeter, + so can be used to communicate between threads and thread groups

+
+ +

+ Parameters +

Attribute
Description
Required
+
Property Name
The property name to be set.
Yes
+
Property Value
The value for the property.
Yes
+
True/False
Should the original value be returned?
No
+
+
+ + +

__time

+
+

The time function returns the current time in various formats.

+
+ +

+ Parameters +

Attribute
Description
Required
+
Format
+ The format to be passed to SimpleDateFormat. + The function supports various shorthand aliases, see below. + If ommitted, the function returns the current time in milliseconds since the epoch. +
No
+
Name of variable
The name of the variable to set.
No
+
+

If the format string is omitted, then the function returns the current time in milliseconds since the epoch. +In versions of JMeter after 2.7, if the format matches "/ddd" (where ddd are decimal digits), +then the function returns the current time in milliseconds divided by the value of ddd. +For example, "/1000" returns the current time in seconds since the epoch. +Otherwise, the current time is passed to SimpleDateFormat. +The following shorthand aliases are provided: +

+
    +
  • YMD = yyyyMMdd
  • +
  • HMS = HHmmss
  • +
  • YMDHMS = yyyyMMdd-HHmmss
  • +
  • USER1 = whatever is in the Jmeter property time.USER1
  • +
  • USER2 = whatever is in the Jmeter property time.USER2
  • +
+

The defaults can be changed by setting the appropriate JMeter property, e.g. +time.YMD=yyMMdd +

+
+ +

__jexl

+
+

The jexl function returns the result of evaluating a + Commons JEXL expression. + See links below for more information on JEXL expressions. +

+

The __jexl function uses Commons JEXL 1, and the __jexl2 function uses Commons JEXL 2

+ +
+ +

+ Parameters +

Attribute
Description
Required
+
Expression
+ The expression to be evaluated. For example, 6*(5+2) +
Yes
+
Name of variable
The name of the variable to set.
No
+
+

+The following variables are made available to the script: +

+
    +
  • log - the logger for the function
  • +
  • ctx - JMeterContext object
  • +
  • vars - JMeterVariables object
  • +
  • props - JMeter Properties object
  • +
  • threadName - String containing the current thread name (in 2.3.2 it was misspelt as "theadName")
  • +
  • sampler - current Sampler object (if any)
  • +
  • sampleResult - previous SampleResult object (if any)
  • +
  • OUT - System.out - e.g. OUT.println("message")
  • +
+

+ Jexl can also create classes and call methods on them, for example: +

+

+ + Systemclass=log.class.forName("java.lang.System");
+ now=Systemclass.currentTimeMillis(); +
+ Note that the Jexl documentation on the web-site wrongly suggests that "div" does integer division. + In fact "div" and "/" both perform normal division. One can get the same effect + as follows: + + i= 5 / 2; + i.intValue(); // or use i.longValue() + +

+
Versions of JMeter after 2.3.2 allow the expression to contain multiple statements. + JMeter 2.3.2 and earlier only processed the first statement (if there were multiple statements a warning was logged). +
+
+ +

__V

+
+

The V (variable) function returns the result of evaluating a variable name expression. + This can be used to evaluate nested variable references (which are not currently supported). +

+

For example, if one has variables A1,A2 and N=1:

+
    +
  • ${A1} - works OK
  • +
  • ${A${N}} - does not work (nested variable reference)
  • +
  • ${__V(A${N})} - works OK. A${N} becomes A1, and the __V function returns the value of A1
  • +
+
+ +

+ Parameters +

Attribute
Description
Required
+
Variable name
+ The variable to be evaluated. +
Yes
+
+
+ +

__evalVar

+
+

The eval function returns the result of evaluating an expression stored in a variable. +

+

+ This allows one to read a string from a file, and process any variable references in it. + For example, if the variable "query" contains "select ${column} from ${table}" + and "column" and "table" contain "name" and "customers", then ${__evalVar(query)} + will evaluate as "select name from customers". +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Variable name
+ The variable to be evaluated. +
Yes
+
+
+

__eval

+
+

The eval function returns the result of evaluating a string expression. +

+

+ This allows one to interpolate variable and function references in a string + which is stored in a variable. For example, given the following variables: +

    +
  • name=Smith
  • +
  • column=age
  • +
  • table=birthdays
  • +
  • SQL=select ${column} from ${table} where name='${name}'
  • +
+ then ${__eval(${SQL})} will evaluate as "select age from birthdays where name='Smith'". +

+

+ This can be used in conjunction with CSV Dataset, for example + where the both SQL statements and the values are defined in the data file. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Variable name
+ The variable to be evaluated. +
Yes
+
+
+ +

__char

+
+

+ The char function returns the result of evaluating a list of numbers as Unicode characters. + See also __unescape(), below. +

+

+ This allows one to add arbitrary character values into fields. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Unicode character number (decimal or 0xhex)
+ The decimal number (or hex number, if prefixed by 0x, or octal, if prefixed by 0) to be converted to a Unicode character. +
Yes
+
+

Examples: +
+${__char(13,10)} = ${__char(0xD,0xA)} = ${__char(015,012)} = CRLF +
+${__char(165)} = ¥ (yen) +

+
+ +

__unescape

+
+

+ The unescape function returns the result of evaluating a Java-escaped string. See also __char() above. +

+

+ This allows one to add characters to fields which are otherwise tricky (or impossible) to define via the GUI. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
String to unescape
+ The string to be unescaped. +
Yes
+
+

Examples: +
+${__unescape(\r\n)} = CRLF +
+${__unescape(1\t2)} = 1[tab]2 +

+
+ +

__unescapeHtml

+
+

+Function to unescape a string containing HTML entity escapes +to a string containing the actual Unicode characters corresponding to the escapes. +Supports HTML 4.0 entities. +

+

+For example, the string "&lt;Fran&ccedil;ais&gt;" +will become "<Français>". +

+

+If an entity is unrecognized, it is left alone, and inserted verbatim into the result string. +e.g. ">&zzzz;x" will become ">&zzzz;x". +

+

+ Uses StringEscapeUtils#unescapeHtml(String) from Commons Lang. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
String to unescape
+ The string to be unescaped. +
Yes
+
+
+ +

__escapeHtml

+
+

+Function which escapes the characters in a String using HTML entities. +Supports HTML 4.0 entities. +

+

+For example,"bread" & "butter" +becomes: +&quot;bread&quot; &amp; &quot;butter&quot;. +

+

+ Uses StringEscapeUtils#escapeHtml(String) from Commons Lang. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
String to escape
+ The string to be escaped. +
Yes
+
+
+ +

__urldecode

+
+

+Function to decode a application/x-www-form-urlencoded string. +Note: use UTF-8 as the encoding scheme. +

+

+For example, the string Word+%22school%22+is+%22%C3%A9cole%22+in+french would get converted to + Word "school" is "école" in french. +

+

+ Uses Java class URLDecoder. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
String to decode
+ The string with URL encoded chars to decode. +
Yes
+
+
+ +

__urlencode

+
+

+Function to encode a string to a application/x-www-form-urlencoded string. +

+

+For example, the string Word "school" is "école" in french would get converted to + Word+%22school%22+is+%22%C3%A9cole%22+in+french. +

+

+ Uses Java class URLEncoder. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
String to encode
+ String to encode in URL encoded chars. +
Yes
+
+
+ +

__FileToString

+ +
+

+ The FileToString function can be used to read an entire file. + Each time it is called it reads the entire file. +

+

If an error occurs opening or reading the file, then the function returns the string "**ERR**"

+
+ +

+ Parameters +

Attribute
Description
Required
+
File Name
Path to the file name. + (The path can be relative to the JMeter launch directory) +
Yes
+
File encoding if not the platform default
+ The encoding to be used to read the file. If not specified, the platform default is used. +
No
+
Variable Name
+A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. +
No
+
+

The file name, encoding and reference name parameters are resolved every time the function is executed.

+
+ +

__samplerName

+ +
+

+ The samplerName function returns the name (i.e. label) of the current sampler. +

+

+ The function does not work in Test elements that don't have an associated sampler. + For example the Test Plan. + Configuration elements also don't have an associated sampler. + However some Configuration elements are referenced directly by samplers, such as the HTTP Header Manager + and Http Cookie Manager, and in this case the functions are resolved in the context of the Http Sampler. + Pre-Processors, Post-Processors and Assertions always have an associated Sampler. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
Variable Name
+A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. +
No
+
+
+ +

__TestPlanName

+ +
+

+ The TestPlanName function returns the name of the current test plan (can be used in Including Plans to know the name of the calling test plan). +

+
+
+ +

__escapeOroRegexpChars

+
+

+Function which escapes the ORO Regexp meta characters, it is the equivalent of \Q \E in Java Regexp Engine. +

+

+For example,[^"].+? +becomes: +\[\^\"\]\.\+\?. +

+

+ Uses Perl5Compiler#quotemeta(String) from ORO. +

+
+ +

+ Parameters +

Attribute
Description
Required
+
String to escape
+ The string to be escaped. +
Yes
+
Variable Name
+A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. +
No
+
+
+ +
+ +

19.6 Pre-defined Variables

+

+Most variables are set by calling functions or by test elements such as User Defined Variables; +in which case the user has full control over the variable name that is used. +However some variables are defined internally by JMeter. These are listed below. +

+
    +
  • COOKIE_cookiename - contains the cookie value (see HTTP Cookie Manager)
  • +
  • JMeterThread.last_sample_ok - whether or not the last sample was OK - true/false. +Note: this is updated after PostProcessors and Assertions have been run. +
  • +
  • START variables (see next section)
  • +
+
+

19.6 Pre-defined Properties

+

+The set of JMeter properties is initialised from the system properties defined when JMeter starts; +additional JMeter properties are defined in jmeter.properties, user.properties or on the command line. +

+

+Some built-in properties are defined by JMeter. These are listed below. +For convenience, the START properties are also copied to variables with the same names. +

+
    +
  • START.MS - JMeter start time in milliseconds
  • +
  • START.YMD - JMeter start time as yyyyMMdd
  • +
  • START.HMS - JMeter start time as HHmmss
  • +
  • TESTSTART.MS - test start time in milliseconds
  • +
+

+Please note that the START variables / properties represent JMeter startup time, not the test start time. +They are mainly intended for use in file names etc. +

+
+
\ No newline at end of file diff --git a/docs/usermanual/get-started.html b/docs/usermanual/get-started.html new file mode 100644 index 00000000000..e6adb49910f --- /dev/null +++ b/docs/usermanual/get-started.html @@ -0,0 +1,627 @@ + +Apache JMeter + - + User's Manual: Getting Started
Logo ASF
Apache JMeter

2. Getting Started

+

The easiest way to begin using JMeter is to first +download the latest production release and install it. +The release contains all of the files you need to build and run most types of tests, +e.g. Web (HTTP/HTTPS), FTP, JDBC, LDAP, Java, JUnit and more.

+

If you want to perform JDBC testing, +then you will, of course, need the appropriate JDBC driver from your vendor. JMeter does not come with +any JDBC drivers.

+

+JMeter includes the JMS API jar, but does not include a JMS client implementation. +If you want to run JMS tests, you will need to download the appropriate jars from the JMS provider. +

+
+See the JMeter Classpath section for details on installing additional jars. +
+

Next, start JMeter and go through the Building a Test Plan section +of the User Guide to familiarize yourself with JMeter basics (for example, adding and removing elements).

+

Finally, go through the appropriate section on how to build a specific type of Test Plan. +For example, if you are interested in testing a Web application, then see the section +Building a Web Test Plan. +The other specific Test Plan sections are: +

+

+

Once you are comfortable with building and running JMeter Test Plans, you can look into the +various configuration elements (timers, listeners, assertions, and others) which give you more control +over your Test Plans.

+ +

2.1 Requirements

+

JMeter requires that your computing environment meets some minimum requirements.

+ +

2.1.1 Java Version

+
JMeter requires a fully compliant JVM 6 or higher. +
+

Because JMeter uses only standard Java APIs, please do not file bug reports if your JRE fails to run +JMeter because of JRE implementation issues.

+
+ +

2.1.2 Operating Systems

+

JMeter is a 100% Java application and should run correctly on any system +that has a compliant Java implementation.

+

Operating systems tested with JMeter can be viewed on +this page +on JMeter wiki.

+

Even if your OS is not listed on the wiki page, JMeter should run on it provided that the JVM is compliant.

+
+

2.2 Optional

+

If you plan on doing JMeter development, then you will need one or more optional packages listed below.

+ + +

2.2.1 Java Compiler

+

If you want to build the JMeter source or develop JMeter plugins, then you will need a fully compliant JDK 6 or higher.

+
+ +

2.2.2 SAX XML Parser

+

JMeter comes with Apache's Xerces XML parser. You have the option of telling JMeter +to use a different XML parser. To do so, include the classes for the third-party parser in JMeter's classpath, +and update the jmeter.properties file with the full classname of the parser +implementation.

+
+ +

2.2.3 Email Support

+

JMeter has extensive Email capabilities. +It can send email based on test results, and has a POP3(S)/IMAP(S) sampler. +It also has an SMTP(S) sampler. +

+
+ +

2.2.4 SSL Encryption

+

To test a web server using SSL encryption (HTTPS), JMeter requires that an +implementation of SSL be provided, as is the case with Sun Java 1.4 and above. +If your version of Java does not include SSL support, then it is possible to add an external implementation. +Include the necessary encryption packages in JMeter's classpath. +Also, update system.properties to register the SSL Provider.

+

+JMeter HTTP defaults to protocol level TLS. This can be changed by editting the JMeter property +https.default.protocol in jmeter.properties or user.properties. +

+

The JMeter HTTP samplers are configured to accept all certificates, +whether trusted or not, regardless of validity periods, etc. +This is to allow the maximum flexibility in testing servers.

+

If the server requires a client certificate, this can be provided.

+

There is also the SSL Manager, for greater control of certificates.

+
The JMeter proxy server (see below) supports recording HTTPS (SSL)
+

+The SMTP sampler can optionally use a local trust store or trust all certificates. +

+
+ +

2.2.5 JDBC Driver

+

You will need to add your database vendor's JDBC driver to the classpath if you want to do JDBC testing. +Make sure the file is a jar file, not a zip. +

+
+ +

2.2.6 JMS client

+

+JMeter now includes the JMS API from Apache Geronimo, so you just need to add the appropriate JMS Client implementation +jar(s) from the JMS provider. Please refer to their documentation for details. +There may also be some information on the JMeter Wiki. +

+
+ +

2.2.7 Libraries for ActiveMQ JMS

+

+You will need to add the jar activemq-all-X.X.X.jar to your classpath, e.g. by storing it in the lib/ directory. +

+

+The other required jars (such as commons-logging) are already included with JMeter. +

+

+See ActiveMQ initial configuration page +for details. +

+
+ +
+See the JMeter Classpath section for more details on installing additional jars. +
+

2.3 Installation

+ +

We recommend that most users run the latest release.

+

To install a release build, simply unzip the zip/tar file into the directory +where you want JMeter to be installed. Provided that you have a JRE/JDK correctly installed +and the JAVA_HOME environment variable set, there is nothing more for you to do.

+

+Note: there can be problems (especially with client-server mode) if the directory path contains any spaces. +

+

+The installation directory structure should look something like this (where X.Y is version number): +

+apache-jmeter-X.Y
+apache-jmeter-X.Y/bin
+apache-jmeter-X.Y/docs
+apache-jmeter-X.Y/extras
+apache-jmeter-X.Y/lib/
+apache-jmeter-X.Y/lib/ext
+apache-jmeter-X.Y/lib/junit
+apache-jmeter-X.Y/licenses
+apache-jmeter-X.Y/printable_docs
+
+You can rename the parent directory (i.e. apache-jmeter-X.Y) if you want, but do not change any of the sub-directory names. +

+

2.4 Running JMeter

+
+

To run JMeter, run the jmeter.bat (for Windows) or jmeter (for Unix) file. +These files are found in the bin directory. +After a short time, the JMeter GUI should appear. +

+ +

+There are some additional scripts in the bin directory that you may find useful. +Windows script files (the .CMD files require Win2K or later): +

    +
  • jmeter.bat - run JMeter (in GUI mode by default)
  • +
  • jmeterw.cmd - run JMeter without the windows shell console (in GUI mode by default)
  • +
  • jmeter-n.cmd - drop a JMX file on this to run a non-GUI test
  • +
  • jmeter-n-r.cmd - drop a JMX file on this to run a non-GUI test remotely
  • +
  • jmeter-t.cmd - drop a JMX file on this to load it in GUI mode
  • +
  • jmeter-server.bat - start JMeter in server mode
  • +
  • mirror-server.cmd - runs the JMeter Mirror Server in non-GUI mode
  • +
  • shutdown.cmd - Run the Shutdown client to stop a non-GUI instance gracefully
  • +
  • stoptest.cmd - Run the Shutdown client to stop a non-GUI instance abruptly
  • +
+Note: the special name LAST can be used with jmeter-n.cmd, jmeter-t.cmd and jmeter-n-r.cmd +and means the last test plan that was run interactively. +

+ +

+The environment variable JVM_ARGS can be used to override JVM settings in the jmeter.bat script. +For example: +

+set JVM_ARGS="-Xms1024m -Xmx1024m -Dpropname=propvalue"
+jmeter -t test.jmx ...
+
+

+ +

+Un*x script files; should work on most Linux/Unix systems: +

    +
  • jmeter - run JMeter (in GUI mode by default). Defines some JVM settings which may not work for all JVMs.
  • +
  • jmeter-server - start JMeter in server mode (calls jmeter script with appropriate parameters)
  • +
  • jmeter.sh - very basic JMeter script (You may need to adapt JVM options like memory settings).
  • +
  • mirror-server.sh - runs the JMeter Mirror Server in non-GUI mode
  • +
  • shutdown.sh - Run the Shutdown client to stop a non-GUI instance gracefully
  • +
  • stoptest.sh - Run the Shutdown client to stop a non-GUI instance abruptly
  • +
+

+

+It may be necessary to edit the jmeter shell script if some of the JVM options are not supported +by the JVM you are using. +The JVM_ARGS environment variable can be used to override or set additional JVM options, for example: +

+JVM_ARGS="-Xms1024m -Xmx1024m" jmeter -t test.jmx [etc.]
+
+will override the HEAP settings in the script. +

+

2.4.1 JMeter's Classpath

+

JMeter automatically finds classes from jars in the following directories:

+
    +
  • JMETER_HOME/lib - used for utility jars
  • +
  • JMETER_HOME/lib/ext - used for JMeter components and plugins
  • +
+

If you have developed new JMeter components, +then you should jar them and copy the jar into JMeter's lib/ext directory. +JMeter will automatically find JMeter components in any jars found here. +Do not use lib/ext for utility jars or dependency jars used by the plugins; +it is only intended for JMeter components and plugins. +

+

If you don't want to put JMeter plugin jars in the lib/ext directory, +then define the property search_paths in jmeter.properties. +

+

Utility and dependency jars (libraries etc) can be placed in the lib directory.

+

If you don't want to put such jars in the lib directory, +then define the property user.classpath or plugin_dependency_paths +in jmeter.properties. See below for an explanation of the differences. +

+

+Other jars (such as JDBC, JMS implementations and any other support libaries needed by the JMeter code) +should be placed in the lib directory - not the lib/ext directory, +or added to user.classpath.

+

Note: JMeter will only find .jar files, not .zip.

+

You can also install utility Jar files in $JAVA_HOME/jre/lib/ext, or you can set the property user.classpath in jmeter.properties

+

Note that setting the CLASSPATH environment variable will have no effect. +This is because JMeter is started with "java -jar", +and the java command silently ignores the CLASSPATH variable, and the -classpath/-cp options when -jar is used. +[This occurs with all Java programs, not just JMeter.]

+
+ +

2.4.2 Create Test Plan from Template

+

You have the ability to create a new Test Plan from existing template.

+

To do so you use the menu File > Templates... or Templates icon: +

Templates icon item
Templates icon item
+

+

A popup appears, you can then choose a template among the list: +

Templates popup
Templates popup
+

+

A documentation for each template explains what to do once test plan is created from template.

+
+ +

2.4.3 Using JMeter behing a proxy

+

If you are testing from behind a firewall/proxy server, you may need to provide JMeter with +the firewall/proxy server hostname and port number. To do so, run the jmeter[.bat] file +from a command line with the following parameters:

+

+-H [proxy server hostname or ip address]
+-P [proxy server port]
+-N [nonproxy hosts] (e.g. *.apache.org|localhost)
+-u [username for proxy authentication - if required]
+-a [password for proxy authentication - if required]
+

+

Example: jmeter -H my.proxy.server -P 8000 -u username -a password -N localhost

+

You can also use --proxyHost, --proxyPort, --username, and --password as parameter names

+
+Parameters provided on a command-line may be visible to other users on the system. +
+

+If the proxy host and port are provided, then JMeter sets the following System properties: +

    +
  • http.proxyHost
  • +
  • http.proxyPort
  • +
  • https.proxyHost
  • +
  • https.proxyPort
  • +
+If a nonproxy host list is provided, then JMeter sets the following System properties: +
    +
  • http.nonProxyHosts
  • +
  • https.nonProxyHosts
  • +
+So if you don't wish to set both http and https proxies, +you can define the relevant properties in system.properties instead of using the command-line parameters. +

+

+Proxy Settings can also be defined in a Test Plan, using either the HTTP Request Defaults +configuration or the HTTP Request sampler elements. +

+
JMeter also has its own in-built Proxy Server, the HTTP(S) Test Script Recorder. +This is only used for recording HTTP or HTTPS browser sessions. +This is not to be confused with the proxy settings described above, which are used when JMeter makes HTTP or HTTPS requests itself.
+
+ +

2.4.4 Non-GUI Mode (Command Line mode)

+

For non-interactive testing, you may choose to run JMeter without the GUI. To do so, use +the following command options:

+

+-n This specifies JMeter is to run in non-gui mode
+-t [name of JMX file that contains the Test Plan].
+-l [name of JTL file to log sample results to].
+-j [name of JMeter run log file].
+-r Run the test in the servers specified by the JMeter property "remote_hosts"
+-R [list of remote servers] Run the test in the specified remote servers +

+

The script also lets you specify the optional firewall/proxy server information:

+

+-H [proxy server hostname or ip address]
+-P [proxy server port] +

+

Example: jmeter -n -t my_test.jmx -l log.jtl -H my.proxy.server -P 8000

+

+If the property jmeterengine.stopfail.system.exit is set to true (default is false), +then JMeter will invoke System.exit(1) if it cannot stop all threads. +Normally this is not necessary. +

+
+ +

2.4.5 Server Mode

+

For distributed testing, run JMeter in server mode on the remote node(s), and then control the server(s) from the GUI. +You can also use non-GUI mode to run remote tests. +To start the server(s), run jmeter-server[.bat] on each server host.

+

The script also lets you specify the optional firewall/proxy server information:

+

-H [proxy server hostname or ip address]
+-P [proxy server port]

+

Example: jmeter-server -H my.proxy.server -P 8000

+

If you want the server to exit after a single test has been run, then define the JMeter property server.exitaftertest=true. +

+

To run the test from the client in non-GUI mode, use the following command:

+
+jmeter -n -t testplan.jmx -r [-Gprop=val] [-Gglobal.properties] [-X]
+where:
+-G is used to define JMeter properties to be set in the servers
+-X means exit the servers at the end of the test
+-Rserver1,server2 - can be used instead of -r to provide a list of servers to start
+  Overrides remote_hosts, but does not define the property.
+
+

+If the property jmeterengine.remote.system.exit is set to true (default is false), +then JMeter will invoke System.exit(0) after stopping RMI at the end of a test. +Normally this is not necessary. +

+
+ +

2.4.6 Overriding Properties Via The Command Line

+

Java system properties, JMeter properties, and logging properties can be overriden directly on the command line (instead of modifying jmeter.properties). +To do so, use the following options:

+

+-D[prop_name]=[value] - defines a java system property value.
+-J[prop name]=[value] - defines a local JMeter property.
+-G[prop name]=[value] - defines a JMeter property to be sent to all remote servers.
+-G[propertyfile] - defines a file containing JMeter properties to be sent to all remote servers.
+-L[category]=[priority] - overrides a logging setting, setting a particular category to the given priority level. +

+

The -L flag can also be used without the category name to set the root logging level.

+

Examples: +

+jmeter -Duser.dir=/home/mstover/jmeter_stuff \
+    -Jremote_hosts=127.0.0.1 -Ljmeter.engine=DEBUG
+
+jmeter -LDEBUG
+

+

+N.B.
+ The command line properties are processed early in startup, but after the logging system has been set up. + Attempts to use the -J flag to update log_level or log_file properties will have no effect.
+

+
+

2.4.7 Logging and error messages

+
+ JMeter does not generally use pop-up dialog boxes for errors, as these would interfere with + running tests. Nor does it report any error for a mis-spelt variable or function; instead the + reference is just used as is. See Functions and Variables for more information. +
+

If JMeter detects an error during a test, a message will be written to the log file. + The log file name is defined in the jmeter.properties file (or using the -j option, see below). + It defaults to jmeter.log, and will be found in the directory from which JMeter was launched. +

+

+ The menu Options > Log Viewer displays the log file in a bottom pane on main JMeter window. +

+

+ In the GUI mode, the number of error/fatal messages logged in the log file is displayed at top-right. +

+
Error/fatal counter
Error/fatal counter
+

+ The command-line option -j jmeterlogfile allow to process + after the initial properties file is read, + and before any further properties are processed. + It therefore allows the default of jmeter.log to be overridden. + The jmeter scripts that take a test plan name as a parameter (e.g. jmeter-n.cmd) have been updated + to define the log file using the test plan name, + e.g. for the test plan Test27.jmx the log file is set to Test27.log. +

+

When running on Windows, the file may appear as just jmeter unless you have set Windows to show file extensions. + [Which you should do anyway, to make it easier to detect viruses and other nasties that pretend to be text files...] +

+

As well as recording errors, the jmeter.log file records some information about the test run. For example:

+
+
+10/17/2003 12:19:20 PM INFO  - jmeter.JMeter: Version 1.9.20031002 
+10/17/2003 12:19:45 PM INFO  - jmeter.gui.action.Load: Loading file: c:\mytestfiles\BSH.jmx 
+10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Running the test! 
+10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Starting 1 threads for group BSH. Ramp up = 1. 
+10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Continue on error 
+10/17/2003 12:19:52 PM INFO  - jmeter.threads.JMeterThread: Thread BSH1-1 started 
+10/17/2003 12:19:52 PM INFO  - jmeter.threads.JMeterThread: Thread BSH1-1 is done 
+10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Test has ended
+
+
+

The log file can be helpful in determining the cause of an error, + as JMeter does not interrupt a test to display an error dialogue.

+
+

2.4.8 Full list of command-line options

+

Invoking JMeter as "jmeter -?" will print a list of all the command-line options. +These are shown below.

+
+        -h, --help
+                print usage information and exit
+        -v, --version
+                print the version information and exit
+        -p, --propfile {argument}
+                the jmeter property file to use
+        -q, --addprop {argument}
+                additional property file(s)
+        -t, --testfile {argument}
+                the jmeter test(.jmx) file to run
+        -j, --jmeterlogfile {argument}
+                the jmeter log file
+        -l, --logfile {argument}
+                the file to log samples to
+        -n, --nongui
+                run JMeter in nongui mode
+        -s, --server
+                run the JMeter server
+        -H, --proxyHost {argument}
+                Set a proxy server for JMeter to use
+        -P, --proxyPort {argument}
+                Set proxy server port for JMeter to use
+        -u, --username {argument}
+                Set username for proxy server that JMeter is to use
+        -a, --password {argument}
+                Set password for proxy server that JMeter is to use
+        -J, --jmeterproperty {argument}={value}
+                Define additional JMeter properties
+        -G, --globalproperty (argument)[=(value)]
+                Define Global properties (sent to servers)
+                e.g. -Gport=123
+                 or -Gglobal.properties
+        -D, --systemproperty {argument}={value}
+                Define additional System properties
+        -S, --systemPropertyFile {filename}
+                a property file to be added as System properties
+        -L, --loglevel {argument}={value}
+                Define loglevel: [category=]level 
+                e.g. jorphan=INFO or jmeter.util=DEBUG
+        -r, --runremote (non-GUI only)
+                Start remote servers (as defined by the jmeter property remote_hosts)
+        -R, --remotestart  server1,... (non-GUI only)
+                Start these remote servers (overrides remote_hosts)
+        -d, --homedir {argument}
+                the jmeter home directory to use
+        -X, --remoteexit
+                Exit the remote servers at end of test (non-GUI)
+
+

+Note: the JMeter log file name is formatted as a SimpleDateFormat (applied to the current date) +if it contains paired single-quotes, .e.g. 'jmeter_'yyyyMMddHHmmss'.log' +

+

+If the special name LAST is used for the -t, -j or -l flags, then JMeter takes that to mean the last test plan +that was run in interactive mode. +

+
+ +

2.4.9 non-GUI shutdown

+

+Prior to version 2.5.1, JMeter invoked System.exit() when a non-GUI test completed. +This caused problems for applications that invoke JMeter directly, so JMeter no longer invokes System.exit() +for a normal test completion. [Some fatal errors may still invoke System.exit()] +JMeter will exit all the non-daemon threads it starts, but it is possible that some non-daemon threads +may still remain; these will prevent the JVM from exitting. +To detect this situation, JMeter starts a new daemon thread just before it exits. +This daemon thread waits a short while; if it returns from the wait, then clearly the +JVM has not been able to exit, and the thread prints a message to say why. +

+

+The property jmeter.exit.check.pause can be used to override the default pause of 2000ms (2secs). +If set to 0, then JMeter does not start the daemon thread. +

+
+ +

2.5 Configuring JMeter

+

If you wish to modify the properties with which JMeter runs you need to + either modify the user.properties in the /bin directory or create + your own copy of the jmeter.properties and specify it in the command line. +

+
+ Note: You can define additional JMeter properties in the file defined by the + JMeter property user.properties which has the default value user.properties. + The file will be automatically loaded if it is found in the current directory + or if it is found in the JMeter bin directory. + Similarly, system.properties is used to update system properties. +
+

+ Parameters +

Attribute
Description
Required
+
ssl.provider
You can specify the class for your SSL + implementation if you don't want to use the built-in Java implementation. +
+ No +
+
xml.parser
You can specify an implementation as your XML + parser. The default value is: org.apache.xerces.parsers.SAXParser
+ No +
+
remote_hosts
Comma-delimited list of remote JMeter hosts (or host:port if required). + If you are running JMeter in a distributed environment, list the machines where + you have JMeter remote servers running. This will allow you to control those + servers from this machine's GUI
+ No +
+
not_in_menu
A list of components you do not want to see in + JMeter's menus. As JMeter has more and more components added, you may wish to + customize your JMeter to show only those components you are interested in. + You may list their classname or their class label (the string that appears + in JMeter's UI) here, and they will no longer appear in the menus.
+ No +
+
search_paths
+ List of paths (separated by ;) that JMeter will search for JMeter plugin classes, + for example additional samplers. A path item can either be a jar file or a directory. + Any jar file in such a directory will be automatically included in search_paths, + jar files in sub directories are ignored. + The given value is in addition to any jars found in the lib/ext directory. +
+ No +
+
user.classpath
+ List of paths that JMeter will search for utility and plugin dependency classes. + Use your platform path separator to separate multiple paths. + A path item can either be a jar file or a directory. + Any jar file in such a directory will be automatically included in user.classpath, + jar files in sub directories are ignored. + The given value is in addition to any jars found in the lib directory. + All entries will be added to the class path of the system class loader + and also to the path of the JMeter internal loader. +
+ No +
+
plugin_dependency_paths
+ List of paths (separated by ;) that JMeter will search for utility + and plugin dependency classes. + A path item can either be a jar file or a directory. + Any jar file in such a directory will be automatically included in plugin_dependency_paths, + jar files in sub directories are ignored. + The given value is in addition to any jars found in the lib directory + or given by the user.classpath property. + All entries will be added to the path of the JMeter internal loader only. + For plugin dependencies using plugin_dependency_paths should be preferred over + user.classpath. +
+ No +
+
user.properties
+ Name of file containing additional JMeter properties. + These are added after the initial property file, but before the -q and -J options are processed. +
+ No +
+
system.properties
+ Name of file containing additional system properties. + These are added before the -S and -D options are processed. +
+ No +
+
+

+ The command line options and properties files are processed in the following order: +

    +
  • -p propfile
  • +
  • jmeter.properties (or the file from the -p option) is then loaded
  • +
  • -j logfile
  • +
  • Logging is initialised
  • +
  • user.properties is loaded
  • +
  • system.properties is loaded
  • +
  • all other command-line options are processed
  • +
+

+

+See also the comments in the jmeter.properties, user.properties and system.properties files for further information on other settings you can change. +

+
\ No newline at end of file diff --git a/docs/usermanual/glossary.html b/docs/usermanual/glossary.html new file mode 100644 index 00000000000..d848a0ef69c --- /dev/null +++ b/docs/usermanual/glossary.html @@ -0,0 +1,115 @@ + +Apache JMeter + - + User's Manual: Glossary
Logo ASF
Apache JMeter

22. Glossary

+ +

+Elapsed time. JMeter measures the elapsed time from just before sending the request to +just after the last response has been received. +JMeter does not include the time needed to render the response, nor does JMeter process any client code, for example +Javascript. +

+ +

+Latency. JMeter measures the latency from just before sending the request to +just after the first response has been received. Thus the time +includes all the processing needed to assemble the request as well as +assembling the first part of the response, which in general will be longer than one +byte. +Protocol analysers (such as Wireshark) measure the time when bytes are actually sent/received over the interface. +The JMeter time should be closer to that which is experienced by a +browser or other application client. +

+ +

+Connect Time. JMeter measures the time it took to establish the connection, including SSL handshake. Note that connect time is not automatically subtracted from latency. +

+ +

+Median is a number which divides the samples into two equal halves. +Half of the samples are smaller than the median, and half are larger. +[Some samples may equal the median.] +This is a standard statistical measure. +See, for example: Median entry at Wikipedia. +The Median is the same as the 50th Percentile +

+ +

+90% Line (90th Percentile) is the value below which 90% of the samples fall. +The remaining samples too at least as long as the value. +This is a standard statistical measure. +See, for example: Percentile entry at Wikipedia. +

+ +

+Standard Deviation is a measure of the variability +of a data set. This is a standard statistical measure. +See, for example: Standard Deviation entry at Wikipedia. +JMeter calculates the population standard deviation (e.g. STDEVP function in spreadheets), not the sample standard deviation (e.g. STDEV). +

+ +

+The Thread Name as it appears in Listeners and logfiles +is derived from the Thread Group name and the thread within the group.
+The name has the format +groupName + " " + groupIndex + "-" + threadIndex +where: +

    +
  • groupName - name of the Thread Group element
  • +
  • groupIndex - number of the Thread Group in the Test Plan, starting from 1
  • +
  • threadIndex - number of the thread within the Thread Group, starting from 1
  • +
+A test plan with two Thread Groups each with two threads would use the names: +
+Thread Group 1-1
+Thread Group 1-2
+Thread Group 2-1
+Thread Group 2-2
+
+

+ +

+Throughput is calculated as requests/unit of time. +The time is calculated from the start of the first sample to the end of the last sample. +This includes any intervals between samples, as it is supposed to represent the load on the server.
+The formula is: Throughput = (number of requests) / (total time). +

+ + +
\ No newline at end of file diff --git a/docs/usermanual/hints_and_tips.html b/docs/usermanual/hints_and_tips.html new file mode 100644 index 00000000000..900d6952a15 --- /dev/null +++ b/docs/usermanual/hints_and_tips.html @@ -0,0 +1,124 @@ + +Apache JMeter + - + User's Manual: Hints and Tips
Logo ASF
Apache JMeter

21. Hints and Tips

+

+This section is a collection of various hints and tips that have been suggested by various questions on the JMeter User list. +If you don't find what you are looking for here, please check the JMeter Wiki. +Also, try search the JMeter User list; someone may well have already provided a solution. +

+

21.1 Passing variables between threads

+

+JMeter variables have thread scope. This is deliberate, so that threads can act indepently. +However sometimes there is a need to pass variables between different threads, in the same or different Thread Groups. +

+

+One way to do this is to use a property instead. +Properties are shared between all JMeter threads, so if one thread sets a property, +another thread can read the updated value. +

+

+If there is a lot of information that needs to be passed between threads, then consider using a file. +For example you could use the Save Responses to a file +listener or perhaps a BeanShell PostProcessor in one thread, and read the file using the HTTP Sampler "file:" protocol, +and extract the information using a PostProcessor or BeanShell element. +

+

+If you can derive the data before starting the test, then it may well be better to store it in a file, +read it using CSV Dataset. +

+
+ +

21.2 Enabling Debug logging

+

+Most test elements include debug logging. If running a test plan from the GUI, +select the test element and use the Help Menu to enable or disable logging. +The Help Menu also has an option to display the GUI and test element class names. +You can use these to determine the correct property setting to change the logging level. +

+ +

+It is sometimes very useful to see Log messages to debug dynamic scripting languages like BeanShell or +groovy used in JMeter. +Since version 2.6, you can view log messages directly in JMeter GUI, to do so: +

    +
  • use menu Options > Log Viewer, a log console will appear at the bottom of the interface
  • +
  • Or click on the Warning icon in the upper right corner of GUI
  • +
+By default this log console is disabled, you can enable it by changing in jmeter.properties: +
    +
  • jmeter.loggerpanel.display=true
  • +
+ +To avoid using too much memory, this components limits the number of characters used by this panel: +
    +
  • jmeter.loggerpanel.maxlength=80000
  • +
+

+
+ +

21.3 Searching

+

+It is sometimes hard to find in a Test Plan tree and elements using a variable or containing a certain URL or parameter. +A new feature is now available since 2.6, you can access it in Menu Search. +It provides search with following options: +

    +
  • Case Sensitive : Makes search case sensitive
  • +
  • Regexp : Is text to search a regexp, if so Regexp will be searched in Tree of components, example "\btest\b" will match any component that contains test in searchable elements of the component
  • +
+

+ +
Figure 1 - Search raw text in TreeView
Figure 1 - Search raw text in TreeView
+
Figure 2 - Result in TreeView
Figure 2 - Result in TreeView
+
Figure 3 - Search Regexp in TreeView (in this example we search whole word)
Figure 3 - Search Regexp in TreeView (in this example we search whole word)
+
Figure 4 - Result in TreeView
Figure 4 - Result in TreeView
+ +
+ +

21.4 Toolbar icons size

+
+

+You can change the size of icons in the toolbar using the property

jmeter.toolbar.icons.size
with these values: 22x22 (default size), 32x32 or 48x48. +

+
+
Icons with the size 22x22.
Icons with the size 22x22.
+
Icons with the size 32x32.
Icons with the size 32x32.
+
Icons with the size 48x48.
Icons with the size 48x48.
+
+
\ No newline at end of file diff --git a/docs/usermanual/include_controller_tutorial.pdf b/docs/usermanual/include_controller_tutorial.pdf new file mode 100644 index 00000000000..9e2f30de2df Binary files /dev/null and b/docs/usermanual/include_controller_tutorial.pdf differ diff --git a/docs/usermanual/index.html b/docs/usermanual/index.html new file mode 100644 index 00000000000..6d56a6d884b --- /dev/null +++ b/docs/usermanual/index.html @@ -0,0 +1,219 @@ + +Apache JMeter + - + User's Manual
Logo ASF
Apache JMeter

User's Manual

+

Click on the section name to go straight to the section. + Click on the "+" to go to the relevant section of the detailed section list, + where you can select individual subsections.

+ + +

Detailed Section List

+ +
+ +
\ No newline at end of file diff --git a/docs/usermanual/intro.html b/docs/usermanual/intro.html new file mode 100644 index 00000000000..d300a6f8fcc --- /dev/null +++ b/docs/usermanual/intro.html @@ -0,0 +1,81 @@ + +Apache JMeter + - + User's Manual: Introduction
Logo ASF
Apache JMeter

1. Introduction

+

+Apache JMeter is a 100% pure Java application designed +to load test client/server software +(such as a web application). It may be used +to test performance both on static and dynamic +resources such as static files, Java Servlets, ASP.NET, PHP, CGI scripts, Java objects, databases, +FTP servers, and more. JMeter can be used to simulate a heavy +load on a server, network or object to test its strength or to analyze +overall performance under different load types.

+

+Additionally, JMeter can help you regression test your application by letting you create +test scripts with assertions to validate that your application is returning the +results you expect. For maximum flexibility, JMeter lets you create these assertions using +regular expressions.

+ +

But please note that JMeter is not a browser, it works at protocol level.

+

1.1 History

+

Stefano Mazzocchi of the Apache Software Foundation was the original developer of JMeter. +He wrote it primarily to test the performance of Apache JServ (a project that has +since been replaced by the Apache Tomcat project). We redesigned JMeter to enhance the GUI +and to add functional-testing capabilities. +

+ +

JMeter became a Top Level Apache project in November 2011, which means it has a Project Management Commitee and a dedicated website.

+
+ +

1.2 The Future

+

We hope to see JMeter's capabilities rapidly expand as developers take advantage of its +pluggable architecture.
+The primary goal of further developments will be: +

    +
  • Addition of Websocket protocol
  • +
  • Addition of FTPS and SFTP protocols
  • +
  • Enhancements to Webservices protocols (SOAP Attachments)
  • +
  • Enhancements to JMS protocol implementation
  • +
  • ...
  • +
+ +

+
+
\ No newline at end of file diff --git a/docs/usermanual/jmeter_accesslog_sampler_step_by_step.pdf b/docs/usermanual/jmeter_accesslog_sampler_step_by_step.pdf new file mode 100644 index 00000000000..2788c82c0d4 Binary files /dev/null and b/docs/usermanual/jmeter_accesslog_sampler_step_by_step.pdf differ diff --git a/docs/usermanual/jmeter_distributed_testing_step_by_step.pdf b/docs/usermanual/jmeter_distributed_testing_step_by_step.pdf new file mode 100644 index 00000000000..8671636d89e Binary files /dev/null and b/docs/usermanual/jmeter_distributed_testing_step_by_step.pdf differ diff --git a/docs/usermanual/jmeter_proxy_step_by_step.pdf b/docs/usermanual/jmeter_proxy_step_by_step.pdf new file mode 100644 index 00000000000..6ac2cc2f323 Binary files /dev/null and b/docs/usermanual/jmeter_proxy_step_by_step.pdf differ diff --git a/docs/usermanual/junitsampler_tutorial.pdf b/docs/usermanual/junitsampler_tutorial.pdf new file mode 100644 index 00000000000..e0fb970229d Binary files /dev/null and b/docs/usermanual/junitsampler_tutorial.pdf differ diff --git a/docs/usermanual/ldapanswer_xml.html b/docs/usermanual/ldapanswer_xml.html new file mode 100644 index 00000000000..af38e19efa5 --- /dev/null +++ b/docs/usermanual/ldapanswer_xml.html @@ -0,0 +1,85 @@ + +Apache JMeter + - + User's Manual: LDAP answer XML description
Logo ASF
Apache JMeter

description of the ldapanswer XML definition

+

+ The extended LDAP sampler was built to support testing for very complex testpurposes. + It was aimed at supporting the LDAP operations as close as possible. + As the results are not passed back in a user-readable form, I invented my own xml definition to + construct an answer in xml encoding, so the results may be parsed with regextracter or alike functions. +

+ +

1 Global overview

+

+ The global structure is as follows:
+ +

+

1.1 The operation section

+

+The operation section defines the operation as it is sent to the LDAP Server. The +following tags (with a short explanation) are used + +

+
+ +

1.2 Response message section

+

+As the response code, the official LDAP error definitions are used, so this section +contains the error message as returned from the server. +A succesfull request always returns "Success" as the responsmessage. +The following tag is used: +

+
+

1.3 Response code section

+

+As the response code, the official LDAP error definitions are used, so this section +contains the error number as returned from the server. +A succesfull request always returns 0 (zero) as the responscode. +The following tag is used: + +

+
+

1.4 Search Result section

+

+The following tag is used: +

+ +
+
+
\ No newline at end of file diff --git a/docs/usermanual/ldapops_tutor.html b/docs/usermanual/ldapops_tutor.html new file mode 100644 index 00000000000..f3f13f3ce4c --- /dev/null +++ b/docs/usermanual/ldapops_tutor.html @@ -0,0 +1,127 @@ + +Apache JMeter + - + JMeter - User's Manual: LDAP Operations
Logo ASF
Apache JMeter

A short LDAP Operations tutorial

+

+ The extended LDAP sampler was built to support testing for very complex testpurposes. + It was aimed at supporting the LDAP operations as close as possible. + In this short tutorial, I will explain which LDAP operations exist and what they do. + Per operation, I will shortly explain how these operations are implemented.
+ LDAP servers are some kind of hierarchical database, they store objects (entries) in a tree. The uppermost part of a tree is called the ROOT of the tree.
+ eg. When a tree starts with dc=com, the root equals dc=com.
+ The next level can exist under the root, eg dc=test. The full name of this object (the "distinghuised name") is "dc=test,dc=com.
+ Again, a following level can be made, by adding the user "cn=admin" under dc=test,dc=com. This object has a DN (distinguished name) of "cn=admin,dc=test,dc=com".
+ The relative distinguished name (RDN) is the last part of the DN, eg. cn=admin.
+ The characteristics of an object are determined by the objectClasses, which can be seen as a collection of attributes.
+ The type of an object is determined by the "structural objectClass" eg person, organizationalUnit or country.
+ The attributes contain the data of an object, eg mailadress, name, streetadress etc. Each attribute can have 0, 1 or more values. +

+

1 Bind operation

+

+ Any contact with an LDAP server MUST start with a bind request. LDAP is a state dependent protocol. Without opening a session to + a LDAP server, no additional request can be made. + Due to some peculiarities in the JAVA libraries, 2 different bind operations are implemented. +

+

1.1 Thread Bind

+

+ This bind is meant to open a session to a LDAP server. Any testplan should use this operation as the starting point from a session. + For each Thread (each virtual user) a seperate connection with the LDAP server is build, and so a seperate Thread bind is performed. +

+
+

1.2 Single bind/unbind

+

+ This bind is used for user authentication verification. + A proper developed LDAP client, who needs an authenticated user, perform a bind with a given distinguished name and password. + This Single bind/unbind operation is for this purpose. It builds it own seperate connection to the LDAP server, performs a + bind operation, and ends the connection again (by sending an unbind). +

+
+
+

2 Unbind

+

+ To close a connection to a LDAP server, an unbind operation is needed. + As the Single bind/unbind operation already (implicitly) performs an unbind, only a Thread unbind operation is needed. + This Thread unbind just closes the connection and cleans up any resources it has used. +

+
+

3 Compare

+

+ The compare operation needs the full distinguished name from a LDAP object, as well as a attribute and a value for the attribute. + It will simply check: "Has this object really this attribute with this value?". + Typical use is checking the membership of a certain user with a given group. +

+
+

4 Search

+

+ The search test simply searches for all objects which comply with a given search filter, eg. + all persons with a "employeeType=inactive" or "all persons with a userID equals user1" +

+
+

5 Add

+

+ This simply add an object to the LDAP directory. + Off course the combination of attributes and distinguishedName must be valid! +

+
+

6 Modify

+

+ This operation modifies one or more attributes from a given object. + It needs the distinghised name from the object, as well as the attributes and the new values for this attribute.
+ Three versions are available, add, for adding an attribute value
+ replace, for overwriting the old attribute value with a new value
+ delete, to delete a value form an attribute, or to delete all the values of an attribute
+

+
+

7 Delete

+

+ This operation deletes an object from the LDAP server. + It needs the distinghised name from the object. +

+
+

8 modDN

+

+ This operation modifies the distinguished name from an object (it "moves" the object).
+ It comes in two flavours, just renaming an entry, then you specify a new RDN (relative distinguished name, this is the lowest part of the DN)
+ eg, you can rename "cn=admin,dc=test,dc=com" to cn=administrator,dc=test,dc=com"
+ The second flavour is renaming (moving) a complete subtree by specifying a "new superior"
+ eg you can move a complete subtree "ou=retired,ou=people,dc=test,dc=com" to a new subtree "ou=retired people,dc=test,dc=com" by specifying + a new rdn "ou=retired people" and a new superior of "dc=test,dc=com" +

+
+
\ No newline at end of file diff --git a/docs/usermanual/listeners.html b/docs/usermanual/listeners.html new file mode 100644 index 00000000000..0c182ab118d --- /dev/null +++ b/docs/usermanual/listeners.html @@ -0,0 +1,485 @@ + +Apache JMeter + - + User's Manual: Listeners
Logo ASF
Apache JMeter

14. Introduction to listeners

+

A listener is a component that shows the results of the +samples. The results can be shown in a tree, tables, graphs or simply written to a log +file. To view the contents of a response from any given sampler, add either of the Listeners "View +Results Tree" or "View Results in table" to a test plan. To view the response time graphically, add +graph results, spline results or distribution graph. +The listeners +section of the components page has full descriptions of all the listeners.

+ +
+Different listeners display the response information in different ways. +However, they all write the same raw data to the output file - if one is specified. +
+

+The "Configure" button can be used to specify which fields to write to the file, and whether to +write it as CSV or XML. +CSV files are much smaller than XML files, so use CSV if you are generating lots of samples. +

+

+The file name can be specified using either a relative or an absolute path name. +Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). +Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). +If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), +then the path is assumed to be relative to the JMX file location. +

+

+If you only wish to record certain samples, add the Listener as a child of the sampler. +Or you can use a Simple Controller to group a set of samplers, and add the Listener to that. +The same filename can be used by multiple samplers - but make sure they all use the same configuration! +

+

14.1 Default Configuration

+

+The default items to be saved can be defined in the jmeter.properties (or user.properties) file. +The properties are used as the initial settings for the Listener Config pop-up, and are also +used for the log file specified by the -l command-line flag (commonly used for non-GUI test runs). +

+

To change the default format, find the following line in jmeter.properties:

+

jmeter.save.saveservice.output_format=

+

+The information to be saved is configurable. For maximum information, choose "xml" as the format and specify "Functional Test Mode" on the Test Plan element. If this box is not checked, the default saved +data includes a time stamp (the number of milliseconds since midnight, +January 1, 1970 UTC), the data type, the thread name, the label, the +response time, message, and code, and a success indicator. If checked, all information, including the full response data will be logged.

+

+The following example indicates how to set +properties to get a vertical bar ("|") delimited format that will +output results like:.

+

+ +

+timeStamp|time|label|responseCode|threadName|dataType|success|failureMessage
+02/06/03 08:21:42|1187|Home|200|Thread Group-1|text|true|
+02/06/03 08:21:42|47|Login|200|Thread Group-1|text|false|Test Failed: 
+    expected to contain: password etc.
+
+

+

+The corresponding jmeter.properties that need to be set are shown below. One oddity +in this example is that the output_format is set to csv, which +typically +indicates comma-separated values. However, the default_delimiter was +set to be a vertical bar instead of a comma, so the csv tag is a +misnomer in this case. (Think of CSV as meaning character separated values)

+

+ +

+jmeter.save.saveservice.output_format=csv
+jmeter.save.saveservice.assertion_results_failure_message=true
+jmeter.save.saveservice.default_delimiter=|
+
+ +

+The full set of properties that affect result file output is shown below. +

+ +
+#---------------------------------------------------------------------------
+# Results file configuration
+#---------------------------------------------------------------------------
+
+# This section helps determine how result data will be saved.
+# The commented out values are the defaults.
+
+# legitimate values: xml, csv, db.  Only xml and csv are currently supported.
+#jmeter.save.saveservice.output_format=csv
+
+
+# true when field should be saved; false otherwise
+
+# assertion_results_failure_message only affects CSV output
+#jmeter.save.saveservice.assertion_results_failure_message=false
+#
+#jmeter.save.saveservice.data_type=true
+#jmeter.save.saveservice.label=true
+#jmeter.save.saveservice.response_code=true
+# response_data is not currently supported for CSV output
+#jmeter.save.saveservice.response_data=false
+# Save ResponseData for failed samples
+#jmeter.save.saveservice.response_data.on_error=false
+#jmeter.save.saveservice.response_message=true
+#jmeter.save.saveservice.successful=true
+#jmeter.save.saveservice.thread_name=true
+#jmeter.save.saveservice.time=true
+#jmeter.save.saveservice.subresults=true
+#jmeter.save.saveservice.assertions=true
+#jmeter.save.saveservice.latency=true
+#jmeter.save.saveservice.connect_time=false
+#jmeter.save.saveservice.samplerData=false
+#jmeter.save.saveservice.responseHeaders=false
+#jmeter.save.saveservice.requestHeaders=false
+#jmeter.save.saveservice.encoding=false
+#jmeter.save.saveservice.bytes=true
+#jmeter.save.saveservice.url=false
+#jmeter.save.saveservice.filename=false
+#jmeter.save.saveservice.hostname=false
+#jmeter.save.saveservice.thread_counts=true
+#jmeter.save.saveservice.sample_count=false
+#jmeter.save.saveservice.idle_time=false
+
+# Timestamp format
+# legitimate values: none, ms, or a format suitable for SimpleDateFormat
+#jmeter.save.saveservice.timestamp_format=ms
+#jmeter.save.saveservice.timestamp_format=yyyy/MM/dd HH:mm:ss.SSS
+
+# Put the start time stamp in logs instead of the end
+sampleresult.timestamp.start=true
+
+# Whether to use System.nanoTime() - otherwise only use System.currentTimeMillis()
+#sampleresult.useNanoTime=true
+
+# Use a background thread to calculate the nanoTime offset
+# Set this to <= 0 to disable the background thread
+#sampleresult.nanoThreadSleep=5000
+
+# legitimate values: none, first, all
+#jmeter.save.saveservice.assertion_results=none
+
+# For use with Comma-separated value (CSV) files or other formats
+# where the fields' values are separated by specified delimiters.
+# Default:
+#jmeter.save.saveservice.default_delimiter=,
+# For TAB, since JMeter 2.3 one can use:
+#jmeter.save.saveservice.default_delimiter=\t
+
+#jmeter.save.saveservice.print_field_names=false
+
+# Optional list of JMeter variable names whose values are to be saved in the result data files.
+# Use commas to separate the names. For example:
+#sample_variables=SESSION_ID,REFERENCE
+# N.B. The current implementation saves the values in XML as attributes,
+# so the names must be valid XML names.
+# Versions of JMeter after 2.3.2 send the variable to all servers
+# to ensure that the correct data is available at the client.
+
+# Optional xml processing instruction for line 2 of the file:
+#jmeter.save.saveservice.xml_pi=<?xml-stylesheet type="text/xsl" href="sample.xsl"?>
+
+# Prefix used to identify filenames that are relative to the current base
+#jmeter.save.saveservice.base_prefix=~/
+
+

+

+The date format to be used for the timestamp_format is described in +SimpleDateFormat. +The timestamp format is used for both writing and reading files. +If the format is set to "ms", and the column does not parse as a long integer, +JMeter (2.9+) will try the following formats: +

    +
  • yyyy/MM/dd HH:mm:ss.SSS
  • +
  • yyyy/MM/dd HH:mm:ss
  • +
  • yyyy-MM-dd HH:mm:ss.SSS
  • +
  • yyyy-MM-dd HH:mm:ss
  • +
  • MM/dd/yy HH:mm:ss (this is for compatibility with previous versions; it is not recommended as a format)
  • +
+Matching is now also strict (non-lenient). +JMeter 2.8 and earlier used lenient mode which could result in timestamps with incorrect dates +(times were usually correct).

+

14.1.1 Sample Variables

+

+JMeter supports the sample_variables +property to define a list of additional JMeter variables which are to be saved with +each sample in the JTL files. The values are written to CSV files as additional columns, +and as additional attributes in XML files. See above for an example. +

+
+ +

14.1.2 Sample Result Save Configuration

+

+Listeners can be configured to save different items to the result log files (JTL) by using the Config popup as shown below. +The defaults are defined as described in the Listener Default Configuration section above. +Items with (CSV) after the name only apply to the CSV format; items with (XML) only apply to XML format. +CSV format cannot currently be used to save any items that include line-breaks. +

+

Configuration dialogue
+
+

+Note that cookies, method and the query string are saved as part of the "Sampler Data" option. +

+

14.2 non-GUI (batch) test runs

+

+When running in non-GUI mode, the -l flag can be used to create a top-level listener for the test run. +This is in addition to any Listeners defined in the test plan. +The configuration of this listener is controlled by entries in the file jmeter.properties +as described in the previous section. +

+

+This feature can be used to specify different data and log files for each test run, for example: +

+jmeter -n -t testplan.jmx -l testplan_01.jtl -j testplan_01.log
+jmeter -n -t testplan.jmx -l testplan_02.jtl -j testplan_02.log
+
+

+

+Note that JMeter logging messages are written to the file jmeter.log by default. +This file is recreated each time, so if you want to keep the log files for each run, +you will need to rename it using the -j option as above. The -j option was added in version 2.3. +

+

Versions of JMeter after 2.3.1 support variables in the log file name. +If the filename contains paired single-quotes, then the name is processed +as a SimpleDateFormat format applied to the current date, for example: +log_file='jmeter_'yyyyMMddHHmmss'.tmp'. +This can be used to generate a unique name for each test run. +

+

14.3 Resource usage

+

Listeners can use a lot of memory if there are a lot of samples. +Most of the listeners currently keep a copy of every sample they display, apart from: +

+
    +
  • Simple Data Writer
  • +
  • BeanShell/BSF Listener
  • +
  • Mailer Visualizer
  • +
  • Monitor Results
  • +
  • Summary Report
  • +
+

+The following Listeners no longer need to keep copies of every single sample. +Instead, samples with the same elapsed time are aggregated. +Less memory is now needed, especially if most samples only take a second or two at most. +

+
    +
  • Aggregate Report
  • +
  • Aggregate Graph
  • +
  • Distribution Graph
  • +
+

To minimise the amount of memory needed, use the Simple Data Writer, and use the CSV format.

+

14.4 CSV Log format

+

+The CSV log format depends on which data items are selected in the configuration. +Only the specified data items are recorded in the file. +The order of appearance of columns is fixed, and is as follows: +

+
    +
  • timeStamp - in milliseconds since 1/1/1970
  • +
  • elapsed - in milliseconds
  • +
  • label - sampler label
  • +
  • responseCode - e.g. 200, 404
  • +
  • responseMessage - e.g. OK
  • +
  • threadName
  • +
  • dataType - e.g. text
  • +
  • success - true or false
  • +
  • failureMessage - if any
  • +
  • bytes - number of bytes in the sample
  • +
  • grpThreads - number of active threads in this thread group
  • +
  • allThreads - total number of active threads in all groups
  • +
  • URL
  • +
  • Filename - if Save Response to File was used
  • +
  • latency - time to first response
  • +
  • connect - time to establish connection
  • +
  • encoding
  • +
  • SampleCount - number of samples (1, unless multiple samples are aggregated)
  • +
  • ErrorCount - number of errors (0 or 1, unless multiple samples are aggregated)
  • +
  • Hostname where the sample was generated
  • +
  • IdleTime - number of milliseconds of 'Idle' time (normally 0)
  • +
  • Variables, if specified
  • +
+ +

14.5 XML Log format 2.1

+

+The format of the updated XML (2.1) is as follows (line breaks will be different): +

+
+<?xml version="1.0" encoding="UTF-8"?>
+<testResults version="1.2">
+
+-- HTTP Sample, with nested samples 
+
+<httpSample t="1392" lt="351" ts="1144371014619" s="true" 
+     lb="HTTP Request" rc="200" rm="OK" 
+     tn="Listen 1-1" dt="text" de="iso-8859-1" by="12407">
+  <httpSample t="170" lt="170" ts="1144371015471" s="true" 
+        lb="http://www.apache.org/style/style.css" rc="200" rm="OK" 
+        tn="Listen 1-1" dt="text" de="ISO-8859-1" by="1002">
+    <responseHeader class="java.lang.String">HTTP/1.1 200 OK
+Date: Fri, 07 Apr 2006 00:50:14 GMT
+...
+Content-Type: text/css
+</responseHeader>
+    <requestHeader class="java.lang.String">MyHeader: MyValue</requestHeader>
+    <responseData class="java.lang.String">body, td, th {
+    font-size: 95%;
+    font-family: Arial, Geneva, Helvetica, sans-serif;
+    color: black;
+    background-color: white;
+}
+...
+</responseData>
+    <cookies class="java.lang.String"></cookies>
+    <method class="java.lang.String">GET</method>
+    <queryString class="java.lang.String"></queryString>
+    <url>http://www.apache.org/style/style.css</url>
+  </httpSample>
+  <httpSample t="200" lt="180" ts="1144371015641" s="true" 
+     lb="http://www.apache.org/images/asf_logo_wide.gif" 
+     rc="200" rm="OK" tn="Listen 1-1" dt="bin" de="ISO-8859-1" by="5866">
+    <responseHeader class="java.lang.String">HTTP/1.1 200 OK
+Date: Fri, 07 Apr 2006 00:50:14 GMT
+...
+Content-Type: image/gif
+</responseHeader>
+    <requestHeader class="java.lang.String">MyHeader: MyValue</requestHeader>
+    <responseData class="java.lang.String">http://www.apache.org/asf.gif</responseData>
+      <responseFile class="java.lang.String">Mixed1.html</responseFile>
+    <cookies class="java.lang.String"></cookies>
+    <method class="java.lang.String">GET</method>
+    <queryString class="java.lang.String"></queryString>
+    <url>http://www.apache.org/asf.gif</url>
+  </httpSample>
+  <responseHeader class="java.lang.String">HTTP/1.1 200 OK
+Date: Fri, 07 Apr 2006 00:50:13 GMT
+...
+Content-Type: text/html; charset=ISO-8859-1
+</responseHeader>
+  <requestHeader class="java.lang.String">MyHeader: MyValue</requestHeader>
+  <responseData class="java.lang.String"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+               "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+...
+&lt;html&gt;
+ &lt;head&gt;
+...
+ &lt;/head&gt;
+ &lt;body&gt;        
+...
+ &lt;/body&gt;
+&lt;/html&gt;
+</responseData>
+  <cookies class="java.lang.String"></cookies>
+  <method class="java.lang.String">GET</method>
+  <queryString class="java.lang.String"></queryString>
+  <url>http://www.apache.org/</url>
+</httpSample>
+
+-- nonHTTPP Sample
+
+<sample t="0" lt="0" ts="1144372616082" s="true" lb="Example Sampler"
+    rc="200" rm="OK" tn="Listen 1-1" dt="text" de="ISO-8859-1" by="10">
+  <responseHeader class="java.lang.String"></responseHeader>
+  <requestHeader class="java.lang.String"></requestHeader>
+  <responseData class="java.lang.String">Listen 1-1</responseData>
+  <responseFile class="java.lang.String">Mixed2.unknown</responseFile>
+  <samplerData class="java.lang.String">ssssss</samplerData>
+</sample>
+
+</testResults>
+
+

+Note that the sample node name may be either "sample" or "httpSample". +

+

14.6 XML Log format 2.2

+

+The format of the JTL files is identical for 2.2 and 2.1. Format 2.2 only affects JMX files. +

+

14.7 Sample Attributes

+

+The sample attributes have the following meaning: +

+ + + + + + + + + + + + + + + + + + + + + +
AttributeContent
byBytes
deData encoding
dtData type
ecError count (0 or 1, unless multiple samples are aggregated)
hnHostname where the sample was generated
itIdle Time = time not spent sampling (milliseconds) (generally 0)
lbLabel
ltLatency = time to initial response (milliseconds) - not all samplers support this
ctConnect Time = time to establish the connection (milliseconds) - not all samplers support this
naNumber of active threads for all thread groups
ngNumber of active threads in this group
rcResponse Code (e.g. 200)
rmResponse Message (e.g. OK)
sSuccess flag (true/false)
scSample count (1, unless multiple samples are aggregated)
tElapsed time (milliseconds)
tnThread Name
tstimeStamp (milliseconds since midnight Jan 1, 1970 UTC)
varnameValue of the named variable (versions of JMeter after 2.3.1)
+

+Versions 2.1 and 2.1.1 of JMeter saved the Response Code as "rs", but read it back expecting to find "rc". +This has been corrected so that it is always saved as "rc"; either "rc" or "rs" can be read. +

+
+Versions of JMeter after 2.3.1 allow additional variables to be saved with the test plan. +Currently, the variables are saved as additional attributes. +The testplan variable name is used as the attribute name. +See Sample variables (above) for more information. +
+

14.8 Saving response data

+

+As shown above, the response data can be saved in the XML log file if required. +However, this can make the file rather large, and the text has to be encoded so +that it is still valid XML. Also, images cannot be included. +
+Another solution is to use the Post-Processor Save_Responses_to_a_file. +This generates a new file for each sample, and saves the file name with the sample. +The file name can then be included in the sample log output. +The data will be retrieved from the file if necessary when the sample log file is reloaded. +

+

14.9 Loading (reading) response data

+

To view an existing results file, you can use the File "Browse..." button to select a file. +If necessary, just create a dummy testplan with the appropriate Listener in it. +

+

Results can be read from XML or CSV format files. +When reading from CSV results files, the header (if present) is used to determine which fields were saved. +In order to interpret a header-less CSV file correctly, the appropriate JMeter properties must be set. +

+
+Versions of JMeter up to 2.3.2 used to clear any current data before loading the new file. +This is no longer done, thus allowing files to be merged. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. +
+

14.10 Saving Listener GUI data

+

JMeter is capable of saving any listener as a PNG file. To do so, select the +listener in the left panel. Click Edit > Save As Image. A file dialog will +appear. Enter the desired name and save the listener. +

+

+The Listeners which generate output as tables can also be saved using Copy/Paste. +Select the desired cells in the table, and use the OS Copy short-cut (normally Control+C). +The data will be saved to the clipboard, from where it can be pasted into another application, +e.g. a spreadsheet or text editor. +

+
Figure 1 - Edit > Save As Image
Figure 1 - Edit > Save As Image
+ +
\ No newline at end of file diff --git a/docs/usermanual/realtime-results.html b/docs/usermanual/realtime-results.html new file mode 100644 index 00000000000..75d9d77d5b2 --- /dev/null +++ b/docs/usermanual/realtime-results.html @@ -0,0 +1,229 @@ + +Apache JMeter + - + User's Manual: Live Statistics
Logo ASF
Apache JMeter

17. Real-time results

+

Since JMeter 2.13 you can get realtime results sent to a backend through the +Backend Listener using potentially any backend (JDBC, JMS, Webservice...) implementing AbstractBackendListenerClient.
+JMeter ships with a GraphiteBackendListenerClient which allows you to send metrics to a Graphite Backend.
+This feature provides: +

    +
  • Live results
  • +
  • Nice graphs for metrics
  • +
  • Ability to compare 2 or more load tests
  • +
  • Storing monitoring data as long as JMeter results in the same backend
  • +
  • ...
  • +
+In this document we will present the configuration setup to graph and historize the data in 2 different backends: +
    +
  • InfluxDB
  • +
  • Graphite
  • +
+

+

17.1 Metrics exposed

+

17.1.1 Thread/Virtual Users metrics

+

+ Threads metrics are the following: +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Metric NameDescription
<rootMetricsPrefix>.test.minATMin active threads
<rootMetricsPrefix>.test.maxATMax active threads
<rootMetricsPrefix>.test.meanATMean active threads
<rootMetricsPrefix>.test.startedTStarted threads
<rootMetricsPrefix>.test.endedTFinished threads
+
+

17.1.2 Response times metrics

+

Response times metrics are the following:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Metric NameDescription
<rootMetricsPrefix>.<samplerName>.ok.countNumber of successful responses for sampler name
<rootMetricsPrefix>.<samplerName>.ok.minMin response time for successful responses of sampler name
<rootMetricsPrefix>.<samplerName>.ok.maxMax response time for successful responses of sampler name
<rootMetricsPrefix>.<samplerName>.ok.pct<percentileValue>Percentile computed for successful responses of sampler name. You can input as many percentiles as you want (3 or 4 being a reasonable value).
+ When percentile contains a comma for example "99.9", dot is sanitized by "_" leading to 99_9. + By default listener computes percentiles 90%, 95% and 99%
<rootMetricsPrefix>.<samplerName>.ko.countNumber of failed responses for sampler name
<rootMetricsPrefix>.<samplerName>.ko.minMin response time for failed responses of sampler name
<rootMetricsPrefix>.<samplerName>.ko.maxMax response time for failed responses of sampler name
<rootMetricsPrefix>.<samplerName>.ko.pct<percentileValue>Percentile computed for failed responses of sampler name. You can input as many percentiles as you want (3 or 4 being a reasonable value).
+ When percentile contains a comma for example "99.9", dot is sanitized by "_" leading to 99_9. + By default listener computes percentiles 90%, 95% and 99%
<rootMetricsPrefix>.<samplerName>.a.countNumber of responses for sampler name
<rootMetricsPrefix>.<samplerName>.a.minMin response time for responses of sampler name
<rootMetricsPrefix>.<samplerName>.a.maxMax response time for responses of sampler name
<rootMetricsPrefix>.<samplerName>.a.pct<percentileValue>Percentile computed for responses of sampler name. You can input as many percentiles as you want (3 or 4 being a reasonable value).
+ When percentile contains a comma for example "99.9", dot is sanitized by "_" leading to 99_9. + By default listener computes percentiles 90%, 95% and 99%
+

+ By default JMeter sends only metrics for all samplers using "all" as samplerName. +

+
+
+

17.2 JMeter configuration

+

+ To make JMeter send metrics to backend add a BackendListener using the GraphiteBackendListenerClient. +

+
Graphite configuration
Graphite configuration
+
+ +

17.2 InfluxDB

+

InfluxDB is an open-source, distributed,time-series database that allows to +easily store metrics. +Installation and configuration is very easy, read this for more details InfluxDB documentation.
+InfluxDB data can be easily viewed in a browser through either Influga or Grafana. +We will use Grafana in this case. +

+

17.2.1 InfluxDB graphite listener configuration

+

To enable Graphite listener in InfluxDB, edit files /opt/influxdb/shared/config.toml or /usr/local/etc/influxdb.conf, find "input_plugins.graphite" and set this: +

+ + # Configure the graphite api
+ [input_plugins.graphite]
+ enabled = true
+ address = "0.0.0.0" # If not set, is actually set to bind-address.
+ port = 2003
+ database = "jmeter" # store graphite data in this database
+ # udp_enabled = true # enable udp interface on the same port as the tcp interface
+
+
+

17.2.2 InfluxDB database configuration

+

Connect to InfluxDB admin console and create 2 databases: +

    +
  • grafana : Used by Grafana to store the dashboards we will create
  • +
  • jmeter : Used by InfluxDB to store the data sent to Graphite Listener as per database="jmeter" config element in influxdb.conf or config.toml
  • +
+

+
+

17.2.3 Grafana configuration

+

+ Installing grafana is just a matter of putting the unzipped bundle behind an Apache HTTP server.
+ Read documentation for more details. + Open config.js file and find datasources element, and edit it like this:
+

+ + datasources: {
+ influxdb: {
+ type: 'influxdb',
+ url: "http://localhost:8086/db/jmeter",
+ username: 'root',
+ password: 'root',
+ }, + grafana: {
+ type: 'influxdb',
+ url: "http://localhost:8086/db/grafana",
+ username: 'root',
+ password: 'root',
+ grafanaDB: true
+ },
+ }, +

+

+ Note that grafana has "grafanaDB:true". Also note that here we use root user for simplicity, it is better to dedicate a special user with less rights.
+ Here is the kind of dashboard that you could obtain: +

+
Grafana dashboard
Grafana dashboard
+ +
+
+ +

17.3 Graphite

+

TODO.

+
+ + +
\ No newline at end of file diff --git a/docs/usermanual/regular_expressions.html b/docs/usermanual/regular_expressions.html new file mode 100644 index 00000000000..eb71f8b1a35 --- /dev/null +++ b/docs/usermanual/regular_expressions.html @@ -0,0 +1,255 @@ + +Apache JMeter + - + User's Manual: Regular Expressions
Logo ASF
Apache JMeter

20. Regular Expressions

+

20.1 Overview

+

+JMeter includes the pattern matching software Apache Jakarta ORO +
+There is some documentation for this on the Jakarta web-site, for example + +a summary of the pattern matching characters +

+

+There is also documentation on an older incarnation of the product at +OROMatcher User's guide, which might prove useful. +

+

+The pattern matching is very similar to the pattern matching in Perl. +A full installation of Perl will include plenty of documentation on regular expressions - look for perlrequick, perlretut, perlre, perlreref. +

+

+It is worth stressing the difference between "contains" and "matches", as used on the Response Assertion test element: +

+
    +
  • +"contains" means that the regular expression matched at least some part of the target, +so 'alphabet' "contains" 'ph.b.' because the regular expression matches the substring 'phabe'. +
  • +
  • +"matches" means that the regular expression matched the whole target. +So 'alphabet' is "matched" by 'al.*t'. +
  • +
+

In this case, it is equivalent to wrapping the regular expression in ^ and $, viz '^al.*t$'. +

+

However, this is not always the case. +For example, the regular expression 'alp|.lp.*' is "contained" in 'alphabet', but does not match 'alphabet'. +

+

Why? Because when the pattern matcher finds the sequence 'alp' in 'alphabet', it stops trying any other combinations - and 'alp' is not the same as 'alphabet', as it does not include 'habet'. +

+

+Note: unlike Perl, there is no need to (i.e. do not) enclose the regular expression in //. +

+

+So how does one use the modifiers ismx etc if there is no trailing /? +The solution is to use extended regular expressions, i.e. /abc/i becomes (?i)abc. +See also Placement of modifiers below. +

+
+

20.2 Examples

+

Extract single string

+

+Suppose you want to match the following portion of a web-page: +
+name="file" value="readme.txt"> +
+and you want to extract readme.txt. +
+A suitable regular expression would be: +
+name="file" value="(.+?)"> +

+The special characters above are: +

+
    +
  • ( and ) - these enclose the portion of the match string to be returned
  • +
  • . - match any character
  • +
  • + - one or more times
  • +
  • ? - don't be greedy, i.e. stop when first match succeeds
  • +
+

+Note: without the ?, the .+ would continue past the first "> +until it found the last possible "> - which is probably not what was intended. +

+

+Note: although the above expression works, it's more efficient to use the following expression: +
+name="file" value="([^"]+)"> +where
+[^"] - means match anything except "
+In this case, the matching engine can stop looking as soon as it sees the first ", +whereas in the previous case the engine has to check that it has found "> rather than say " >. +

+

Extract multiple strings

+

+Suppose you want to match the following portion of a web-page:
+name="file.name" value="readme.txt" +and you want to extract both file.name and readme.txt. +
+A suitable reqular expression would be: +
+name="([^"]+)" value="([^"]+)" +
+This would create 2 groups, which could be used in the JMeter Regular Expression Extractor template as $1$ and $2$. +

+

+The JMeter Regex Extractor saves the values of the groups in additional variables. +

+

+For example, assume: +

+
    +
  • Reference Name: MYREF
  • +
  • Regex: name="(.+?)" value="(.+?)"
  • +
  • Template: $1$$2$
  • +
+
Do not enclose the regular expression in / /
+

+The following variables would be set: +

+
    +
  • MYREF: file.namereadme.txt
  • +
  • MYREF_g0: name="file.name" value="readme.txt"
  • +
  • MYREF_g1: file.name
  • +
  • MYREF_g2: readme.txt
  • +
+These variables can be referred to later on in the JMeter test plan, as ${MYREF}, ${MYREF_g1} etc +

+
+

20.3 Line mode

+

The pattern matching behaves in various slightly different ways, +depending on the setting of the multi-line and single-line modifiers. +Note that the single-line and multi-line operators have nothing to do with each other; +they can be specified independently. +

+

Single-line mode

+

+Single-line mode only affects how the '.' meta-character is interpreted. +

+

+Default behaviour is that '.' matches any character except newline. +In single-line mode, '.' also matches newline. +

+ +

Multi-line mode

+

+Multi-line mode only affects how the meta-characters '^' and '$' are interpreted. +

+

+Default behaviour is that '^' and '$' only match at the very beginning and end of the string. +When Multi-line mode is used, the '^' metacharacter matches at the beginning of every line, +and the '$' metacharacter matches at the end of every line.

+ +
+ +

20.4 Meta characters

+

+Regular expressions use certain characters as meta characters - these characters have a special meaning to the RE engine. +Such characters must be escaped by preceeding them with \ (backslash) in order to treat them as ordinary characters. +Here is a list of the meta characters and their meaning (please check the ORO documentation if in doubt). +

+
    +
  • ( ) - grouping
  • +
  • [ ] - character classes
  • +
  • { } - repetition
  • +
  • * + ? - repetition
  • +
  • . - wild-card character
  • +
  • \ - escape character
  • +
  • | - alternatives
  • +
  • ^ $ - start and end of string or line
  • +
+
+

Please note that ORO does not support the \Q and \E meta-characters. +[In other RE engines, these can be used to quote a portion of an RE so that the meta-characters stand for themselves.] +You can use function to do the equivalent, see ${__escapeOroRegexpChars(valueToEscape)}. +

+
+

+The following Perl5 extended regular expressions are supported by ORO. + +

+
(?#text)
+
An embedded comment causing text to be ignored.
+
(?:regexp)
+
Groups things like "()" but doesn't cause the group match to be saved.
+
(?=regexp)
+
A zero-width positive lookahead assertion. For example, \w+(?=\s) matches a word followed by whitespace, without including whitespace in the MatchResult.
+
(?!regexp)
+
A zero-width negative lookahead assertion. For example foo(?!bar) matches any occurrence of "foo" that isn't followed by "bar". Remember that this is a zero-width assertion, which means that a(?!b)d will match ad because a is followed by a character that is not b (the d) and a d follows the zero-width assertion.
+
(?imsx)
+
One or more embedded pattern-match modifiers. i enables case insensitivity, m enables multiline treatment of the input, s enables single line treatment of the input, and x enables extended whitespace comments.
+
+Note that (?<=regexp) - lookbehind - is not supported. +

+ +
+ +

20.5 Placement of modifiers

+

+Modifiers can be placed anywhere in the regex, and apply from that point onwards. +[A bug in ORO means that they cannot be used at the very end of the regex. +However they would have no effect there anyway.] +

+

+The single-line (?s) and multi-line (?m) modifiers are normally placed at the start of the regex. +

+

+The ignore-case modifier (?i) may be usefully applied to just part of a regex, +for example: +

+Match ExAct case or (?i)ArBiTrARY(?-i) case
+
+

+
+

20.6 Testing Regular Expressions

+

+Since JMeter 2.4, the listener View Results Tree +include a RegExp Tester to test regular expressions directly on sampler response data. +

+

+There is a Website to test Java Regular expressions. +

+

+Another approach is to use a simple test plan to test the regular expressions. +The Java Request sampler can be used to generate a sample, or the HTTP Sampler can be used to load a file. +Add a Debug Sampler and a Tree View Listener and changes to the regular expression can be tested quickly, +without needing to access any external servers. +

+
\ No newline at end of file diff --git a/docs/usermanual/remote-test.html b/docs/usermanual/remote-test.html new file mode 100644 index 00000000000..a3fc48de6a2 --- /dev/null +++ b/docs/usermanual/remote-test.html @@ -0,0 +1,323 @@ + +Apache JMeter + - + User's Manual: Remote (Distributed) Testing
Logo ASF
Apache JMeter

15. Remote Testing

+ +

In the event that your JMeter client machine is unable, performance-wise, to simulate +enough users to stress your server or is limited at network level, an option exists to control multiple, remote JMeter +engines from a single JMeter client. By running JMeter remotely, you can replicate +a test across many low-end computers and thus simulate a larger load on the server. One +instance of the JMeter client can control any number of remote JMeter instances, and collect +all the data from them. This offers the following features: + +

    +
  • Saving of test samples to the local machine
  • +
  • Managment of multiple JMeterEngines from a single machine
  • +
  • No need to copy the test plan to each server - the client sends it to all the servers
  • +
+

+
+Note: The same test plan is run by all the servers. +JMeter does not distribute the load between servers, each runs the full test plan. +So if you set 1000 Threads and have 6 JMeter server, you end up injecting 6000 Threads. +
+

+However, remote mode does use more resources than running the same number of non-GUI tests independently. +If many server instances are used, the client JMeter can become overloaded, as can the client network connection. +This has been improved by switching to Stripped modes (see below) but you should always check that your client is not overloaded. +

+

Note that while you can execute the JMeterEngine on your application +server, you need to be mindful of the fact that this will be adding processing +overhead on the application server and thus your testing results will be +somewhat tainted. The recommended approach is to have one or more machines on +the same Ethernet segment as your application server that you configure to run +the JMeter Engine. This will minimize the impact of the network on the test +results without impacting the performance of the application server +itself. +

+ +

Step 0: Configure the nodes

+

+Make sure that all the nodes (client and servers) : +

    +
  • are running exactly the same version of JMeter.
  • +
  • are using the same version of Java on all systems. Using different versions of Java may work but is discouraged.
  • +
+

+

+If the test uses any data files, note that these are not sent across by the client so +make sure that these are available in the appropriate directory on each server. +If necessary you can define different values for properties by editing the user.properties or system.properties +files on each server. These properties will be picked up when the server is started and may be +used in the test plan to affect its behaviour (e.g. connecting to a different remote server). +Alternatively use different content in any datafiles used by the test +(e.g. if each server must use unique ids, divide these between the data files) +

+ +

Step 1: Start the servers

+

To run JMeter in remote node, start the JMeter server component on all machines you wish to run on by running the JMETER_HOME/bin/jmeter-server (unix) or JMETER_HOME/bin/jmeter-server.bat (windows) script.

+

Note that there can only be one JMeter server on each node unless different RMI ports are used.

+

Since JMeter 2.3.1, the JMeter server application starts the RMI registry itself; +there is no need to start RMI registry separately. +To revert to the previous behaviour, define the JMeter property server.rmi.create=false on the server host systems. +

+

+By default, RMI uses a dynamic port for the JMeter server engine. This can cause problems for firewalls, +so with versions of JMeter after 2.3.2 you can define the JMeter property server.rmi.localport +to control this port number. +If this is non-zero, it will be used as the local port number for the server engine. +

+

Step 2: Add the server IP to your client's Properties File

+

Edit the properties file on the controlling JMeter machine. In /bin/jmeter.properties, find the property named, "remote_hosts", and +add the value of your running JMeter server's IP address. Multiple such servers can be added, comma-delimited.

+

Note that you can use the -R command line option +instead to specify the remote host(s) to use. This has the same effect as using -r and -Jremote_hosts={serverlist}. + E.g. jmeter -Rhost1,127.0.0.1,host2

+

If you define the JMeter property server.exitaftertest=true, then the server will exit after it runs a single test. +See also the -X flag (described below) +

+

Step 3a: Start the JMeter Client from a GUI client to check configuration

+

Now you are ready to start the controlling JMeter client. For MS-Windows, start the client with the script "bin/jmeter.bat". For UNIX, +use the script "bin/jmeter". You will notice that the Run menu contains two new sub-menus: "Remote Start" and "Remote Stop" +(see figure 1). These menus contain the client that you set in the properties file. Use the remote start and stop instead of the +normal JMeter start and stop menu items.

+
Figure 1 - Run Menu
Figure 1 - Run Menu
+ +

Step 3b: Start the JMeter from a non-GUI Client

+

+GUI mode should only be used for debugging, as a better alternative, you should start the test on remote server(s) from a non-GUI (command-line) client. +The command to do this is: +

+jmeter -n -t script.jmx -r
+or
+jmeter -n -t script.jmx -R server1,server2...
+
+Other flags that may be useful:
+-Gproperty=value - define a property in all the servers (may appear more than once)
+-X - Exit remote servers at the end of the test.
+
+The first example will start the test on whatever servers are defined in the JMeter property remote_hosts;
+The second example will define remote_hosts from the list of servers and then start the test on the remote servers. +
+The command-line client will exit when all the remote servers have stopped. +

+ +

15.1 Doing it Manually

+

In some cases, the jmeter-server script may not work for you (if you are using an OS platform not anticipated by the JMeter developers). Here is how to start the JMeter servers (step 1 above) with a more manual process:

+

Step 1a: Start the RMI Registry

+

+Since JMeter 2.3.1, the RMI registry is started by the JMeter server, so this section does not apply in the normal case. +To revert to the previous behaviour, define the JMeter property server.rmi.create=false on the server host systems +and follow the instructions below. +

+

JMeter uses Remote Method Invocation (RMI) as the remote communication mechanism. Therefore, you need +to run the RMI Registry application (which is named, "rmiregistry") that comes with the JDK and is located in the "bin" +directory. Before running rmiregistry, make sure that the following jars are in your system claspath: +

    +
  • JMETER_HOME/lib/ext/ApacheJMeter_core.jar
  • +
  • JMETER_HOME/lib/jorphan.jar
  • +
  • JMETER_HOME/lib/logkit-2.0.jar
  • +
+The +rmiregistry application needs access to certain JMeter classes. Run rmiregistry with no parameters. By default the +application listens to port 1099.

+ +

Step 1b: Start the JMeter Server

+

Once the RMI Registry application is running, start the JMeter Server. +Use the "-s" option with the jmeter startup script ("jmeter -s").

+ +

Steps 2 and 3 remain the same.

+
+

15.2 Tips

+

+JMeter/RMI requires a connection from the client to the server. This will use the port you chose, default 1099.
+JMeter/RMI also requires a reverse connection in order to return sample results from the server to the client.
+This will use a high-numbered port.
+This port can be controlled by jmeter property called client.rmi.localport in jmeter.properties.
+If there are any firewalls or other network filters between JMeter client and server, +you will need to make sure that they are set up to allow the connections through. +If necessary, use monitoring software to show what traffic is being generated. +

+

If you're running Suse Linux, these tips may help. The default installation may enable the firewall. In that case, remote testing will not work properly. The following tips were contributed by Sergey Ten.

+

If you see connections refused, turn on debugging by passing the following options.

+ rmiregistry -J-Dsun.rmi.log.debug=true + -J-Dsun.rmi.server.exceptionTrace=true + -J-Dsun.rmi.loader.logLevel=verbose + -J-Dsun.rmi.dgc.logLevel=verbose + -J-Dsun.rmi.transport.logLevel=verbose + -J-Dsun.rmi.transport.tcp.logLevel=verbose +

Since JMeter 2.3.1, the RMI registry is started by the server; however the options can still be passed in from the JMeter command line. +For example: "jmeter -s -Dsun.rmi.loader.logLevel=verbose" (i.e. omit the -J prefixes). +Alternatively the properties can be defined in the system.properties file. +

+

The solution to the problem is to remove the loopbacks 127.0.0.1 and 127.0.0.2 from etc/hosts. What happens is jmeter-server can't connect to rmiregistry if 127.0.0.2 loopback is not available. Use the following settings to fix the problem.

+

Replace

+
    +
  • `dirname $0`/jmeter -s "$@"
  • +
+

With

+
    +
  • HOST="-Djava.rmi.server.hostname=[computer_name][computer_domain]
  • +
  • -Djava.security.policy=`dirname $0`/[policy_file]"
  • +
  • `dirname $0`/jmeter $HOST -s "$@"
  • +
+

Also create a policy file and add [computer_name][computer_domain] line to /etc/hosts.

+ +

In order to better support SSH-tunneling of the RMI communication channels used +in remote testing, since JMeter 2.6:

+
    +
  • a new property "client.rmi.localport" can be set to control the RMI port used by the RemoteSampleListenerImpl
  • +
  • To support tunneling RMI traffic over an SSH tunnel as the remote endpoint using a port on the local machine, + loopback interface is now allowed to be used if it has been specified directly using the Java System Property "java.rmi.server.hostname" parameter.
  • +
+
+

15.3 Using a different port

+

By default, JMeter uses the standard RMI port 1099. It is possible to change this. For this to work successfully, all the following need to agree:

+
    +
  • On the server, start rmiregistry using the new port number
  • +
  • On the server, start JMeter with the property server_port defined
  • +
  • On the client, update the remote_hosts property to include the new remote host:port settings
  • +
+ +

Since Jmeter 2.1.1, the jmeter-server scripts provide support for changing the port. +For example, assume you want to use port 1664 (perhaps 1099 is already used).

+
+On Windows (in a DOS box)
+C:\JMETER> SET SERVER_PORT=1664
+C:\JMETER> JMETER-SERVER [other options]
+
+On Unix:
+$ SERVER_PORT=1664 jmeter-server [other options]
+[N.B. use upper case for the environment variable]
+
+

+In both cases, the script starts rmiregistry on the specified port, +and then starts JMeter in server mode, having defined the "server_port" property. +

+

+The chosen port will be logged in the server jmeter.log file (rmiregistry does not create a log file). +

+
+ +

15.4 Using a different sample sender

+

+Listeners in the test plan send their results back to the client JMeter which writes the results to the specified files +By default, samples are sent back synchronously as they are generated. +This can affect the maximum throughput of the server test; the sample result has to be sent back before the thread can +continue. +There are some JMeter properties that can be set to alter this behaviour. +

+
    +
  • mode - sample sending mode - default is StrippedBatch since 2.9. This should be set on the client node.
  • +
      +
    • Standard - send samples synchronously as soon as they are generated
    • +
    • Hold - hold samples in an array until the end of a run. This may use a lot of memory on the server and is discouraged.
    • +
    • DiskStore - store samples in a disk file (under java.io.temp) until the end of a run. + The serialised data file is deleted on JVM exit.
    • +
    • StrippedDiskStore - remove responseData from succesful samples, and use DiskStore sender to send them.
    • +
    • Batch - send saved samples when either the count (num_sample_threshold) or time (time_threshold) exceeds a threshold, + at which point the samples are sent synchronously. + The thresholds can be configured on the server using the following properties: +
        +
      • num_sample_threshold - number of samples to accumulate, default 100
      • +
      • time_threshold - time threshold, default 60000 ms = 60 seconds
      • +
      +
    • + See also the Asynch mode, described below. +
    • Statistical - send a summary sample when either the count or time exceeds a threshold. + The samples are summarised by thread group name and sample label. + The following fields are accumulated: +
        +
      • elapsed time
      • +
      • latency
      • +
      • bytes
      • +
      • sample count
      • +
      • error count
      • +
      + Other fields that vary between samples are lost. +
    • +
    • Stripped - remove responseData from succesful samples
    • +
    • StrippedBatch - remove responseData from succesful samples, and use Batch sender to send them.
    • +
    • Asynch - samples are temporarily stored in a local queue. A separate worker thread sends the samples. + This allows the test thread to continue without waiting for the result to be sent back to the client. + However, if samples are being created faster than they can be sent, the queue will eventually fill up, + and the sampler thread will block until some samples can be drained from the queue. + This mode is useful for smoothing out peaks in sample generation. + The queue size can be adjusted by setting the JMeter property + asynch.batch.queue.size (default 100) on the server node. +
    • +
    • StrippedAsynch - remove responseData from succesful samples, and use Async sender to send them.
    • +
    • Custom implementation : set the mode parameter to your custom sample sender class name. + This must implement the interface SampleSender and have a constructor which takes a single + parameter of type RemoteSampleListener. +
    • +
    +
+
Stripped mode family strips responseData so this means that some Elements that rely on the previous responseData being available will not work.
+This is not really a problem as there is always a more efficient way to implement this feature. +
+

The following properties apply to the Batch and Statistical modes:

+
    +
  • num_sample_threshold - number of samples in a batch (default 100)
  • +
  • time_threshold - number of milliseconds to wait (default 60 seconds)
  • +
+
+ + +

15.5 Dealing with nodes that failed starting

+

+ For large-scale tests there is a chance that some part of remote servers will be unavailable or down. + For example, when you use automation script to allocate many cloud machines and use them as generators, + some of requested machines might fail booting because of cloud's issues. + Since JMeter 2.13 there are new properties to control this behaviour. +

+

+ First what you might want is to retry initialization attempts in hope that failed nodes just slightly delayed their boot. + To enable retries, you should set client.tries property to total number of connection attempts. + By default it does only one attempt. To control retry delay, set the client.retries_delay property + to number of milliseconds to sleep between attempts. +

+ +

+ Finally, you might still want to run the test with those generators that succeeded initialization and skipping failed nodes. + To enable that, set the client.continue_on_fail=true property. +

+
+ +
\ No newline at end of file diff --git a/docs/usermanual/test_plan.html b/docs/usermanual/test_plan.html new file mode 100644 index 00000000000..760c112590e --- /dev/null +++ b/docs/usermanual/test_plan.html @@ -0,0 +1,509 @@ + +Apache JMeter + - + User's Manual: Elements of a Test Plan
Logo ASF
Apache JMeter

4. Elements of a Test Plan

+ +

The Test Plan object has a checkbox called "Functional Testing". If selected, it +will cause JMeter to record the data returned from the server for each sample. If you have +selected a file in your test listeners, this data will be written to file. This can be useful if +you are doing a small run to ensure that JMeter is configured correctly, and that your server +is returning the expected results. The consequence is that the file will grow huge quickly, and +JMeter's performance will suffer. This option should be off if you are doing stress-testing (it +is off by default).

+

If you are not recording the data to file, this option makes no difference.

+

You can also use the Configuration button on a listener to decide what fields to save.

+ +

4.1 Thread Group

+

Thread group elements are the beginning points of any test plan. +All controllers and samplers must be under a thread group. +Other elements, e.g. Listeners, may be placed directly under the test plan, +in which case they will apply to all the thread groups. +As the name implies, the thread group +element controls the number of threads JMeter will use to execute your test. The +controls for a thread group allow you to: +

  • Set the number of threads
  • +
  • Set the ramp-up period
  • +
  • Set the number of times to execute the test
  • +

+ +

Each thread will execute the test plan in its entirety and completely independently +of other test threads. Multiple threads are used to simulate concurrent connections +to your server application.

+ +

The ramp-up period tells JMeter how long to take to "ramp-up" to the full number of +threads chosen. If 10 threads are used, and the ramp-up period is 100 seconds, then +JMeter will take 100 seconds to get all 10 threads up and running. Each thread will +start 10 (100/10) seconds after the previous thread was begun. If there are 30 threads +and a ramp-up period of 120 seconds, then each successive thread will be delayed by 4 seconds.

+ +

Ramp-up needs to be long enough to avoid too large a work-load at the start +of a test, and short enough that the last threads start running before +the first ones finish (unless one wants that to happen). +

+

+Start with Ramp-up = number of threads and adjust up or down as needed. +

+ +

By default, the thread group is configured to loop once through its elements.

+ +

Version 1.9 introduces a test run scheduler. + Click the checkbox at the bottom of the Thread Group panel to reveal extra fields + in which you can enter the start and end times of the run. + When the test is started, JMeter will wait if necessary until the start-time has been reached. + At the end of each cycle, JMeter checks if the end-time has been reached, and if so, the run is stopped, + otherwise the test is allowed to continue until the iteration limit is reached.

+

Alternatively, one can use the relative delay and duration fields. + Note that delay overrides start-time, and duration over-rides end-time.

+
+ +

4.2 Controllers

+ +

+JMeter has two types of Controllers: Samplers and Logical Controllers. +These drive the processing of a test. +

+ +

Samplers tell JMeter to send requests to a server. For +example, add an HTTP Request Sampler if you want JMeter +to send an HTTP request. You can also customize a request by adding one +or more Configuration Elements to a Sampler. For more +information, see +Samplers.

+ +

Logical Controllers let you customize the logic that JMeter uses to +decide when to send requests. For example, you can add an Interleave +Logic Controller to alternate between two HTTP Request Samplers. +For more information, see Logical Controllers.

+ +
+ +

4.2.1 Samplers

+ +

+Samplers tell JMeter to send requests to a server and wait for a response. +They are processed in the order they appear in the tree. +Controllers can be used to modify the number of repetitions of a sampler. +

+

+JMeter samplers include: +

    +
  • FTP Request
  • +
  • HTTP Request
  • +
  • JDBC Request
  • +
  • Java object request
  • +
  • LDAP Request
  • +
  • SOAP/XML-RPC Request
  • +
  • WebService (SOAP) Request
  • +
+Each sampler has several properties you can set. +You can further customize a sampler by adding one or more Configuration Elements to the Test Plan. +

+ +

If you are going to send multiple requests of the same type (for example, +HTTP Request) to the same server, consider using a Defaults Configuration +Element. Each controller has one or more Defaults elements (see below).

+ +

Remember to add a Listener to your test plan to view and/or store the +results of your requests to disk.

+ +

If you are interested in having JMeter perform basic validation on +the response of your request, add an Assertion to +the sampler. For example, in stress testing a web application, the server +may return a successful "HTTP Response" code, but the page may have errors on it or +may be missing sections. You could add assertions to check for certain HTML tags, +common error strings, and so on. JMeter lets you create these assertions using regular +expressions.

+ +

JMeter's built-in samplers

+
+ +

4.2.2 Logic Controllers

+

Logic Controllers let you customize the logic that JMeter uses to +decide when to send requests. +Logic Controllers can change the order of requests coming from their +child elements. They can modify the requests themselves, cause JMeter to repeat +requests, etc. +

+ +

To understand the effect of Logic Controllers on a test plan, consider the +following test tree:

+ +

+

    +
  • Test Plan
  • +
      +
    • Thread Group
    • +
        +
      • Once Only Controller
      • + +
      • Load Search Page (HTTP Sampler)
      • +
      • Interleave Controller
      • +
          +
        • Search "A" (HTTP Sampler)
        • +
        • Search "B" (HTTP Sampler)
        • +
        • HTTP default request (Configuration Element)
        • +
        +
      • HTTP default request (Configuration Element)
      • +
      • Cookie Manager (Configuration Element)
      • +
      +
    +
+

+ +

The first thing about this test is that the login request will be executed only +the first time through. Subsequent iterations will skip it. This is due to the +effects of the Once Only Controller.

+ +

After the login, the next Sampler loads the search page (imagine a +web application where the user logs in, and then goes to a search page to do a search). This +is just a simple request, not filtered through any Logic Controller.

+ +

After loading the search page, we want to do a search. Actually, we want to do +two different searches. However, we want to re-load the search page itself between +each search. We could do this by having 4 simple HTTP request elements (load search, +search "A", load search, search "B"). Instead, we use the Interleave Controller which passes on one child request each time through the test. It keeps the +ordering (ie - it doesn't pass one on at random, but "remembers" its place) of its +child elements. Interleaving 2 child requests may be overkill, but there could easily have +been 8, or 20 child requests.

+ +

Note the HTTP Request Defaults that +belongs to the Interleave Controller. Imagine that "Search A" and "Search B" share +the same PATH info (an HTTP request specification includes domain, port, method, protocol, +path, and arguments, plus other optional items). This makes sense - both are search requests, + hitting the same back-end search engine (a servlet or cgi-script, let's say). Rather than + configure both HTTP Samplers with the same information in their PATH field, we + can abstract that information out to a single Configuration Element. When the Interleave + Controller "passes on" requests from "Search A" or "Search B", it will fill in the blanks with + values from the HTTP default request Configuration Element. So, we leave the PATH field + blank for those requests, and put that information into the Configuration Element. In this +case, this is a minor benefit at best, but it demonstrates the feature.

+ +

The next element in the tree is another HTTP default request, this time added to the +Thread Group itself. The Thread Group has a built-in Logic Controller, and thus, it uses +this Configuration Element exactly as described above. It fills in the blanks of any +Request that passes through. It is extremely useful in web testing to leave the DOMAIN +field blank in all your HTTP Sampler elements, and instead, put that information +into an HTTP default request element, added to the Thread Group. By doing so, you can +test your application on a different server simply by changing one field in your Test Plan. +Otherwise, you'd have to edit each and every Sampler.

+ +

The last element is a HTTP Cookie Manager. A Cookie Manager should be added to all web tests - otherwise JMeter will +ignore cookies. By adding it at the Thread Group level, we ensure that all HTTP requests +will share the same cookies.

+ +

Logic Controllers can be combined to achieve various results. See the list of built-in +Logic Controllers.

+
+ +

4.2.3 Test Fragments

+

The Test Fragment element is a special type of controller that +exists on the Test Plan tree at the same level as the Thread Group element. It is distinguished +from a Thread Group in that it is not executed unless it is +referenced by either a Module Controller or an Include_Controller. +

+

This element is purely for code re-use within Test Plans and was introduced in Version 2.5

+
+ +

4.3 Listeners

+

Listeners provide access to the information JMeter gathers about the test cases while +JMeter runs. The Graph Results listener plots the response times on a graph. +The "View Results Tree" Listener shows details of sampler requests and responses, and can display basic HTML and XML representations of the response. +Other listeners provide summary or aggregation information. +

+ +

+Additionally, listeners can direct the data to a file for later use. +Every listener in JMeter provides a field to indicate the file to store data to. +There is also a Configuration button which can be used to choose which fields to save, and whether to use CSV or XML format. +Note that all Listeners save the same data; the only difference is in the way the data is presented on the screen. +

+ +

+Listeners can be added anywhere in the test, including directly under the test plan. +They will collect data only from elements at or below their level. +

+ +

There are several listeners +that come with JMeter.

+
+ +

4.4 Timers

+ +

By default, a JMeter thread sends requests without pausing between each request. +We recommend that you specify a delay by adding one of the available timers to +your Thread Group. If you do not add a delay, JMeter could overwhelm your server by +making too many requests in a very short amount of time.

+ +

The timer will cause JMeter to delay a certain amount of time before each +sampler which is in its scope.

+ +

+If you choose to add more than one timer to a Thread Group, JMeter takes the sum of +the timers and pauses for that amount of time before executing the samplers to which the timers apply. +Timers can be added as children of samplers or controllers in order to restrict the samplers to which they are applied. +

+

+To provide a pause at a single place in a test plan, one can use the Test Action Sampler. +

+
+ +

4.5 Assertions

+ +

Assertions allow you to assert facts about responses received from the +server being tested. Using an assertion, you can essentially "test" that your +application is returning the results you expect it to.

+ +

For instance, you can assert that the response to a query will contain some +particular text. The text you specify can be a Perl-style regular expression, and +you can indicate that the response is to contain the text, or that it should match +the whole response.

+ +

You can add an assertion to any Sampler. For example, you can +add an assertion to a HTTP Request that checks for the text, "</HTML>". JMeter +will then check that the text is present in the HTTP response. If JMeter cannot find the +text, then it will mark this as a failed request.

+ +

+Note that assertions apply to all samplers which are in its scope. +To restrict the assertion to a single sampler, add the assertion as a child of the sampler. +

+ +

To view the assertion results, add an Assertion Listener to the Thread Group. +Failed Assertions will also show up in the Tree View and Table Listeners, +and will count towards the error %age for example in the Aggregate and Summary reports. +

+
+ +

4.6 Configuration Elements

+

A configuration element works closely with a Sampler. Although it does not send requests +(except for HTTP(S) Test Script Recorder), it can add to or modify requests.

+ +

A configuration element is accessible from only inside the tree branch where you place the element. +For example, if you place an HTTP Cookie Manager inside a Simple Logic Controller, the Cookie Manager will +only be accessible to HTTP Request Controllers you place inside the Simple Logic Controller (see figure 1). +The Cookie Manager is accessible to the HTTP requests "Web Page 1" and "Web Page 2", but not "Web Page 3".

+

Also, a configuration element inside a tree branch has higher precedence than the same element in a "parent" +branch. For example, we defined two HTTP Request Defaults elements, "Web Defaults 1" and "Web Defaults 2". +Since we placed "Web Defaults 1" inside a Loop Controller, only "Web Page 2" can access it. The other HTTP +requests will use "Web Defaults 2", since we placed it in the Thread Group (the "parent" of all other branches).

+ +
Figure 1 -
+    Test Plan Showing Accessability of Configuration Elements
Figure 1 - + Test Plan Showing Accessability of Configuration Elements
+ +
+The User Defined Variables Configuration element is different. +It is processed at the start of a test, no matter where it is placed. +For simplicity, it is suggested that the element is placed only at the start of a Thread Group. +
+
+ +

4.7 Pre-Processor Elements

+

A Pre-Processor executes some action prior to a Sampler Request being made. +If a Pre-Processor is attached to a Sampler element, then it will execute just prior to that sampler element running. +A Pre-Processor is most often used to modify the settings of a Sample Request just before it runs, or to update variables that aren't extracted from response text. +See the scoping rules for more details on when Pre-Processors are executed.

+
+ +

4.8 Post-Processor Elements

+

A Post-Processor executes some action after a Sampler Request has been made. +If a Post-Processor is attached to a Sampler element, then it will execute just after that sampler element runs. +A Post-Processor is most often used to process the response data, often to extract values from it. +See the scoping rules for more details on when Post-Processors are executed.

+
+ +

4.9 Execution order

+
    +
  1. Configuration elements
  2. +
  3. Pre-Processors
  4. +
  5. Timers
  6. +
  7. Sampler
  8. +
  9. Post-Processors (unless SampleResult is null)
  10. +
  11. Assertions (unless SampleResult is null)
  12. +
  13. Listeners (unless SampleResult is null)
  14. +
+ +
+Please note that Timers, Assertions, Pre- and Post-Processors are only processed if there is a sampler to which they apply. +Logic Controllers and Samplers are processed in the order in which they appear in the tree. +Other test elements are processed according to the scope in which they are found, and the type of test element. +[Within a type, elements are processed in the order in which they appear in the tree]. +
+

+For example, in the following test plan: +

    +
  • Controller
  • +
      +
    • Post-Processor 1
    • +
    • Sampler 1
    • +
    • Sampler 2
    • +
    • Timer 1
    • +
    • Assertion 1
    • +
    • Pre-Processor 1
    • +
    • Timer 2
    • +
    • Post-Processor 2
    • +
    +
+The order of execution would be: +
+Pre-Processor 1
+Timer 1
+Timer 2
+Sampler 1
+Post-Processor 1
+Post-Processor 2
+Assertion 1
+
+Pre-Processor 1
+Timer 1
+Timer 2
+Sampler 2
+Post-Processor 1
+Post-Processor 2
+Assertion 1
+
+

+
+ +

4.10 Scoping Rules

+

+The JMeter test tree contains elements that are both hierarchical and ordered. Some elements in the test trees are strictly hierarchical (Listeners, Config Elements, Post-Procesors, Pre-Processors, Assertions, Timers), and some are primarily ordered (controllers, samplers). When you create your test plan, you will create an ordered list of sample request (via Samplers) that represent a set of steps to be executed. These requests are often organized within controllers that are also ordered. Given the following test tree:

+
Example test tree
Example test tree
+

The order of requests will be, One, Two, Three, Four.

+

Some controllers affect the order of their subelements, and you can read about these specific controllers in the component reference.

+

Other elements are hierarchical. An Assertion, for instance, is hierarchical in the test tree. +If its parent is a request, then it is applied to that request. If its +parent is a Controller, then it affects all requests that are descendants of +that Controller. In the following test tree:

+
Hierarchy example
Hierarchy example
+

Assertion #1 is applied only to Request One, while Assertion #2 is applied to Requests Two and Three.

+

Another example, this time using Timers:

+
complex example
complex example
+

In this example, the requests are named to reflect the order in which they will be executed. Timer #1 will apply to Requests Two, Three, and Four (notice how order is irrelevant for hierarchical elements). Assertion #1 will apply only to Request Three. Timer #2 will affect all the requests.

+

Hopefully these examples make it clear how configuration (hierarchical) elements are applied. If you imagine each Request being passed up the tree branches, to its parent, then to its parent's parent, etc, and each time collecting all the configuration elements of that parent, then you will see how it works.

+ +The Configuration elements Header Manager, Cookie Manager and Authorization manager are +treated differently from the Configuration Default elements. +The settings from the Configuration Default elements are merged into a set of values that the Sampler has access to. +However, the settings from the Managers are not merged. +If more than one Manager is in the scope of a Sampler, +only one Manager is used, but there is currently no way to specify which is used. + +
+ + +

4.11 Properties and Variables

+ +

+JMeter properties are defined in jmeter.properties (see Gettting Started - Configuring JMeter for more details). +
+Properties are global to jmeter, and are mostly used to define some of the defaults JMeter uses. +For example the property remote_hosts defines the servers that JMeter will try to run remotely. +Properties can be referenced in test plans +- see Functions - read a property - +but cannot be used for thread-specific values. +

+

+JMeter variables are local to each thread. The values may be the same for each thread, or they may be different. +
+If a variable is updated by a thread, only the thread copy of the variable is changed. +For example the Regular Expression Extractor Post-Processor +will set its variables according to the sample that its thread has read, and these can be used later +by the same thread. +For details of how to reference variables and functions, see Functions and Variables +

+

+Note that the values defined by the Test Plan and the User Defined Variables configuration element +are made available to the whole test plan at startup. +If the same variable is defined by multiple UDV elements, then the last one takes effect. +Once a thread has started, the initial set of variables is copied to each thread. +Other elements such as the +User Parameters Pre-Processor or Regular Expression Extractor Post-Processor +may be used to redefine the same variables (or create new ones). These redefinitions only apply to the current thread. +

+

+The setProperty function can be used to define a JMeter property. +These are global to the test plan, so can be used to pass information between threads - should that be needed. +

+
Both variables and properties are case-sensitive.
+
+ +

4.12 Using Variables to parameterise tests

+

+Variables don't have to vary - they can be defined once, and if left alone, will not change value. +So you can use them as short-hand for expressions that appear frequently in a test plan. +Or for items which are constant during a run, but which may vary between runs. +For example, the name of a host, or the number of threads in a thread group. +

+

+When deciding how to structure a Test Plan, +make a note of which items are constant for the run, but which may change between runs. +Decide on some variable names for these - +perhaps use a naming convention such as prefixing them with C_ or K_ or using uppercase only +to distinguish them from variables that need to change during the test. +Also consider which items need to be local to a thread - +for example counters or values extracted with the Regular Expression Post-Processor. +You may wish to use a different naming convention for these. +

+

+For example, you might define the following on the Test Plan: +

+HOST             www.example.com
+THREADS          10
+LOOPS            20
+
+You can refer to these in the test plan as ${HOST} ${THREADS} etc. +If you later want to change the host, just change the value of the HOST variable. +This works fine for small numbers of tests, but becomes tedious when testing lots of different combinations. +One solution is to use a property to define the value of the variables, for example: +
+HOST             ${__P(host,www.example.com)}
+THREADS          ${__P(threads,10)}
+LOOPS            ${__P(loops,20)}
+
+You can then change some or all of the values on the command-line as follows: +
+jmeter ... -Jhost=www3.example.org -Jloops=13
+
+

+
+ +
\ No newline at end of file diff --git a/eclipse.classpath b/eclipse.classpath new file mode 100644 index 00000000000..fa596cb3781 --- /dev/null +++ b/eclipse.classpath @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eclipse.readme b/eclipse.readme new file mode 100644 index 00000000000..2f475e882a7 --- /dev/null +++ b/eclipse.readme @@ -0,0 +1,104 @@ + 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. + + + +Eclipse settings +---------------- +The following files should be excluded from build output: +*.metaprops +See Preferences/Java/Building/Output Folder/Filtered Resources + + +Eclipse.classpath +----------------- +[This has been tested with Eclipse 3.2 up to 4.3.2. It may not work with other versions.] + +The file eclipse.classpath is intended as a starter .classpath file +for building JMeter using Eclipse version 3 to 4.3. Make sure to execute +the "ant download_jars" task to download and install the jars referred +to in the classpath before creating the Eclipse project. +If you do after creating project, then don't forget to refresh Eclipse project. + +Note that Eclipse is not easy to use for creating jar files. +However, it is easy to use Eclipse to run Ant. + +The following targets may prove useful: + + clean - Clean up to force a build from source + package-only - creates the jars + package - compiles everything and then packages it + run_gui - compiles, packages, and then start the JMeter GUI from the jars + +Invoking Ant targets inside Eclipse +---------------------------------- +You can use the "Run As --> Ant Build" and select target, or you can use +the "Windows->Show View->Ant View". Then select the "build.xml" file and +drag and drop to the "Ant View". +Now you can invoke targets by clicking on them. +Note that if you invoke for example the "compile" target, and get error +messages about +" +Unable to find a javac compiler; +com.sun.tools.javac.Main is not on the classpath. +Perhaps JAVA_HOME does not point to the JDK +" +it just means that your Eclipse project is set up with JRE libraries instead of JDK libraries. +The suggested fix is to add a JDK in "Window->Preferences->Java->Installed JREs". +Then do a "Project->Properties" and select "Java Build Path" in the left pane, and then +select the "Libraries" tab in the right pane. Scroll to the bottom, select the "JRE System Library", +and click "Remove". Then click "Add library..." , select "JRE System Library", and then select +the JDK. Now it should work when you invoke the "compile" target. + + +Finishing the build using Ant +----------------------------- + +Find the build.xml file in the project, +right click on it, and click "Run As --> Ant Build". + +Make sure you select the "package" target. + +This will compile any remaining classes, +and then create all the jars. + +Now refresh the project (you should add this to the Ant build properties) + +Launching from Eclipse +---------------------- + +You can use the Ant target run_gui to run the JMeter GUI, or you can follow the instructions +below to add a Java Application launch, which will for example, allow you to use the debugger to +run JMeter. + +These instructions assume you have configured Eclipse to use the classpath +as suggested in eclipse.classpath, and have run "ant package" to compile +the RMI classes and build the jars. + +Create a new Java Application launch configuration. + +On the Main tab, enter the following as the main class: + + org.apache.jmeter.NewDriver + +On the Arguments tab, in the Working Directory area, pick the radio +button next to "Other" and enter the following in the text box: + + ${workspace_loc}/jmeterproject/bin + + where "jmeterproject" is the name of the JMeter project. + + [It would be nicer to use ${project_loc}/bin + but unfortunately the Eclipse Debug view does not seem to preserve any of the project variables] diff --git a/extras/ConvertHTTPSampler.txt b/extras/ConvertHTTPSampler.txt new file mode 100644 index 00000000000..1b83942193a --- /dev/null +++ b/extras/ConvertHTTPSampler.txt @@ -0,0 +1,16 @@ +=== HTTPSampler to HTTPSampler2 convertion === + +If the testcase was created with an old version, load it into 2.1.1 and save it. +Edit the testcase and replace the following: + +Old +=== + +... + + +New +=== + +... + \ No newline at end of file diff --git a/extras/Test.jmx b/extras/Test.jmx new file mode 100644 index 00000000000..96f1e0655a1 --- /dev/null +++ b/extras/Test.jmx @@ -0,0 +1,145 @@ + + + + + + + + false + false + Sample test for demonstrating JMeter Ant build script and Schematic stylesheet + + + + 1143889321000 + + + 3 + false + + 5 + false + + 1143889321000 + continue + 1 + + + + 1 + 1000 + C + false + 1000000 + + + + + + + = + 100 + Sleep_Time + + + = + 0xFF + Sleep_Mask + + + = + + Label + + + = + 200 + ResponseCode + + + = + OK + ResponseMessage + + + = + OK + Status + + + = + Request + SamplerData + + + = + Response C=${C} + ResultData + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + 3 + + Assertion.response_data + 6 + false + + + + + + + + = + 100 + Sleep_Time + + + = + 0xFF + Sleep_Mask + + + = + + Label + + + = + 200 + ResponseCode + + + = + OK + ResponseMessage + + + = + OK + Status + + + = + Request + SamplerData + + + = + Response C=${C} Tn=${__threadNum} + ResultData + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + diff --git a/extras/addons.txt b/extras/addons.txt new file mode 100644 index 00000000000..d45c391568a --- /dev/null +++ b/extras/addons.txt @@ -0,0 +1,35 @@ +This file describes how to create local additions to JMeter. + +Create a new directory for the sources: + +cd JMETER_HOME + +mkdir addons + +Copy addons.xml into JMETER_HOME + +To build the addons, run ant as follows: + +ant -buildfile=addons.xml + +This will compile the sources to build/addons/... + +If successful, it will also create the jar file: + +JMETER_HOME/lib/ext/ApacheJmeter_addons.jar + +As the filename of this jar is alphabetically earlier, +any classes in it will be used in preference to existing JMeter classes. + +This allows the addons to be used to supply new functionality as well +as overriding existing functionality, without needing to rebuild JMeter. + +Such addons are intended mainly to be used locally - for example if you +have developed any new code that is only relevant to your organisation. + +It can also be useful for developing general purpose add-ons that are +intended for general release. Once tested, these can be moved into one of +the normal JMeter source directories. + +N.B. The build file assumes that JMeter has been built separately, as +JMeter classes are resolved from jars in the lib and lib/ext directories. diff --git a/extras/addons.xml b/extras/addons.xml new file mode 100644 index 00000000000..e0278b9e04d --- /dev/null +++ b/extras/addons.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extras/ant-jmeter-1.1.1.jar b/extras/ant-jmeter-1.1.1.jar new file mode 100644 index 00000000000..bd7d15ab327 Binary files /dev/null and b/extras/ant-jmeter-1.1.1.jar differ diff --git a/extras/build.xml b/extras/build.xml new file mode 100644 index 00000000000..2a4d8683c51 --- /dev/null +++ b/extras/build.xml @@ -0,0 +1,167 @@ + + + + + + Sample build file for use with ant-jmeter.jar + See http://www.programmerplanet.org/pages/projects/jmeter-ant-task.php + + To run a test and create the output report: + ant -Dtest=script + + To run a test only: + ant -Dtest=script run + + To run report on existing test output + ant -Dtest=script report + + The "script" parameter is the name of the script without the .jmx suffix. + + Additional options: + -Dshow-data=y - include response data in Failure Details + -Dtestpath=xyz - path to test file(s) (default user.dir). + N.B. Ant interprets relative paths against the build file + -Djmeter.home=.. - path to JMeter home directory (defaults to parent of this build file) + -Dreport.title="My Report" - title for html report (default is 'Load Test Results') + + Deprecated: + -Dformat=2.0 - use version 2.0 JTL files rather than 2.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + funcMode = ${funcMode} + + + + + + + + + + + + + + + + + + + + + + Report generated at ${report.datestamp} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cannot find all xalan and/or serialiser jars + The XSLT formatting may not work correctly. + Check you have xalan and serializer jars in ${lib.dir} + + + + diff --git a/extras/collapse.png b/extras/collapse.png new file mode 100644 index 00000000000..3ec05e4ec19 Binary files /dev/null and b/extras/collapse.png differ diff --git a/extras/convertjmx.fdl b/extras/convertjmx.fdl new file mode 100644 index 00000000000..aa35eb3d344 --- /dev/null +++ b/extras/convertjmx.fdl @@ -0,0 +1,27 @@ +! 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. +! +! Convert JMX files so Java can read them on OpenVMS +! ================================================== +! +! This will be needed if the JMX is in VARIABLE format. +! +! Usage: +! CONVERT/FDL=CONVERTJMX input.jmx output.jmx +! +! +RECORD + CARRIAGE_CONTROL carriage_return + FORMAT stream_lf \ No newline at end of file diff --git a/extras/execcode.bsh b/extras/execcode.bsh new file mode 100644 index 00000000000..fb20c956cc1 --- /dev/null +++ b/extras/execcode.bsh @@ -0,0 +1,36 @@ +/* + * 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. + * + */ + +/** + Start an external application using the Java Runtime exec() method. + Display any output to the standard BeanShell output using print(). + Return the process exit code. + Note: does not display stderr. +*/ + +bsh.help.execcode = "usage: execcode( String arg )"; + +int execcode( String arg ) +{ + this.proc = Runtime.getRuntime().exec(arg); + this.din = new DataInputStream( proc.getInputStream() ); + while( (line=din.readLine()) != null ) { + print(line); + } + return this.proc.waitFor(); +} diff --git a/extras/expand.png b/extras/expand.png new file mode 100644 index 00000000000..f1e7b8b02a8 Binary files /dev/null and b/extras/expand.png differ diff --git a/extras/jmeter-results-detail-report.xsl b/extras/jmeter-results-detail-report.xsl new file mode 100644 index 00000000000..ea00f137858 --- /dev/null +++ b/extras/jmeter-results-detail-report.xsl @@ -0,0 +1,407 @@ + + + + + + + + + + + + + Load Test Results + + + + + + + + +
+ + +
+ + + + + +
+ + +

Load Test Results

+ + + + + +
Designed for use with JMeter and Ant.
+
+
+ + +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + +
TestsFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Pages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + + + + + + page_details_ + + + + +
URLTestsFailuresSuccess RateAverage TimeMin TimeMax Time
+ + # + + + + + + + + + + + + + + + + + + + + + + + + + + + + + javascript:change('page_details_') + expand/collapsepage_details__image + +
+
+ Details for Page "" + + + + + + + + + + + + + + + + + + +
ThreadIterationTimeSuccess
ms
+
+
+
+ + + + + +

Failure Detail

+ + + + + + +

+ + + + + + + + + + + + + + + + + + + + +
ResponseFailure MessageResponse Data
-
+
+ +
+
+
+ + + + + NaN + + + + + + + + + + + + + + + NaN + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/extras/jmeter-results-detail-report_21.xsl b/extras/jmeter-results-detail-report_21.xsl new file mode 100644 index 00000000000..b0344bd2edf --- /dev/null +++ b/extras/jmeter-results-detail-report_21.xsl @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + <xsl:value-of select="$titleReport" /> + + + + + + + + +
+ + +
+ + + + + +
+ + +

+ + + + + +
Date report: Designed for use with JMeter and Ant.
+
+
+ + +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + +
# SamplesFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Pages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + + + + + + page_details_ + + + + +
URL# SamplesFailuresSuccess RateAverage TimeMin TimeMax Time
+ + # + + + + + + + + + + + + + + + + + + + + + + + + + + + + + javascript:change('page_details_') + expand/collapsepage_details__image + +
+
+ Details for Page "" + + + + + + + + + + + + + + + + + + + + +
ThreadIterationTime (milliseconds)BytesSuccess
+
+
+
+ + + + + +

Failure Detail

+ + + + + + +

+ + + + + + + + + + + + + + + + + + + + +
ResponseFailure MessageResponse Data
-
+
+ +
+
+
+ + + + + NaN + + + + + + + + + + + + + + + NaN + + + + + + + + + + + + + + + + + + + + + +
diff --git a/extras/jmeter-results-report.xsl b/extras/jmeter-results-report.xsl new file mode 100644 index 00000000000..e434b66661f --- /dev/null +++ b/extras/jmeter-results-report.xsl @@ -0,0 +1,291 @@ + + + + + + + + + + Load Test Results + + + + + + + +
+ + +
+ + + + + +
+ + +

Load Test Results

+ + + + + +
Designed for use with JMeter and Ant.
+
+
+ + +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + +
TestsFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Pages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + + + +
URLTestsFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +

Failure Detail

+ + + + + + +

+ + + + + + + + + + + + + + +
ResponseFailure Message
-
+
+ +
+
+
+ + + + + NaN + + + + + + + + + + + + + + + NaN + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/extras/jmeter-results-report_21.xsl b/extras/jmeter-results-report_21.xsl new file mode 100644 index 00000000000..5c195191321 --- /dev/null +++ b/extras/jmeter-results-report_21.xsl @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + <xsl:value-of select="$titleReport" /> + + + + + + + +
+ + +
+ + + + + +
+ + +

+ + + + + +
Date report: Designed for use with JMeter and Ant.
+
+
+ + +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + +
# SamplesFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Pages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + + + +
URL# SamplesFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +

Failure Detail

+ + + + + + +

+ + + + + + + + + + + + + + +
ResponseFailure Message
-
+
+ +
+
+
+ + + + + NaN + + + + + + + + + + + + + + + NaN + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/extras/jmeter.fb b/extras/jmeter.fb new file mode 100644 index 00000000000..a321c763545 --- /dev/null +++ b/extras/jmeter.fb @@ -0,0 +1,62 @@ +[Jar files] +bin/apachejmeter.jar +lib/ext/apachejmeter_components.jar +lib/ext/apachejmeter_core.jar +lib/ext/apachejmeter_ftp.jar +lib/ext/apachejmeter_functions.jar +lib/ext/apachejmeter_http.jar +lib/ext/apachejmeter_java.jar +lib/ext/apachejmeter_jdbc.jar +lib/ext/apachejmeter_ldap.jar +lib/ext/apachejmeter_mail.jar +lib/ext/apachejmeter_monitors.jar +lib/ext/apachejmeter_tcp.jar +lib/jorphan.jar +[Source dirs] +src/components +src/core +src/examples +src/functions +src/htmlparser +src/jorphan +src/monitor/components +src/monitor/model +src/protocol/ftp +src/protocol/html +src/protocol/java +src/protocol/jdbc +src/protocol/ldap +src/protocol/mail +src/protocol/tcp +[Aux classpath entries] +lib/avalon-framework-4.1.4.jar +lib/batik-awt-util.jar +lib/commons-collections.jar +lib/commons-httpclient-2.0.jar +lib/commons-logging.jar +lib/excalibur-compatibility-1.1.jar +lib/excalibur-datasource-1.1.1.jar +lib/excalibur-i18n-1.1.jar +lib/excalibur-instrument-1.0.jar +lib/excalibur-logger-1.1.jar +lib/excalibur-pool-1.2.jar +lib/htmlparser.jar +lib/jakarta-oro-2.0.8.jar +lib/jdom-b9.jar +lib/js.jar +lib/junit.jar +lib/logkit-1.2.jar +lib/soap.jar +lib/tidy.jar +lib/velocity-1.4-dev.jar +lib/xalan.jar +lib/xercesimpl.jar +lib/xml-apis.jar +lib/xpp3-1.1.3.4.d.jar +lib/xstream-1.0.1.jar +lib/opt/activation.jar +lib/opt/bsf.jar +lib/opt/bsh-2.0b1.jar +lib/opt/mail.jar +[Options] +relative_paths=true diff --git a/extras/printvars.bsh b/extras/printvars.bsh new file mode 100644 index 00000000000..f232d59e31e --- /dev/null +++ b/extras/printvars.bsh @@ -0,0 +1,29 @@ +/* + * 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. + * + */ + +// Sample script to print JMeter variables +print(">>>>"); +Iterator i = vars.getIterator(); +while(i.hasNext()) +{ + Map.Entry me = i.next(); + if(String.class.equals(me.getValue().getClass())){ + print(me); + } +} +print("<<<<"); diff --git a/extras/proxycert.cmd b/extras/proxycert.cmd new file mode 100644 index 00000000000..aeec77002e5 --- /dev/null +++ b/extras/proxycert.cmd @@ -0,0 +1,45 @@ +@echo off + + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem Generate temporary certificate for use with JMeter Proxy recorder +rem Usage: proxycert [validity(default 1)] +rem e.g. proxycert 7 + +setlocal + +set KEYSTORE=proxyserver.jks +if not exist %KEYSTORE% goto NOTEXISTS +echo %KEYSTORE% exists; please rename or delete it before creating a replacement +goto :EOF +:NOTEXISTS + +set DNAME="cn=JMeter Proxy (DO NOT TRUST)" + +set VALIDITY=1 +if not .%1 == . set VALIDITY=%1 + +rem Must agree with property proxy.cert.keystorepass +set STOREPASSWORD=password +rem Must agree with proxy.cert.keypassword +set KEYPASSWORD=password + +rem generate the keystore with the certificate +keytool -genkeypair -alias jmeter -keystore %KEYSTORE% -keypass %KEYPASSWORD% -storepass %STOREPASSWORD% -validity %VALIDITY% -keyalg RSA -dname %DNAME% + +rem show the contents +keytool -list -v -keystore %KEYSTORE% -storepass %STOREPASSWORD% diff --git a/extras/proxycert.sh b/extras/proxycert.sh new file mode 100644 index 00000000000..d9abbd72a62 --- /dev/null +++ b/extras/proxycert.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +## 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. + + +## Generate temporary proxyserver for use with JMeter Proxy recorder +## Usage: sh proxycert.sh [validity(default 1)] +## e.g. sh proxyxcert.sh 7 + +KEYSTORE=proxyserver.jks +if [ -r ${KEYSTORE} ] +then + echo "${KEYSTORE} exists; please rename or delete it before creating a replacement" + exit 1 +fi + +DNAME="cn=JMeter Proxy (DO NOT TRUST)" +VALIDITY=${1:-1} +# Must agree with property proxy.cert.keystorepass +STOREPASSWORD=password +# Must agree with proxy.cert.keypassword +KEYPASSWORD=password + +## generate the keystore with the certificate +keytool -genkeypair -alias jmeter -keystore ${KEYSTORE} -keypass ${KEYPASSWORD} -storepass ${STOREPASSWORD} -validity ${VALIDITY} -keyalg RSA -dname "${DNAME}" + +## show the contents +keytool -list -v -keystore ${KEYSTORE} -storepass ${STOREPASSWORD} diff --git a/extras/remote.bsh b/extras/remote.bsh new file mode 100644 index 00000000000..46de81f2398 --- /dev/null +++ b/extras/remote.bsh @@ -0,0 +1,48 @@ +/* + * 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. + * + */ + +// remote.bsh +// Sample remote file for use with bshclient +// +// Usage: +// java -jar ../lib/bshclent.jar localhost 9000 ../extras/bsh.remote [arg1 arg2 ...] +// Note: port 9000 is specified, but the jar actually uses 9001 (telnet) +// + +print("remote.bsh starting"); + +if (args.length > 0){ +print("Arguments:"); +print(args); +} + +printsysprop("user.home"); +printsysprop("user.dir"); + +printprop("log_level.jmeter"); +printprop("log_level.jorphan"); + +// loglevel("DEBUG","jmeter"); + +for(i=0;i<10;i++){ + setprop("EXAMPLE",i.toString()); + Thread.sleep(1000); +} +printprop("EXAMPLE"); + +print("remote.bsh ended"); \ No newline at end of file diff --git a/extras/schematic.cmd b/extras/schematic.cmd new file mode 100644 index 00000000000..d796a17766e --- /dev/null +++ b/extras/schematic.cmd @@ -0,0 +1,24 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem Drop a JMX file on this script to create a schematic of the test plan as an HTML file + +cd /d %~dp0 +set name=%~n1 +if .%1 ==. set name=Test +call ant -f schematic.xml -Dtest=%name% +pause \ No newline at end of file diff --git a/extras/schematic.xml b/extras/schematic.xml new file mode 100644 index 00000000000..865e8f62973 --- /dev/null +++ b/extras/schematic.xml @@ -0,0 +1,39 @@ + + + + + To create the schematic report: + ant -Dtest=script + + + + + + + + + + + + + + diff --git a/extras/schematic.xsl b/extras/schematic.xsl new file mode 100644 index 00000000000..21f3070186f --- /dev/null +++ b/extras/schematic.xsl @@ -0,0 +1,119 @@ + + + + + + + + Test Plan Schematic + + + + + + + +
    + +
+
+ + + + + +
+ + + +
+
+ + + + +
+ Threads: + + Loops: + + Ramp up: + +
+ + + + +
+ + + + :// + + : + + / + +
+ + + + + +
+ Output: + XML: +
+
+ + + + + + + + +
+ + + +
+
+ + + +( + + + + + SimpleController + + + + + + : + +) + + + +
\ No newline at end of file diff --git a/extras/startup.bsh b/extras/startup.bsh new file mode 100644 index 00000000000..0bb859bbd87 --- /dev/null +++ b/extras/startup.bsh @@ -0,0 +1,99 @@ +/* + * 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. + * + */ + +// +// Sample BeanShell Server Startup file +// +// Use as follows: +// -Jbeanshell.server.port=nnnn +// -Jbeanshell.server.file=../extras/startup.bsh +// +// Defines various utility routines for properties and logging +// +// + +// Stop exit() from calling System.exit(); +bsh.system.shutdownOnExit = false; + +print("Startup script running"); + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; + +getprop(p){// get a JMeter property +return JMeterUtils.getPropDefault(p,""); +} + +setprop(p,v){// set a JMeter property +print("Setting property '"+p+"' to '"+v+"'."); +JMeterUtils.getJMeterProperties().setProperty(p, v); +} + +printprop(p){// print a JMeter property +print(p + " = " + getprop(p)); +} + +loglevel(String priority, String category){ +LoggingManager.setPriority(priority, category); +} + +logdebug(String text){ +loglevel("DEBUG",text); +} + +loginfo(String text){ +loglevel("INFO",text); +} + +// Define routines to stop the test or a thread +stopEngine(){// Stop the JMeter test +print("Stop Engine called"); +org.apache.jmeter.engine.StandardJMeterEngine.stopEngine(); +} + +stopEngineNow(){// Stop the JMeter test now +print("Stop Engine NOW called"); +org.apache.jmeter.engine.StandardJMeterEngine.stopEngineNow(); +} + +stopThread(t){// Stop a JMeter thread +print("Stop Thread "+t+" called"); +ok=org.apache.jmeter.engine.StandardJMeterEngine.stopThread(t); +if (ok){print("Thread requested to stop");} else { print("Thread not found");} +} + +stopThreadNow(t){// Stop a JMeter thread +print("Stop Thread Now "+t+" called"); +ok=org.apache.jmeter.engine.StandardJMeterEngine.stopThreadNow(t); +if (ok){print("Thread stopped");} else { print("Thread not found");} +} + +getsysprop(p){// get a system property +return System.getProperty(p,""); +} + +setsysprop(p,v){// set a system property +print("Setting property '"+p+"' to '"+v+"'."); +System.setProperty(p, v); +} + +printsysprop(p){// print a system property +print(p + " = " + getsysprop(p)); +} + +print("Startup script completed"); \ No newline at end of file diff --git a/fb-csv.xsl b/fb-csv.xsl new file mode 100644 index 00000000000..eb4237731ec --- /dev/null +++ b/fb-csv.xsl @@ -0,0 +1,59 @@ + + + + + + + + + Priority,Type,Classname,Method,Field,SourceLine + + + + , + + + , + + + + + , + + + + + + , + + + + + , + + + + (start:) + + + + + + + + diff --git a/fb-excludes.xml b/fb-excludes.xml new file mode 100644 index 00000000000..f9e2fbda0d8 --- /dev/null +++ b/fb-excludes.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --> + diff --git a/index.html b/index.html deleted file mode 100644 index 22c23e83fc7..00000000000 --- a/index.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Apache JMeter 1.0b - - - - -


-is proud to present

- -

-

version 1.0b1

-

Copyright (c) 1997-98 -The Java Apache Project
All rights reserved.
- - - \ No newline at end of file diff --git a/lib/aareadme.txt b/lib/aareadme.txt new file mode 100644 index 00000000000..a8e5e08777b --- /dev/null +++ b/lib/aareadme.txt @@ -0,0 +1,255 @@ +Directories +=========== +lib - utility jars +lib/api - Directory where API spec libraries live. +lib/doc - jars needed for generating documentation. Not included with JMeter releases. +lib/ext - JMeter jars only +lib/junit - test jar for JUnit sampler +lib/opt - Directory where Optional 3rd party libraries live +lib/src - storage area for source and javadoc jars, e.g. for use in IDEs + Excluded from SVN, not included in classpath + +Which jars are used by which modules? +==================================== +[not exhaustive] + +avalon-framework-4.1.4 (org.apache.avalon.framework) +---------------------- +- LogKit (LoggingManager) +- Configuration (DataSourceElement) +- OldSaveService + +bsf-2.4.0.jar (org.apache.bsf) +------------- +http://jakarta.apache.org/site/downloads/downloads_bsf.cgi +- BSF test elements (sampler etc.) + +bsh-2.0b5.jar (org.bsh) +------------- +- BeanShell test elements + +commons-codec-1.10 +----------------- +http://commons.apache.org/downloads/download_codec.cgi +- used by commons-httpclient-3.1 +- also HtmlParserTester for Base64 + +commons-collections-3.2.1 +------------------------- +http://commons.apache.org/downloads/download_collections.cgi +- ListenerNotifier +- Anakia + +commons-httpclient-3.1 +---------------------- +http://hc.apache.org/downloads.cgi +- httpclient version of HTTP sampler +- Cookie manager implementation + +commons-io-2.4 +-------------- +http://commons.apache.org/downloads/download_io.cgi +- FTPSampler + +commons-jexl-1.1 +---------------- +http://commons.apache.org/downloads/download_jexl.cgi +- Jexl function and BSF test elements + +commons-lang-2.6 +---------------- +http://commons.apache.org/downloads/download_lang.cgi +- velocity (Anakia) + +commons-lang3-3.3.4 +---------------- +http://commons.apache.org/downloads/download_lang.cgi +- URLCollection (unescapeXml) + +commons-logging-1.2 +--------------------- +http://commons.apache.org/downloads/download_logging.cgi +- httpclient + +commons-math3-3.5 +----------------- +http://commons.apache.org/proper/commons-math/download_math.cgi +- BackendListener + +commons-net-3.3 +----------------- +http://commons.apache.org/downloads/download_net.cgi +- FTPSampler + +commons-pool2-2.3 +----------------- +http://commons.apache.org/proper/commons-pool/download_pool.cgi +- BackendListener + +dnsjava-2.1.7 +----------------- +http://www.dnsjava.org/download/ +- DNSCacheManager + +excalibur-datasource-1.1.1 (org.apache.avalon.excalibur.datasource) +-------------------------- +- DataSourceElement (JDBC) + +excalibur-instrument-1.0 (org.apache.excalibur.instrument) +------------------------ +- used by excalibur-datasource + +excalibur-logger-1.1 (org.apache.avalon.excalibur.logger) +-------------------- +- LoggingManager + +excalibur-pool-1.2 (org.apache.avalon.excalibur.pool) +------------------ +- used by excalibur-datasource + +htmlparser-2.1 +htmllexer-2.1 +---------------------- +http://htmlparser.sourceforge.net/ +- http: parsing html + +jCharts-0.7.5 (org.jCharts) +------------- +http://jcharts.sourceforge.net/downloads.html +- AxisGraph,LineGraph,LineChart + +jdom-1.1.3 +-------- +http://www.jdom.org/downloads/index.html +- XMLAssertion, JMeterTest ONLY +- Anakia + +jodd-core-3.6.4 +-------- +http://www.jodd.org/ +- CSS/JQuery like extractor dependency + +jodd-lagarto-3.6.4 +-------- +http://jodd.org/doc/csselly/ +- CSS/JQuery like extractor + +jodd-log-3.6.4 +-------- +http://www.jodd.org/ +- CSS/JQuery like extractor dependency + +jsoup-1.8.1 +-------- +http://www.jsoup.org/ +- CSS/JQuery like extractor + +rhino-1.7R5 +-------- +http://www.mozilla.org/rhino/download.html +- javascript function +- IfController +- WhileController +- BSF (Javascript) + +jTidy-r938 +---- +- http: various modules for parsing html +- org.xml.sax - various +- XPathUtil (XPath assertion) + +junit 4.12 +----------- +- unit tests, JUnit sampler + +HttpComponents (HttpComponents Core 4.x and HttpComponents Client 4.x) +----------- +http://hc.apache.org/ +- httpclient 4 implementation for HTTP sampler + +logkit-2.0 +---------- +- logging +- Anakia + +mongo-java-driver 2.11.3 +------------------------ +http://www.mongodb.org/ +- MongoDB sampler + +oro-2.0.8 +--------- +http://jakarta.apache.org/site/downloads/downloads_oro.cgi +- regular expressions: various + +rsyntaxtextarea-2.5.6 +--------------------- +http://fifesoft.com/rsyntaxtextarea/ +- syntax coloration + +serialiser-2.7.1 +---------------- +http://www.apache.org/dyn/closer.cgi/xml/xalan-j +- xalan + +slf4j-api-1.7.10, slf4j-nop-1.7.10 +---------------- +http://www.slf4j.org/ +- jodd-core + +soap-2.3.1 +---------- +- WebServiceSampler ONLY + +tika-1.8 +-------------- +http://tika.apache.org/ +- Regular Expression Extractor + +velocity-1.7 +-------------- +http://velocity.apache.org/download.cgi +- Anakia (create documentation) Not used by JMeter runtime + +xalan_2.7.1 +----------- +http://www.apache.org/dyn/closer.cgi/xml/xalan-j ++org.apache.xalan|xml|xpath + +xercesimpl-2.11.0 +---------------- +http://xerces.apache.org/xerces2-j/download.cgi ++org.apache.html.dom|org.apache.wml|org.apache.xerces|org.apache.xml.serialize ++org.w3c.dom.html|ls + +xml-apis-1.4.01 +-------------- +http://xerces.apache.org/xerces2-j/download.cgi ++javax.xml ++org.w3c.dom ++org.xml.sax + +The x* jars above are used for XML handling + +xmlgraphics-commons-1.5 (org.apache.xmlgraphics.image.codec) +------------------ +http://xmlgraphics.apache.org/commons/download.html +- SaveGraphicsService + +xmlpull-1.1.3.1 +--------------- +http://www.xmlpull.org/impls.shtml +- xstream + + +xpp3_min-1.1.4c +--------------- +http://xstream.codehaus.org/download.html +or +http://www.extreme.indiana.edu/dist/java-repository/xpp3/distributions/ +- xstream + +xstream-1.4.6 +------------- +http://xstream.codehaus.org/download.html +- SaveService \ No newline at end of file diff --git a/lib/opt/README.txt b/lib/opt/README.txt new file mode 100644 index 00000000000..e80abf4926e --- /dev/null +++ b/lib/opt/README.txt @@ -0,0 +1,8 @@ +lib/opt +======= + +This directory is included in the Ant build classpath, +and is used for optional jars that may be needed to build JMeter, +but which are not included in the distribution. + +For example: Doccheck and svnant. \ No newline at end of file diff --git a/licenses/README.txt b/licenses/README.txt new file mode 100644 index 00000000000..7cd29a10ff2 --- /dev/null +++ b/licenses/README.txt @@ -0,0 +1,7 @@ +This directory structure contains licenses for non-ASF software. + +The bin/ directory contains license files for 3rd party jars bundled with the binary distribution + +The src/ directory contains license files for 3rd party source; it also applies to the binary distribution + +Note that the bin/ directory is included in the source distribution because it is needed in order to create the binary distribution \ No newline at end of file diff --git a/licenses/bin/README.txt b/licenses/bin/README.txt new file mode 100644 index 00000000000..27f133d6914 --- /dev/null +++ b/licenses/bin/README.txt @@ -0,0 +1,39 @@ +This directory contains additional LICENSE files for software contained in binary distributions + +The following files are ASF products, and are licensed under the Apache License 2.0 + +avalon-framework-4.1.4.jar +bsf-2.4.0.jar +bshclient.jar (part of JMeter) +commons-codec-1.10.jar +commons-collections-3.2.1.jar +commons-httpclient-3.1.jar +commons-io-2.4.jar +commons-jexl-1.1.jar +commons-jexl-2.1.1.jar +commons-lang3-3.3.2.jar +commons-logging-1.2.jar +commons-math3-3.5.jar +commons-net-3.3.jar +commons-pool2-2.3.jar +excalibur-datasource-2.1.jar +excalibur-instrument-1.0.jar +excalibur-logger-1.1.jar +excalibur-pool-api-2.1.jar +excalibur-pool-impl-2.1.jar +excalibur-pool-instrumented-2.1.jar +geronimo-jms_1.1_spec-1.1.1.jar +httpclient-4.2.6.jar +httpcore-4.2.5.jar +httpmime-4.2.6.jar +jorphan.jar (part of JMeter) +logkit-2.0.jar +oro-2.0.8.jar +serializer-2.7.2.jar +soap-2.3.1.jar +tika-core-1.8.jar +tika-parsers-1.8.jar +xalan-2.7.2.jar +xercesImpl-2.11.0.jar +xml-apis-1.4.01.jar +xmlgraphics-commons-1.5.jar diff --git a/licenses/bin/beanshell-2.0b5.txt b/licenses/bin/beanshell-2.0b5.txt new file mode 100644 index 00000000000..e865edc7e0e --- /dev/null +++ b/licenses/bin/beanshell-2.0b5.txt @@ -0,0 +1,189 @@ +BSH 2.0-b5 is dual licensed under SPL and LGPL - see http://www.beanshell.org/license.html +The JMeter project chooses to use the SPL. + +SUN PUBLIC LICENSE Version 1.0 +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original Code, prior Modifications used by a Contributor, and the Modifications made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the combination of the Original Code and Modifications, in each case including portions thereof and corresponding documentation released with the source code. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally accepted in the software development community for the electronic transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source Code. + + 1.6. "Initial Developer" means the individual or entity identified as the Initial Developer in the Source Code notice required by Exhibit A. + + 1.7. "Larger Work" means a work which combines Covered Code or portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the substance or structure of either the Original Code or any previous Modifications. When Covered Code is released as a series of files, a Modification is: + + A. Any addition to or deletion from the contents of a file containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or previous Modifications. + + 1.10. "Original Code"../ means Source Code of computer software code which is described in the Source Code notice required by Exhibit A as Original Code, and which, at the time of its release under this License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code"../ means the preferred form of the Covered Code for making modifications to it, including all modules it contains, plus any associated documentation, interface definition files, scripts used to control compilation and installation of an Executable, or source code differential comparisons against either the Original Code or another well known, available Covered Code of the Contributor's choice. The Source Code can be in a compressed or archival form, provided the appropriate decompression or de-archiving software is widely available for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License or a future version of this License issued under Section 6.1. For legal entities, "You" includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, "control"../ means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. + +2. Source Code License. + + 2.1 The Initial Developer Grant. + + The Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license, subject to third party intellectual property claims: + + (a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer to use, reproduce, modify, display, perform, sublicense and distribute the Original Code (or portions thereof) with or without Modifications, and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using or selling of Original Code, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are effective on the date Initial Developer first distributes Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is granted: 1) for code that You delete from the Original Code; 2) separate from the Original Code; or 3) for infringements caused by: i) the modification of the Original Code or ii) the combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + + Subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or trademark) Licensable by Contributor, to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof) either on an unmodified basis, with other Modifications, as Covered Code and/or as part of a Larger Work; and + + b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: 1) Modifications made by that Contributor (or portions thereof); and 2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first makes Commercial Use of the Covered Code. + + (d) notwithstanding Section 2.2(b) above, no patent license is granted: 1) for any code that Contributor has deleted from the Contributor Version; 2) separate from the Contributor Version; 3) for infringements caused by: i) third party modifications of Contributor Version or ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or 4) under Patent Claims infringed by Covered Code in the absence of Modifications made by that Contributor. + + 3. Distribution Obligations. + + 3.1. Application of License. + + The Modifications which You create or to which You contribute are governed by the terms of this License, including without limitation Section 2.2. The Source Code version of Covered Code may be distributed only under the terms of this License or a future version of this License released under Section 6.1, and You must include a copy of this License with every copy of the Source Code You distribute. You may not offer or impose any terms on any Source Code version that alters or restricts the applicable version of this License or the recipients' rights hereunder. However, You may include an additional document offering the additional rights described in Section 3.5. + + 3.2. Availability of Source Code. + + Any Modification which You create or to which You contribute must be made available in Source Code form under the terms of this License either on the same media as an Executable version or via an accepted Electronic Distribution Mechanism to anyone to whom you made an Executable version available; and if made available via Electronic Distribution Mechanism, must remain available for at least twelve (12) months after the date it initially became available, or at least six (6) months after a subsequent version of that particular Modification has been made available to such recipients. You are responsible for ensuring that the Source Code version remains available even if the Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + + You must cause all Covered Code to which You contribute to contain a file documenting the changes You made to create that Covered Code and the date of any change. You must include a prominent statement that the Modification is derived, directly or indirectly, from Original Code provided by the Initial Developer and including the name of the Initial Developer in (a) the Source Code, and (b) in any notice in an Executable version or related documentation in which You describe the origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters. + + (a) Third Party Claims. If Contributor has knowledge that a license under a third party's intellectual property rights is required to exercise the rights granted by such Contributor under Sections 2.1 or 2.2, Contributor must include a text file with the Source Code distribution titled "../LEGAL'' which describes the claim and the party making the claim in sufficient detail that a recipient will know whom to contact. If Contributor obtains such knowledge after the Modification is made available as described in Section 3.2, Contributor shall promptly modify the LEGAL file in all copies Contributor makes available thereafter and shall take other steps (such as notifying appropriate mailing lists or newsgroups) reasonably calculated to inform those who received the Covered Code that new knowledge has been obtained. + + (b) Contributor APIs. + + If Contributor's Modifications include an application programming interface ("API"../) and Contributor has knowledge of patent licenses which are reasonably necessary to implement that API, Contributor must also include this information in the LEGAL file. + + (c) Representations. + + Contributor represents that, except as disclosed pursuant to Section 3.4(a) above, Contributor believes that Contributor's Modifications are Contributor's original creation(s) and/or Contributor has sufficient rights to grant the rights conveyed by this License + . + + 3.5. Required Notices. + + You must duplicate the notice in Exhibit A in each file of the Source Code. If it is not possible to put such notice in a particular Source Code file due to its structure, then You must include such notice in a location (such as a relevant directory) where a user would be likely to look for such a notice. If You created one or more Modification(s) You may add your name as a Contributor to the notice described in Exhibit A. You must also duplicate this License in any documentation for the Source Code where You describe recipients' rights or ownership rights relating to Covered Code. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Code. However, You may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear than any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + + You may distribute Covered Code in Executable form only if the requirements of Section 3.1-3.5 have been met for that Covered Code, and if You include a notice stating that the Source Code version of the Covered Code is available under the terms of this License, including a description of how and where You have fulfilled the obligations of Section 3.2. The notice must be conspicuously included in any notice in an Executable version, related documentation or collateral in which You describe recipients' rights relating to the Covered Code. You may distribute the Executable version of Covered Code or ownership rights under a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable version does not attempt to limit or alter the recipient's rights in the Source Code version from the rights set forth in this License. If You distribute the Executable version under a different license You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or any Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer. + + 3.7. Larger Works. + + You may create a Larger Work by combining Covered Code with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Code. + + 4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Code due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be included in the LEGAL file described in Section 3.4 and must be included with all distributions of the Source Code. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. + + 5. Application of this License. + + This License applies to code to which the Initial Developer has attached the notice in Exhibit A and to related Covered Code. + + 6. Versions of the License. + + 6.1. New Versions. Sun Microsystems, Inc. ("Sun") may publish revised and/or new versions of the License from time to time. Each version will be given a distinguishing version number. + + 6.2. Effect of New Versions. + + Once Covered Code has been published under a particular version of the License, You may always continue to use it under the terms of that version. You may also choose to use such Covered Code under the terms of any subsequent version of the License published by Sun. No one other than Sun has the right to modify the terms applicable to Covered Code created under this License. + + 6.3. Derivative Works. + + If You create or use a modified version of this License (which you may only do in order to apply it to code which is not already Covered Code governed by this License), You must: (a) rename Your license so that the phrases "Sun," "Sun Public License," or "SPL"../ or any confusingly similar phrase do not appear in your license (except to note that your license differs from this License) and (b) otherwise make it clear that Your version of the license contains terms which differ from the Sun Public License. (Filling in the name of the Initial Developer, Original Code or Contributor in the notice described in Exhibit A shall not of themselves be deemed to be modifications of this License.) + + 7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS'' BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + + 8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. All sublicenses to the Covered Code which are properly granted shall survive any termination of this License. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly infringes any patent, then any and all rights granted by such Participant to You under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively, unless if within 60 days after receipt of notice You either: (i) agree in writing to pay Participant a mutually agreeable reasonable royalty for Your past and future use of Modifications made by such Participant, or (ii) withdraw Your litigation claim with respect to the Contributor Version against such Participant. If within 60 days of notice, a reasonable royalty and payment arrangement are not mutually agreed upon in writing by the parties or the litigation claim is not withdrawn, the rights granted by Participant to You under Sections 2.1 and/or 2.2 automatically terminate at the expiration of the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's Contributor Version, directly or indirectly infringes any patent, then any rights granted to You by such Participant under Sections 2.1(b) and 2.2(b) are revoked effective as of the date You first made, used, sold, distributed, or had made, Modifications made by that Participant. + + 8.3. If You assert a patent infringement claim against Participant alleging that such Participant's Contributor Version directly or indirectly infringes any patent where such claim is resolved (such as by license or settlement) prior to the initiation of patent infringement litigation, then the reasonable value of the licenses granted by such Participant under Sections 2.1 or 2.2 shall be taken into account in determining the amount or value of any payment or license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or any distributor hereunder prior to termination shall survive termination. + + 9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + + 10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer software" and "commercial computer software documentation,"../ as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Code with only those rights set forth herein. + + 11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by California law provisions (except to the extent applicable law, if any, provides otherwise), excluding its conflict-of-law provisions. With respect to disputes in which at least one party is a citizen of, or an entity chartered or registered to do business in the United States of America, any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California, with venue lying in Santa Clara County, California, with the losing party responsible for costs, including without limitation, court costs and reasonable attorneys' fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. + + 12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability. + + 13. MULTIPLE-LICENSED CODE. Initial Developer may designate portions of the Covered Code as ?Multiple-Licensed?. ?Multiple-Licensed? means that the Initial Developer permits you to utilize portions of the Covered Code under Your choice of the alternative licenses, if any, specified by the Initial Developer in the file described in Exhibit A. + + Exhibit A -Sun Public License Notice. + + The contents of this file are subject to the Sun Public License + Version 1.0 (the License); you may not use this file except in + compliance with the License. A copy of the License is available at + http://www.sun.com/ + + The Original Code is _________________. The Initial Developer of the + Original Code is ___________. Portions created by ______ are Copyright + (C)_________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the ?[___] License?), in which case the + provisions of [______] License are applicable instead of those above. + If you wish to allow use of your version of this file only under the + terms of the [____] License and not to allow others to use your + version of this file under the SPL, indicate your decision by deleting + the provisions above and replace them with the notice and other + provisions required by the [___] License. If you do not delete the + provisions above, a recipient may use your version of this file under + either the SPL or the [___] License. + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] \ No newline at end of file diff --git a/licenses/bin/htmllexer-2.1.txt b/licenses/bin/htmllexer-2.1.txt new file mode 100644 index 00000000000..9764a3a0424 --- /dev/null +++ b/licenses/bin/htmllexer-2.1.txt @@ -0,0 +1 @@ +htmllexer-2.1 is part of htmlparser-2.1; please see the file htmlparser-2.1.txt \ No newline at end of file diff --git a/licenses/bin/htmlparser-2.1.txt b/licenses/bin/htmlparser-2.1.txt new file mode 100644 index 00000000000..bdbb912ed06 --- /dev/null +++ b/licenses/bin/htmlparser-2.1.txt @@ -0,0 +1,215 @@ +http://htmlparser.sourceforge.net/license.html + +Common Public License Version 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial code and +documentation distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + + i) changes to the Program, and + + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) are not +derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and such +derivative works, in source code and object code form. + + b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed +Patents to make, use, sell, offer to sell, import and otherwise transfer the +Contribution of such Contributor, if any, in source code and object code form. +This patent license shall apply to the combination of the Contribution and the +Program if, at the time the Contribution is added by the Contributor, such +addition of the Contribution causes such combination to be covered by the +Licensed Patents. The patent license shall not apply to any other combinations +which include the Contribution. No hardware per se is licensed hereunder. + + c) Recipient understands that although each Contributor grants the licenses +to its Contributions set forth herein, no assurances are provided by any +Contributor that the Program does not infringe the patent or other intellectual +property rights of any other entity. Each Contributor disclaims any liability to +Recipient for claims brought by any other entity based on infringement of +intellectual property rights or otherwise. As a condition to exercising the +rights and licenses granted hereunder, each Recipient hereby assumes sole +responsibility to secure any other intellectual property rights needed, if any. +For example, if a third party patent license is required to allow Recipient to +distribute the Program, it is Recipient's responsibility to acquire that license +before distributing the Program. + + d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright license set +forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its +own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + + b) its license agreement: + + i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title and +non-infringement, and implied warranties or conditions of merchantability and +fitness for a particular purpose; + + ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + + iii) states that any provisions which differ from this Agreement are offered +by that Contributor alone and not by any other party; and + + iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner on or +through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + + b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained within the +Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor to +control, and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may participate in +any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its exercise of +rights under this Agreement, including but not limited to the risks and costs of +program errors, compliance with applicable laws, damage to or loss of data, +programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against a Contributor with respect to +a patent applicable to software (including a cross-claim or counterclaim in a +lawsuit), then any patent licenses granted by that Contributor to such Recipient +under this Agreement shall terminate as of the date such litigation is filed. In +addition, if Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the Program +itself (excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted under +Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue and +survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +IBM is the initial Agreement Steward. IBM may assign the responsibility to serve +as the Agreement Steward to a suitable separate entity. Each new version of the +Agreement will be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the Agreement +under which it was received. In addition, after a new version of the Agreement +is published, Contributor may elect to distribute the Program (including its +Contributions) under the new version. Except as expressly stated in Sections +2(a) and 2(b) above, Recipient receives no rights or licenses to the +intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. \ No newline at end of file diff --git a/licenses/bin/javamail-1.5.0-b01.txt b/licenses/bin/javamail-1.5.0-b01.txt new file mode 100644 index 00000000000..cbdef795877 --- /dev/null +++ b/licenses/bin/javamail-1.5.0-b01.txt @@ -0,0 +1,265 @@ +Extracted from mail-1.5.0-b01.jar + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + +1. Definitions. + + 1.1. Contributor. means each individual or entity that creates or contributes to the creation of Modifications. + + 1.2. Contributor Version. means the combination of the Original Software, prior Modifications used by a Contributor (if any), and the Modifications made by that particular Contributor. + + 1.3. Covered Software. means (a) the Original Software, or (b) Modifications, or (c) the combination of files containing Original Software with files containing Modifications, in each case including portions thereof. + + 1.4. Executable. means the Covered Software in any form other than Source Code. + + 1.5. Initial Developer. means the individual or entity that first makes Original Software available under this License. + + 1.6. Larger Work. means a work which combines Covered Software or portions thereof with code not governed by the terms of this License. + + 1.7. License. means this document. + + 1.8. Licensable. means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein. + + 1.9. Modifications. means the Source Code and Executable form of any of the following: + + A. Any file that results from an addition to, deletion from or modification of the contents of a file containing Original Software or previous Modifications; + + B. Any new file that contains any part of the Original Software or previous Modification; or + + C. Any new file that is contributed or otherwise made available under the terms of this License. + + 1.10. Original Software. means the Source Code and Executable form of computer software code that is originally released under this License. + + 1.11. Patent Claims. means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor. + + 1.12. Source Code. means (a) the common form of computer software code in which modifications are made and (b) associated documentation included in or with such code. + + 1.13. You. (or .Your.) means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, .You. includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, .control. means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license: + + (a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer, to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions thereof), with or without Modifications, and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are effective on the date Initial Developer first distributes or otherwise makes the Original Software available to a third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is granted: (1) for code that You delete from the Original Software, or (2) for infringements caused by: (i) the modification of the Original Software, or (ii) the combination of the Original Software with other software or devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: + + (a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: (1) Modifications made by that Contributor (or portions thereof); and (2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first distributes or otherwise makes the Modifications available to a third party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is granted: (1) for any code that Contributor has deleted from the Contributor Version; (2) for infringements caused by: (i) third party modifications of Contributor Version, or (ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or (3) under Patent Claims infringed by Covered Software in the absence of Modifications made by that Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + Any Covered Software that You distribute or otherwise make available in Executable form must also be made available in Source Code form and that Source Code form must be distributed only under the terms of this License. You must include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used for software exchange. + + 3.2. Modifications. + The Modifications that You create or to which You contribute are governed by the terms of this License. You represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to grant the rights conveyed by this License. + + 3.3. Required Notices. + You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification. You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer. + + 3.4. Application of Additional Terms. + You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the applicable version of this License or the recipients. rights hereunder. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer. + + 3.5. Distribution of Executable Versions. + You may distribute the Executable form of the Covered Software under the terms of this License or under the terms of a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable form does not attempt to limit or alter the recipient.s rights in the Source Code form from the rights set forth in this License. If You distribute the Covered Software in Executable form under a different license, You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer. + + 3.6. Larger Works. + You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + Sun Microsystems, Inc. is the initial license steward and may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3, no one other than the license steward has the right to modify this License. + + 4.2. Effect of New Versions. + You may always continue to use, distribute or otherwise make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. If the Initial Developer includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under any subsequent version of the License, You must distribute and make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. Otherwise, You may also choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version of the License published by the license steward. + + 4.3. Modified Versions. + When You are an Initial Developer and You want to create a new license for Your Original Software, You may create and use a modified version of this License if You: (a) rename the license and remove any references to the name of the license steward (except to note that the license differs from this License); and (b) otherwise make it clear that the license contains terms which differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN .AS IS. BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as .Participant.) alleging that the Participant Software (meaning the Contributor Version where the Participant is a Contributor or the Original Software where the Participant is the Initial Developer) directly or indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant, the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement with Participant. + + 6.3. In the event of termination under Sections 6.1 or 6.2 above, all end user licenses that have been validly granted by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTY.S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a .commercial item,. as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of .commercial computer software. (as that term is defined at 48 C.F.R. § 252.227-7014(a)(1)) and .commercial computer software documentation. as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of, and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any, provides otherwise), excluding such jurisdiction.s conflict-of-law provisions. Any litigation relating to this License shall be subject to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the losing party responsible for costs, including, without limitation, court costs and reasonable attorneys. fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use, distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability. + + NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) + + The code released under the CDDL shall be governed by the laws of the State of California (excluding conflict-of-law provisions). Any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California and the state courts of the State of California, with venue lying in Santa Clara County, California. + + +The GNU General Public License (GPL) Version 2, June 1991 + + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + + c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + + Copyright (C) + + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. + + +"CLASSPATH" EXCEPTION TO THE GPL VERSION 2 + +Certain source files distributed by Sun Microsystems, Inc. are subject to the following clarification and special exception to the GPL Version 2, but only where Sun has expressly included in the particular source file's header the words + +"Sun designates this particular file as subject to the "Classpath" exception as provided by Sun in the License file that accompanied this code." + +Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License Version 2 cover the whole combination. + +As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module.? An independent module is a module which is not derived from or based on this library.? If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so.? If you do not wish to do so, delete this exception statement from your version. diff --git a/licenses/bin/jcharts-0.7.5.txt b/licenses/bin/jcharts-0.7.5.txt new file mode 100644 index 00000000000..5d68a513097 --- /dev/null +++ b/licenses/bin/jcharts-0.7.5.txt @@ -0,0 +1,33 @@ +http://jcharts.sourceforge.net/license.html + +Copyright 2002 (C) Nathaniel G. Auvil. All Rights Reserved. + +Redistribution and use of this software and associated documentation ("Software"), with or +without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain copyright statements and notices. +Redistributions must also contain a copy of this document. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other materials +provided with the distribution. + +3. The name "jCharts" or "Nathaniel G. Auvil" must not be used to endorse or promote +products derived from this Software without prior written permission of Nathaniel G. +Auvil. For written permission, please contact nathaniel_auvil@users.sourceforge.net + +4. Products derived from this Software may not be called "jCharts" nor may "jCharts" appear +in their names without prior written permission of Nathaniel G. Auvil. jCharts is a +registered trademark of Nathaniel G. Auvil. + +5. Due credit should be given to the jCharts Project (http://jcharts.krysalis.org). + +THIS SOFTWARE IS PROVIDED BY Nathaniel G. Auvil AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +jCharts OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE diff --git a/licenses/bin/jdom-1.1.3.txt b/licenses/bin/jdom-1.1.3.txt new file mode 100644 index 00000000000..5ca9205957a --- /dev/null +++ b/licenses/bin/jdom-1.1.3.txt @@ -0,0 +1,58 @@ +Extracted from jdom-1.1.3.jar + +/*-- + + $Id: LICENSE.txt,v 1.11 2004/02/06 09:32:57 jhunter Exp $ + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + diff --git a/licenses/bin/jodd-core-3.6.4.txt b/licenses/bin/jodd-core-3.6.4.txt new file mode 100644 index 00000000000..c4ba69803fa --- /dev/null +++ b/licenses/bin/jodd-core-3.6.4.txt @@ -0,0 +1,33 @@ +http://jodd.org/license.html + +Copyright (c) 2003-2014, Jodd Team +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +Neither the name of the Jodd nor the names of its contributors +may be used to endorse or promote products derived from this +software without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licenses/bin/jodd-lagarto-3.6.4.txt b/licenses/bin/jodd-lagarto-3.6.4.txt new file mode 100644 index 00000000000..62ef518f246 --- /dev/null +++ b/licenses/bin/jodd-lagarto-3.6.4.txt @@ -0,0 +1 @@ +See jodd-core-3.5.2.txt \ No newline at end of file diff --git a/licenses/bin/jodd-log-3.6.4.txt b/licenses/bin/jodd-log-3.6.4.txt new file mode 100644 index 00000000000..62ef518f246 --- /dev/null +++ b/licenses/bin/jodd-log-3.6.4.txt @@ -0,0 +1 @@ +See jodd-core-3.5.2.txt \ No newline at end of file diff --git a/licenses/bin/jsoup-1.8.1.txt b/licenses/bin/jsoup-1.8.1.txt new file mode 100644 index 00000000000..ef5654be8de --- /dev/null +++ b/licenses/bin/jsoup-1.8.1.txt @@ -0,0 +1,26 @@ +http://jsoup.org/license + +jsoup License + +The jsoup code-base (include source and compiled packages) are distributed under the open +source MIT license as described below. + +The MIT License +Copyright © 2009 - 2013 Jonathan Hedley (jonathan@hedley.net) + +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. diff --git a/licenses/bin/jtidy-r938.txt b/licenses/bin/jtidy-r938.txt new file mode 100644 index 00000000000..a49e34a3080 --- /dev/null +++ b/licenses/bin/jtidy-r938.txt @@ -0,0 +1,55 @@ +Extracted from jtidy-r938.jar + +/** +* Java HTML Tidy - JTidy +* HTML parser and pretty printer +* +* Copyright (c) 1998-2000 World Wide Web Consortium (Massachusetts +* Institute of Technology, Institut National de Recherche en +* Informatique et en Automatique, Keio University). All Rights +* Reserved. +* +* Contributing Author(s): +* +* Dave Raggett +* Andy Quick (translation to Java) +* Gary L Peskin (Java development) +* Sami Lempinen (release management) +* Fabrizio Giustina +* +* The contributing author(s) would like to thank all those who +* helped with testing, bug fixes, and patience. This wouldn't +* have been possible without all of you. +* +* COPYRIGHT NOTICE: +* +* This software and documentation is provided "as is," and +* the copyright holders and contributing author(s) make no +* representations or warranties, express or implied, including +* but not limited to, warranties of merchantability or fitness +* for any particular purpose or that the use of the software or +* documentation will not infringe any third party patents, +* copyrights, trademarks or other rights. +* +* The copyright holders and contributing author(s) will not be +* liable for any direct, indirect, special or consequential damages +* arising out of any use of the software or documentation, even if +* advised of the possibility of such damage. +* +* Permission is hereby granted to use, copy, modify, and distribute +* this source code, or portions hereof, documentation and executables, +* for any purpose, without fee, subject to the following restrictions: +* +* 1. The origin of this source code must not be misrepresented. +* 2. Altered versions must be plainly marked as such and must +* not be misrepresented as being the original source. +* 3. This Copyright notice may not be removed or altered from any +* source or altered source distribution. +* +* The copyright holders and contributing author(s) specifically +* permit, without fee, and encourage the use of this source code +* as a component for supporting the Hypertext Markup Language in +* commercial products. If you use this source code in a product, +* acknowledgment is not required but would be appreciated. +* +*/ \ No newline at end of file diff --git a/licenses/bin/junit-4.12.txt b/licenses/bin/junit-4.12.txt new file mode 100644 index 00000000000..14d988a64bc --- /dev/null +++ b/licenses/bin/junit-4.12.txt @@ -0,0 +1,29 @@ +Extracted from JUnit-4.11.jar + +BSD License + +Copyright (c) 2000-2006, www.hamcrest.org +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. Redistributions in binary form must reproduce +the above copyright notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. + +Neither the name of Hamcrest nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/licenses/bin/mongo-java-driver-2.11.3.txt b/licenses/bin/mongo-java-driver-2.11.3.txt new file mode 100644 index 00000000000..dba5f9cb4af --- /dev/null +++ b/licenses/bin/mongo-java-driver-2.11.3.txt @@ -0,0 +1,205 @@ +http://www.mongodb.org/about/licensing/ +mongodb.org supported drivers: Apache License v2.0 + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/licenses/bin/rhino-1.7R5.txt b/licenses/bin/rhino-1.7R5.txt new file mode 100644 index 00000000000..2c5550d7aa7 --- /dev/null +++ b/licenses/bin/rhino-1.7R5.txt @@ -0,0 +1,377 @@ +Extracted from rhino-1.7R4.txt + +The majority of Rhino is licensed under the MPL 2.0: + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/licenses/bin/rsyntaxtextarea-2.5.6.txt b/licenses/bin/rsyntaxtextarea-2.5.6.txt new file mode 100644 index 00000000000..8080d4b4b6b --- /dev/null +++ b/licenses/bin/rsyntaxtextarea-2.5.6.txt @@ -0,0 +1,26 @@ +http://fifesoft.com/rsyntaxtextarea/RSyntaxTextArea.License.txt + +Copyright (c) 2012, Robert Futrell +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licenses/bin/slf4j-api-1.7.10.txt b/licenses/bin/slf4j-api-1.7.10.txt new file mode 100644 index 00000000000..c5ae560900b --- /dev/null +++ b/licenses/bin/slf4j-api-1.7.10.txt @@ -0,0 +1,24 @@ +http://www.slf4j.org/license.html + +SLF4J source code and binaries are distributed under the MIT license. +Copyright (c) 2004-2013 QOS.ch +All rights reserved. + +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. \ No newline at end of file diff --git a/licenses/bin/xmlpull-1.1.3.1.txt b/licenses/bin/xmlpull-1.1.3.1.txt new file mode 100644 index 00000000000..5f9170e7516 --- /dev/null +++ b/licenses/bin/xmlpull-1.1.3.1.txt @@ -0,0 +1,3 @@ +http://www.xmlpull.org/ + +The XmlPull API is in public domain \ No newline at end of file diff --git a/licenses/bin/xpp3-1.1.4c.txt b/licenses/bin/xpp3-1.1.4c.txt new file mode 100644 index 00000000000..06db0a114f4 --- /dev/null +++ b/licenses/bin/xpp3-1.1.4c.txt @@ -0,0 +1,48 @@ +Extracted from http://www.extreme.indiana.edu/dist/java-repository/xpp3/distributions/xpp3-1.1.4c_src.zip + +Indiana University Extreme! Lab Software License + +Version 1.1.1 + +Copyright (c) 2002 Extreme! Lab, Indiana University. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + +3. The end-user documentation included with the redistribution, if any, + must include the following acknowledgment: + + "This product includes software developed by the Indiana University + Extreme! Lab (http://www.extreme.indiana.edu/)." + +Alternately, this acknowledgment may appear in the software itself, +if and wherever such third-party acknowledgments normally appear. + +4. The names "Indiana Univeristy" and "Indiana Univeristy Extreme! Lab" +must not be used to endorse or promote products derived from this +software without prior written permission. For written permission, +please contact http://www.extreme.indiana.edu/. + +5. Products derived from this software may not use "Indiana Univeristy" +name nor may "Indiana Univeristy" appear in their name, without prior +written permission of the Indiana University. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS OR ITS CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/bin/xstream-1.4.8.txt b/licenses/bin/xstream-1.4.8.txt new file mode 100644 index 00000000000..039784d62ca --- /dev/null +++ b/licenses/bin/xstream-1.4.8.txt @@ -0,0 +1,32 @@ +http://xstream.codehaus.org/license.html + +XStream is open source software, made available under a BSD license. + +Copyright (c) 2003-2006, Joe Walnes +Copyright (c) 2006-2009, 2011 XStream Committers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other materials provided +with the distribution. + +3. Neither the name of XStream nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/licenses/src/README.txt b/licenses/src/README.txt new file mode 100644 index 00000000000..eb50133e3dd --- /dev/null +++ b/licenses/src/README.txt @@ -0,0 +1 @@ +This directory contains additional licenses for software contained in source distributions diff --git a/licenses/src/ThreadLocalRandom.txt b/licenses/src/ThreadLocalRandom.txt new file mode 100644 index 00000000000..6a413631025 --- /dev/null +++ b/licenses/src/ThreadLocalRandom.txt @@ -0,0 +1,30 @@ +ThreadLocalRandom by Doug Lea + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: + + the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; + moral rights retained by the original author(s) and/or performer(s); + publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; + rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; + rights protecting the extraction, dissemination, use and reuse of data in a Work; + database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and + other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. + Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. + Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. + Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. diff --git a/licenses/src/openiconlibrary.txt b/licenses/src/openiconlibrary.txt new file mode 100644 index 00000000000..7020f7f0361 --- /dev/null +++ b/licenses/src/openiconlibrary.txt @@ -0,0 +1,107 @@ + Open Icon Library Licenses +========================================= + +Open Icon Library from +http://openiconlibrary.sourceforge.net/ + +Detailed Licenses information: +http://openiconlibrary.sourceforge.net/LICENSES.html + +============ Packages used by Apache JMeter ========= + +open_icon_library-devel-CC (Creative Commons and Public Domain only) +open_icon_library-CC (Creative Commons and Public Domain only) + List of licenses used in these packages: + Creative Commons Attribution + Creative Commons Attribution-Share Alike + Public Domain + +========Icons's sources used by Apache JMeter========== + +echo-icon-theme (echo) + link: https://fedorahosted.org/echo-icon-theme/ + license: CC-BY-SA 3.0 + License link: http://creativecommons.org/licenses/by-sa/3.0/ + formats: png + subdirectories: open_icon_library-devel/icons/echo + +Oxygen Icons 4.3.1 (KDE) (oxygen) + link: http://www.oxygen-icons.org/ + license: Dual: CC-BY-SA 3.0 or LGPL + License link: http://creativecommons.org/licenses/by-sa/3.0/ + http://creativecommons.org/licenses/LGPL/2.1/ + formats: svg, png + subdirectory: open_icon_library-devel/icons/oxygen + +Tango Icon Library 0.8.90 (tango) + link: http://tango.freedesktop.org/Tango_Icon_Library + license: Public Domain + License link: http://en.wikipedia.org/wiki/Public_domain + formats: svg, png + subdirectory: open_icon_library-devel/icons/tango + +============== CC-BY-SA 3.0 License ============= +http://creativecommons.org/licenses/by-sa/3.0/legalcode + +Creative Commons Attribution-ShareAlike 3.0 Unported License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. + +1. Definitions + + a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. + b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. + c. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. + d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. + e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. + f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. + g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. + h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. + i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. + j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. + k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: + + a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; + b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; + c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, + d. to Distribute and Publicly Perform Adaptations. + e. For the avoidance of doubt: + i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; + ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, + iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. + +The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: + + a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. + b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. + c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. + d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. + b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. + +8. Miscellaneous + + a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. + b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. + c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. + e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. + f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. + diff --git a/rat-excludes.txt b/rat-excludes.txt new file mode 100644 index 00000000000..ba021da15da --- /dev/null +++ b/rat-excludes.txt @@ -0,0 +1,28 @@ + 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. + + Excludes file for Apache RAT tool run by ASF Buildbot, + http://incubator.apache.org/rat/ + +**/*.jmx +bin/examples/** +bin/testfiles/** +build/** +docs/** +extras/*.txt +lib/aareadme.txt +src/core/org/apache/jmeter/help.txt +*.log +**/download_jmeter.cgi \ No newline at end of file diff --git a/res/META-INF/default.license b/res/META-INF/default.license new file mode 100644 index 00000000000..f433b1a53f5 --- /dev/null +++ b/res/META-INF/default.license @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/res/META-INF/default.notice b/res/META-INF/default.notice new file mode 100644 index 00000000000..cf17436e0df --- /dev/null +++ b/res/META-INF/default.notice @@ -0,0 +1,5 @@ +Apache JMeter +Copyright 1999-@YEAR@ The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/res/maven/ApacheJMeter.pom b/res/maven/ApacheJMeter.pom new file mode 100644 index 00000000000..a2ad06a56f5 --- /dev/null +++ b/res/maven/ApacheJMeter.pom @@ -0,0 +1,29 @@ + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter + Apache JMeter launcher + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_components.pom b/res/maven/ApacheJMeter_components.pom new file mode 100644 index 00000000000..8b99c023189 --- /dev/null +++ b/res/maven/ApacheJMeter_components.pom @@ -0,0 +1,45 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_components + Apache JMeter Components + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_config.pom b/res/maven/ApacheJMeter_config.pom new file mode 100644 index 00000000000..63c7b7287c6 --- /dev/null +++ b/res/maven/ApacheJMeter_config.pom @@ -0,0 +1,32 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_config + Apache JMeter Configuration + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_core.pom b/res/maven/ApacheJMeter_core.pom new file mode 100644 index 00000000000..0b971d6565a --- /dev/null +++ b/res/maven/ApacheJMeter_core.pom @@ -0,0 +1,40 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_core + Apache JMeter Core + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_ftp.pom b/res/maven/ApacheJMeter_ftp.pom new file mode 100644 index 00000000000..99477116c81 --- /dev/null +++ b/res/maven/ApacheJMeter_ftp.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_ftp + Apache JMeter FTP + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_functions.pom b/res/maven/ApacheJMeter_functions.pom new file mode 100644 index 00000000000..6608e2ff1cf --- /dev/null +++ b/res/maven/ApacheJMeter_functions.pom @@ -0,0 +1,45 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_functions + Apache JMeter Functions + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_http.pom b/res/maven/ApacheJMeter_http.pom new file mode 100644 index 00000000000..763056beb25 --- /dev/null +++ b/res/maven/ApacheJMeter_http.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_http + Apache JMeter HTTP + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_java.pom b/res/maven/ApacheJMeter_java.pom new file mode 100644 index 00000000000..e284dc41a59 --- /dev/null +++ b/res/maven/ApacheJMeter_java.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_java + Apache JMeter Java + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_jdbc.pom b/res/maven/ApacheJMeter_jdbc.pom new file mode 100644 index 00000000000..560ec143dcf --- /dev/null +++ b/res/maven/ApacheJMeter_jdbc.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_jdbc + Apache JMeter JDBC + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_jms.pom b/res/maven/ApacheJMeter_jms.pom new file mode 100644 index 00000000000..aa6ca6b454a --- /dev/null +++ b/res/maven/ApacheJMeter_jms.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_jms + Apache JMeter JMS + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_junit-test.pom b/res/maven/ApacheJMeter_junit-test.pom new file mode 100644 index 00000000000..ef5798e9a4f --- /dev/null +++ b/res/maven/ApacheJMeter_junit-test.pom @@ -0,0 +1,31 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_junit-test + Apache JMeter JUnit sample test classes + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_junit.pom b/res/maven/ApacheJMeter_junit.pom new file mode 100644 index 00000000000..1687a7e0a84 --- /dev/null +++ b/res/maven/ApacheJMeter_junit.pom @@ -0,0 +1,44 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_junit + Apache JMeter jUnit + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_ldap.pom b/res/maven/ApacheJMeter_ldap.pom new file mode 100644 index 00000000000..6d95fa953dc --- /dev/null +++ b/res/maven/ApacheJMeter_ldap.pom @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_ldap + Apache JMeter LDAP + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_mail.pom b/res/maven/ApacheJMeter_mail.pom new file mode 100644 index 00000000000..5984168ac22 --- /dev/null +++ b/res/maven/ApacheJMeter_mail.pom @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_mail + Apache JMeter Mail + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_mongodb.pom b/res/maven/ApacheJMeter_mongodb.pom new file mode 100644 index 00000000000..b326a86a2c2 --- /dev/null +++ b/res/maven/ApacheJMeter_mongodb.pom @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_mongodb + Apache JMeter MongoDB + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_monitors.pom b/res/maven/ApacheJMeter_monitors.pom new file mode 100644 index 00000000000..4f94bc7e2f3 --- /dev/null +++ b/res/maven/ApacheJMeter_monitors.pom @@ -0,0 +1,54 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_monitors + Apache JMeter Monitor + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_http + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_native.pom b/res/maven/ApacheJMeter_native.pom new file mode 100644 index 00000000000..044bf1acb6f --- /dev/null +++ b/res/maven/ApacheJMeter_native.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_native + Apache JMeter Native + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/ApacheJMeter_parent.pom b/res/maven/ApacheJMeter_parent.pom new file mode 100644 index 00000000000..d08fcbaebc8 --- /dev/null +++ b/res/maven/ApacheJMeter_parent.pom @@ -0,0 +1,433 @@ + + + 4.0.0 + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + Apache JMeter parent + + pom + + Apache JMeter is open source software, a 100% pure Java desktop application designed to load test + functional behavior and measure performance. It was originally designed for testing Web Applications but has + since expanded to other test functions. + + http://jmeter.apache.org/ + 1998 + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + bugzilla + https://bz.apache.org/bugzilla/describecomponents.cgi?product=JMeter + + + http://svn.apache.org/repos/asf/jmeter/trunk/ + https://svn.apache.org/repos/asf/jmeter/trunk/ + http://svn.apache.org/viewvc/jmeter/trunk/ + + + + + + 2.4.0 + 4.1.4 + 2.0b5 + 1.49 + 1.49 + 1.49 + 1.10 + 3.2.1 + 3.1 + 2.4 + 1.1 + 2.1.1 + 3.3.2 + 1.2 + 3.5 + 3.3 + 2.3 + 2.1.7 + 2.1 + 1.0 + 1.1 + 2.1 + ${excalibur-pool.version} + ${excalibur-pool.version} + ${excalibur-pool.version} + 2.1 + 4.2.6 + 4.2.5 + 2.0.8 + 0.7.5 + 1.1.3 + 3.6.4 + 3.6.4 + 3.6.4 + 1.8.1 + 1.7R5 + 4.12 + 2.0 + 2.11.3 + 2.5.6 + 1.7.10 + 1.7.10 + 2.3.1 + r938 + 1.8 + 1.8 + 1.1.3.1 + 1.4.8 + 1.1.4c + 2.7.2 + 2.7.2 + 2.11.0 + 1.4.01 + 1.5 + 1.5.0-b01 + 1.1.1 + 1.7 + + + + + bsf + bsf + ${apache-bsf.version} + + + avalon-framework + avalon-framework + ${avalon-framework.version} + + + org.beanshell + bsh + ${beanshell.version} + + + org.bouncycastle + bcmail-jdk15on + ${bcmail.version} + + + org.bouncycastle + bcprov-jdk15on + ${bcprov.version} + + + org.bouncycastle + bcpkix-jdk15on + ${bcpkix.version} + + + commons-codec + commons-codec + ${commons-codec.version} + + + commons-collections + commons-collections + ${commons-collections.version} + + + commons-httpclient + commons-httpclient + ${commons-httpclient.version} + + + commons-io + commons-io + ${commons-io.version} + + + commons-jexl + commons-jexl + ${commons-jexl.version} + + + org.apache.commons + commons-jexl + ${commons-jexl2.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + commons-logging + commons-logging + ${commons-logging.version} + + + org.apache.commons + commons-math3 + ${commons-math3.version} + + + commons-net + commons-net + ${commons-net.version} + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + dnsjava + dnsjava + ${dnsjava.version} + + + excalibur-datasource + excalibur-datasource + ${excalibur-datasource.version} + + + excalibur-fortress + excalibur-fortress-container-api + + + excalibur-fortress + excalibur-fortress-meta + + + + + excalibur-instrument + excalibur-instrument + ${excalibur-instrument.version} + + + excalibur-logger + excalibur-logger + ${excalibur-logger.version} + + + excalibur-pool + excalibur-pool-api + ${excalibur-pool-api.version} + + + excalibur-pool + excalibur-pool-impl + ${excalibur-pool-impl.version} + + + excalibur-pool + excalibur-pool-instrumented + ${excalibur-pool-instrumented.version} + + + org.htmlparser + htmllexer + ${htmlparser.version} + + + org.htmlparser + htmlparser + ${htmlparser.version} + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + org.apache.httpcomponents + httpmime + ${httpclient.version} + + + org.apache.httpcomponents + httpcore + ${httpcore.version} + + + oro + oro + ${jakarta-oro.version} + + + jcharts + jcharts + 0.7.5 + + + org.jdom + jdom + ${jdom.version} + + + + jaxen + jaxen + + + + + org.mozilla + rhino + ${rhino.version} + + + junit + junit + ${junit.version} + + + logkit + logkit + ${logkit.version} + + + soap + soap + ${soap.version} + + + net.sf.jtidy + jtidy + ${jtidy.version} + + + org.apache.tika + tika-core + ${tika-core.version} + + + org.apache.tika + tika-parsers + ${tika-parsers.version} + + + com.thoughtworks.xstream + xstream + ${xstream.version} + + + xmlpull + xmlpull + ${xmlpull.version} + + + xpp3 + xpp3_min + ${xpp3.version} + + + xalan + xalan + ${xalan.version} + + + xalan + serializer + ${serializer.version} + + + xerces + xercesImpl + ${xerces.version} + + + xml-apis + xml-apis + ${xml-apis.version} + + + org.apache.xmlgraphics + xmlgraphics-commons + ${xmlgraphics-commons.version} + + + javax.mail + mail + ${javamail.version} + + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + ${jms.version} + + + org.jsoup + jsoup + ${jsoup.version} + + + org.jodd + jodd-core + ${jodd-core.version} + + + org.jodd + jodd-lagarto + ${jodd-lagarto.version} + + + org.jodd + jodd-log + ${jodd-log.version} + + + org.mongodb + mongo-java-driver + ${mongo-java-driver.version} + + + com.fifesoft + rsyntaxtextarea + ${rsyntaxtextarea.version} + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + org.slf4j + slf4j-nop + ${slf4j-nop.version} + + + + + + + + JBoss + https://repository.jboss.org/nexus/content/repositories/thirdparty-releases/ + + + diff --git a/res/maven/ApacheJMeter_tcp.pom b/res/maven/ApacheJMeter_tcp.pom new file mode 100644 index 00000000000..855b39b458d --- /dev/null +++ b/res/maven/ApacheJMeter_tcp.pom @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_tcp + Apache JMeter TCP + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/res/maven/jorphan.pom b/res/maven/jorphan.pom new file mode 100644 index 00000000000..01fe3723b53 --- /dev/null +++ b/res/maven/jorphan.pom @@ -0,0 +1,32 @@ + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + Apache JMeter jorphan library + + \ No newline at end of file diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index 270b4a34183..00000000000 --- a/src/Makefile +++ /dev/null @@ -1 +0,0 @@ -Sorry! Yet to be written! \ No newline at end of file diff --git a/src/components/org/apache/jmeter/assertions/BSFAssertion.java b/src/components/org/apache/jmeter/assertions/BSFAssertion.java new file mode 100644 index 00000000000..3980086c209 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/BSFAssertion.java @@ -0,0 +1,57 @@ +/* + * 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.assertions; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFAssertion extends BSFTestElement implements Cloneable, Assertion, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 234L; + + @Override + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + BSFManager mgr =null; + try { + mgr = getManager(); + mgr.declareBean("SampleResult", response, SampleResult.class); + mgr.declareBean("AssertionResult", result, AssertionResult.class); + processFileOrScript(mgr); + result.setError(false); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + result.setFailure(true); + result.setError(true); + result.setFailureMessage(e.toString()); + } finally { + if(mgr != null) { + mgr.terminate(); + } + } + return result; + } +} diff --git a/src/components/org/apache/jmeter/assertions/BSFAssertionBeanInfo.java b/src/components/org/apache/jmeter/assertions/BSFAssertionBeanInfo.java new file mode 100644 index 00000000000..1734a810551 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/BSFAssertionBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.assertions; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFAssertionBeanInfo extends BSFBeanInfoSupport { + + public BSFAssertionBeanInfo() { + super(BSFAssertion.class); + } + +} diff --git a/src/components/org/apache/jmeter/assertions/BSFAssertionResources.properties b/src/components/org/apache/jmeter/assertions/BSFAssertionResources.properties new file mode 100644 index 00000000000..15e8a7fe47c --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/BSFAssertionResources.properties @@ -0,0 +1,28 @@ +# 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. + +displayName=BSF Assertion +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/assertions/BSFAssertionResources_fr.properties b/src/components/org/apache/jmeter/assertions/BSFAssertionResources_fr.properties new file mode 100644 index 00000000000..edd8838d96e --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/BSFAssertionResources_fr.properties @@ -0,0 +1,29 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Assertion BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script en langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom de langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pl.properties b/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pl.properties new file mode 100644 index 00000000000..0fb1d24d1ef --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pl.properties @@ -0,0 +1,29 @@ +# 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. +# Uwagi co do tlumaczenia: mr0vek@o2.pl + +displayName=Asercje BSF +filename.displayName=Nazwa pliku +filename.shortDescription=Plik ze skryptem (zast\u0119puje skrypt) +filenameGroup.displayName=Plik ze skryptem (zast\u0119puje skrypt) +parameterGroup.displayName=Parametry dla skryptu (=> String Parameters and String []args) +parameters.displayName=Parametry +parameters.shortDescription=Parametry do przekazania do pliku lub skryptu +script.displayName=Skrypt +script.shortDescription=Skrypt w j\u0119zyku zgodnym z BSF +scriptLanguage.displayName=J\u0119zyk +scriptLanguage.shortDescription=Nazwa j\u0119zyka BSF, np. beanshell, javascript, jexl +scripting.displayName=Skrypt (zmienne: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=J\u0119zyk skryptu (np. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pt_BR.properties b/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pt_BR.properties new file mode 100644 index 00000000000..f2dbafb1e9d --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pt_BR.properties @@ -0,0 +1,28 @@ +# 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. + +displayName=Asser\u00E7\u00E3o BSF +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao script (\=> String Parameters e String []args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao arquivo ou script +script.displayName=Script +script.shortDescription=Script na linguagem BSF apropriada +scriptLanguage.displayName=Linguagem +scriptLanguage.shortDescription=Nome da linguagem BSF, ex\: beanshell, javascript, jexl +scripting.displayName=Script (vari\u00E1veis\: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Linguagem do script (ex\: beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/assertions/BeanShellAssertion.java b/src/components/org/apache/jmeter/assertions/BeanShellAssertion.java new file mode 100644 index 00000000000..aef53d2dc06 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/BeanShellAssertion.java @@ -0,0 +1,133 @@ +/* + * 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.assertions; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * An Assertion which understands BeanShell + * + */ +public class BeanShellAssertion extends BeanShellTestElement implements Assertion { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 3; + + public static final String FILENAME = "BeanShellAssertion.filename"; //$NON-NLS-1$ + + public static final String SCRIPT = "BeanShellAssertion.query"; //$NON-NLS-1$ + + public static final String PARAMETERS = "BeanShellAssertion.parameters"; //$NON-NLS-1$ + + public static final String RESET_INTERPRETER = "BeanShellAssertion.resetInterpreter"; //$NON-NLS-1$ + + // can be specified in jmeter.properties + public static final String INIT_FILE = "beanshell.assertion.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + @Override + public String getScript() { + return getPropertyAsString(SCRIPT); + } + + @Override + public String getFilename() { + return getPropertyAsString(FILENAME); + } + + @Override + public String getParameters() { + return getPropertyAsString(PARAMETERS); + } + + @Override + public boolean isResetInterpreter() { + return getPropertyAsBoolean(RESET_INTERPRETER); + } + + /** + * {@inheritDoc} + */ + @Override + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + result.setFailure(true); + result.setError(true); + result.setFailureMessage("BeanShell Interpreter not found"); + return result; + } + try { + + // Add SamplerData for consistency with BeanShell Sampler + bshInterpreter.set("SampleResult", response); //$NON-NLS-1$ + bshInterpreter.set("Response", response); //$NON-NLS-1$ + bshInterpreter.set("ResponseData", response.getResponseData());//$NON-NLS-1$ + bshInterpreter.set("ResponseCode", response.getResponseCode());//$NON-NLS-1$ + bshInterpreter.set("ResponseMessage", response.getResponseMessage());//$NON-NLS-1$ + bshInterpreter.set("ResponseHeaders", response.getResponseHeaders());//$NON-NLS-1$ + bshInterpreter.set("RequestHeaders", response.getRequestHeaders());//$NON-NLS-1$ + bshInterpreter.set("SampleLabel", response.getSampleLabel());//$NON-NLS-1$ + bshInterpreter.set("SamplerData", response.getSamplerData());//$NON-NLS-1$ + bshInterpreter.set("Successful", response.isSuccessful());//$NON-NLS-1$ + + // The following are used to set the Result details on return from + // the script: + bshInterpreter.set("FailureMessage", "");//$NON-NLS-1$ //$NON-NLS-2$ + bshInterpreter.set("Failure", false);//$NON-NLS-1$ + + processFileOrScript(bshInterpreter); + + result.setFailureMessage(bshInterpreter.get("FailureMessage").toString());//$NON-NLS-1$ + result.setFailure(Boolean.parseBoolean(bshInterpreter.get("Failure") //$NON-NLS-1$ + .toString())); + result.setError(false); + } + /* + * To avoid class loading problems when the BSH jar is missing, we don't + * try to catch this error separately catch (bsh.EvalError ex) { + * log.debug("",ex); result.setError(true); + * result.setFailureMessage(ex.toString()); } + */ + // but we do trap this error to make tests work better + catch (NoClassDefFoundError ex) { + log.error("BeanShell Jar missing? " + ex.toString()); + result.setError(true); + result.setFailureMessage("BeanShell Jar missing? " + ex.toString()); + response.setStopThread(true); // No point continuing + } catch (Exception ex) // Mainly for bsh.EvalError + { + result.setError(true); + result.setFailureMessage(ex.toString()); + log.warn(ex.toString()); + } + + return result; + } +} diff --git a/src/components/org/apache/jmeter/assertions/CompareAssertion.java b/src/components/org/apache/jmeter/assertions/CompareAssertion.java new file mode 100644 index 00000000000..1bafa8c0057 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/CompareAssertion.java @@ -0,0 +1,212 @@ +/* + * 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.assertions; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.StringSubstitution; +import org.apache.oro.text.regex.Util; + +public class CompareAssertion extends AbstractTestElement implements Assertion, TestBean, Serializable, + LoopIterationListener { + + private static final long serialVersionUID = 240L; + + private transient List responses; + + private transient final StringSubstitution emptySub = new StringSubstitution(""); //$NON-NLS-1$ + + private boolean compareContent = true; + + private long compareTime = -1; + + private Collection stringsToSkip; + + public CompareAssertion() { + super(); + } + + @Override + public AssertionResult getResult(SampleResult response) { + responses.add(response); + if (responses.size() > 1) { + CompareAssertionResult result = new CompareAssertionResult(getName()); + compareContent(result); + compareTime(result); + return result; + } else { + return new AssertionResult(getName()); + } + } + + private void compareTime(CompareAssertionResult result) { + if (compareTime >= 0) { + long prevTime = -1; + SampleResult prevResult = null; + boolean success = true; + StringBuilder buf = new StringBuilder(); + for(SampleResult sResult : responses) { + long currentTime = sResult.getTime(); + if (prevTime != -1) { + success = Math.abs(prevTime - currentTime) <= compareTime; + prevResult = sResult; + } + if (!success) { + result.setFailure(true); + buf.setLength(0); + appendResultDetails(buf, prevResult); + buf.append(JMeterUtils.getResString("comparison_response_time")).append(prevTime); //$NON-NLS-1$ + result.addToBaseResult(buf.toString()); + buf.setLength(0); + appendResultDetails(buf, sResult); + buf.append(JMeterUtils.getResString("comparison_response_time")).append(currentTime); //$NON-NLS-1$ + result.addToSecondaryResult(buf.toString()); + result.setFailureMessage( + JMeterUtils.getResString("comparison_differ_time")+ //$NON-NLS-1$ + compareTime+ + JMeterUtils.getResString("comparison_unit")); //$NON-NLS-1$ + break; + } + prevResult = sResult; + prevTime = currentTime; + } + } + } + + private void compareContent(CompareAssertionResult result) { + if (compareContent) { + String prevContent = null; + SampleResult prevResult = null; + boolean success = true; + StringBuilder buf = new StringBuilder(); + for (SampleResult sResult : responses) { + String currentContent = sResult.getResponseDataAsString(); + currentContent = filterString(currentContent); + if (prevContent != null) { + success = prevContent.equals(currentContent); + } + if (!success) { + result.setFailure(true); + buf.setLength(0); + appendResultDetails(buf, prevResult); + buf.append(prevContent); + result.addToBaseResult(buf.toString()); + buf.setLength(0); + appendResultDetails(buf, sResult); + buf.append(currentContent); + result.addToSecondaryResult(buf.toString()); + result.setFailureMessage(JMeterUtils.getResString("comparison_differ_content")); //$NON-NLS-1$ + break; + } + prevResult = sResult; + prevContent = currentContent; + } + } + } + + private void appendResultDetails(StringBuilder buf, SampleResult result) { + final String samplerData = result.getSamplerData(); + if (samplerData != null){ + buf.append(samplerData.trim()); + } + buf.append("\n"); //$NON-NLS-1$ + final String requestHeaders = result.getRequestHeaders(); + if (requestHeaders != null){ + buf.append(requestHeaders); + } + buf.append("\n\n"); //$NON-NLS-1$ + } + + private String filterString(String content) { + if (stringsToSkip == null || stringsToSkip.size() == 0) { + return content; + } else { + for (SubstitutionElement regex : stringsToSkip) { + emptySub.setSubstitution(regex.getSubstitute()); + content = Util.substitute(JMeterUtils.getMatcher(), JMeterUtils.getPatternCache().getPattern(regex.getRegex()), + emptySub, content, Util.SUBSTITUTE_ALL); + } + } + return content; + } + + @Override + public void iterationStart(LoopIterationEvent iterEvent) { + responses = new LinkedList(); + } + + public void iterationEnd(LoopIterationEvent iterEvent) { + responses = null; + } + + /** + * @return Returns the compareContent. + */ + public boolean isCompareContent() { + return compareContent; + } + + /** + * @param compareContent + * The compareContent to set. + */ + public void setCompareContent(boolean compareContent) { + this.compareContent = compareContent; + } + + /** + * @return Returns the compareTime. + */ + public long getCompareTime() { + return compareTime; + } + + /** + * @param compareTime + * The compareTime to set. + */ + public void setCompareTime(long compareTime) { + this.compareTime = compareTime; + } + + /** + * @return Returns the stringsToSkip. + */ + public Collection getStringsToSkip() { + return stringsToSkip; + } + + /** + * @param stringsToSkip + * The stringsToSkip to set. + */ + public void setStringsToSkip(Collection stringsToSkip) { + this.stringsToSkip = stringsToSkip; + } + +} diff --git a/src/components/org/apache/jmeter/assertions/CompareAssertionBeanInfo.java b/src/components/org/apache/jmeter/assertions/CompareAssertionBeanInfo.java new file mode 100644 index 00000000000..e55b72fa2f5 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/CompareAssertionBeanInfo.java @@ -0,0 +1,56 @@ +/* + * 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.assertions; + +import java.beans.PropertyDescriptor; +import java.util.ArrayList; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TableEditor; +import org.apache.jmeter.util.JMeterUtils; + +public class CompareAssertionBeanInfo extends BeanInfoSupport { + + public CompareAssertionBeanInfo() { + super(CompareAssertion.class); + createPropertyGroup("compareChoices", new String[] { "compareContent", "compareTime" }); //$NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + createPropertyGroup("comparison_filters", new String[]{"stringsToSkip"}); //$NON-NLS-1$ $NON-NLS-2$ + PropertyDescriptor p = property("compareContent"); //$NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p = property("compareTime"); //$NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Long.valueOf(-1)); + p.setValue(NOT_EXPRESSION, Boolean.FALSE); + p = property("stringsToSkip"); //$NON-NLS-1$ + p.setPropertyEditorClass(TableEditor.class); + p.setValue(TableEditor.CLASSNAME,SubstitutionElement.class.getName()); + p.setValue(TableEditor.HEADERS,new String[]{ + JMeterUtils.getResString("comparison_regex_string"), //$NON-NLS-1$ + JMeterUtils.getResString("comparison_regex_substitution")}); //$NON-NLS-1$ + p.setValue(TableEditor.OBJECT_PROPERTIES, // These are the names of the get/set methods + new String[]{SubstitutionElement.REGEX, SubstitutionElement.SUBSTITUTE}); + p.setValue(NOT_UNDEFINED,Boolean.TRUE); + p.setValue(DEFAULT,new ArrayList()); + p.setValue(MULTILINE,Boolean.TRUE); + + } + +} diff --git a/src/components/org/apache/jmeter/assertions/CompareAssertionResources.properties b/src/components/org/apache/jmeter/assertions/CompareAssertionResources.properties new file mode 100644 index 00000000000..54997571349 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/CompareAssertionResources.properties @@ -0,0 +1,24 @@ +# 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. + +displayName=Compare Assertion +compareChoices.displayName=Select Comparison Operators +compareContent.displayName=Compare Content +compareContent.shortDescription=Verify that all Samplers within the Controller return the same data +compareTime.displayName=Compare Time +compareTime.shortDescription=Verify that all Samplers' return times are within a given number of milliseconds +comparison_filters.displayName=Comparison Filters +stringsToSkip.displayName=Regular Expression Substitutions +stringsToSkip.shortDescription=Regular expressions to match elements of response data to be substituted when comparing diff --git a/src/components/org/apache/jmeter/assertions/CompareAssertionResources_fr.properties b/src/components/org/apache/jmeter/assertions/CompareAssertionResources_fr.properties new file mode 100644 index 00000000000..1c47ae9f641 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/CompareAssertionResources_fr.properties @@ -0,0 +1,24 @@ +# 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. + +compareChoices.displayName=Type de comparaison +compareContent.displayName=Contenu +compareContent.shortDescription=V\u00E9rifie que tous les \u00E9chantillons fils d'un contr\u00F4leur retournent les m\u00EAmes donn\u00E9es +compareTime.displayName=Temps de r\u00E9ponse (ms) +compareTime.shortDescription=V\u00E9rifie que tous les \u00E9chantillons fils d'un contr\u00F4leur sont retourn\u00E9s dans un nombre donn\u00E9 de millisecondes +comparison_filters.displayName=Filtres de comparaison +displayName=Assertion Comparaison +stringsToSkip.displayName=Substitutions par expressions r\u00E9guli\u00E8res +stringsToSkip.shortDescription=Expressions r\u00E9guli\u00E8res pour substituer des \u00E9l\u00E9ments dans les donn\u00E9es de r\u00E9ponses avant la comparaison diff --git a/src/components/org/apache/jmeter/assertions/DurationAssertion.java b/src/components/org/apache/jmeter/assertions/DurationAssertion.java new file mode 100644 index 00000000000..1bcd83e1d14 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/DurationAssertion.java @@ -0,0 +1,73 @@ +/* + * 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.assertions; + +import java.io.Serializable; +import java.text.MessageFormat; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Checks if an Sample is sampled within a specified time-frame. If the duration + * is larger than the timeframe the Assertion is considered a failure. + * + */ +public class DurationAssertion extends AbstractScopedAssertion implements Serializable, Assertion { + private static final long serialVersionUID = 240L; + + /** Key for storing assertion-informations in the jmx-file. */ + public static final String DURATION_KEY = "DurationAssertion.duration"; // $NON-NLS-1$ + + /** + * Returns the result of the Assertion. Here it checks wether the Sample + * took to long to be considered successful. If so an AssertionResult + * containing a FailureMessage will be returned. Otherwise the returned + * AssertionResult will reflect the success of the Sample. + */ + @Override + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + result.setFailure(false); + long duration=getAllowedDuration(); + if (duration > 0) { + long responseTime=response.getTime(); + // has the Sample lasted too long? + if ( responseTime > duration) { + result.setFailure(true); + Object[] arguments = { Long.valueOf(responseTime), Long.valueOf(duration) }; + String message = MessageFormat.format( + JMeterUtils.getResString("duration_assertion_failure") // $NON-NLS-1$ + , arguments); + result.setFailureMessage(message); + } + } + return result; + } + + /** + * Returns the duration to be asserted. A duration of 0 indicates this + * assertion is to be ignored. + */ + private long getAllowedDuration() { + return getPropertyAsLong(DURATION_KEY); + } + +} diff --git a/src/components/org/apache/jmeter/assertions/HTMLAssertion.java b/src/components/org/apache/jmeter/assertions/HTMLAssertion.java new file mode 100644 index 00000000000..8d79281eaf5 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/HTMLAssertion.java @@ -0,0 +1,385 @@ +/* + * 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.assertions; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import java.text.MessageFormat; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.tidy.Node; +import org.w3c.tidy.Tidy; + +/** + * Assertion to validate the response of a Sample with Tidy. + */ +public class HTMLAssertion extends AbstractTestElement implements Serializable, Assertion { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String DEFAULT_DOCTYPE = "omit"; //$NON-NLS-1$ + + public static final String DOCTYPE_KEY = "html_assertion_doctype"; //$NON-NLS-1$ + + public static final String ERRORS_ONLY_KEY = "html_assertion_errorsonly"; //$NON-NLS-1$ + + public static final String ERROR_THRESHOLD_KEY = "html_assertion_error_threshold"; //$NON-NLS-1$ + + public static final String WARNING_THRESHOLD_KEY = "html_assertion_warning_threshold"; //$NON-NLS-1$ + + public static final String FORMAT_KEY = "html_assertion_format"; //$NON-NLS-1$ + + public static final String FILENAME_KEY = "html_assertion_filename"; //$NON-NLS-1$ + + /** + * + */ + public HTMLAssertion() { + log.debug("HTMLAssertion(): called"); + } + + /** + * Returns the result of the Assertion. If so an AssertionResult containing + * a FailureMessage will be returned. Otherwise the returned AssertionResult + * will reflect the success of the Sample. + */ + @Override + public AssertionResult getResult(SampleResult inResponse) { + log.debug("HTMLAssertions.getResult() called"); + + // no error as default + AssertionResult result = new AssertionResult(getName()); + + if (inResponse.getResponseData().length == 0) { + return result.setResultForNull(); + } + + result.setFailure(false); + + // create parser + Tidy tidy = null; + try { + if (log.isDebugEnabled()){ + log.debug("HTMLAssertions.getResult(): Setup tidy ..."); + log.debug("doctype: " + getDoctype()); + log.debug("errors only: " + isErrorsOnly()); + log.debug("error threshold: " + getErrorThreshold()); + log.debug("warning threshold: " + getWarningThreshold()); + log.debug("html mode: " + isHTML()); + log.debug("xhtml mode: " + isXHTML()); + log.debug("xml mode: " + isXML()); + } + tidy = new Tidy(); + tidy.setInputEncoding("UTF8"); + tidy.setOutputEncoding("UTF8"); + tidy.setQuiet(false); + tidy.setShowWarnings(true); + tidy.setOnlyErrors(isErrorsOnly()); + tidy.setDocType(getDoctype()); + if (isXHTML()) { + tidy.setXHTML(true); + } else if (isXML()) { + tidy.setXmlTags(true); + } + tidy.setErrfile(getFilename()); + + if (log.isDebugEnabled()) { + log.debug("err file: " + getFilename()); + log.debug("getParser : tidy parser created - " + tidy); + log.debug("HTMLAssertions.getResult(): Tidy instance created!"); + } + + } catch (Exception e) {//TODO replace with proper Exception + log.error("Unable to instantiate tidy parser", e); + result.setFailure(true); + result.setFailureMessage("Unable to instantiate tidy parser"); + // return with an error + return result; + } + + /* + * Run tidy. + */ + try { + log.debug("HTMLAssertions.getResult(): start parsing with tidy ..."); + + StringWriter errbuf = new StringWriter(); + tidy.setErrout(new PrintWriter(errbuf)); + // Node node = tidy.parseDOM(new + // ByteArrayInputStream(response.getResponseData()), null); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + log.debug("Start : parse"); + Node node = tidy.parse(new ByteArrayInputStream(inResponse.getResponseData()), os); + if (log.isDebugEnabled()) { + log.debug("node : " + node); + log.debug("End : parse"); + log.debug("HTMLAssertions.getResult(): parsing with tidy done!"); + log.debug("Output: " + os.toString()); + } + + // write output to file + writeOutput(errbuf.toString()); + + // evaluate result + if ((tidy.getParseErrors() > getErrorThreshold()) + || (!isErrorsOnly() && (tidy.getParseWarnings() > getWarningThreshold()))) { + if (log.isDebugEnabled()) { + log.debug("HTMLAssertions.getResult(): errors/warnings detected:"); + log.debug(errbuf.toString()); + } + result.setFailure(true); + result.setFailureMessage(MessageFormat.format("Tidy Parser errors: " + tidy.getParseErrors() + + " (allowed " + getErrorThreshold() + ") " + "Tidy Parser warnings: " + + tidy.getParseWarnings() + " (allowed " + getWarningThreshold() + ")", new Object[0])); + // return with an error + + } else if ((tidy.getParseErrors() > 0) || (tidy.getParseWarnings() > 0)) { + // return with no error + log.debug("HTMLAssertions.getResult(): there were errors/warnings but threshold to high"); + result.setFailure(false); + } else { + // return with no error + log.debug("HTMLAssertions.getResult(): no errors/warnings detected:"); + result.setFailure(false); + } + + } catch (Exception e) {//TODO replace with proper Exception + // return with an error + log.warn("Cannot parse result content", e); + result.setFailure(true); + result.setFailureMessage(e.getMessage()); + } + return result; + } + + /** + * Writes the output of tidy to file. + * + * @param inOutput + */ + private void writeOutput(String inOutput) { + String lFilename = getFilename(); + + // check if filename defined + if ((lFilename != null) && (!"".equals(lFilename.trim()))) { + FileWriter lOutputWriter = null; + try { + + // open file + lOutputWriter = new FileWriter(lFilename, false); + + // write to file + lOutputWriter.write(inOutput); + + // flush + lOutputWriter.flush(); + + if (log.isDebugEnabled()) { + log.debug("writeOutput() -> output successfully written to file " + lFilename); + } + + } catch (IOException ex) { + log.warn("writeOutput() -> could not write output to file " + lFilename, ex); + } finally { + // close file + IOUtils.closeQuietly(lOutputWriter); + } + } + } + + /** + * Gets the doctype + * + * @return the documemt type + */ + public String getDoctype() { + return getPropertyAsString(DOCTYPE_KEY); + } + + /** + * Check if errors will be reported only + * + * @return boolean - report errors only? + */ + public boolean isErrorsOnly() { + return getPropertyAsBoolean(ERRORS_ONLY_KEY); + } + + /** + * Gets the threshold setting for errors + * + * @return long error threshold + */ + public long getErrorThreshold() { + return getPropertyAsLong(ERROR_THRESHOLD_KEY); + } + + /** + * Gets the threshold setting for warnings + * + * @return long warning threshold + */ + public long getWarningThreshold() { + return getPropertyAsLong(WARNING_THRESHOLD_KEY); + } + + /** + * Sets the doctype setting + * + * @param inDoctype + * The doctype to be set. If doctype is + * null or a blank string, {@link HTMLAssertion#DEFAULT_DOCTYPE} will be + * used + */ + public void setDoctype(String inDoctype) { + if ((inDoctype == null) || (inDoctype.trim().equals(""))) { + setProperty(new StringProperty(DOCTYPE_KEY, DEFAULT_DOCTYPE)); + } else { + setProperty(new StringProperty(DOCTYPE_KEY, inDoctype)); + } + } + + /** + * Sets if errors should be tracked only + * + * @param inErrorsOnly Flag whether only errors should be tracked + */ + public void setErrorsOnly(boolean inErrorsOnly) { + setProperty(new BooleanProperty(ERRORS_ONLY_KEY, inErrorsOnly)); + } + + /** + * Sets the threshold on error level + * + * @param inErrorThreshold + * The max number of parse errors which are to be tolerated + * @throws IllegalArgumentException + * if inErrorThreshold is less or equals zero + */ + public void setErrorThreshold(long inErrorThreshold) { + if (inErrorThreshold < 0L) { + throw new IllegalArgumentException(JMeterUtils.getResString("argument_must_not_be_negative")); //$NON-NLS-1$ + } + if (inErrorThreshold == Long.MAX_VALUE) { + setProperty(new LongProperty(ERROR_THRESHOLD_KEY, 0)); + } else { + setProperty(new LongProperty(ERROR_THRESHOLD_KEY, inErrorThreshold)); + } + } + + /** + * Sets the threshold on warning level + * + * @param inWarningThreshold + * The max number of warnings which are to be tolerated + * @throws IllegalArgumentException + * if inWarningThreshold is less or equal zero + */ + public void setWarningThreshold(long inWarningThreshold) { + if (inWarningThreshold < 0L) { + throw new IllegalArgumentException(JMeterUtils.getResString("argument_must_not_be_negative")); //$NON-NLS-1$ + } + if (inWarningThreshold == Long.MAX_VALUE) { + setProperty(new LongProperty(WARNING_THRESHOLD_KEY, 0)); + } else { + setProperty(new LongProperty(WARNING_THRESHOLD_KEY, inWarningThreshold)); + } + } + + /** + * Enables html validation mode + */ + public void setHTML() { + setProperty(new LongProperty(FORMAT_KEY, 0)); + } + + /** + * Check if html validation mode is set + * + * @return boolean + */ + public boolean isHTML() { + return getPropertyAsLong(FORMAT_KEY) == 0; + } + + /** + * Enables xhtml validation mode + */ + public void setXHTML() { + setProperty(new LongProperty(FORMAT_KEY, 1)); + } + + /** + * Check if xhtml validation mode is set + * + * @return boolean + */ + public boolean isXHTML() { + return getPropertyAsLong(FORMAT_KEY) == 1; + } + + /** + * Enables xml validation mode + */ + public void setXML() { + setProperty(new LongProperty(FORMAT_KEY, 2)); + } + + /** + * Check if xml validation mode is set + * + * @return boolean + */ + public boolean isXML() { + return getPropertyAsLong(FORMAT_KEY) == 2; + } + + /** + * Sets the name of the file where tidy writes the output to + * + * @return name of file + */ + public String getFilename() { + return getPropertyAsString(FILENAME_KEY); + } + + /** + * Sets the name of the tidy output file + * + * @param inName The name of the file tidy will put its output to + */ + public void setFilename(String inName) { + setProperty(FILENAME_KEY, inName); + } +} diff --git a/src/components/org/apache/jmeter/assertions/JSR223Assertion.java b/src/components/org/apache/jmeter/assertions/JSR223Assertion.java new file mode 100644 index 00000000000..2a270b8200c --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/JSR223Assertion.java @@ -0,0 +1,60 @@ +/* + * 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.assertions; + +import java.io.IOException; + +import javax.script.Bindings; +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223Assertion extends JSR223TestElement implements Cloneable, Assertion, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 234L; + + @Override + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + try { + ScriptEngine scriptEngine = getScriptEngine(); + Bindings bindings = scriptEngine.createBindings(); + bindings.put("SampleResult", response); + bindings.put("AssertionResult", result); + processFileOrScript(scriptEngine, bindings); + result.setError(false); + } catch (IOException e) { + log.error("Problem in JSR223 script "+getName(), e); + result.setError(true); + result.setFailureMessage(e.toString()); + } catch (ScriptException e) { + log.error("Problem in JSR223 script "+getName(), e); + result.setError(true); + result.setFailureMessage(e.toString()); + } + return result; + } +} diff --git a/src/components/org/apache/jmeter/assertions/JSR223AssertionBeanInfo.java b/src/components/org/apache/jmeter/assertions/JSR223AssertionBeanInfo.java new file mode 100644 index 00000000000..db81f93762a --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/JSR223AssertionBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.assertions; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223AssertionBeanInfo extends JSR223BeanInfoSupport { + + public JSR223AssertionBeanInfo() { + super(JSR223Assertion.class); + } + +} diff --git a/src/components/org/apache/jmeter/assertions/JSR223AssertionResources.properties b/src/components/org/apache/jmeter/assertions/JSR223AssertionResources.properties new file mode 100644 index 00000000000..c3c14677fcd --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/JSR223AssertionResources.properties @@ -0,0 +1,31 @@ +# 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. + +displayName=JSR223 Assertion +cacheKey.displayName=Compilation cache key +cacheKey.shortDescription=If Cache key is not empty, script will be compiled if JSR223 underlying script language supports it and CompiledScript will be cached, ensure script does not use any variable before making it cacheable +cacheKey_group.displayName=Script compilation caching +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/assertions/JSR223AssertionResources_fr.properties b/src/components/org/apache/jmeter/assertions/JSR223AssertionResources_fr.properties new file mode 100644 index 00000000000..b2f2a0be56c --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/JSR223AssertionResources_fr.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Assertion JSR223 +cacheKey.displayName=Clef de caching +cacheKey.shortDescription=Si la clef de caching n'est pas vide, le script sera compil\u00E9 si le language sous-jacent JSR223 fournit cette fonctionnalit\u00E9 et l'objet CompiledScript sera mis en cache, assurez vous avant d'utiliser ce caching que le script n'utilise pas de variables JMeter +cacheKey_group.displayName=Param\u00E8tres de caching du Script compil\u00E9 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props SampleResult (avant prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/assertions/MD5HexAssertion.java b/src/components/org/apache/jmeter/assertions/MD5HexAssertion.java new file mode 100644 index 00000000000..c9cceb566fb --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/MD5HexAssertion.java @@ -0,0 +1,112 @@ +/* + * 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. + * + */ + +/** + * MD5HexAssertion class creates an MD5 checksum from the response
+ * and matches it with the MD5 hex provided. + * The assertion will fail when the expected hex is different from the
+ * one calculated from the response OR when the expected hex is left empty. + * + */ +package org.apache.jmeter.assertions; + +import java.io.Serializable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.MessageFormat; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class MD5HexAssertion extends AbstractTestElement implements Serializable, Assertion { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** Key for storing assertion-informations in the jmx-file. */ + private static final String MD5HEX_KEY = "MD5HexAssertion.size"; + + /* + * @param response @return + */ + @Override + public AssertionResult getResult(SampleResult response) { + + AssertionResult result = new AssertionResult(getName()); + result.setFailure(false); + byte[] resultData = response.getResponseData(); + + if (resultData.length == 0) { + result.setError(false); + result.setFailure(true); + result.setFailureMessage("Response was null"); + return result; + } + + // no point in checking if we don't have anything to compare against + if (getAllowedMD5Hex().equals("")) { + result.setError(false); + result.setFailure(true); + result.setFailureMessage("MD5Hex to test against is empty"); + return result; + } + + String md5Result = baMD5Hex(resultData); + + // String md5Result = DigestUtils.md5Hex(resultData); + + if (!md5Result.equalsIgnoreCase(getAllowedMD5Hex())) { + result.setFailure(true); + + Object[] arguments = { md5Result, getAllowedMD5Hex() }; + String message = MessageFormat.format(JMeterUtils.getResString("md5hex_assertion_failure"), arguments); // $NON-NLS-1$ + result.setFailureMessage(message); + + } + + return result; + } + + public void setAllowedMD5Hex(String hex) { + setProperty(new StringProperty(MD5HexAssertion.MD5HEX_KEY, hex)); + } + + public String getAllowedMD5Hex() { + return getPropertyAsString(MD5HexAssertion.MD5HEX_KEY); + } + + // package protected so can be accessed by test class + static String baMD5Hex(byte ba[]) { + byte[] md5Result = {}; + + try { + MessageDigest md; + md = MessageDigest.getInstance("MD5"); + md5Result = md.digest(ba); + } catch (NoSuchAlgorithmException e) { + log.error("", e); + } + return JOrphanUtils.baToHexString(md5Result); + } +} diff --git a/src/components/org/apache/jmeter/assertions/ResponseAssertion.java b/src/components/org/apache/jmeter/assertions/ResponseAssertion.java new file mode 100644 index 00000000000..861b05b92d5 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/ResponseAssertion.java @@ -0,0 +1,548 @@ +/* + * 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.assertions; + +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.Document; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +// @see org.apache.jmeter.assertions.ResponseAssertionTest for unit tests + +/** + * Test element to handle Response Assertions, @see AssertionGui + */ +public class ResponseAssertion extends AbstractScopedAssertion implements Serializable, Assertion { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final String TEST_FIELD = "Assertion.test_field"; // $NON-NLS-1$ + + // Values for TEST_FIELD + // N.B. we cannot change the text value as it is in test plans + private static final String SAMPLE_URL = "Assertion.sample_label"; // $NON-NLS-1$ + + private static final String RESPONSE_DATA = "Assertion.response_data"; // $NON-NLS-1$ + + private static final String RESPONSE_DATA_AS_DOCUMENT = "Assertion.response_data_as_document"; // $NON-NLS-1$ + + private static final String RESPONSE_CODE = "Assertion.response_code"; // $NON-NLS-1$ + + private static final String RESPONSE_MESSAGE = "Assertion.response_message"; // $NON-NLS-1$ + + private static final String RESPONSE_HEADERS = "Assertion.response_headers"; // $NON-NLS-1$ + + private static final String ASSUME_SUCCESS = "Assertion.assume_success"; // $NON-NLS-1$ + + private static final String TEST_STRINGS = "Asserion.test_strings"; // $NON-NLS-1$ + + private static final String TEST_TYPE = "Assertion.test_type"; // $NON-NLS-1$ + + /* + * Mask values for TEST_TYPE TODO: remove either MATCH or CONTAINS - they + * are mutually exclusive + */ + private static final int MATCH = 1 << 0; + + private static final int CONTAINS = 1 << 1; + + private static final int NOT = 1 << 2; + + private static final int EQUALS = 1 << 3; + + private static final int SUBSTRING = 1 << 4; + + // Mask should contain all types (but not NOT) + private static final int TYPE_MASK = CONTAINS | EQUALS | MATCH | SUBSTRING; + + private static final int EQUALS_SECTION_DIFF_LEN + = JMeterUtils.getPropDefault("assertion.equals_section_diff_len", 100); + + /** Signifies truncated text in diff display. */ + private static final String EQUALS_DIFF_TRUNC = "..."; + + private static final String RECEIVED_STR = "****** received : "; + private static final String COMPARISON_STR = "****** comparison: "; + private static final String DIFF_DELTA_START + = JMeterUtils.getPropDefault("assertion.equals_diff_delta_start", "[[["); + private static final String DIFF_DELTA_END + = JMeterUtils.getPropDefault("assertion.equals_diff_delta_end", "]]]"); + + public ResponseAssertion() { + setProperty(new CollectionProperty(TEST_STRINGS, new ArrayList())); + } + + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(TEST_STRINGS, new ArrayList())); + } + + private void setTestField(String testField) { + setProperty(TEST_FIELD, testField); + } + + public void setTestFieldURL(){ + setTestField(SAMPLE_URL); + } + + public void setTestFieldResponseCode(){ + setTestField(RESPONSE_CODE); + } + + public void setTestFieldResponseData(){ + setTestField(RESPONSE_DATA); + } + + public void setTestFieldResponseDataAsDocument(){ + setTestField(RESPONSE_DATA_AS_DOCUMENT); + } + + public void setTestFieldResponseMessage(){ + setTestField(RESPONSE_MESSAGE); + } + + public void setTestFieldResponseHeaders(){ + setTestField(RESPONSE_HEADERS); + } + + public boolean isTestFieldURL(){ + return SAMPLE_URL.equals(getTestField()); + } + + public boolean isTestFieldResponseCode(){ + return RESPONSE_CODE.equals(getTestField()); + } + + public boolean isTestFieldResponseData(){ + return RESPONSE_DATA.equals(getTestField()); + } + + public boolean isTestFieldResponseDataAsDocument() { + return RESPONSE_DATA_AS_DOCUMENT.equals(getTestField()); + } + + public boolean isTestFieldResponseMessage(){ + return RESPONSE_MESSAGE.equals(getTestField()); + } + + public boolean isTestFieldResponseHeaders(){ + return RESPONSE_HEADERS.equals(getTestField()); + } + + private void setTestType(int testType) { + setProperty(new IntegerProperty(TEST_TYPE, testType)); + } + + private void setTestTypeMasked(int testType) { + int value = getTestType() & ~(TYPE_MASK) | testType; + setProperty(new IntegerProperty(TEST_TYPE, value)); + } + + public void addTestString(String testString) { + getTestStrings().addProperty(new StringProperty(String.valueOf(testString.hashCode()), testString)); + } + + public void clearTestStrings() { + getTestStrings().clear(); + } + + @Override + public AssertionResult getResult(SampleResult response) { + AssertionResult result; + + // None of the other Assertions check the response status, so remove + // this check + // for the time being, at least... + // if (!response.isSuccessful()) + // { + // result = new AssertionResult(); + // result.setError(true); + // byte [] ba = response.getResponseData(); + // result.setFailureMessage( + // ba == null ? "Unknown Error (responseData is empty)" : new String(ba) + // ); + // return result; + // } + + result = evaluateResponse(response); + return result; + } + + /*************************************************************************** + * !ToDoo (Method description) + * + * @return !ToDo (Return description) + **************************************************************************/ + public String getTestField() { + return getPropertyAsString(TEST_FIELD); + } + + /*************************************************************************** + * !ToDoo (Method description) + * + * @return !ToDo (Return description) + **************************************************************************/ + public int getTestType() { + JMeterProperty type = getProperty(TEST_TYPE); + if (type instanceof NullProperty) { + return CONTAINS; + } + return type.getIntValue(); + } + + /*************************************************************************** + * !ToDoo (Method description) + * + * @return !ToDo (Return description) + **************************************************************************/ + public CollectionProperty getTestStrings() { + return (CollectionProperty) getProperty(TEST_STRINGS); + } + + public boolean isEqualsType() { + return (getTestType() & EQUALS) != 0; + } + + public boolean isSubstringType() { + return (getTestType() & SUBSTRING) != 0; + } + + public boolean isContainsType() { + return (getTestType() & CONTAINS) != 0; + } + + public boolean isMatchType() { + return (getTestType() & MATCH) != 0; + } + + public boolean isNotType() { + return (getTestType() & NOT) != 0; + } + + public void setToContainsType() { + setTestTypeMasked(CONTAINS); + } + + public void setToMatchType() { + setTestTypeMasked(MATCH); + } + + public void setToEqualsType() { + setTestTypeMasked(EQUALS); + } + + public void setToSubstringType() { + setTestTypeMasked(SUBSTRING); + } + + public void setToNotType() { + setTestType((getTestType() | NOT)); + } + + public void unsetNotType() { + setTestType(getTestType() & ~NOT); + } + + public boolean getAssumeSuccess() { + return getPropertyAsBoolean(ASSUME_SUCCESS, false); + } + + public void setAssumeSuccess(boolean b) { + setProperty(ASSUME_SUCCESS, b); + } + + /** + * Make sure the response satisfies the specified assertion requirements. + * + * @param response + * an instance of SampleResult + * @return an instance of AssertionResult + */ + private AssertionResult evaluateResponse(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + String toCheck = ""; // The string to check (Url or data) + + if (getAssumeSuccess()) { + response.setSuccessful(true);// Allow testing of failure codes + } + + // What are we testing against? + if (isScopeVariable()){ + toCheck = getThreadContext().getVariables().get(getVariableName()); + } else if (isTestFieldResponseData()) { + toCheck = response.getResponseDataAsString(); // (bug25052) + } else if (isTestFieldResponseDataAsDocument()) { + toCheck = Document.getTextFromDocument(response.getResponseData()); + } else if (isTestFieldResponseCode()) { + toCheck = response.getResponseCode(); + } else if (isTestFieldResponseMessage()) { + toCheck = response.getResponseMessage(); + } else if (isTestFieldResponseHeaders()) { + toCheck = response.getResponseHeaders(); + } else { // Assume it is the URL + toCheck = ""; + final URL url = response.getURL(); + if (url != null){ + toCheck = url.toString(); + } + } + + result.setFailure(false); + result.setError(false); + + boolean notTest = (NOT & getTestType()) > 0; + boolean contains = isContainsType(); // do it once outside loop + boolean equals = isEqualsType(); + boolean substring = isSubstringType(); + boolean matches = isMatchType(); + boolean debugEnabled = log.isDebugEnabled(); + if (debugEnabled){ + log.debug("Type:" + (contains?"Contains" : "Match") + (notTest? "(not)" : "")); + } + + if (StringUtils.isEmpty(toCheck)) { + if (notTest) { // Not should always succeed against an empty result + return result; + } + if (debugEnabled){ + log.debug("Not checking empty response field in: "+response.getSampleLabel()); + } + return result.setResultForNull(); + } + + boolean pass = true; + try { + // Get the Matcher for this thread + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + PropertyIterator iter = getTestStrings().iterator(); + while (iter.hasNext()) { + String stringPattern = iter.next().getStringValue(); + Pattern pattern = null; + if (contains || matches) { + pattern = JMeterUtils.getPatternCache().getPattern(stringPattern, Perl5Compiler.READ_ONLY_MASK); + } + boolean found; + if (contains) { + found = localMatcher.contains(toCheck, pattern); + } else if (equals) { + found = toCheck.equals(stringPattern); + } else if (substring) { + found = toCheck.indexOf(stringPattern) != -1; + } else { + found = localMatcher.matches(toCheck, pattern); + } + pass = notTest ? !found : found; + if (!pass) { + if (debugEnabled){log.debug("Failed: "+stringPattern);} + result.setFailure(true); + result.setFailureMessage(getFailText(stringPattern,toCheck)); + break; + } + if (debugEnabled){log.debug("Passed: "+stringPattern);} + } + } catch (MalformedCachePatternException e) { + result.setError(true); + result.setFailure(false); + result.setFailureMessage("Bad test configuration " + e); + } + return result; + } + + /** + * Generate the failure reason from the TestType + * + * @param stringPattern + * @return the message for the assertion report + */ + // TODO strings should be resources + private String getFailText(String stringPattern, String toCheck) { + + StringBuilder sb = new StringBuilder(200); + sb.append("Test failed: "); + + if (isScopeVariable()){ + sb.append("variable(").append(getVariableName()).append(')'); + } else if (isTestFieldResponseData()) { + sb.append("text"); + } else if (isTestFieldResponseCode()) { + sb.append("code"); + } else if (isTestFieldResponseMessage()) { + sb.append("message"); + } else if (isTestFieldResponseHeaders()) { + sb.append("headers"); + } else if (isTestFieldResponseDataAsDocument()) { + sb.append("document"); + } else // Assume it is the URL + { + sb.append("URL"); + } + + switch (getTestType()) { + case CONTAINS: + case SUBSTRING: + sb.append(" expected to contain "); + break; + case NOT | CONTAINS: + case NOT | SUBSTRING: + sb.append(" expected not to contain "); + break; + case MATCH: + sb.append(" expected to match "); + break; + case NOT | MATCH: + sb.append(" expected not to match "); + break; + case EQUALS: + sb.append(" expected to equal "); + break; + case NOT | EQUALS: + sb.append(" expected not to equal "); + break; + default:// should never happen... + sb.append(" expected something using "); + } + + sb.append("/"); + + if (isEqualsType()){ + sb.append(equalsComparisonText(toCheck, stringPattern)); + } else { + sb.append(stringPattern); + } + + sb.append("/"); + + return sb.toString(); + } + + + private static String trunc(final boolean right, final String str) + { + if (str.length() <= EQUALS_SECTION_DIFF_LEN) { + return str; + } else if (right) { + return str.substring(0, EQUALS_SECTION_DIFF_LEN) + EQUALS_DIFF_TRUNC; + } else { + return EQUALS_DIFF_TRUNC + str.substring(str.length() - EQUALS_SECTION_DIFF_LEN, str.length()); + } + } + + /** + * Returns some helpful logging text to determine where equality between two strings + * is broken, with one pointer working from the front of the strings and another working + * backwards from the end. + * + * @param received String received from sampler. + * @param comparison String specified for "equals" response assertion. + * @return Two lines of text separated by newlines, and then forward and backward pointers + * denoting first position of difference. + */ + private static StringBuilder equalsComparisonText(final String received, final String comparison) + { + int firstDiff; + int lastRecDiff = -1; + int lastCompDiff = -1; + final int recLength = received.length(); + final int compLength = comparison.length(); + final int minLength = Math.min(recLength, compLength); + final String startingEqSeq; + String recDeltaSeq = ""; + String compDeltaSeq = ""; + String endingEqSeq = ""; + + final StringBuilder text = new StringBuilder(Math.max(recLength, compLength) * 2); + for (firstDiff = 0; firstDiff < minLength; firstDiff++) { + if (received.charAt(firstDiff) != comparison.charAt(firstDiff)){ + break; + } + } + if (firstDiff == 0) { + startingEqSeq = ""; + } else { + startingEqSeq = trunc(false, received.substring(0, firstDiff)); + } + + lastRecDiff = recLength - 1; + lastCompDiff = compLength - 1; + + while ((lastRecDiff > firstDiff) && (lastCompDiff > firstDiff) + && received.charAt(lastRecDiff) == comparison.charAt(lastCompDiff)) + { + lastRecDiff--; + lastCompDiff--; + } + endingEqSeq = trunc(true, received.substring(lastRecDiff + 1, recLength)); + if (endingEqSeq.length() == 0) + { + recDeltaSeq = trunc(true, received.substring(firstDiff, recLength)); + compDeltaSeq = trunc(true, comparison.substring(firstDiff, compLength)); + } + else + { + recDeltaSeq = trunc(true, received.substring(firstDiff, lastRecDiff + 1)); + compDeltaSeq = trunc(true, comparison.substring(firstDiff, lastCompDiff + 1)); + } + final StringBuilder pad = new StringBuilder(Math.abs(recDeltaSeq.length() - compDeltaSeq.length())); + for (int i = 0; i < pad.capacity(); i++){ + pad.append(' '); + } + if (recDeltaSeq.length() > compDeltaSeq.length()){ + compDeltaSeq += pad.toString(); + } else { + recDeltaSeq += pad.toString(); + } + + text.append("\n\n"); + text.append(RECEIVED_STR); + text.append(startingEqSeq); + text.append(DIFF_DELTA_START); + text.append(recDeltaSeq); + text.append(DIFF_DELTA_END); + text.append(endingEqSeq); + text.append("\n\n"); + text.append(COMPARISON_STR); + text.append(startingEqSeq); + text.append(DIFF_DELTA_START); + text.append(compDeltaSeq); + text.append(DIFF_DELTA_END); + text.append(endingEqSeq); + text.append("\n\n"); + return text; + } + +} diff --git a/src/components/org/apache/jmeter/assertions/SMIMEAssertion.java b/src/components/org/apache/jmeter/assertions/SMIMEAssertion.java new file mode 100644 index 00000000000..b0baca9b488 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/SMIMEAssertion.java @@ -0,0 +1,358 @@ +/* + * 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.assertions; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.Security; +import java.security.cert.CertStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; + +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.security.auth.x500.X500Principal; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.X509Name; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.SignerInformation; +import org.bouncycastle.cms.SignerInformationStore; +import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter; +import org.bouncycastle.jce.PrincipalUtil; +import org.bouncycastle.jce.X509Principal; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.mail.smime.SMIMEException; +import org.bouncycastle.mail.smime.SMIMESignedParser; +import org.bouncycastle.operator.bc.BcDigestCalculatorProvider; +import org.bouncycastle.x509.extension.X509ExtensionUtil; + +/** + * Helper class which isolates the BouncyCastle code. + */ +class SMIMEAssertion { + + // Use the name of the test element, otherwise cannot enable/disable debug from the GUI + private static final Logger log = LoggingManager.getLoggerForShortName(SMIMEAssertionTestElement.class.getName()); + + SMIMEAssertion() { + super(); + } + + public static AssertionResult getResult(SMIMEAssertionTestElement testElement, SampleResult response, String name) { + checkForBouncycastle(); + AssertionResult res = new AssertionResult(name); + try { + MimeMessage msg = null; + final int msgPos = testElement.getSpecificMessagePositionAsInt(); + if (msgPos < 0){ // means counting from end + SampleResult subResults[] = response.getSubResults(); + final int pos = subResults.length + msgPos; + log.debug("Getting message number: "+pos+" of "+subResults.length); + msg = getMessageFromResponse(response,pos); + } else { + log.debug("Getting message number: "+msgPos); + msg = getMessageFromResponse(response, msgPos); + } + + SMIMESignedParser s = null; + if (log.isDebugEnabled()) { + log.debug("Content-type: "+msg.getContentType()); + } + if (msg.isMimeType("multipart/signed")) { // $NON-NLS-1$ + MimeMultipart multipart = (MimeMultipart) msg.getContent(); + s = new SMIMESignedParser(new BcDigestCalculatorProvider(), multipart); + } else if (msg.isMimeType("application/pkcs7-mime") // $NON-NLS-1$ + || msg.isMimeType("application/x-pkcs7-mime")) { // $NON-NLS-1$ + s = new SMIMESignedParser(new BcDigestCalculatorProvider(), msg); + } + + if (null != s) { + log.debug("Found signature"); + + if (testElement.isNotSigned()) { + res.setFailure(true); + res.setFailureMessage("Mime message is signed"); + } else if (testElement.isVerifySignature() || !testElement.isSignerNoCheck()) { + res = verifySignature(testElement, s, name); + } + + } else { + log.debug("Did not find signature"); + if (!testElement.isNotSigned()) { + res.setFailure(true); + res.setFailureMessage("Mime message is not signed"); + } + } + + } catch (MessagingException e) { + String msg = "Cannot parse mime msg: " + e.getMessage(); + log.warn(msg, e); + res.setFailure(true); + res.setFailureMessage(msg); + } catch (CMSException e) { + res.setFailure(true); + res.setFailureMessage("Error reading the signature: " + + e.getMessage()); + } catch (SMIMEException e) { + res.setFailure(true); + res + .setFailureMessage("Cannot extract signed body part from signature: " + + e.getMessage()); + } catch (IOException e) { // should never happen + log.error("Cannot read mime message content: " + e.getMessage(), e); + res.setError(true); + res.setFailureMessage(e.getMessage()); + } + + return res; + } + + private static AssertionResult verifySignature(SMIMEAssertionTestElement testElement, SMIMESignedParser s, String name) + throws CMSException { + AssertionResult res = new AssertionResult(name); + + try { + CertStore certs = s.getCertificatesAndCRLs("Collection", "BC"); // $NON-NLS-1$ // $NON-NLS-2$ + SignerInformationStore signers = s.getSignerInfos(); + Iterator signerIt = signers.getSigners().iterator(); + + if (signerIt.hasNext()) { + + SignerInformation signer = (SignerInformation) signerIt.next(); + Iterator certIt = certs.getCertificates( + (new JcaX509CertSelectorConverter()).getCertSelector(signer.getSID())).iterator(); + + if (certIt.hasNext()) { + // the signer certificate + X509Certificate cert = (X509Certificate) certIt.next(); + + if (testElement.isVerifySignature()) { + + if (!signer.verify(cert.getPublicKey(), "BC")) { // $NON-NLS-1$ + res.setFailure(true); + res.setFailureMessage("Signature is invalid"); + } + } + + if (testElement.isSignerCheckConstraints()) { + StringBuilder failureMessage = new StringBuilder(); + + String serial = testElement.getSignerSerial(); + if (!JOrphanUtils.isBlank(serial)) { + BigInteger serialNbr = readSerialNumber(serial); + if (!serialNbr.equals(cert.getSerialNumber())) { + res.setFailure(true); + failureMessage + .append("Serial number ") + .append(serialNbr) + .append(" does not match serial from signer certificate: ") + .append(cert.getSerialNumber()).append("\n"); + } + } + + String email = testElement.getSignerEmail(); + if (!JOrphanUtils.isBlank(email)) { + List emailfromCert = getEmailFromCert(cert); + if (!emailfromCert.contains(email)) { + res.setFailure(true); + failureMessage + .append("Email address \"") + .append(email) + .append("\" not present in signer certificate\n"); + } + + } + + String subject = testElement.getSignerDn(); + if (subject.length() > 0) { + final X500Principal certPrincipal = cert.getSubjectX500Principal(); + log.debug(certPrincipal.getName(X500Principal.CANONICAL)); + X500Principal principal = new X500Principal(subject); + log.debug(principal.getName(X500Principal.CANONICAL)); + if (!principal.equals(certPrincipal)) { + res.setFailure(true); + failureMessage + .append("Distinguished name of signer certificate does not match \"") + .append(subject).append("\"\n"); + } + } + + String issuer = testElement.getIssuerDn(); + if (issuer.length() > 0) { + final X500Principal issuerX500Principal = cert.getIssuerX500Principal(); + log.debug(issuerX500Principal.getName(X500Principal.CANONICAL)); + X500Principal principal = new X500Principal(issuer); + log.debug(principal.getName(X500Principal.CANONICAL)); + if (!principal.equals(issuerX500Principal)) { + res.setFailure(true); + failureMessage + .append("Issuer distinguished name of signer certificate does not match \"") + .append(subject).append("\"\n"); + } + } + + if (failureMessage.length() > 0) { + res.setFailureMessage(failureMessage.toString()); + } + } + + if (testElement.isSignerCheckByFile()) { + CertificateFactory cf = CertificateFactory + .getInstance("X.509"); + X509Certificate certFromFile; + InputStream inStream = null; + try { + inStream = new BufferedInputStream(new FileInputStream(testElement.getSignerCertFile())); + certFromFile = (X509Certificate) cf.generateCertificate(inStream); + } finally { + IOUtils.closeQuietly(inStream); + } + + if (!certFromFile.equals(cert)) { + res.setFailure(true); + res.setFailureMessage("Signer certificate does not match certificate " + + testElement.getSignerCertFile()); + } + } + + } else { + res.setFailure(true); + res.setFailureMessage("No signer certificate found in signature"); + } + + } + + // TODO support multiple signers + if (signerIt.hasNext()) { + log.warn("SMIME message contains multiple signers! Checking multiple signers is not supported."); + } + + } catch (GeneralSecurityException e) { + log.error(e.getMessage(), e); + res.setError(true); + res.setFailureMessage(e.getMessage()); + } catch (FileNotFoundException e) { + res.setFailure(true); + res.setFailureMessage("certificate file not found: " + e.getMessage()); + } + + return res; + } + + /** + * extracts a MIME message from the SampleResult + */ + private static MimeMessage getMessageFromResponse(SampleResult response, + int messageNumber) throws MessagingException { + SampleResult subResults[] = response.getSubResults(); + + if (messageNumber >= subResults.length || messageNumber < 0) { + throw new MessagingException("Message number not present in results: "+messageNumber); + } + + final SampleResult sampleResult = subResults[messageNumber]; + if (log.isDebugEnabled()) { + log.debug("Bytes: "+sampleResult.getBytes()+" CT: "+sampleResult.getContentType()); + } + byte[] data = sampleResult.getResponseData(); + Session session = Session.getDefaultInstance(new Properties()); + MimeMessage msg = new MimeMessage(session, new ByteArrayInputStream(data)); + + log.debug("msg.getSize() = " + msg.getSize()); + return msg; + } + + /** + * Convert the value of serialString into a BigInteger. Strings + * starting with 0x or 0X are parsed as hex numbers, otherwise as decimal + * number. + * + * @param serialString + * the String representation of the serial Number + * @return + */ + private static BigInteger readSerialNumber(String serialString) { + if (serialString.startsWith("0x") || serialString.startsWith("0X")) { // $NON-NLS-1$ // $NON-NLS-2$ + return new BigInteger(serialString.substring(2), 16); + } + return new BigInteger(serialString); + } + + /** + * Extract email addresses from a certificate + * + * @param cert + * @return a List of all email addresses found + * @throws CertificateException + */ + private static List getEmailFromCert(X509Certificate cert) + throws CertificateException { + List res = new ArrayList(); + + X509Principal subject = PrincipalUtil.getSubjectX509Principal(cert); + Iterator addressIt = subject.getValues(X509Name.EmailAddress).iterator(); + while (addressIt.hasNext()) { + String address = (String) addressIt.next(); + res.add(address); + } + + Iterator subjectAltNamesIt = + X509ExtensionUtil.getSubjectAlternativeNames(cert).iterator(); + while (subjectAltNamesIt.hasNext()) { + List altName = (List) subjectAltNamesIt.next(); + int type = ((Integer) altName.get(0)).intValue(); + if (type == GeneralName.rfc822Name) { + String address = (String) altName.get(1); + res.add(address); + } + } + + return res; + } + + /** + * Check if the Bouncycastle jce provider is installed and dynamically load + * it, if needed; + * + */ + private static void checkForBouncycastle() { + if (null == Security.getProvider("BC")) { // $NON-NLS-1$ + Security.addProvider(new BouncyCastleProvider()); + } + } +} diff --git a/src/components/org/apache/jmeter/assertions/SMIMEAssertionTestElement.java b/src/components/org/apache/jmeter/assertions/SMIMEAssertionTestElement.java new file mode 100644 index 00000000000..9fbdd6f7adf --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/SMIMEAssertionTestElement.java @@ -0,0 +1,154 @@ +/* + * 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.assertions; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class SMIMEAssertionTestElement extends AbstractTestElement implements + Serializable, Assertion { + + private static final long serialVersionUID = 1L; + + //+JMX file attributes - do not change values! + private static final String VERIFY_SIGNATURE_KEY = "SMIMEAssert.verifySignature"; // $NON-NLS-1$ + private static final String NOT_SIGNED_KEY = "SMIMEAssert.notSigned"; // $NON-NLS-1$ + private static final String SIGNER_NO_CHECK_KEY = "SMIMEAssert.signerNoCheck"; // $NON-NLS-1$ + private static final String SIGNER_CHECK_BY_FILE_KEY = "SMIMEAssert.signerCheckByFile"; // $NON-NLS-1$ + private static final String SIGNER_CERT_FILE_KEY = "SMIMEAssert.signerCertFile"; // $NON-NLS-1$ + private static final String SINGER_CHECK_CONSTRAINTS_KEY = "SMIMEAssert.signerCheckConstraints"; // $NON-NLS-1$ + private static final String SIGNER_SERIAL_KEY = "SMIMEAssert.signerSerial"; // $NON-NLS-1$ + private static final String SIGNER_EMAIL_KEY = "SMIMEAssert.signerEmail"; // $NON-NLS-1$ + private static final String SIGNER_DN_KEY = "SMIMEAssert.signerDn"; // $NON-NLS-1$ + private static final String ISSUER_DN_KEY = "SMIMEAssert.issuerDn"; // $NON-NLS-1$ + private static final String MESSAGE_POSITION = "SMIMEAssert.messagePosition"; // $NON-NLS-1$ + //-JMX file attributes + + public SMIMEAssertionTestElement() { + super(); + } + + @Override + public AssertionResult getResult(SampleResult response) { + try { + return SMIMEAssertion.getResult(this, response, getName()); + } catch (NoClassDefFoundError e) { + AssertionResult assertionResult = new AssertionResult(getName()); + assertionResult.setError(true); + assertionResult.setResultForFailure(JMeterUtils + .getResString("bouncy_castle_unavailable_message")); //$NON-NLS-1$ + return assertionResult; + } + } + + public boolean isVerifySignature() { + return getPropertyAsBoolean(VERIFY_SIGNATURE_KEY); + } + + public void setVerifySignature(boolean verifySignature) { + setProperty(VERIFY_SIGNATURE_KEY, verifySignature); + } + + public String getIssuerDn() { + return getPropertyAsString(ISSUER_DN_KEY); + } + + public void setIssuerDn(String issuertDn) { + setProperty(ISSUER_DN_KEY, issuertDn); + } + + public boolean isSignerCheckByFile() { + return getPropertyAsBoolean(SIGNER_CHECK_BY_FILE_KEY); + } + + public void setSignerCheckByFile(boolean signerCheckByFile) { + setProperty(SIGNER_CHECK_BY_FILE_KEY, signerCheckByFile); + } + + public boolean isSignerCheckConstraints() { + return getPropertyAsBoolean(SINGER_CHECK_CONSTRAINTS_KEY); + } + + public void setSignerCheckConstraints(boolean signerCheckConstraints) { + setProperty(SINGER_CHECK_CONSTRAINTS_KEY, signerCheckConstraints); + } + + public boolean isSignerNoCheck() { + return getPropertyAsBoolean(SIGNER_NO_CHECK_KEY); + } + + public void setSignerNoCheck(boolean signerNoCheck) { + setProperty(SIGNER_NO_CHECK_KEY, signerNoCheck); + } + + public String getSignerCertFile() { + return getPropertyAsString(SIGNER_CERT_FILE_KEY); + } + + public void setSignerCertFile(String signerCertFile) { + setProperty(SIGNER_CERT_FILE_KEY, signerCertFile); + } + + public String getSignerDn() { + return getPropertyAsString(SIGNER_DN_KEY); + } + + public void setSignerDn(String signerDn) { + setProperty(SIGNER_DN_KEY, signerDn); + } + + public String getSignerSerial() { + return getPropertyAsString(SIGNER_SERIAL_KEY); + } + + public void setSignerSerial(String signerSerial) { + setProperty(SIGNER_SERIAL_KEY, signerSerial); + } + + public String getSignerEmail() { + return getPropertyAsString(SIGNER_EMAIL_KEY); + } + + public void setSignerEmail(String signerEmail) { + setProperty(SIGNER_EMAIL_KEY, signerEmail); + } + + public boolean isNotSigned() { + return getPropertyAsBoolean(NOT_SIGNED_KEY); + } + + public void setNotSigned(boolean notSigned) { + setProperty(NOT_SIGNED_KEY, notSigned); + } + + public String getSpecificMessagePosition() { + return getPropertyAsString(MESSAGE_POSITION); + } + + public int getSpecificMessagePositionAsInt() { + return getPropertyAsInt(MESSAGE_POSITION, 0); + } + + public void setSpecificMessagePosition(String position) { + setProperty(MESSAGE_POSITION, position); + } +} diff --git a/src/components/org/apache/jmeter/assertions/SizeAssertion.java b/src/components/org/apache/jmeter/assertions/SizeAssertion.java new file mode 100644 index 00000000000..d9442b4c42f --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/SizeAssertion.java @@ -0,0 +1,263 @@ +/* + * 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.assertions; + +import java.io.Serializable; +import java.text.MessageFormat; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.util.JMeterUtils; + +//@see org.apache.jmeter.assertions.SizeAssertionTest for unit tests + +/** + * Checks if the results of a Sample matches a particular size. + * + */ +public class SizeAssertion extends AbstractScopedAssertion implements Serializable, Assertion { + + private static final long serialVersionUID = 241L; + + // * Static int to signify the type of logical comparitor to assert + public static final int EQUAL = 1; + + public static final int NOTEQUAL = 2; + + public static final int GREATERTHAN = 3; + + public static final int LESSTHAN = 4; + + public static final int GREATERTHANEQUAL = 5; + + public static final int LESSTHANEQUAL = 6; + + /** Key for storing assertion-informations in the jmx-file. */ + private static final String SIZE_KEY = "SizeAssertion.size"; // $NON-NLS-1$ + + private static final String OPERATOR_KEY = "SizeAssertion.operator"; // $NON-NLS-1$ + + private static final String TEST_FIELD = "Assertion.test_field"; // $NON-NLS-1$ + + private static final String RESPONSE_NETWORK_SIZE = "SizeAssertion.response_network_size"; // $NON-NLS-1$ + + private static final String RESPONSE_HEADERS = "SizeAssertion.response_headers"; // $NON-NLS-1$ + + private static final String RESPONSE_BODY = "SizeAssertion.response_data"; // $NON-NLS-1$ + + private static final String RESPONSE_CODE = "SizeAssertion.response_code"; // $NON-NLS-1$ + + private static final String RESPONSE_MESSAGE = "SizeAssertion.response_message"; // $NON-NLS-1$ + + /** + * Returns the result of the Assertion. + * Here it checks the Sample responseData length. + */ + @Override + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + result.setFailure(false); + long resultSize=0; + if (isScopeVariable()){ + String variableName = getVariableName(); + String value = getThreadContext().getVariables().get(variableName); + try { + resultSize = Integer.parseInt(value); + } catch (NumberFormatException e) { + result.setFailure(true); + result.setFailureMessage("Error parsing variable name: "+variableName+" value: "+value); + return result; + } + } else if (isTestFieldResponseHeaders()) { + resultSize = response.getHeadersSize(); + } else if (isTestFieldResponseBody()) { + resultSize = response.getBodySize(); + } else if (isTestFieldResponseCode()) { + resultSize = response.getResponseCode().length(); + } else if (isTestFieldResponseMessage()) { + resultSize = response.getResponseMessage().length(); + } else { + resultSize = response.getBytes(); + } + // is the Sample the correct size? + final String msg = compareSize(resultSize); + if (msg.length() > 0) { + result.setFailure(true); + Object[] arguments = { Long.valueOf(resultSize), msg, Long.valueOf(getAllowedSize()) }; + String message = MessageFormat.format(JMeterUtils.getResString("size_assertion_failure"), arguments); //$NON-NLS-1$ + result.setFailureMessage(message); + } + return result; + } + + /** + * Returns the size in bytes to be asserted. + * @return The allowed size + */ + public String getAllowedSize() { + return getPropertyAsString(SIZE_KEY); + } + + /** + Set the operator used for the assertion. Has to be one of +
+ *
EQUAL
1
+ *
NOTEQUAL
2
+ *
GREATERTHAN
3
+ *
LESSTHAN
4
+ *
GREATERTHANEQUAL
5
+ *
LESSTHANEQUAL
6
+ *
+ * @param operator The operator to be used in the assertion + */ + public void setCompOper(int operator) { + setProperty(new IntegerProperty(OPERATOR_KEY, operator)); + + } + + /** + * Returns the operator to be asserted. + *
+ *
EQUAL
1
+ *
NOTEQUAL
2
+ *
GREATERTHAN
3
+ *
LESSTHAN
4
+ *
GREATERTHANEQUAL
5
+ *
LESSTHANEQUAL
6
+ *
+ * @return The operator used for the assertion + */ + + public int getCompOper() { + return getPropertyAsInt(OPERATOR_KEY); + } + + /** + * Set the size that shall be asserted. + * + * @param size a number of bytes. + */ + public void setAllowedSize(String size) { + setProperty(SIZE_KEY, size); + } + + /** + * Set the size that should be used in the assertion + * @param size The number of bytes + */ + public void setAllowedSize(long size) { + setProperty(SIZE_KEY, Long.toString(size)); + } + + /** + * Compares the the size of a return result to the set allowed size using a + * logical comparator set in setLogicalComparator(). + * + * Possible values are: equal, not equal, greater than, less than, greater + * than equal, less than equal. + * + */ + private String compareSize(long resultSize) { + String comparatorErrorMessage; + long allowedSize = Long.parseLong(getAllowedSize()); + boolean result = false; + int comp = getCompOper(); + switch (comp) { + case EQUAL: + result = (resultSize == allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_equal"); //$NON-NLS-1$ + break; + case NOTEQUAL: + result = (resultSize != allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_notequal"); //$NON-NLS-1$ + break; + case GREATERTHAN: + result = (resultSize > allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_greater"); //$NON-NLS-1$ + break; + case LESSTHAN: + result = (resultSize < allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_less"); //$NON-NLS-1$ + break; + case GREATERTHANEQUAL: + result = (resultSize >= allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_greaterequal"); //$NON-NLS-1$ + break; + case LESSTHANEQUAL: + result = (resultSize <= allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_lessequal"); //$NON-NLS-1$ + break; + default: + result = false; + comparatorErrorMessage = "ERROR - invalid condition"; + break; + } + return result ? "" : comparatorErrorMessage; + } + + private void setTestField(String testField) { + setProperty(TEST_FIELD, testField); + } + + public void setTestFieldNetworkSize(){ + setTestField(RESPONSE_NETWORK_SIZE); + } + + public void setTestFieldResponseHeaders(){ + setTestField(RESPONSE_HEADERS); + } + + public void setTestFieldResponseBody(){ + setTestField(RESPONSE_BODY); + } + + public void setTestFieldResponseCode(){ + setTestField(RESPONSE_CODE); + } + + public void setTestFieldResponseMessage(){ + setTestField(RESPONSE_MESSAGE); + } + + public String getTestField() { + return getPropertyAsString(TEST_FIELD); + } + + public boolean isTestFieldNetworkSize(){ + return RESPONSE_NETWORK_SIZE.equals(getTestField()); + } + + public boolean isTestFieldResponseHeaders(){ + return RESPONSE_HEADERS.equals(getTestField()); + } + + public boolean isTestFieldResponseBody(){ + return RESPONSE_BODY.equals(getTestField()); + } + + public boolean isTestFieldResponseCode(){ + return RESPONSE_CODE.equals(getTestField()); + } + + public boolean isTestFieldResponseMessage(){ + return RESPONSE_MESSAGE.equals(getTestField()); + } + +} diff --git a/src/components/org/apache/jmeter/assertions/SubstitutionElement.java b/src/components/org/apache/jmeter/assertions/SubstitutionElement.java new file mode 100644 index 00000000000..39de415229e --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/SubstitutionElement.java @@ -0,0 +1,55 @@ +/* + * 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.assertions; + +import org.apache.jmeter.testelement.AbstractTestElement; + +public class SubstitutionElement extends AbstractTestElement { + private static final long serialVersionUID = 1; + + // These constants are used both for the JMX file and for the setters/getters + public static final String REGEX = "regex"; // $NON-NLS-1$ + + public static final String SUBSTITUTE = "substitute"; // $NON-NLS-1$ + + public SubstitutionElement() { + super(); + } + + public String getRegex() + { + return getProperty(REGEX).getStringValue(); + } + + public void setRegex(String regex) + { + setProperty(REGEX,regex); + } + + public String getSubstitute() + { + return getProperty(SUBSTITUTE).getStringValue(); + } + + public void setSubstitute(String sub) + { + setProperty(SUBSTITUTE,sub); + } + +} diff --git a/src/components/org/apache/jmeter/assertions/XMLAssertion.java b/src/components/org/apache/jmeter/assertions/XMLAssertion.java new file mode 100644 index 00000000000..96ca4683f63 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/XMLAssertion.java @@ -0,0 +1,90 @@ +/* + * 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.assertions; + +import java.io.IOException; +import java.io.Serializable; +import java.io.StringReader; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; + +/** + * Checks if the result is a well-formed XML content using jdom + * + */ +public class XMLAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + // one builder for all requests in a thread + private static final ThreadLocal myBuilder = new ThreadLocal() { + @Override + protected SAXBuilder initialValue() { + return new SAXBuilder(); + } + }; + + /** + * Returns the result of the Assertion. Here it checks wether the Sample + * took to long to be considered successful. If so an AssertionResult + * containing a FailureMessage will be returned. Otherwise the returned + * AssertionResult will reflect the success of the Sample. + */ + @Override + public AssertionResult getResult(SampleResult response) { + // no error as default + AssertionResult result = new AssertionResult(getName()); + String resultData = response.getResponseDataAsString(); + if (resultData.length() == 0) { + return result.setResultForNull(); + } + result.setFailure(false); + SAXBuilder builder = myBuilder.get(); + + try { + builder.build(new StringReader(resultData)); + } catch (JDOMException e) { + log.debug("Cannot parse result content", e); // may well happen + result.setFailure(true); + result.setFailureMessage(e.getMessage()); + } catch (IOException e) { + log.error("Cannot read result content", e); // should never happen + result.setError(true); + result.setFailureMessage(e.getMessage()); + } + + return result; + } + + @Override + public void threadStarted() { + } + + @Override + public void threadFinished() { + myBuilder.set(null); + } +} diff --git a/src/components/org/apache/jmeter/assertions/XMLSchemaAssertion.java b/src/components/org/apache/jmeter/assertions/XMLSchemaAssertion.java new file mode 100644 index 00000000000..85d6a82fb90 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/XMLSchemaAssertion.java @@ -0,0 +1,214 @@ +/* + * 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.assertions; + +import java.io.IOException; +import java.io.Serializable; +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +// See Bug 34383 + +/** + * XMLSchemaAssertion.java Validate response against an XML Schema author + * Dave Maung + * + */ +public class XMLSchemaAssertion extends AbstractTestElement implements Serializable, Assertion { + + private static final long serialVersionUID = 233L; + + public static final String FILE_NAME_IS_REQUIRED = "FileName is required"; + + public static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; + + public static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; + + public static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource"; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String XSD_FILENAME_KEY = "xmlschema_assertion_filename"; + + /** + * getResult + * + */ + @Override + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + // Note: initialised with error = failure = false + + String resultData = response.getResponseDataAsString(); + if (resultData.length() == 0) { + return result.setResultForNull(); + } + + String xsdFileName = getXsdFileName(); + if (log.isDebugEnabled()) { + log.debug("xmlString: " + resultData); + log.debug("xsdFileName: " + xsdFileName); + } + if (xsdFileName == null || xsdFileName.length() == 0) { + result.setResultForFailure(FILE_NAME_IS_REQUIRED); + } else { + setSchemaResult(result, resultData, xsdFileName); + } + return result; + } + + public void setXsdFileName(String xmlSchemaFileName) throws IllegalArgumentException { + setProperty(XSD_FILENAME_KEY, xmlSchemaFileName); + } + + public String getXsdFileName() { + return getPropertyAsString(XSD_FILENAME_KEY); + } + + /** + * set Schema result + * + * @param result + * @param xmlStr + * @param xsdFileName + */ + private void setSchemaResult(AssertionResult result, String xmlStr, String xsdFileName) { + try { + // boolean toReturn = true; + + // Document doc = null; + DocumentBuilderFactory parserFactory = DocumentBuilderFactory.newInstance(); + parserFactory.setValidating(true); + parserFactory.setNamespaceAware(true); + parserFactory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA); + parserFactory.setAttribute(JAXP_SCHEMA_SOURCE, xsdFileName); + + // create a parser: + DocumentBuilder parser = parserFactory.newDocumentBuilder(); + parser.setErrorHandler(new SAXErrorHandler(result)); + + // doc = + parser.parse(new InputSource(new StringReader(xmlStr))); + // if everything went fine then xml schema validation is valid + } catch (SAXParseException e) { + + // Only set message if error not yet flagged + if (!result.isError() && !result.isFailure()) { + result.setError(true); + result.setFailureMessage(errorDetails(e)); + } + + } catch (SAXException e) { + + log.warn(e.toString()); + result.setResultForFailure(e.getMessage()); + + } catch (IOException e) { + + log.warn("IO error", e); + result.setResultForFailure(e.getMessage()); + + } catch (ParserConfigurationException e) { + + log.warn("Problem with Parser Config", e); + result.setResultForFailure(e.getMessage()); + + } + + } + + // Helper method to construct SAX error details + private static String errorDetails(SAXParseException spe) { + StringBuilder str = new StringBuilder(80); + int i; + i = spe.getLineNumber(); + if (i != -1) { + str.append("line="); + str.append(i); + str.append(" col="); + str.append(spe.getColumnNumber()); + str.append(" "); + } + str.append(spe.getLocalizedMessage()); + return str.toString(); + } + + /** + * SAXErrorHandler class + */ + private static class SAXErrorHandler implements ErrorHandler { + private final AssertionResult result; + + public SAXErrorHandler(AssertionResult result) { + this.result = result; + } + + /* + * Can be caused by: - failure to read XSD file - xml does not match XSD + */ + @Override + public void error(SAXParseException exception) throws SAXParseException { + + String msg = "error: " + errorDetails(exception); + log.debug(msg); + result.setFailureMessage(msg); + result.setError(true); + throw exception; + } + + /* + * Can be caused by: - premature end of file - non-whitespace content + * after trailer + */ + @Override + public void fatalError(SAXParseException exception) throws SAXParseException { + + String msg = "fatal: " + errorDetails(exception); + log.debug(msg); + result.setFailureMessage(msg); + result.setError(true); + throw exception; + } + + /* + * Not clear what can cause this ? conflicting versions perhaps + */ + @Override + public void warning(SAXParseException exception) throws SAXParseException { + + String msg = "warning: " + errorDetails(exception); + log.debug(msg); + result.setFailureMessage(msg); + // result.setError(true); // TODO is this the correct strategy? + // throw exception; // allow assertion to pass + + } + } +} diff --git a/src/components/org/apache/jmeter/assertions/XPathAssertion.java b/src/components/org/apache/jmeter/assertions/XPathAssertion.java new file mode 100644 index 00000000000..c06400eaaf3 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/XPathAssertion.java @@ -0,0 +1,274 @@ +/* + * 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.assertions; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.Serializable; + +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.TidyException; +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Checks if the result is a well-formed XML content and whether it matches an + * XPath + * + */ +public class XPathAssertion extends AbstractScopedAssertion implements Serializable, Assertion { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + //+ JMX file attributes + private static final String XPATH_KEY = "XPath.xpath"; // $NON-NLS-1$ + private static final String WHITESPACE_KEY = "XPath.whitespace"; // $NON-NLS-1$ + private static final String VALIDATE_KEY = "XPath.validate"; // $NON-NLS-1$ + private static final String TOLERANT_KEY = "XPath.tolerant"; // $NON-NLS-1$ + private static final String NEGATE_KEY = "XPath.negate"; // $NON-NLS-1$ + private static final String NAMESPACE_KEY = "XPath.namespace"; // $NON-NLS-1$ + private static final String QUIET_KEY = "XPath.quiet"; // $NON-NLS-1$ + private static final String REPORT_ERRORS_KEY = "XPath.report_errors"; // $NON-NLS-1$ + private static final String SHOW_WARNINGS_KEY = "XPath.show_warnings"; // $NON-NLS-1$ + private static final String DOWNLOAD_DTDS = "XPath.download_dtds"; // $NON-NLS-1$ + //- JMX file attributes + + public static final String DEFAULT_XPATH = "/"; + + /** + * Returns the result of the Assertion. Checks if the result is well-formed + * XML, and that the XPath expression is matched (or not, as the case may + * be) + */ + @Override + public AssertionResult getResult(SampleResult response) { + // no error as default + AssertionResult result = new AssertionResult(getName()); + result.setFailure(false); + result.setFailureMessage(""); + + byte[] responseData = null; + Document doc = null; + + try { + if (isScopeVariable()){ + String inputString=getThreadContext().getVariables().get(getVariableName()); + if(!StringUtils.isEmpty(inputString)) { + responseData = inputString.getBytes("UTF-8"); + } + } else { + responseData = response.getResponseData(); + } + + if (responseData == null || responseData.length == 0) { + return result.setResultForNull(); + } + + if (log.isDebugEnabled()) { + log.debug(new StringBuilder("Validation is set to ").append(isValidating()).toString()); + log.debug(new StringBuilder("Whitespace is set to ").append(isWhitespace()).toString()); + log.debug(new StringBuilder("Tolerant is set to ").append(isTolerant()).toString()); + } + + + boolean isXML = JOrphanUtils.isXML(responseData); + + doc = XPathUtil.makeDocument(new ByteArrayInputStream(responseData), isValidating(), + isWhitespace(), isNamespace(), isTolerant(), isQuiet(), showWarnings() , reportErrors(), isXML + , isDownloadDTDs()); + } catch (SAXException e) { + log.debug("Caught sax exception: " + e); + result.setError(true); + result.setFailureMessage(new StringBuilder("SAXException: ").append(e.getMessage()).toString()); + return result; + } catch (IOException e) { + log.warn("Cannot parse result content", e); + result.setError(true); + result.setFailureMessage(new StringBuilder("IOException: ").append(e.getMessage()).toString()); + return result; + } catch (ParserConfigurationException e) { + log.warn("Cannot parse result content", e); + result.setError(true); + result.setFailureMessage(new StringBuilder("ParserConfigurationException: ").append(e.getMessage()) + .toString()); + return result; + } catch (TidyException e) { + result.setError(true); + result.setFailureMessage(e.getMessage()); + return result; + } + + if (doc == null || doc.getDocumentElement() == null) { + result.setError(true); + result.setFailureMessage("Document is null, probably not parsable"); + return result; + } + XPathUtil.computeAssertionResult(result, doc, getXPathString(), isNegated()); + return result; + } + + /** + * Get The XPath String that will be used in matching the document + * + * @return String xpath String + */ + public String getXPathString() { + return getPropertyAsString(XPATH_KEY, DEFAULT_XPATH); + } + + /** + * Set the XPath String this will be used as an xpath + * + * @param xpath + * String + */ + public void setXPathString(String xpath) { + setProperty(new StringProperty(XPATH_KEY, xpath)); + } + + /** + * Set whether to ignore element whitespace + * + * @param whitespace Flag whether whitespace elements should be ignored + */ + public void setWhitespace(boolean whitespace) { + setProperty(new BooleanProperty(WHITESPACE_KEY, whitespace)); + } + + /** + * Set use validation + * + * @param validate Flag whether validation should be used + */ + public void setValidating(boolean validate) { + setProperty(new BooleanProperty(VALIDATE_KEY, validate)); + } + + /** + * Set whether this is namespace aware + * + * @param namespace Flag whether namespace should be used + */ + public void setNamespace(boolean namespace) { + setProperty(new BooleanProperty(NAMESPACE_KEY, namespace)); + } + + /** + * Set tolerant mode if required + * + * @param tolerant + * true/false + */ + public void setTolerant(boolean tolerant) { + setProperty(new BooleanProperty(TOLERANT_KEY, tolerant)); + } + + public void setNegated(boolean negate) { + setProperty(new BooleanProperty(NEGATE_KEY, negate)); + } + + /** + * Is this whitepsace ignored. + * + * @return boolean + */ + public boolean isWhitespace() { + return getPropertyAsBoolean(WHITESPACE_KEY, false); + } + + /** + * Is this validating + * + * @return boolean + */ + public boolean isValidating() { + return getPropertyAsBoolean(VALIDATE_KEY, false); + } + + /** + * Is this namespace aware? + * + * @return boolean + */ + public boolean isNamespace() { + return getPropertyAsBoolean(NAMESPACE_KEY, false); + } + + /** + * Is this using tolerant mode? + * + * @return boolean + */ + public boolean isTolerant() { + return getPropertyAsBoolean(TOLERANT_KEY, false); + } + + /** + * Negate the XPath test, that is return true if something is not found. + * + * @return boolean negated + */ + public boolean isNegated() { + return getPropertyAsBoolean(NEGATE_KEY, false); + } + + public void setReportErrors(boolean val) { + setProperty(REPORT_ERRORS_KEY, val, false); + } + + public boolean reportErrors() { + return getPropertyAsBoolean(REPORT_ERRORS_KEY, false); + } + + public void setShowWarnings(boolean val) { + setProperty(SHOW_WARNINGS_KEY, val, false); + } + + public boolean showWarnings() { + return getPropertyAsBoolean(SHOW_WARNINGS_KEY, false); + } + + public void setQuiet(boolean val) { + setProperty(QUIET_KEY, val, true); + } + + public boolean isQuiet() { + return getPropertyAsBoolean(QUIET_KEY, true); + } + + public void setDownloadDTDs(boolean val) { + setProperty(DOWNLOAD_DTDS, val, false); + } + + public boolean isDownloadDTDs() { + return getPropertyAsBoolean(DOWNLOAD_DTDS, false); + } + +} diff --git a/src/components/org/apache/jmeter/assertions/gui/AssertionGui.java b/src/components/org/apache/jmeter/assertions/gui/AssertionGui.java new file mode 100644 index 00000000000..56865dc92d1 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/AssertionGui.java @@ -0,0 +1,435 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTable; + +import org.apache.jmeter.assertions.ResponseAssertion; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.gui.util.TextAreaCellRenderer; +import org.apache.jmeter.gui.util.TextAreaTableCellEditor; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; + +/** + * GUI interface for a {@link ResponseAssertion}. + * + */ +public class AssertionGui extends AbstractAssertionGui { + private static final long serialVersionUID = 240L; + + /** The name of the table column in the list of patterns. */ + private static final String COL_RESOURCE_NAME = "assertion_patterns_to_test"; //$NON-NLS-1$ + + /** Radio button indicating that the text response should be tested. */ + private JRadioButton responseStringButton; + + /** Radio button indicating that the text of a document should be tested. */ + private JRadioButton responseAsDocumentButton; + + /** Radio button indicating that the URL should be tested. */ + private JRadioButton urlButton; + + /** Radio button indicating that the responseMessage should be tested. */ + private JRadioButton responseMessageButton; + + /** Radio button indicating that the responseCode should be tested. */ + private JRadioButton responseCodeButton; + + /** Radio button indicating that the headers should be tested. */ + private JRadioButton responseHeadersButton; + + /** + * Checkbox to indicate whether the response should be forced successful + * before testing. This is intended for use when checking the status code or + * status message. + */ + private JCheckBox assumeSuccess; + + /** + * Radio button indicating to test if the field contains one of the + * patterns. + */ + private JRadioButton containsBox; + + /** + * Radio button indicating to test if the field matches one of the patterns. + */ + private JRadioButton matchesBox; + + /** + * Radio button indicating if the field equals the string. + */ + private JRadioButton equalsBox; + + /** + * Radio button indicating if the field contains the string. + */ + private JRadioButton substringBox; + + /** + * Checkbox indicating to test that the field does NOT contain/match the + * patterns. + */ + private JCheckBox notBox; + + /** A table of patterns to test against. */ + private JTable stringTable; + + /** Button to delete a pattern. */ + private JButton deletePattern; + + /** Table model for the pattern table. */ + private PowerTableModel tableModel; + + /** + * Create a new AssertionGui panel. + */ + public AssertionGui() { + init(); + } + + @Override + public String getLabelResource() { + return "assertion_title"; // $NON-NLS-1$ + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + ResponseAssertion el = new ResponseAssertion(); + modifyTestElement(el); + return el; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement el) { + GuiUtils.stopTableEditing(stringTable); + configureTestElement(el); + if (el instanceof ResponseAssertion) { + ResponseAssertion ra = (ResponseAssertion) el; + + saveScopeSettings(ra); + + ra.clearTestStrings(); + String[] testStrings = tableModel.getData().getColumn(COL_RESOURCE_NAME); + for (String testString : testStrings) { + ra.addTestString(testString); + } + + if (responseStringButton.isSelected()) { + ra.setTestFieldResponseData(); + } else if (responseAsDocumentButton.isSelected()) { + ra.setTestFieldResponseDataAsDocument(); + } else if (responseCodeButton.isSelected()) { + ra.setTestFieldResponseCode(); + } else if (responseMessageButton.isSelected()) { + ra.setTestFieldResponseMessage(); + } else if (responseHeadersButton.isSelected()) { + ra.setTestFieldResponseHeaders(); + } else { // Assume URL + ra.setTestFieldURL(); + } + + ra.setAssumeSuccess(assumeSuccess.isSelected()); + + if (containsBox.isSelected()) { + ra.setToContainsType(); + } else if (equalsBox.isSelected()) { + ra.setToEqualsType(); + } else if (substringBox.isSelected()) { + ra.setToSubstringType(); + } else { + ra.setToMatchType(); + } + + if (notBox.isSelected()) { + ra.setToNotType(); + } else { + ra.unsetNotType(); + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + GuiUtils.stopTableEditing(stringTable); + tableModel.clearData(); + + responseStringButton.setSelected(true); + urlButton.setSelected(false); + responseCodeButton.setSelected(false); + responseMessageButton.setSelected(false); + responseHeadersButton.setSelected(false); + assumeSuccess.setSelected(false); + + substringBox.setSelected(true); + notBox.setSelected(false); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + ResponseAssertion model = (ResponseAssertion) el; + + showScopeSettings(model, true); + + if (model.isContainsType()) { + containsBox.setSelected(true); + } else if (model.isEqualsType()) { + equalsBox.setSelected(true); + } else if (model.isSubstringType()) { + substringBox.setSelected(true); + } else { + matchesBox.setSelected(true); + } + + notBox.setSelected(model.isNotType()); + + if (model.isTestFieldResponseData()) { + responseStringButton.setSelected(true); + } else if (model.isTestFieldResponseDataAsDocument()) { + responseAsDocumentButton.setSelected(true); + } else if (model.isTestFieldResponseCode()) { + responseCodeButton.setSelected(true); + } else if (model.isTestFieldResponseMessage()) { + responseMessageButton.setSelected(true); + } else if (model.isTestFieldResponseHeaders()) { + responseHeadersButton.setSelected(true); + } else // Assume it is the URL + { + urlButton.setSelected(true); + } + + assumeSuccess.setSelected(model.getAssumeSuccess()); + + tableModel.clearData(); + PropertyIterator tests = model.getTestStrings().iterator(); + while (tests.hasNext()) { + tableModel.addRow(new Object[] { tests.next().getStringValue() }); + } + + if (model.getTestStrings().size() == 0) { + deletePattern.setEnabled(false); + } else { + deletePattern.setEnabled(true); + } + + tableModel.fireTableDataChanged(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout()); + Box box = Box.createVerticalBox(); + setBorder(makeBorder()); + + box.add(makeTitlePanel()); + box.add(createScopePanel(true)); + box.add(createFieldPanel()); + box.add(createTypePanel()); + add(box, BorderLayout.NORTH); + add(createStringPanel(), BorderLayout.CENTER); + } + + /** + * Create a panel allowing the user to choose which response field should be + * tested. + * + * @return a new panel for selecting the response field + */ + private JPanel createFieldPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("assertion_resp_field"))); //$NON-NLS-1$ + + responseStringButton = new JRadioButton(JMeterUtils.getResString("assertion_text_resp")); //$NON-NLS-1$ + responseAsDocumentButton = new JRadioButton(JMeterUtils.getResString("assertion_text_document")); //$NON-NLS-1$ + urlButton = new JRadioButton(JMeterUtils.getResString("assertion_url_samp")); //$NON-NLS-1$ + responseCodeButton = new JRadioButton(JMeterUtils.getResString("assertion_code_resp")); //$NON-NLS-1$ + responseMessageButton = new JRadioButton(JMeterUtils.getResString("assertion_message_resp")); //$NON-NLS-1$ + responseHeadersButton = new JRadioButton(JMeterUtils.getResString("assertion_headers")); //$NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + group.add(responseStringButton); + group.add(responseAsDocumentButton); + group.add(urlButton); + group.add(responseCodeButton); + group.add(responseMessageButton); + group.add(responseHeadersButton); + + panel.add(responseStringButton); + panel.add(responseAsDocumentButton); + panel.add(urlButton); + panel.add(responseCodeButton); + panel.add(responseMessageButton); + panel.add(responseHeadersButton); + + responseStringButton.setSelected(true); + + assumeSuccess = new JCheckBox(JMeterUtils.getResString("assertion_assume_success")); //$NON-NLS-1$ + panel.add(assumeSuccess); + + return panel; + } + + /** + * Create a panel allowing the user to choose what type of test should be + * performed. + * + * @return a new panel for selecting the type of assertion test + */ + private JPanel createTypePanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("assertion_pattern_match_rules"))); //$NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + + containsBox = new JRadioButton(JMeterUtils.getResString("assertion_contains")); //$NON-NLS-1$ + group.add(containsBox); + containsBox.setSelected(true); + panel.add(containsBox); + + matchesBox = new JRadioButton(JMeterUtils.getResString("assertion_matches")); //$NON-NLS-1$ + group.add(matchesBox); + panel.add(matchesBox); + + equalsBox = new JRadioButton(JMeterUtils.getResString("assertion_equals")); //$NON-NLS-1$ + group.add(equalsBox); + panel.add(equalsBox); + + substringBox = new JRadioButton(JMeterUtils.getResString("assertion_substring")); //$NON-NLS-1$ + group.add(substringBox); + panel.add(substringBox); + + notBox = new JCheckBox(JMeterUtils.getResString("assertion_not")); //$NON-NLS-1$ + panel.add(notBox); + + return panel; + } + + /** + * Create a panel allowing the user to supply a list of string patterns to + * test against. + * + * @return a new panel for adding string patterns + */ + private JPanel createStringPanel() { + tableModel = new PowerTableModel(new String[] { COL_RESOURCE_NAME }, new Class[] { String.class }); + stringTable = new JTable(tableModel); + stringTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + + TextAreaCellRenderer renderer = new TextAreaCellRenderer(); + stringTable.setRowHeight(renderer.getPreferredHeight()); + stringTable.setDefaultRenderer(String.class, renderer); + stringTable.setDefaultEditor(String.class, new TextAreaTableCellEditor()); + stringTable.setPreferredScrollableViewportSize(new Dimension(100, 70)); + + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("assertion_patterns_to_test"))); //$NON-NLS-1$ + + panel.add(new JScrollPane(stringTable), BorderLayout.CENTER); + panel.add(createButtonPanel(), BorderLayout.SOUTH); + + return panel; + } + + /** + * Create a panel with buttons to add and delete string patterns. + * + * @return the new panel with add and delete buttons + */ + private JPanel createButtonPanel() { + JButton addPattern = new JButton(JMeterUtils.getResString("add")); //$NON-NLS-1$ + addPattern.addActionListener(new AddPatternListener()); + + deletePattern = new JButton(JMeterUtils.getResString("delete")); //$NON-NLS-1$ + deletePattern.addActionListener(new ClearPatternsListener()); + deletePattern.setEnabled(false); + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addPattern); + buttonPanel.add(deletePattern); + return buttonPanel; + } + + /** + * An ActionListener for deleting a pattern. + * + */ + private class ClearPatternsListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + int index = stringTable.getSelectedRow(); + if (index > -1) { + stringTable.getCellEditor(index, stringTable.getSelectedColumn()).cancelCellEditing(); + tableModel.removeRow(index); + tableModel.fireTableDataChanged(); + } + if (stringTable.getModel().getRowCount() == 0) { + deletePattern.setEnabled(false); + } + } + } + + /** + * An ActionListener for adding a pattern. + * + */ + private class AddPatternListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + tableModel.addNewRow(); + deletePattern.setEnabled(true); + tableModel.fireTableDataChanged(); + } + } +} diff --git a/src/components/org/apache/jmeter/assertions/gui/BeanShellAssertionGui.java b/src/components/org/apache/jmeter/assertions/gui/BeanShellAssertionGui.java new file mode 100644 index 00000000000..c00b6651437 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/BeanShellAssertionGui.java @@ -0,0 +1,170 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.BeanShellAssertion; +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.util.JMeterUtils; + +public class BeanShellAssertionGui extends AbstractAssertionGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox resetInterpreter;// reset the bsh.Interpreter before each execution + + private JTextField filename;// script file name (if present) + + private JTextField parameters;// parameters to pass to script file (or script) + + private JSyntaxTextArea scriptField; // script area + + public BeanShellAssertionGui() { + init(); + } + + @Override + public void configure(TestElement element) { + scriptField.setInitialText(element.getPropertyAsString(BeanShellAssertion.SCRIPT)); + scriptField.setCaretPosition(0); + filename.setText(element.getPropertyAsString(BeanShellAssertion.FILENAME)); + parameters.setText(element.getPropertyAsString(BeanShellAssertion.PARAMETERS)); + resetInterpreter.setSelected(element.getPropertyAsBoolean(BeanShellAssertion.RESET_INTERPRETER)); + super.configure(element); + } + + @Override + public TestElement createTestElement() { + BeanShellAssertion sampler = new BeanShellAssertion(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement te) { + te.clear(); + this.configureTestElement(te); + te.setProperty(BeanShellAssertion.SCRIPT, scriptField.getText()); + te.setProperty(BeanShellAssertion.FILENAME, filename.getText()); + te.setProperty(BeanShellAssertion.PARAMETERS, parameters.getText()); + te.setProperty(new BooleanProperty(BeanShellAssertion.RESET_INTERPRETER, resetInterpreter.isSelected())); + } + + @Override + public String getLabelResource() { + return "bsh_assertion_title"; // $NON-NLS-1$ + } + + private JPanel createFilenamePanel()// TODO ought to be a FileChooser ... + { + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script_file")); //$NON-NLS-1$ + + filename = new JTextField(10); + filename.setName(BeanShellAssertion.FILENAME); + label.setLabelFor(filename); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(filename, BorderLayout.CENTER); + return filenamePanel; + } + + private JPanel createResetPanel() { + resetInterpreter = new JCheckBox(JMeterUtils.getResString("bsh_script_reset_interpreter")); // $NON-NLS-1$ + resetInterpreter.setName(BeanShellAssertion.PARAMETERS); + + JPanel resetInterpreterPanel = new JPanel(new BorderLayout()); + resetInterpreterPanel.add(resetInterpreter, BorderLayout.WEST); + return resetInterpreterPanel; + } + + private JPanel createParameterPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script_parameters")); //$NON-NLS-1$ + + parameters = new JTextField(10); + parameters.setName(BeanShellAssertion.PARAMETERS); + label.setLabelFor(parameters); + + JPanel parameterPanel = new JPanel(new BorderLayout(5, 0)); + parameterPanel.add(label, BorderLayout.WEST); + parameterPanel.add(parameters, BorderLayout.CENTER); + return parameterPanel; + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createResetPanel()); + box.add(createParameterPanel()); + box.add(createFilenamePanel()); + add(box, BorderLayout.NORTH); + + JPanel panel = createScriptPanel(); + add(panel, BorderLayout.CENTER); + // Don't let the input field shrink too much + add(Box.createVerticalStrut(panel.getPreferredSize().height), BorderLayout.WEST); + } + + private JPanel createScriptPanel() { + scriptField = new JSyntaxTextArea(20,20); + + JLabel label = new JLabel(JMeterUtils.getResString("bsh_assertion_script")); //$NON-NLS-1$ + label.setLabelFor(scriptField); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(label, BorderLayout.NORTH); + panel.add(new JTextScrollPane(scriptField), BorderLayout.CENTER); + + JTextArea explain = new JTextArea(JMeterUtils.getResString("bsh_assertion_script_variables")); //$NON-NLS-1$ + explain.setLineWrap(true); + explain.setEditable(false); + explain.setBackground(this.getBackground()); + panel.add(explain, BorderLayout.SOUTH); + + return panel; + } + + @Override + public void clearGui() { + super.clearGui(); + filename.setText(""); // $NON-NLS-1$ + parameters.setText(""); // $NON-NLS-1$ + scriptField.setInitialText(""); // $NON-NLS-1$ + resetInterpreter.setSelected(false); + } +} diff --git a/src/components/org/apache/jmeter/assertions/gui/DurationAssertionGui.java b/src/components/org/apache/jmeter/assertions/gui/DurationAssertionGui.java new file mode 100644 index 00000000000..1ae64d0cad7 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/DurationAssertionGui.java @@ -0,0 +1,124 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.DurationAssertion; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * GUI for {@link DurationAssertion} + */ +public class DurationAssertionGui extends AbstractAssertionGui { + + private static final long serialVersionUID = 240L; + + private JTextField duration; + + public DurationAssertionGui() { + init(); + } + + @Override + public String getLabelResource() { + return "duration_assertion_title"; // $NON-NLS-1$ + } + + public String getDurationAttributesTitle() { + return JMeterUtils.getResString("duration_assertion_duration_test"); // $NON-NLS-1$ + } + + @Override + public TestElement createTestElement() { + DurationAssertion el = new DurationAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement el) { + configureTestElement(el); + if (el instanceof DurationAssertion) { + DurationAssertion assertion = (DurationAssertion) el; + assertion.setProperty(DurationAssertion.DURATION_KEY,duration.getText()); + saveScopeSettings(assertion); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + duration.setText(""); //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof DurationAssertion){ + DurationAssertion da = (DurationAssertion) el; + duration.setText(da.getPropertyAsString(DurationAssertion.DURATION_KEY)); + showScopeSettings(da); + } + } + + private void init() { + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new VerticalPanel(); + mainPanel.add(createScopePanel()); + + // USER_INPUT + VerticalPanel durationPanel = new VerticalPanel(); + durationPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + getDurationAttributesTitle())); + + JPanel labelPanel = new JPanel(new BorderLayout(5, 0)); + JLabel durationLabel = + new JLabel(JMeterUtils.getResString("duration_assertion_label")); // $NON-NLS-1$ + labelPanel.add(durationLabel, BorderLayout.WEST); + + duration = new JTextField(); + labelPanel.add(duration, BorderLayout.CENTER); + durationLabel.setLabelFor(duration); + durationPanel.add(labelPanel); + + mainPanel.add(durationPanel); + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/src/components/org/apache/jmeter/assertions/gui/HTMLAssertionGui.java b/src/components/org/apache/jmeter/assertions/gui/HTMLAssertionGui.java new file mode 100644 index 00000000000..4242ffcd3bd --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/HTMLAssertionGui.java @@ -0,0 +1,373 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.HTMLAssertion; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * GUI for HTMLAssertion + */ +public class HTMLAssertionGui extends AbstractAssertionGui implements KeyListener, ActionListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 1L; + + // Names for the fields + private static final String WARNING_THRESHOLD_FIELD = "warningThresholdField"; // $NON-NLS-1$ + + private static final String ERROR_THRESHOLD_FIELD = "errorThresholdField"; // $NON-NLS-1$ + + // instance attributes + private JTextField errorThresholdField = null; + + private JTextField warningThresholdField = null; + + private JCheckBox errorsOnly = null; + + private JComboBox docTypeBox = null; + + private JRadioButton htmlRadioButton = null; + + private JRadioButton xhtmlRadioButton = null; + + private JRadioButton xmlRadioButton = null; + + private FilePanel filePanel = null; + + /** + * The constructor. + */ + public HTMLAssertionGui() { + init(); + } + + /** + * Returns the label to be shown within the JTree-Component. + */ + @Override + public String getLabelResource() { + return "html_assertion_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + HTMLAssertion el = new HTMLAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement inElement) { + + log.debug("HTMLAssertionGui.modifyTestElement() called"); + + configureTestElement(inElement); + + String errorThresholdString = errorThresholdField.getText(); + long errorThreshold = 0; + + try { + errorThreshold = Long.parseLong(errorThresholdString); + } catch (NumberFormatException e) { + errorThreshold = 0; + } + ((HTMLAssertion) inElement).setErrorThreshold(errorThreshold); + + String warningThresholdString = warningThresholdField.getText(); + long warningThreshold = 0; + try { + warningThreshold = Long.parseLong(warningThresholdString); + } catch (NumberFormatException e) { + warningThreshold = 0; + } + ((HTMLAssertion) inElement).setWarningThreshold(warningThreshold); + + String docTypeString = docTypeBox.getSelectedItem().toString(); + ((HTMLAssertion) inElement).setDoctype(docTypeString); + + boolean trackErrorsOnly = errorsOnly.isSelected(); + ((HTMLAssertion) inElement).setErrorsOnly(trackErrorsOnly); + + if (htmlRadioButton.isSelected()) { + ((HTMLAssertion) inElement).setHTML(); + } else if (xhtmlRadioButton.isSelected()) { + ((HTMLAssertion) inElement).setXHTML(); + } else { + ((HTMLAssertion) inElement).setXML(); + } + ((HTMLAssertion) inElement).setFilename(filePanel.getFilename()); + } + + /** + * Implements JMeterGUIComponent.clearGui + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + + docTypeBox.setSelectedIndex(0); + htmlRadioButton.setSelected(true); + xhtmlRadioButton.setSelected(false); + xmlRadioButton.setSelected(false); + errorThresholdField.setText("0"); //$NON-NLS-1$ + warningThresholdField.setText("0"); //$NON-NLS-1$ + filePanel.setFilename(""); //$NON-NLS-1$ + errorsOnly.setSelected(false); + } + + /** + * Configures the associated test element. + * {@inheritDoc} + */ + @Override + public void configure(TestElement inElement) { + super.configure(inElement); + HTMLAssertion lAssertion = (HTMLAssertion) inElement; + errorThresholdField.setText(String.valueOf(lAssertion.getErrorThreshold())); + warningThresholdField.setText(String.valueOf(lAssertion.getWarningThreshold())); + errorsOnly.setSelected(lAssertion.isErrorsOnly()); + docTypeBox.setSelectedItem(lAssertion.getDoctype()); + if (lAssertion.isHTML()) { + htmlRadioButton.setSelected(true); + } else if (lAssertion.isXHTML()) { + xhtmlRadioButton.setSelected(true); + } else { + xmlRadioButton.setSelected(true); + } + if (lAssertion.isErrorsOnly()) { + warningThresholdField.setEnabled(false); + warningThresholdField.setEditable(false); + } + else { + warningThresholdField.setEnabled(true); + warningThresholdField.setEditable(true); + } + filePanel.setFilename(lAssertion.getFilename()); + } + + /** + * Inits the GUI. + */ + private void init() { + + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + // USER_INPUT + VerticalPanel assertionPanel = new VerticalPanel(); + assertionPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Tidy Settings")); + + // doctype + HorizontalPanel docTypePanel = new HorizontalPanel(); + docTypeBox = new JComboBox(new String[] { "omit", "auto", "strict", "loose" }); + // docTypePanel.add(new + // JLabel(JMeterUtils.getResString("duration_assertion_label"))); //$NON-NLS-1$ + docTypePanel.add(new JLabel("Doctype:")); + docTypePanel.add(docTypeBox); + assertionPanel.add(docTypePanel); + + // format (HMTL, XHTML, XML) + VerticalPanel formatPanel = new VerticalPanel(); + formatPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Format")); + htmlRadioButton = new JRadioButton("HTML", true); //$NON-NLS-1$ + xhtmlRadioButton = new JRadioButton("XHTML", false); //$NON-NLS-1$ + xmlRadioButton = new JRadioButton("XML", false); //$NON-NLS-1$ + ButtonGroup buttonGroup = new ButtonGroup(); + buttonGroup.add(htmlRadioButton); + buttonGroup.add(xhtmlRadioButton); + buttonGroup.add(xmlRadioButton); + formatPanel.add(htmlRadioButton); + formatPanel.add(xhtmlRadioButton); + formatPanel.add(xmlRadioButton); + assertionPanel.add(formatPanel); + + // errors only + errorsOnly = new JCheckBox("Errors only", false); + errorsOnly.addActionListener(this); + assertionPanel.add(errorsOnly); + + // thresholds + HorizontalPanel thresholdPanel = new HorizontalPanel(); + thresholdPanel.add(new JLabel("Error threshold:")); + errorThresholdField = new JTextField("0", 5); // $NON-NLS-1$ + errorThresholdField.setName(ERROR_THRESHOLD_FIELD); + errorThresholdField.addKeyListener(this); + thresholdPanel.add(errorThresholdField); + thresholdPanel.add(new JLabel("Warning threshold:")); + warningThresholdField = new JTextField("0", 5); // $NON-NLS-1$ + warningThresholdField.setName(WARNING_THRESHOLD_FIELD); + warningThresholdField.addKeyListener(this); + thresholdPanel.add(warningThresholdField); + assertionPanel.add(thresholdPanel); + + // file panel + filePanel = new FilePanel(JMeterUtils.getResString("html_assertion_file"), ".txt"); //$NON-NLS-1$ //$NON-NLS-2$ + assertionPanel.add(filePanel); + + mainPanel.add(assertionPanel, BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } + + /** + * This method is called if one of the threshold field looses the focus + * + * @param inEvent The {@link FocusEvent} with detailed information about the focus loss + */ + public void focusLost(FocusEvent inEvent) { + log.debug("HTMLAssertionGui.focusLost() called"); + + String errorThresholdString = errorThresholdField.getText(); + if (errorThresholdString != null) { + boolean isInvalid = false; + try { + long errorThreshold = Long.parseLong(errorThresholdString); + if (errorThreshold < 0) { + isInvalid = true; + } + } catch (NumberFormatException ex) { + isInvalid = true; + } + if (isInvalid) { + log.warn("HTMLAssertionGui: Error threshold Not a valid number!"); + JOptionPane.showMessageDialog(null, "Threshold for errors is invalid", "Error", + JOptionPane.ERROR_MESSAGE); + } + } + + String warningThresholdString = warningThresholdField.getText(); + if (warningThresholdString != null) { + boolean isInvalid = false; + try { + long warningThreshold = Long.parseLong(warningThresholdString); + if (warningThreshold < 0) { + isInvalid = true; + } + } catch (NumberFormatException ex) { + isInvalid = true; + } + if (isInvalid) { + log.warn("HTMLAssertionGui: Error threshold Not a valid number!"); + JOptionPane.showMessageDialog(null, "Threshold for warnings is invalid", "Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + + /** + * Method gets called when one of the threshold fields gains focus + * + * @param e The {@link FocusEvent} with detailed information about the focus gain + * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) + */ + public void focusGained(FocusEvent e) { + // NOOP + } + + /** + * This method is called from erros-only checkbox + * + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + @Override + public void actionPerformed(ActionEvent e) { + if (errorsOnly.isSelected()) { + warningThresholdField.setEnabled(false); + warningThresholdField.setEditable(false); + } else { + warningThresholdField.setEnabled(true); + warningThresholdField.setEditable(true); + } + } + + @Override + public void keyPressed(KeyEvent e) { + // NOOP + } + + @Override + public void keyReleased(KeyEvent e) { + String fieldName = e.getComponent().getName(); + + if (fieldName.equals(WARNING_THRESHOLD_FIELD)) { + validateInteger(warningThresholdField); + } + + if (fieldName.equals(ERROR_THRESHOLD_FIELD)) { + validateInteger(errorThresholdField); + } + } + + private void validateInteger(JTextField field){ + try { + Integer.parseInt(field.getText()); + } catch (NumberFormatException nfe) { + int length = field.getText().length(); + if (length > 0) { + JOptionPane.showMessageDialog(this, "Only digits allowed", "Invalid data", + JOptionPane.WARNING_MESSAGE); + // Drop the last character: + field.setText(field.getText().substring(0, length-1)); + } + } + + } + @Override + public void keyTyped(KeyEvent e) { + // NOOP + } + +} diff --git a/src/components/org/apache/jmeter/assertions/gui/MD5HexAssertionGUI.java b/src/components/org/apache/jmeter/assertions/gui/MD5HexAssertionGUI.java new file mode 100644 index 00000000000..1ba60450bee --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/MD5HexAssertionGUI.java @@ -0,0 +1,119 @@ +/* + * 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. + * + */ + +/** + * GUI class supporting the MD5Hex assertion functionality. + * + */ +package org.apache.jmeter.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.MD5HexAssertion; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class MD5HexAssertionGUI extends AbstractAssertionGui { + + private static final long serialVersionUID = 240L; + + private JTextField md5HexInput; + + public MD5HexAssertionGUI() { + init(); + } + + private void init() { + + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + // USER_INPUT + HorizontalPanel md5HexPanel = new HorizontalPanel(); + md5HexPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("md5hex_assertion_md5hex_test"))); // $NON-NLS-1$ + + md5HexPanel.add(new JLabel(JMeterUtils.getResString("md5hex_assertion_label"))); //$NON-NLS-1$ + + md5HexInput = new JTextField(25); + // md5HexInput.addFocusListener(this); + md5HexPanel.add(md5HexInput); + + mainPanel.add(md5HexPanel, BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + + } + + @Override + public void configure(TestElement el) { + super.configure(el); + MD5HexAssertion assertion = (MD5HexAssertion) el; + this.md5HexInput.setText(String.valueOf(assertion.getAllowedMD5Hex())); + } + + @Override + public String getLabelResource() { + return "md5hex_assertion_title"; // $NON-NLS-1$ + } + + /* + * @return + */ + @Override + public TestElement createTestElement() { + + MD5HexAssertion el = new MD5HexAssertion(); + modifyTestElement(el); + return el; + + } + + /* + * @param element + */ + @Override + public void modifyTestElement(TestElement element) { + configureTestElement(element); + String md5HexString = this.md5HexInput.getText(); + // initialize to empty string, this will fail the assertion + if (md5HexString == null || md5HexString.length() == 0) { + md5HexString = ""; + } + ((MD5HexAssertion) element).setAllowedMD5Hex(md5HexString); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + md5HexInput.setText(""); //$NON-NLS-1$ + } +} diff --git a/src/components/org/apache/jmeter/assertions/gui/SMIMEAssertionGui.java b/src/components/org/apache/jmeter/assertions/gui/SMIMEAssertionGui.java new file mode 100644 index 00000000000..fa8468ca820 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/SMIMEAssertionGui.java @@ -0,0 +1,250 @@ + /* + * 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.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.assertions.SMIMEAssertionTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + + public class SMIMEAssertionGui extends AbstractAssertionGui { + + private static final long serialVersionUID = 1L; + + private final JCheckBox verifySignature = + new JCheckBox(JMeterUtils.getResString("smime_assertion_verify_signature")); // $NON-NLS-1$ + + private final JCheckBox notSigned = + new JCheckBox(JMeterUtils.getResString("smime_assertion_not_signed")); // $NON-NLS-1$ + + private final JRadioButton signerNoCheck = + new JRadioButton(JMeterUtils.getResString("smime_assertion_signer_no_check")); // $NON-NLS-1$ + + private final JRadioButton signerCheckConstraints = + new JRadioButton(JMeterUtils.getResString("smime_assertion_signer_constraints")); // $NON-NLS-1$ + + private final JRadioButton signerCheckByFile = + new JRadioButton(JMeterUtils.getResString("smime_assertion_signer_by_file")); // $NON-NLS-1$ + + private final JTextField signerDnField = new JTextField(50); + + private final JTextField signerSerialNumberField = new JTextField(25); + + private final JTextField signerEmailField = new JTextField(25); + + private final JTextField issuerDnField = new JTextField(50); + + private final JTextField signerCertFile = new JTextField(25); + + private final JTextField messagePositionTf = new JTextField(25); + + public SMIMEAssertionGui() { + init(); + } + + @Override + public String getLabelResource() { + return "smime_assertion_title"; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + issuerDnField.setText(""); + messagePositionTf.setText(""); + notSigned.setSelected(false); + signerCertFile.setText(""); + signerCheckByFile.setSelected(false); + signerCheckConstraints.setSelected(false); + signerDnField.setText(""); + signerEmailField.setText(""); + signerNoCheck.setSelected(false); + signerSerialNumberField.setText(""); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createSignaturePanel()); + box.add(createSignerPanel()); + box.add(createMessagePositionPanel()); + add(box, BorderLayout.NORTH); + } + + private JPanel createSignaturePanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils + .getResString("smime_assertion_signature"))); // $NON-NLS-1$ + notSigned.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + verifySignature.setEnabled(!notSigned.isSelected()); + } + }); + + panel.add(verifySignature); + panel.add(notSigned); + + return panel; + } + + private JPanel createSignerPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils + .getResString("smime_assertion_signer"))); // $NON-NLS-1$ + + panel.setLayout(new VerticalLayout(5, VerticalLayout.LEFT)); + + ButtonGroup buttonGroup = new ButtonGroup(); + buttonGroup.add(signerNoCheck); + buttonGroup.add(signerCheckConstraints); + buttonGroup.add(signerCheckByFile); + + panel.add(signerNoCheck); + + panel.add(signerCheckConstraints); + signerCheckConstraints.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + signerDnField.setEnabled(signerCheckConstraints.isSelected()); + signerSerialNumberField.setEnabled(signerCheckConstraints.isSelected()); + signerEmailField.setEnabled(signerCheckConstraints.isSelected()); + issuerDnField.setEnabled(signerCheckConstraints.isSelected()); + } + }); + Box box = Box.createHorizontalBox(); + box.add(new JLabel(JMeterUtils.getResString("smime_assertion_signer_dn"))); // $NON-NLS-1$ + box.add(Box.createHorizontalStrut(5)); + box.add(signerDnField); + panel.add(box); + + box = Box.createHorizontalBox(); + box.add(new JLabel(JMeterUtils.getResString("smime_assertion_signer_email"))); // $NON-NLS-1$ + box.add(Box.createHorizontalStrut(5)); + box.add(signerEmailField); + panel.add(box); + + box = Box.createHorizontalBox(); + box.add(new JLabel(JMeterUtils.getResString("smime_assertion_issuer_dn"))); // $NON-NLS-1$ + box.add(Box.createHorizontalStrut(5)); + box.add(issuerDnField); + panel.add(box); + + box = Box.createHorizontalBox(); + box.add(new JLabel(JMeterUtils.getResString("smime_assertion_signer_serial"))); // $NON-NLS-1$ + box.add(Box.createHorizontalStrut(5)); + box.add(signerSerialNumberField); + panel.add(box); + + // panel.add(signerCheckByFile); + signerCheckByFile.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + signerCertFile.setEnabled(signerCheckByFile.isSelected()); + } + }); + box = Box.createHorizontalBox(); + box.add(signerCheckByFile); + box.add(Box.createHorizontalStrut(5)); + box.add(signerCertFile); + panel.add(box); + + return panel; + } + + private JPanel createMessagePositionPanel(){ + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils + .getResString("smime_assertion_message_position"))); // $NON-NLS-1$ + panel.add(messagePositionTf); + return panel; + } + @Override + public void configure(TestElement el) { + super.configure(el); + SMIMEAssertionTestElement smimeAssertion = (SMIMEAssertionTestElement) el; + verifySignature.setSelected(smimeAssertion.isVerifySignature()); + notSigned.setSelected(smimeAssertion.isNotSigned()); + + if (smimeAssertion.isSignerNoCheck()) { + signerNoCheck.setSelected(true); + } + if (smimeAssertion.isSignerCheckConstraints()) { + signerCheckConstraints.setSelected(true); + } + if (smimeAssertion.isSignerCheckByFile()) { + signerCheckByFile.setSelected(true); + } + + issuerDnField.setText(smimeAssertion.getIssuerDn()); + signerDnField.setText(smimeAssertion.getSignerDn()); + signerSerialNumberField.setText(smimeAssertion.getSignerSerial()); + signerEmailField.setText(smimeAssertion.getSignerEmail()); + + signerCertFile.setText(smimeAssertion.getSignerCertFile()); + messagePositionTf.setText(smimeAssertion.getSpecificMessagePosition()); + } + + @Override + public void modifyTestElement(TestElement el) { + configureTestElement(el); + SMIMEAssertionTestElement smimeAssertion = (SMIMEAssertionTestElement) el; + smimeAssertion.setVerifySignature(verifySignature.isSelected()); + smimeAssertion.setNotSigned(notSigned.isSelected()); + + smimeAssertion.setIssuerDn(issuerDnField.getText()); + smimeAssertion.setSignerDn(signerDnField.getText()); + smimeAssertion.setSignerSerial(signerSerialNumberField.getText()); + smimeAssertion.setSignerEmail(signerEmailField.getText()); + + smimeAssertion.setSignerCertFile(signerCertFile.getText()); + + smimeAssertion.setSignerNoCheck(signerNoCheck.isSelected()); + smimeAssertion.setSignerCheckConstraints(signerCheckConstraints.isSelected()); + smimeAssertion.setSignerCheckByFile(signerCheckByFile.isSelected()); + smimeAssertion.setSpecificMessagePosition(messagePositionTf.getText()); + } + + @Override + public TestElement createTestElement() { + SMIMEAssertionTestElement smimeAssertion = new SMIMEAssertionTestElement(); + modifyTestElement(smimeAssertion); + return smimeAssertion; + } + +} diff --git a/src/components/org/apache/jmeter/assertions/gui/SizeAssertionGui.java b/src/components/org/apache/jmeter/assertions/gui/SizeAssertionGui.java new file mode 100644 index 00000000000..e0e37aca82e --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/SizeAssertionGui.java @@ -0,0 +1,305 @@ +/* + * 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.assertions.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.SizeAssertion; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * GUI for {@link SizeAssertion} + */ +public class SizeAssertionGui extends AbstractAssertionGui implements ActionListener { + + private static final long serialVersionUID = 241L; + + /** Radio button indicating that the body response should be tested. */ + private JRadioButton responseBodyButton; + + /** Radio button indicating that the network response size should be tested. */ + private JRadioButton responseNetworkButton; + + /** Radio button indicating that the responseMessage should be tested. */ + private JRadioButton responseMessageButton; + + /** Radio button indicating that the responseCode should be tested. */ + private JRadioButton responseCodeButton; + + /** Radio button indicating that the headers should be tested. */ + private JRadioButton responseHeadersButton; + + private JTextField size; + + private JRadioButton equalButton, notequalButton, greaterthanButton, lessthanButton, greaterthanequalButton, + lessthanequalButton; + + private int execState; // store the operator + + /** + * Simple Constructor which initializes the gui component + */ + public SizeAssertionGui() { + init(); + } + + @Override + public String getLabelResource() { + return "size_assertion_title"; //$NON-NLS-1$ + } + + @Override + public TestElement createTestElement() { + SizeAssertion el = new SizeAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement el) { + configureTestElement(el); + SizeAssertion assertion = (SizeAssertion) el; + + if (responseHeadersButton.isSelected()) { + assertion.setTestFieldResponseHeaders(); + } else if (responseBodyButton.isSelected()) { + assertion.setTestFieldResponseBody(); + } else if (responseCodeButton.isSelected()) { + assertion.setTestFieldResponseCode(); + } else if (responseMessageButton.isSelected()) { + assertion.setTestFieldResponseMessage(); + } else { + assertion.setTestFieldNetworkSize(); + } + assertion.setAllowedSize(size.getText()); + assertion.setCompOper(getState()); + saveScopeSettings(assertion); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + responseNetworkButton.setSelected(true); // default + responseHeadersButton.setSelected(false); + responseBodyButton.setSelected(false); + responseCodeButton.setSelected(false); + responseMessageButton.setSelected(false); + + size.setText(""); //$NON-NLS-1$ + equalButton.setSelected(true); + notequalButton.setSelected(false); + greaterthanButton.setSelected(false); + lessthanButton.setSelected(false); + greaterthanequalButton.setSelected(false); + lessthanequalButton.setSelected(false); + execState = SizeAssertion.EQUAL; + } + + @Override + public void configure(TestElement el) { + super.configure(el); + SizeAssertion assertion = (SizeAssertion) el; + size.setText(assertion.getAllowedSize()); + setState(assertion.getCompOper()); + showScopeSettings(assertion, true); + + if (assertion.isTestFieldResponseHeaders()) { + responseHeadersButton.setSelected(true); + } else if (assertion.isTestFieldResponseBody()) { + responseBodyButton.setSelected(true); + } else if (assertion.isTestFieldResponseCode()) { + responseCodeButton.setSelected(true); + } else if (assertion.isTestFieldResponseMessage()) { + responseMessageButton.setSelected(true); + } else { + responseNetworkButton.setSelected(true); + } + } + + /** + * Set the state of the radio Button. + *

+ * Allowed states are + *

    + *
  • {@link SizeAssertion#EQUAL}
  • + *
  • {@link SizeAssertion#NOTEQUAL}
  • + *
  • {@link SizeAssertion#GREATERTHAN}
  • + *
  • {@link SizeAssertion#LESSTHAN}
  • + *
  • {@link SizeAssertion#GREATERTHANEQUAL}
  • + *
  • {@link SizeAssertion#LESSTHANEQUAL}
  • + *
+ * @param state One of the allowed states + */ + public void setState(int state) { + if (state == SizeAssertion.EQUAL) { + equalButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.NOTEQUAL) { + notequalButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.GREATERTHAN) { + greaterthanButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.LESSTHAN) { + lessthanButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.GREATERTHANEQUAL) { + greaterthanequalButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.LESSTHANEQUAL) { + lessthanequalButton.setSelected(true); + execState = state; + } + } + + /** + * Get the state of the radio Button + *

+ * Possible states are + *

    + *
  • {@link SizeAssertion#EQUAL}
  • + *
  • {@link SizeAssertion#NOTEQUAL}
  • + *
  • {@link SizeAssertion#GREATERTHAN}
  • + *
  • {@link SizeAssertion#LESSTHAN}
  • + *
  • {@link SizeAssertion#GREATERTHANEQUAL}
  • + *
  • {@link SizeAssertion#LESSTHANEQUAL}
  • + *
+ * @return The current state of the radio Button + */ + public int getState() { + return execState; + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + + add(createScopePanel(true)); + add(createFieldPanel()); + + // USER_INPUT + JPanel sizePanel = new JPanel(); + sizePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("size_assertion_size_test"))); //$NON-NLS-1$ + + sizePanel.add(new JLabel(JMeterUtils.getResString("size_assertion_label"))); //$NON-NLS-1$ + size = new JTextField(12); + sizePanel.add(size); + + sizePanel.add(createComparatorButtonPanel()); + + add(sizePanel); + } + + /** + * Create a panel allowing the user to choose which response field should be + * tested. + * + * @return a new panel for selecting the response field + */ + private JPanel createFieldPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("assertion_resp_size_field"))); //$NON-NLS-1$ + + responseNetworkButton = new JRadioButton(JMeterUtils.getResString("assertion_network_size")); //$NON-NLS-1$ + responseHeadersButton = new JRadioButton(JMeterUtils.getResString("assertion_headers")); //$NON-NLS-1$ + responseBodyButton = new JRadioButton(JMeterUtils.getResString("assertion_body_resp")); //$NON-NLS-1$ + responseCodeButton = new JRadioButton(JMeterUtils.getResString("assertion_code_resp")); //$NON-NLS-1$ + responseMessageButton = new JRadioButton(JMeterUtils.getResString("assertion_message_resp")); //$NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + group.add(responseNetworkButton); + group.add(responseHeadersButton); + group.add(responseBodyButton); + group.add(responseCodeButton); + group.add(responseMessageButton); + + panel.add(responseNetworkButton); + panel.add(responseHeadersButton); + panel.add(responseBodyButton); + panel.add(responseCodeButton); + panel.add(responseMessageButton); + + responseNetworkButton.setSelected(true); + + return panel; + } + + private Box createComparatorButtonPanel() { + ButtonGroup group = new ButtonGroup(); + + equalButton = createComparatorButton("=", SizeAssertion.EQUAL, group); //$NON-NLS-1$ + notequalButton = createComparatorButton("!=", SizeAssertion.NOTEQUAL, group); //$NON-NLS-1$ + greaterthanButton = createComparatorButton(">", SizeAssertion.GREATERTHAN, group); //$NON-NLS-1$ + lessthanButton = createComparatorButton("<", SizeAssertion.LESSTHAN, group); //$NON-NLS-1$ + greaterthanequalButton = createComparatorButton(">=", SizeAssertion.GREATERTHANEQUAL, group); //$NON-NLS-1$ + lessthanequalButton = createComparatorButton("<=", SizeAssertion.LESSTHANEQUAL, group); //$NON-NLS-1$ + + equalButton.setSelected(true); + execState = Integer.parseInt(equalButton.getActionCommand()); + + // Put the check boxes in a column in a panel + Box checkPanel = Box.createVerticalBox(); + JLabel compareLabel = new JLabel(JMeterUtils.getResString("size_assertion_comparator_label")); //$NON-NLS-1$ + checkPanel.add(compareLabel); + checkPanel.add(equalButton); + checkPanel.add(notequalButton); + checkPanel.add(greaterthanButton); + checkPanel.add(lessthanButton); + checkPanel.add(greaterthanequalButton); + checkPanel.add(lessthanequalButton); + return checkPanel; + } + + private JRadioButton createComparatorButton(String name, int value, ButtonGroup group) { + JRadioButton button = new JRadioButton(name); + button.setActionCommand(String.valueOf(value)); + button.addActionListener(this); + group.add(button); + return button; + } + + @Override + public void actionPerformed(ActionEvent e) { + int comparator = Integer.parseInt(e.getActionCommand()); + execState = comparator; + } +} diff --git a/src/components/org/apache/jmeter/assertions/gui/XMLAssertionGui.java b/src/components/org/apache/jmeter/assertions/gui/XMLAssertionGui.java new file mode 100644 index 00000000000..0352c4e484a --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/XMLAssertionGui.java @@ -0,0 +1,69 @@ +/* + * 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.assertions.gui; + +import org.apache.jmeter.assertions.XMLAssertion; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class XMLAssertionGui extends AbstractAssertionGui { + private static final long serialVersionUID = 240L; + + /** + * The constructor. + */ + public XMLAssertionGui() { + init(); + } + + /** + * Returns the label to be shown within the JTree-Component. + */ + @Override + public String getLabelResource() { + return "xml_assertion_title"; // $NON-NLS-1$ + } + + @Override + public TestElement createTestElement() { + XMLAssertion el = new XMLAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement el) { + configureTestElement(el); + } + + /** + * Inits the GUI. + */ + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + } +} diff --git a/src/components/org/apache/jmeter/assertions/gui/XMLConfPanel.java b/src/components/org/apache/jmeter/assertions/gui/XMLConfPanel.java new file mode 100644 index 00000000000..8a357284dea --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/XMLConfPanel.java @@ -0,0 +1,165 @@ +/* + * 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.assertions.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JPanel; + +import org.apache.jmeter.assertions.XPathAssertion; +import org.apache.jmeter.extractor.XPathExtractor; +import org.apache.jmeter.util.JMeterUtils; + +public class XMLConfPanel extends JPanel { + private static final long serialVersionUID = 240L; + + private JCheckBox validate, tolerant, whitespace, namespace; + + private JCheckBox quiet; // Should Tidy be quiet? + + private JCheckBox reportErrors; // Report Tidy errors as Assertion failure? + + private JCheckBox showWarnings; // Show Tidy warnings ? + + private JCheckBox downloadDTDs; // Should we download external DTDs? + + /** + * + */ + public XMLConfPanel() { + super(); + init(); + } + + private void init() { + quiet = new JCheckBox(JMeterUtils.getResString("xpath_tidy_quiet"),true);//$NON-NLS-1$ + reportErrors = new JCheckBox(JMeterUtils.getResString("xpath_tidy_report_errors"),true);//$NON-NLS-1$ + showWarnings = new JCheckBox(JMeterUtils.getResString("xpath_tidy_show_warnings"),true);//$NON-NLS-1$ + namespace = new JCheckBox(JMeterUtils.getResString("xml_namespace_button")); //$NON-NLS-1$ + whitespace = new JCheckBox(JMeterUtils.getResString("xml_whitespace_button")); //$NON-NLS-1$ + validate = new JCheckBox(JMeterUtils.getResString("xml_validate_button")); //$NON-NLS-1$ + tolerant = new JCheckBox(JMeterUtils.getResString("xml_tolerant_button")); //$NON-NLS-1$ + tolerant.addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent e) { + tolerant(); + } + }); + downloadDTDs = new JCheckBox(JMeterUtils.getResString("xml_download_dtds")); //$NON-NLS-1$ + Box tidyOptions = Box.createHorizontalBox(); + tidyOptions.setBorder(BorderFactory.createEtchedBorder()); + tidyOptions.add(tolerant); + tidyOptions.add(quiet); + tidyOptions.add(reportErrors); + tidyOptions.add(showWarnings); + + Box untidyOptions = Box.createHorizontalBox(); + untidyOptions.setBorder(BorderFactory.createEtchedBorder()); + untidyOptions.add(namespace); + untidyOptions.add(validate); + untidyOptions.add(whitespace); + untidyOptions.add(downloadDTDs); + + Box options = Box.createVerticalBox(); + options.add(tidyOptions); + options.add(untidyOptions); + add(options); + setDefaultValues(); + } + + public void setDefaultValues() { + whitespace.setSelected(false); + validate.setSelected(false); + tolerant.setSelected(false); + namespace.setSelected(false); + quiet.setSelected(true); + reportErrors.setSelected(false); + showWarnings.setSelected(false); + downloadDTDs.setSelected(false); + tolerant(); + } + + // Process tolerant settings + private void tolerant() { + final boolean isTolerant = tolerant.isSelected(); + // Non-Tidy options + validate.setEnabled(!isTolerant); + whitespace.setEnabled(!isTolerant); + namespace.setEnabled(!isTolerant); + downloadDTDs.setEnabled(!isTolerant); + // Tidy options + quiet.setEnabled(isTolerant); + reportErrors.setEnabled(isTolerant); + showWarnings.setEnabled(isTolerant); + } + + // Called by XPathAssertionGui + public void modifyTestElement(XPathAssertion assertion) { + assertion.setValidating(validate.isSelected()); + assertion.setWhitespace(whitespace.isSelected()); + assertion.setTolerant(tolerant.isSelected()); + assertion.setNamespace(namespace.isSelected()); + assertion.setShowWarnings(showWarnings.isSelected()); + assertion.setReportErrors(reportErrors.isSelected()); + assertion.setQuiet(quiet.isSelected()); + assertion.setDownloadDTDs(downloadDTDs.isSelected()); + } + + // Called by XPathExtractorGui + public void modifyTestElement(XPathExtractor assertion) { + assertion.setValidating(validate.isSelected()); + assertion.setWhitespace(whitespace.isSelected()); + assertion.setTolerant(tolerant.isSelected()); + assertion.setNameSpace(namespace.isSelected()); + assertion.setShowWarnings(showWarnings.isSelected()); + assertion.setReportErrors(reportErrors.isSelected()); + assertion.setQuiet(quiet.isSelected()); + assertion.setDownloadDTDs(downloadDTDs.isSelected()); + } + + // Called by XPathAssertionGui + public void configure(XPathAssertion assertion) { + whitespace.setSelected(assertion.isWhitespace()); + validate.setSelected(assertion.isValidating()); + tolerant.setSelected(assertion.isTolerant()); + namespace.setSelected(assertion.isNamespace()); + quiet.setSelected(assertion.isQuiet()); + showWarnings.setSelected(assertion.showWarnings()); + reportErrors.setSelected(assertion.reportErrors()); + downloadDTDs.setSelected(assertion.isDownloadDTDs()); + tolerant(); + } + + // Called by XPathExtractorGui + public void configure(XPathExtractor assertion) { + whitespace.setSelected(assertion.isWhitespace()); + validate.setSelected(assertion.isValidating()); + tolerant.setSelected(assertion.isTolerant()); + namespace.setSelected(assertion.useNameSpace()); + quiet.setSelected(assertion.isQuiet()); + showWarnings.setSelected(assertion.showWarnings()); + reportErrors.setSelected(assertion.reportErrors()); + downloadDTDs.setSelected(assertion.isDownloadDTDs()); + tolerant(); + } +} diff --git a/src/components/org/apache/jmeter/assertions/gui/XMLSchemaAssertionGUI.java b/src/components/org/apache/jmeter/assertions/gui/XMLSchemaAssertionGUI.java new file mode 100644 index 00000000000..f5880d07a5a --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/XMLSchemaAssertionGUI.java @@ -0,0 +1,145 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +// import javax.swing.event.ChangeEvent; +import org.apache.jmeter.assertions.XMLSchemaAssertion; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// See Bug 34383 + +/** + * XMLSchemaAssertionGUI.java author Dave Maung + * + */ + +public class XMLSchemaAssertionGUI extends AbstractAssertionGui { + // class attributes + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private JTextField xmlSchema; + + /** + * The constructor. + */ + public XMLSchemaAssertionGUI() { + init(); + } + + /** + * Returns the label to be shown within the JTree-Component. + */ + @Override + public String getLabelResource() { + return "xmlschema_assertion_title"; //$NON-NLS-1$ + } + + /** + * create Test Element + */ + @Override + public TestElement createTestElement() { + log.debug("XMLSchemaAssertionGui.createTestElement() called"); + XMLSchemaAssertion el = new XMLSchemaAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement inElement) { + + log.debug("XMLSchemaAssertionGui.modifyTestElement() called"); + configureTestElement(inElement); + ((XMLSchemaAssertion) inElement).setXsdFileName(xmlSchema.getText()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + xmlSchema.setText(""); //$NON-NLS-1$ + } + + /** + * Configures the GUI from the associated test element. + * + * @param el - + * the test element (should be XMLSchemaAssertion) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + XMLSchemaAssertion assertion = (XMLSchemaAssertion) el; + xmlSchema.setText(assertion.getXsdFileName()); + } + + /** + * Inits the GUI. + */ + private void init() { + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + // USER_INPUT + VerticalPanel assertionPanel = new VerticalPanel(); + assertionPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "XML Schema")); + + // doctype + HorizontalPanel xmlSchemaPanel = new HorizontalPanel(); + + xmlSchemaPanel.add(new JLabel(JMeterUtils.getResString("xmlschema_assertion_label"))); //$NON-NLS-1$ + + xmlSchema = new JTextField(26); + xmlSchemaPanel.add(xmlSchema); + + assertionPanel.add(xmlSchemaPanel); + + mainPanel.add(assertionPanel, BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } + + // public void stateChanged(ChangeEvent e) { + // log.debug("XMLSchemaAssertionGui.stateChanged() called"); + // } + +} diff --git a/src/components/org/apache/jmeter/assertions/gui/XPathAssertionGui.java b/src/components/org/apache/jmeter/assertions/gui/XPathAssertionGui.java new file mode 100644 index 00000000000..6ab55a518e7 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/XPathAssertionGui.java @@ -0,0 +1,133 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JPanel; + +import org.apache.jmeter.assertions.XPathAssertion; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class XPathAssertionGui extends AbstractAssertionGui { + + private static final long serialVersionUID = 240L; + + private XPathPanel xpath; + + private XMLConfPanel xml; + + public XPathAssertionGui() { + super(); + init(); + } + + /** + * Returns the label to be shown within the JTree-Component. + */ + @Override + public String getLabelResource() { + return "xpath_assertion_title"; //$NON-NLS-1$ + } + + /** + * Create test element + */ + @Override + public TestElement createTestElement() { + XPathAssertion el = new XPathAssertion(); + modifyTestElement(el); + return el; + } + + public String getXPathAttributesTitle() { + return JMeterUtils.getResString("xpath_assertion_test"); //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + XPathAssertion assertion = (XPathAssertion) el; + showScopeSettings(assertion, true); + xpath.setXPath(assertion.getXPathString()); + xpath.setNegated(assertion.isNegated()); + + xml.configure(assertion); + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + Box box = Box.createVerticalBox(); + box.add(createScopePanel(true)); + add(box); + + // USER_INPUT + JPanel sizePanel = new JPanel(new BorderLayout()); + sizePanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + sizePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + getXPathAttributesTitle())); + xpath = new XPathPanel(); + sizePanel.add(xpath); + + xml = new XMLConfPanel(); + xml.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("xpath_assertion_option"))); //$NON-NLS-1$ + add(xml); + + add(sizePanel); + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement el) { + super.configureTestElement(el); + if (el instanceof XPathAssertion) { + XPathAssertion assertion = (XPathAssertion) el; + saveScopeSettings(assertion); + assertion.setNegated(xpath.isNegated()); + assertion.setXPathString(xpath.getXPath()); + xml.modifyTestElement(assertion); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + xpath.setXPath("/"); //$NON-NLS-1$ + xpath.setNegated(false); + + xml.setDefaultValues(); + + } +} diff --git a/src/components/org/apache/jmeter/assertions/gui/XPathPanel.java b/src/components/org/apache/jmeter/assertions/gui/XPathPanel.java new file mode 100644 index 00000000000..465649b4430 --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/gui/XPathPanel.java @@ -0,0 +1,225 @@ +/* + * 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.assertions.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Gui component for representing a xpath expression + * + */ +public class XPathPanel extends JPanel { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private JCheckBox negated; + + private JSyntaxTextArea xpath; + + private JButton checkXPath; + + /** + * + */ + public XPathPanel() { + super(); + init(); + } + + private void init() { + Box hbox = Box.createHorizontalBox(); + hbox.add(Box.createHorizontalGlue()); + hbox.add(new JTextScrollPane(getXPathField())); + hbox.add(Box.createHorizontalGlue()); + hbox.add(getCheckXPathButton()); + + Box vbox = Box.createVerticalBox(); + vbox.add(hbox); + vbox.add(Box.createVerticalGlue()); + vbox.add(getNegatedCheckBox()); + + add(vbox); + + setDefaultValues(); + } + + /** + * Set default values on this component + */ + public void setDefaultValues() { + setXPath("/"); //$NON-NLS-1$ + setNegated(false); + } + + /** + * Get the XPath String + * + * @return String + */ + public String getXPath() { + return this.xpath.getText(); + } + + /** + * Set the string that will be used in the xpath evaluation + * + * @param xpath The string representing the xpath expression + */ + public void setXPath(String xpath) { + this.xpath.setInitialText(xpath); + } + + /** + * Does this negate the xpath results + * + * @return boolean + */ + public boolean isNegated() { + return this.negated.isSelected(); + } + + /** + * Set this to true, if you want success when the xpath does not match. + * + * @param negated Flag whether xpath match should be negated + */ + public void setNegated(boolean negated) { + this.negated.setSelected(negated); + } + + /** + * Negated chechbox + * + * @return JCheckBox + */ + public JCheckBox getNegatedCheckBox() { + if (negated == null) { + negated = new JCheckBox(JMeterUtils.getResString("xpath_assertion_negate"), false); //$NON-NLS-1$ + } + + return negated; + } + + /** + * Check XPath button + * + * @return JButton + */ + public JButton getCheckXPathButton() { + if (checkXPath == null) { + checkXPath = new JButton(JMeterUtils.getResString("xpath_assertion_button")); //$NON-NLS-1$ + checkXPath.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + validXPath(xpath.getText(), true); + } + }); + } + return checkXPath; + } + + /** + * Returns the current {@link JSyntaxTextArea} for the xpath expression, or + * creates a new one, if none is found. + * + * @return {@link JSyntaxTextArea} for the xpath expression + */ + public JSyntaxTextArea getXPathField() { + if (xpath == null) { + xpath = new JSyntaxTextArea(20, 80); + xpath.setLanguage("xpath"); //$NON-NLS-1$ + } + return xpath; + } + + /** + * @return Returns the showNegate. + */ + public boolean isShowNegated() { + return this.getNegatedCheckBox().isVisible(); + } + + /** + * @param showNegate + * The showNegate to set. + */ + public void setShowNegated(boolean showNegate) { + getNegatedCheckBox().setVisible(showNegate); + } + + /** + * Test whether an XPath is valid. It seems the Xalan has no easy way to + * check, so this creates a dummy test document, then tries to evaluate the xpath against it. + * + * @param xpathString + * XPath String to validate + * @param showDialog + * weather to show a dialog + * @return returns true if valid, valse otherwise. + */ + public static boolean validXPath(String xpathString, boolean showDialog) { + String ret = null; + boolean success = true; + Document testDoc = null; + try { + testDoc = XPathUtil.makeDocumentBuilder(false, false, false, false).newDocument(); + Element el = testDoc.createElement("root"); //$NON-NLS-1$ + testDoc.appendChild(el); + XPathUtil.validateXPath(testDoc, xpathString); + } catch (IllegalArgumentException e) { + log.warn(e.getLocalizedMessage()); + success = false; + ret = e.getLocalizedMessage(); + } catch (ParserConfigurationException e) { + success = false; + ret = e.getLocalizedMessage(); + } catch (TransformerException e) { + success = false; + ret = e.getLocalizedMessage(); + } + + if (showDialog) { + JOptionPane.showMessageDialog(null, (success) ? JMeterUtils.getResString("xpath_assertion_valid") : ret, //$NON-NLS-1$ + (success) ? JMeterUtils.getResString("xpath_assertion_valid") : JMeterUtils //$NON-NLS-1$ + .getResString("xpath_assertion_failed"), (success) ? JOptionPane.INFORMATION_MESSAGE //$NON-NLS-1$ + : JOptionPane.ERROR_MESSAGE); + } + return success; + + } +} diff --git a/src/components/org/apache/jmeter/assertions/package.html b/src/components/org/apache/jmeter/assertions/package.html new file mode 100644 index 00000000000..92d4f8c272d --- /dev/null +++ b/src/components/org/apache/jmeter/assertions/package.html @@ -0,0 +1,33 @@ + + + + Assertions + + +

Assertions

+

Methods to be implemented

+ getResult(SampleResult) +

Calling sequence

+

When the test plan is prepared for running, one instance of the class is created for each occurrence + of an assertion in each thread.

+

Assertions are called from the same thread as the sampler

+ + \ No newline at end of file diff --git a/src/components/org/apache/jmeter/config/CSVDataSet.java b/src/components/org/apache/jmeter/config/CSVDataSet.java new file mode 100644 index 00000000000..65c20b6a62f --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSet.java @@ -0,0 +1,301 @@ +/* + * 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.config; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.io.IOException; +import java.util.ResourceBundle; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.NoConfigMerge; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Read lines from a file and split int variables. + * + * The iterationStart() method is used to set up each set of values. + * + * By default, the same file is shared between all threads + * (and other thread groups, if they use the same file name). + * + * The shareMode can be set to: + *
    + *
  • All threads - default, as described above
  • + *
  • Current thread group
  • + *
  • Current thread
  • + *
  • Identifier - all threads sharing the same identifier
  • + *
+ * + * The class uses the FileServer alias mechanism to provide the different share modes. + * For all threads, the file alias is set to the file name. + * Otherwise, a suffix is appended to the filename to make it unique within the required context. + * For current thread group, the thread group identityHashcode is used; + * for individual threads, the thread hashcode is used as the suffix. + * Or the user can provide their own suffix, in which case the file is shared between all + * threads with the same suffix. + * + */ +public class CSVDataSet extends ConfigTestElement + implements TestBean, LoopIterationListener, NoConfigMerge { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + private static final String EOFVALUE = // value to return at EOF + JMeterUtils.getPropDefault("csvdataset.eofstring", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + private transient String filename; + + private transient String fileEncoding; + + private transient String variableNames; + + private transient String delimiter; + + private transient boolean quoted; + + private transient boolean recycle = true; + + private transient boolean stopThread; + + private transient String[] vars; + + private transient String alias; + + private transient String shareMode; + + private boolean firstLineIsNames = false; + + private Object readResolve(){ + recycle = true; + return this; + } + + /** + * Override the setProperty method in order to convert + * the original String shareMode property. + * This used the locale-dependent display value, so caused + * problems when the language was changed. + * If the "shareMode" value matches a resource value then it is converted + * into the resource key. + * To reduce the need to look up resources, we only attempt to + * convert values with spaces in them, as these are almost certainly + * not variables (and they are definitely not resource keys). + */ + @Override + public void setProperty(JMeterProperty property) { + if (property instanceof StringProperty) { + final String propName = property.getName(); + if (propName.equals("shareMode")) { // The original name of the property + final String propValue = property.getStringValue(); + if (propValue.contains(" ")){ // variables are unlikely to contain spaces, so most likely a translation + try { + final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass()); + final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE); + for(String resKey : CSVDataSetBeanInfo.SHARE_TAGS) { + if (propValue.equals(rb.getString(resKey))) { + if (log.isDebugEnabled()) { + log.debug("Converted " + propName + "=" + propValue + " to " + resKey + " using Locale: " + rb.getLocale()); + } + ((StringProperty) property).setValue(resKey); // reset the value + super.setProperty(property); + return; + } + } + // This could perhaps be a variable name + log.warn("Could not translate " + propName + "=" + propValue + " using Locale: " + rb.getLocale()); + } catch (IntrospectionException e) { + log.error("Could not find BeanInfo; cannot translate shareMode entries", e); + } + } + } + } + super.setProperty(property); + } + + @Override + public void iterationStart(LoopIterationEvent iterEvent) { + FileServer server = FileServer.getFileServer(); + final JMeterContext context = getThreadContext(); + String delim = getDelimiter(); + if (delim.equals("\\t")) { // $NON-NLS-1$ + delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$ + } else if (delim.length()==0){ + log.warn("Empty delimiter converted to ','"); + delim=","; + } + if (vars == null) { + String _fileName = getFilename(); + String mode = getShareMode(); + int modeInt = CSVDataSetBeanInfo.getShareModeAsInt(mode); + switch(modeInt){ + case CSVDataSetBeanInfo.SHARE_ALL: + alias = _fileName; + break; + case CSVDataSetBeanInfo.SHARE_GROUP: + alias = _fileName+"@"+System.identityHashCode(context.getThreadGroup()); + break; + case CSVDataSetBeanInfo.SHARE_THREAD: + alias = _fileName+"@"+System.identityHashCode(context.getThread()); + break; + default: + alias = _fileName+"@"+mode; // user-specified key + break; + } + final String names = getVariableNames(); + if (names == null || names.length()==0) { + String header = server.reserveFile(_fileName, getFileEncoding(), alias, true); + try { + vars = CSVSaveService.csvSplitString(header, delim.charAt(0)); + firstLineIsNames = true; + } catch (IOException e) { + log.warn("Could not split CSV header line",e); + } + } else { + server.reserveFile(_fileName, getFileEncoding(), alias); + vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$ + } + } + + // TODO: fetch this once as per vars above? + JMeterVariables threadVars = context.getVariables(); + String[] lineValues = {}; + try { + if (getQuotedData()) { + lineValues = server.getParsedLine(alias, recycle, firstLineIsNames, delim.charAt(0)); + } else { + String line = server.readLine(alias, recycle, firstLineIsNames); + lineValues = JOrphanUtils.split(line, delim, false); + } + for (int a = 0; a < vars.length && a < lineValues.length; a++) { + threadVars.put(vars[a], lineValues[a]); + } + } catch (IOException e) { // treat the same as EOF + log.error(e.toString()); + } + if (lineValues.length == 0) {// i.e. EOF + if (getStopThread()) { + throw new JMeterStopThreadException("End of file detected"); + } + for (String var :vars) { + threadVars.put(var, EOFVALUE); + } + } + } + + /** + * @return Returns the filename. + */ + public String getFilename() { + return filename; + } + + /** + * @param filename + * The filename to set. + */ + public void setFilename(String filename) { + this.filename = filename; + } + + /** + * @return Returns the file encoding. + */ + public String getFileEncoding() { + return fileEncoding; + } + + /** + * @param fileEncoding + * The fileEncoding to set. + */ + public void setFileEncoding(String fileEncoding) { + this.fileEncoding = fileEncoding; + } + + /** + * @return Returns the variableNames. + */ + public String getVariableNames() { + return variableNames; + } + + /** + * @param variableNames + * The variableNames to set. + */ + public void setVariableNames(String variableNames) { + this.variableNames = variableNames; + } + + public String getDelimiter() { + return delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + public boolean getQuotedData() { + return quoted; + } + + public void setQuotedData(boolean quoted) { + this.quoted = quoted; + } + + public boolean getRecycle() { + return recycle; + } + + public void setRecycle(boolean recycle) { + this.recycle = recycle; + } + + public boolean getStopThread() { + return stopThread; + } + + public void setStopThread(boolean value) { + this.stopThread = value; + } + + public String getShareMode() { + return shareMode; + } + + public void setShareMode(String value) { + this.shareMode = value; + } +} diff --git a/src/components/org/apache/jmeter/config/CSVDataSetBeanInfo.java b/src/components/org/apache/jmeter/config/CSVDataSetBeanInfo.java new file mode 100644 index 00000000000..de928b5a551 --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSetBeanInfo.java @@ -0,0 +1,109 @@ +/* + * 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.config; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TypeEditor; + +public class CSVDataSetBeanInfo extends BeanInfoSupport { + + // These names must agree case-wise with the variable and property names + private static final String FILENAME = "filename"; //$NON-NLS-1$ + private static final String FILE_ENCODING = "fileEncoding"; //$NON-NLS-1$ + private static final String VARIABLE_NAMES = "variableNames"; //$NON-NLS-1$ + private static final String DELIMITER = "delimiter"; //$NON-NLS-1$ + private static final String RECYCLE = "recycle"; //$NON-NLS-1$ + private static final String STOPTHREAD = "stopThread"; //$NON-NLS-1$ + private static final String QUOTED_DATA = "quotedData"; //$NON-NLS-1$ + private static final String SHAREMODE = "shareMode"; //$NON-NLS-1$ + + // Access needed from CSVDataSet + static final String[] SHARE_TAGS = new String[3]; + static final int SHARE_ALL = 0; + static final int SHARE_GROUP = 1; + static final int SHARE_THREAD = 2; + + // Store the resource keys + static { + SHARE_TAGS[SHARE_ALL] = "shareMode.all"; //$NON-NLS-1$ + SHARE_TAGS[SHARE_GROUP] = "shareMode.group"; //$NON-NLS-1$ + SHARE_TAGS[SHARE_THREAD] = "shareMode.thread"; //$NON-NLS-1$ + } + + public CSVDataSetBeanInfo() { + super(CSVDataSet.class); + + createPropertyGroup("csv_data", //$NON-NLS-1$ + new String[] { FILENAME, FILE_ENCODING, VARIABLE_NAMES, DELIMITER, QUOTED_DATA, RECYCLE, STOPTHREAD, SHAREMODE }); + + PropertyDescriptor p = property(FILENAME); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); //$NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + + p = property(FILE_ENCODING); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); //$NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + + p = property(VARIABLE_NAMES); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); //$NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + + p = property(DELIMITER); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ","); //$NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + + p = property(QUOTED_DATA); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + p = property(RECYCLE); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + + p = property(STOPTHREAD); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + p = property(SHAREMODE, TypeEditor.ComboStringEditor); + p.setValue(RESOURCE_BUNDLE, getBeanDescriptor().getValue(RESOURCE_BUNDLE)); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, SHARE_TAGS[SHARE_ALL]); + p.setValue(NOT_OTHER, Boolean.FALSE); + p.setValue(NOT_EXPRESSION, Boolean.FALSE); + p.setValue(TAGS, SHARE_TAGS); + } + + public static int getShareModeAsInt(String mode) { + if (mode == null || mode.length() == 0){ + return SHARE_ALL; // default (e.g. if test plan does not have definition) + } + for (int i = 0; i < SHARE_TAGS.length; i++) { + if (SHARE_TAGS[i].equals(mode)) { + return i; + } + } + return -1; + } +} diff --git a/src/components/org/apache/jmeter/config/CSVDataSetResources.properties b/src/components/org/apache/jmeter/config/CSVDataSetResources.properties new file mode 100644 index 00000000000..538a20d34b0 --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSetResources.properties @@ -0,0 +1,36 @@ +# 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. + +displayName=CSV Data Set Config +csv_data.displayName=Configure the CSV Data Source +filename.displayName=Filename +filename.shortDescription=Name of the file that holds the cvs data (relative or absolute filename) +fileEncoding.displayName=File encoding +fileEncoding.shortDescription=The character set encoding used in the file +variableNames.displayName=Variable Names (comma-delimited) +variableNames.shortDescription=List your variable names in order to match the order of columns in your csv data. Separate by commas. +delimiter.displayName=Delimiter (use '\\t' for tab) +delimiter.shortDescription=Enter the delimiter ('\\t' for tab) +quotedData.displayName=Allow quoted data? +quotedData.shortDescription=Allow CSV data values to be quoted? +recycle.displayName=Recycle on EOF ? +recycle.shortDescription=Should the file be re-read from the start on reaching EOF ? +stopThread.displayName=Stop thread on EOF ? +stopThread.shortDescription=Should the thread be stopped on reaching EOF (if Recycle is false) ? +shareMode.displayName=Sharing mode +shareMode.shortDescription=Select which threads share the same file pointer +shareMode.all=All threads +shareMode.group=Current thread group +shareMode.thread=Current thread diff --git a/src/components/org/apache/jmeter/config/CSVDataSetResources_de.properties b/src/components/org/apache/jmeter/config/CSVDataSetResources_de.properties new file mode 100644 index 00000000000..426ae129469 --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSetResources_de.properties @@ -0,0 +1,29 @@ +# 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. + +csv_data.displayName=Einstellungen der CSV Quellen +delimiter.displayName=Trennzeichen ('\\t' f\u00FCr Tab) +delimiter.shortDescription=Geben sie ein Trennzeichen ein ('\\t' f\u00FCr Tab) +displayName=CSV Einstellungen +fileEncoding.displayName=Zeichensatz der Datei +fileEncoding.shortDescription=Der, in der Datei, benutzte Zeichensatz +filename.displayName=Dateiname +filename.shortDescription=Name der Datei, die die CSV Daten enth\u00E4lt (relativer oder absoluter Pfadname m\u00F6glich) +recycle.displayName=Datei erneut einlesen? +recycle.shortDescription=Soll die Datei nach dem erreichen des Dateiendes erneut eingelesen werden? +stopThread.displayName=Thread stoppen? +stopThread.shortDescription=Bei erreichen des Dateiende den Thread stoppen? +variableNames.displayName=Variablenname (getrennt durch Komma) +variableNames.shortDescription=Geben sie die Variablennamen, durch Komma getrennt, ein, die den Spalten ihrer CSV Datei entsprechen. Die Variablennamen m\u00FCssen der Reihenfolge der CVS Datei entsprechen\! diff --git a/src/components/org/apache/jmeter/config/CSVDataSetResources_es.properties b/src/components/org/apache/jmeter/config/CSVDataSetResources_es.properties new file mode 100644 index 00000000000..1f17023b2f2 --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSetResources_es.properties @@ -0,0 +1,37 @@ +# 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. + +#Stored by I18NEdit, may be edited! +csv_data.displayName=Configura el Data Source de CSV +delimiter.displayName=Delimitador (utilice '\\t' para poner un tabulador) +delimiter.shortDescription=Introduzca el delimitador (utilice '\\t' para poner un tabulador) +displayName=Configuraci\u00F3n del CSV Data Set +fileEncoding.displayName=Codificaci\u00F3n del fichero +fileEncoding.shortDescription=El conjunto de caracteres usado en el fichero +filename.displayName=Nombre de Archivo +filename.shortDescription=Nombre del archivo (dentro de su directorio de archivos) que mantiene los datos CVS +quotedData.displayName=\u00BFPermitir datos entrecomillados? +quotedData.shortDescription=\u00BFPermitir que valores de datos CSV sean entrecomillados? +recycle.displayName=\u00BFReciclar en el fin de fichero (EOF)? +recycle.shortDescription=\u00BFDeber\u00EDa el fichero ser rele\u00EDdo desde el comiendo cuando se alcance el final del fichero (EOF)? +shareMode.all=Todos los hilos +shareMode.displayName=Modo compartido +shareMode.group=Actual grupo de hilos +shareMode.shortDescription=Seleccionar que hilos comparten el mismo puntero a fichero +shareMode.thread=Hilo actual +stopThread.displayName=\u00BFPara el hilo al final del fichero (EOF)? +stopThread.shortDescription=\u00BFDeber\u00EDa el hilo ser parado cuando se alcance el final del fichero(EOF) (Si 'Reciclar' es falso?) +variableNames.displayName=Nombres de Variable (delimitados por coma) +variableNames.shortDescription=Lista sus nombres de variable para ordenar las columnas en sus datos CSV.\nSeparados por comas. diff --git a/src/components/org/apache/jmeter/config/CSVDataSetResources_fr.properties b/src/components/org/apache/jmeter/config/CSVDataSetResources_fr.properties new file mode 100644 index 00000000000..3710a509b94 --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSetResources_fr.properties @@ -0,0 +1,37 @@ +# 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. + +#Stored by I18NEdit, may be edited! +csv_data.displayName=Configuration de la source de donn\u00E9es CSV +delimiter.displayName=D\u00E9limiteur (utiliser '\\t' pour la tabulation) +delimiter.shortDescription=D\u00E9limiteur (utiliser '\\t' pour la tabulation) +displayName=Source de donn\u00E9es CSV +fileEncoding.displayName=Encodage du fichier +fileEncoding.shortDescription=Encodage des caract\u00E8res utilis\u00E9s dans le fichier +filename.displayName=Nom de fichier +filename.shortDescription=Nom du fichier qui contient des donn\u00E9es CSV (chemin relatif ou absolu) +quotedData.displayName=Autoriser les donn\u00E9es avec des quotes ? +quotedData.shortDescription=Permettre aux valeurs des donn\u00E9es CSV d'\u00EAtre quot\u00E9es ? +recycle.displayName=Recycler en fin de fichier (EOF) ? +recycle.shortDescription=Voulez-vous que le fichier soit relu depuis son d\u00E9but apr\u00E8s avoir atteint la fin de fichier (EOF) ? +shareMode.all=Toutes les unit\u00E9s +shareMode.displayName=Mode de partage +shareMode.group=Groupe d'unit\u00E9s courant +shareMode.shortDescription=S\u00E9lectionner les unit\u00E9s partageant le m\u00EAme pointeur de fichier +shareMode.thread=Unit\u00E9 courante +stopThread.displayName=Arr\u00EAter l'unit\u00E9 \u00E0 la fin de fichier (EOF) ? +stopThread.shortDescription=L'unit\u00E9 sera arr\u00EAt\u00E9e en atteignant la fin de fichier (EOF) (si Recycler est \u00E0 faux) ? +variableNames.displayName=Noms des variables (s\u00E9par\u00E9s par des virgules) +variableNames.shortDescription=Liste de vos variables dans l'ordre des colonnes de vos donn\u00E9es CSV. S\u00E9par\u00E9 par des virgules.\t diff --git a/src/components/org/apache/jmeter/config/CSVDataSetResources_pl.properties b/src/components/org/apache/jmeter/config/CSVDataSetResources_pl.properties new file mode 100644 index 00000000000..7cdb97be25d --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSetResources_pl.properties @@ -0,0 +1,37 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +# 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. + +csv_data.displayName = Konfiguracja wczytywania plik\u00F3w CSV +delimiter.displayName=Separator (np. '\\t' oznacza TABa) +delimiter.shortDescription=Wpisz separator (np. '\\t' oznacza TABa) +displayName=Konfiguracja plik\u00F3w CSV +fileEncoding.displayName=Kodowanie pliku +fileEncoding.shortDescription=Strona kodowa znak\u00F3w u\u017Cywana w pliku +filename.displayName=Nazwa pliku +filename.shortDescription=Nazwa pliku CSV (wzgl\u0119dna lub absolutna) +quotedData.displayName=Zezwala\u0107 na dane "w cudzys\u0142owiach" ? +quotedData.shortDescription=Zezwala\u0107 na "otaczanie danych" cudzys\u0142owami? +recycle.displayName=Recycle on EOF ? +recycle.shortDescription=Je\u015Bli zaczn\u0119 czyta\u0107 plik w \u015Brodku i dojd\u0119 do ko\u0144ca (EOF) to mam doczyta\u0107 brakuj\u0105c\u0105 cz\u0119\u015B\u0107 z pocz\u0105tku pliku?\r\n +shareMode.all=Wszystkie w\u0105tki +shareMode.displayName=Tryb wsp\u00F3\u0142dzielenia +shareMode.group=Bie\u017C\u0105ca grupa w\u0105tk\u00F3w +shareMode.shortDescription=Wybierz w\u0105tki, kt\u00F3re powinny dzieli\u0107 ten sam wska\u017Anik do pliku +shareMode.thread=Bie\u017C\u0105cy w\u0105tek +stopThread.displayName=Zatrzyma\u0107 w\u0105tek na EOF ? +stopThread.shortDescription=Zatrzyma\u0107 w\u0105tek gdy dojdzie do ko\u0144ca pliku (EOF, je\u015Bli nie wybra\u0142e\u015B doczytywania brakuj\u0105cego fragmentu pliku z pocz\u0105tku)? +variableNames.displayName=Nazwy zmiennych (oddzielane przecinkami) +variableNames.shortDescription=Wpisz nazwy Twoich zmiennych, w odpowieniej kolejno\u015Bci, tak by pasowa\u0142y Ci do kolumn w pliku CSV. Nazwy oddzielaj przecinkami. diff --git a/src/components/org/apache/jmeter/config/CSVDataSetResources_pt_BR.properties b/src/components/org/apache/jmeter/config/CSVDataSetResources_pt_BR.properties new file mode 100644 index 00000000000..51235bee8b3 --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSetResources_pt_BR.properties @@ -0,0 +1,36 @@ +# 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. + +csv_data.displayName=Configurar fonte de dados CSV +delimiter.displayName=Separador (usar '\\t' para tabula\u00E7\u00F5es) +delimiter.shortDescription=Informe o separador ('\\t' para tabula\u00E7\u00F5es) +displayName=Configura\u00E7\u00E3o dos dados CSV +fileEncoding.displayName=Codifica\u00E7\u00E3o do arquivo (encoding) +fileEncoding.shortDescription=O conjunto de caracteres (charset) usado no arquivo +filename.displayName=Nome do arquivo +filename.shortDescription=Nome do arquivo que cont\u00E9m os dados csv (nome do arquivo relativo ou absoluto) +quotedData.displayName=Permitir dados com cita\u00E7\u00F5es? +quotedData.shortDescription=Permitir que valores de dados CSV possuam cita\u00E7\u00F5es (aspas) +recycle.displayName=Reciclar no final do arquivo (EOF)? +recycle.shortDescription=Os dados do arquivo devem ser lidos novamente a partir do in\u00EDcio ao alcan\u00E7ar o fim do arquivo (EOF)? +shareMode.all=Todos os usu\u00E1rios virtuais +shareMode.displayName=Modo de compartilhamento +shareMode.group=Grupo de usu\u00E1rios atual +shareMode.shortDescription=Selecionar quais usu\u00E1rios virtuais compartilham o mesmo ponteiro para o arquivo +shareMode.thread=Usu\u00E1rio virtual atual +stopThread.displayName=Finalizar usu\u00E1rio virtual no final do arquivo? +stopThread.shortDescription=O usu\u00E1rio virtual dever\u00E1 ser parado quando o fim do arquivo for alcan\u00E7ado (se Reciclar n\u00E3o est\u00E1 configurado)? +variableNames.displayName=Nomes das vari\u00E1veis (separados por v\u00EDrgula) +variableNames.shortDescription=Informar os nomes das vari\u00E1veis que representam as colunas na sua fonte de dados CSV. Separe por v\u00EDrgula. diff --git a/src/components/org/apache/jmeter/config/CSVDataSetResources_tr.properties b/src/components/org/apache/jmeter/config/CSVDataSetResources_tr.properties new file mode 100644 index 00000000000..319375fb47c --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSetResources_tr.properties @@ -0,0 +1,30 @@ +# 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. + +#Stored by I18NEdit, may be edited! +csv_data.displayName=CSV Veri Kayna\u011F\u0131n\u0131 Ayarla +delimiter.displayName=S\u0131n\u0131rlay\u0131c\u0131 (tab i\u00E7in '\\t' kullan) +delimiter.shortDescription=S\u0131n\u0131rlay\u0131c\u0131 gir (tab i\u00E7in '\\t') +displayName=CSV Veri K\u00FCmesi Ayar\u0131 +fileEncoding.displayName=Dosya Kodlamas\u0131 +fileEncoding.shortDescription=Dosyada kullan\u0131lan karakter kodlamas\u0131 +filename.displayName=Dosya ad\u0131 +filename.shortDescription=cvs verisini tutan dosyan\u0131n ad\u0131 (g\u00F6reli veya tam dosya yolu) +recycle.displayName=Dosya sonunda geri d\u00F6n\u00FC\u015F\u00FCm ? +recycle.shortDescription=Dosya sonundan itibaren tekrar okunsun mu? +stopThread.displayName=Dosya sonunda i\u015F par\u00E7ac\u0131\u011F\u0131n\u0131 durdur ? +stopThread.shortDescription=\u0130\u015F par\u00E7ac\u0131c\u0131\u011F\u0131 dosya sonuna var\u0131ld\u0131\u011F\u0131nda durdurulsun mu (e\u011Fer geri d\u00F6n\u00FC\u015F\u00FCm etkin de\u011Filse) ? +variableNames.displayName=De\u011Fi\u015Fken isimleri (virg\u00FClle ayr\u0131lm\u0131\u015F) +variableNames.shortDescription=De\u011Fi\u015Fken isimlerini csv verisindeki kolon s\u0131ras\u0131yla \u00F6rt\u00FC\u015Fecek \u015Fekilde listele. Virg\u00FClle ay\u0131rarak. diff --git a/src/components/org/apache/jmeter/config/CSVDataSetResources_zh_TW.properties b/src/components/org/apache/jmeter/config/CSVDataSetResources_zh_TW.properties new file mode 100644 index 00000000000..2ee02f76cb6 --- /dev/null +++ b/src/components/org/apache/jmeter/config/CSVDataSetResources_zh_TW.properties @@ -0,0 +1,22 @@ +# 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. + +#Stored by I18NEdit, may be edited! +csv_data.displayName=\u8A2D\u5B9A CSV \u8CC7\u6599\u4F86\u6E90 +displayName=CSV \u8CC7\u6599\u8A2D\u5B9A +filename.displayName=\u6A94\u540D +filename.shortDescription=\u5DF2\u63D0\u4F9B\u8DEF\u5F91\u4E2D CVS \u6A94\u540D +variableNames.displayName=\u8B8A\u6578\u540D\u7A31(\u4EE5\u9017\u865F\u5206\u9694) +variableNames.shortDescription=\u8207CSV\u6A94\u6848\u8CC7\u6599\u76F8\u5C0D\u61C9\u7684\u8B8A\u6578\u540D\u7A31, \u4EE5\u9017\u865F\u5206\u9694 diff --git a/src/components/org/apache/jmeter/config/KeystoreConfig.java b/src/components/org/apache/jmeter/config/KeystoreConfig.java new file mode 100644 index 00000000000..d064e5537a5 --- /dev/null +++ b/src/components/org/apache/jmeter/config/KeystoreConfig.java @@ -0,0 +1,158 @@ +/* + * 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.config; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopTestException; +import org.apache.log.Logger; + +/** + * Configure Keystore + */ +public class KeystoreConfig extends ConfigTestElement implements TestBean, TestStateListener { + + private static final long serialVersionUID = -5781402012242794890L; + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY_STORE_START_INDEX = "https.keyStoreStartIndex"; // $NON-NLS-1$ + private static final String KEY_STORE_END_INDEX = "https.keyStoreEndIndex"; // $NON-NLS-1$ + + private String startIndex; + private String endIndex; + private String preload; + private String clientCertAliasVarName; + + public KeystoreConfig() { + super(); + } + + @Override + public void testEnded() { + testEnded(null); + } + + @Override + public void testEnded(String host) { + log.info("Destroying Keystore"); + SSLManager.getInstance().destroyKeystore(); + } + + @Override + public void testStarted() { + testStarted(null); + } + + @Override + public void testStarted(String host) { + String reuseSSLContext = JMeterUtils.getProperty("https.use.cached.ssl.context"); + if(StringUtils.isEmpty(reuseSSLContext)||"true".equals(reuseSSLContext)) { + log.warn("https.use.cached.ssl.context property must be set to false to ensure Multiple Certificates are used"); + } + int startIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_START_INDEX, 0); + int endIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_END_INDEX, 0); + + if(!StringUtils.isEmpty(this.startIndex)) { + try { + startIndexAsInt = Integer.parseInt(this.startIndex); + } catch(NumberFormatException e) { + log.warn("Failed parsing startIndex :'"+this.startIndex+"', will default to:'"+startIndexAsInt+"', error message:"+ e.getMessage(), e); + } + } + + if(!StringUtils.isEmpty(this.endIndex)) { + try { + endIndexAsInt = Integer.parseInt(this.endIndex); + } catch(NumberFormatException e) { + log.warn("Failed parsing endIndex :'"+this.endIndex+"', will default to:'"+endIndexAsInt+"', error message:"+ e.getMessage(), e); + } + } + if(startIndexAsInt>endIndexAsInt) { + throw new JMeterStopTestException("Keystore Config error : Alias start index must be lower than Alias end index"); + } + log.info("Configuring Keystore with (preload:"+preload+", startIndex:"+ + startIndexAsInt+", endIndex:"+endIndexAsInt+ + ", clientCertAliasVarName:'" + clientCertAliasVarName +"')"); + + SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload), + startIndexAsInt, + endIndexAsInt, + clientCertAliasVarName); + } + + /** + * @return the endIndex + */ + public String getEndIndex() { + return endIndex; + } + + /** + * @param endIndex the endIndex to set + */ + public void setEndIndex(String endIndex) { + this.endIndex = endIndex; + } + + /** + * @return the startIndex + */ + public String getStartIndex() { + return startIndex; + } + + /** + * @param startIndex the startIndex to set + */ + public void setStartIndex(String startIndex) { + this.startIndex = startIndex; + } + + /** + * @return the preload + */ + public String getPreload() { + return preload; + } + + /** + * @param preload the preload to set + */ + public void setPreload(String preload) { + this.preload = preload; + } + + /** + * @return the clientCertAliasVarName + */ + public String getClientCertAliasVarName() { + return clientCertAliasVarName; + } + + /** + * @param clientCertAliasVarName the clientCertAliasVarName to set + */ + public void setClientCertAliasVarName(String clientCertAliasVarName) { + this.clientCertAliasVarName = clientCertAliasVarName; + } +} diff --git a/src/components/org/apache/jmeter/config/KeystoreConfigBeanInfo.java b/src/components/org/apache/jmeter/config/KeystoreConfigBeanInfo.java new file mode 100644 index 00000000000..d9947fe6950 --- /dev/null +++ b/src/components/org/apache/jmeter/config/KeystoreConfigBeanInfo.java @@ -0,0 +1,64 @@ +/* + * 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.config; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +/** + * Keystore Configuration BeanInfo + */ +public class KeystoreConfigBeanInfo extends BeanInfoSupport { + + private static final String ALIASES_GROUP = "aliases"; + private static final String ALIAS_END_INDEX = "endIndex"; + private static final String ALIAS_START_INDEX = "startIndex"; + private static final String CLIENT_CERT_ALIAS_VAR_NAME = "clientCertAliasVarName"; + private static final String PRELOAD = "preload"; + + /** + * Constructor + */ + public KeystoreConfigBeanInfo() { + super(KeystoreConfig.class); + + createPropertyGroup(ALIASES_GROUP, new String[] { + PRELOAD, CLIENT_CERT_ALIAS_VAR_NAME, ALIAS_START_INDEX, ALIAS_END_INDEX }); + + PropertyDescriptor p = property(PRELOAD); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "true"); // $NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(TAGS, new String[]{"True", "False"}); // $NON-NLS-1$ $NON-NLS-2$ + + p = property(CLIENT_CERT_ALIAS_VAR_NAME); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property(ALIAS_START_INDEX); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property(ALIAS_END_INDEX); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + } +} diff --git a/src/components/org/apache/jmeter/config/KeystoreConfigResources.properties b/src/components/org/apache/jmeter/config/KeystoreConfigResources.properties new file mode 100644 index 00000000000..a3511ad4a38 --- /dev/null +++ b/src/components/org/apache/jmeter/config/KeystoreConfigResources.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=Keystore Configuration +# Groups +aliases.displayName=Aliases selection configuration +# fields +preload.displayName=Preload +preload.shortDescription=Preload Keystore before test. Setting is to true is usually the best option. +startIndex.displayName=Alias Start index (0-based) +startIndex.shortDescription=First index of Alias in Keystore +endIndex.displayName=Alias End index (0-based) +endIndex.shortDescription=Last index of Alias in Keystore. When using Variable name ensure it is large enough so that all keys are loaded at startup. +clientCertAliasVarName.displayName=Variable name holding certificate alias +clientCertAliasVarName.shortDescription=Variable name that will contain the alias to use for Cert authentication. Var content can come from CSV Data Set. \ No newline at end of file diff --git a/src/components/org/apache/jmeter/config/KeystoreConfigResources_fr.properties b/src/components/org/apache/jmeter/config/KeystoreConfigResources_fr.properties new file mode 100644 index 00000000000..aecb0cdcab3 --- /dev/null +++ b/src/components/org/apache/jmeter/config/KeystoreConfigResources_fr.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=Configuration du coffre de cl\u00E9s (JKS) +# Groups +aliases.displayName=S\u00E9lection des alias +# fields +preload.displayName=Pr\u00E9chargement +preload.shortDescription=Pr\u00E9chargement du coffre de cl\u00E9s (JKS) avant le d\u00E9marrage du test +startIndex.displayName=Num\u00E9ro d'index premi\u00E8re cl\u00E9 (d\u00E9marre \u00E0 0) +startIndex.shortDescription=Num\u00E9ro d'index du premier alias de cl\u00E9 dans le coffre de cl\u00E9s (JKS) +endIndex.displayName=Num\u00E9ro d'index derni\u00E8re cl\u00E9 (d\u00E9marre \u00E0 0) +endIndex.shortDescription=Num\u00E9ro d'index du dernier alias de cl\u00E9 dans le coffre de cl\u00E9s (JKS) +clientCertAliasVarName.displayName=Variable contenant l'alias du certificat +clientCertAliasVarName.shortDescription=Nom de la variable qui contiendra l'alias \u00E0 utiliser pour l'authentification par Certificat. La variable peut \u00E8tre aliment\u00E9e depuis un CSV Data Set. \ No newline at end of file diff --git a/src/components/org/apache/jmeter/config/RandomVariableConfig.java b/src/components/org/apache/jmeter/config/RandomVariableConfig.java new file mode 100644 index 00000000000..26a9f42efd9 --- /dev/null +++ b/src/components/org/apache/jmeter/config/RandomVariableConfig.java @@ -0,0 +1,245 @@ +/* + * 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.config; + +import java.text.DecimalFormat; +import java.util.Random; + +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.NoConfigMerge; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class RandomVariableConfig extends ConfigTestElement + implements TestBean, LoopIterationListener, NoThreadClone, NoConfigMerge +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + /* + * N.B. this class is shared between threads (NoThreadClone) so all access to variables + * needs to be protected by a lock (either sync. or volatile) to ensure safe publication. + */ + + private String minimumValue; + + private String maximumValue; + + private String variableName; + + private String outputFormat; + + private String randomSeed; + + private boolean perThread; + + // This class is not cloned per thread, so this is shared + private Random globalRandom = null; + + // Used for per-thread/user numbers + // Cannot be static, as random numbers are not to be shared between instances + private transient ThreadLocal perThreadRandom = initThreadLocal(); + + private ThreadLocal initThreadLocal() { + return new ThreadLocal() { + @Override + protected Random initialValue() { + init(); + return new Random(getRandomSeedAsLong()); + }}; + } + + private int n; + private long minimum; + + private Object readResolve(){ + perThreadRandom = initThreadLocal(); + return this; + } + + /* + * nextInt(n) returns values in the range [0,n), + * so n must be set to max-min+1 + */ + private void init(){ + final String minAsString = getMinimumValue(); + minimum = NumberUtils.toLong(minAsString); + final String maxAsString = getMaximumValue(); + long maximum = NumberUtils.toLong(maxAsString); + long rangeL=maximum-minimum+1; // This can overflow + if (minimum >= maximum){ + log.error("maximum("+maxAsString+") must be > minimum"+minAsString+")"); + n=0;// This is used as an error indicator + return; + } + if (rangeL > Integer.MAX_VALUE || rangeL <= 0){// check for overflow too + log.warn("maximum("+maxAsString+") - minimum"+minAsString+") must be <="+Integer.MAX_VALUE); + rangeL=Integer.MAX_VALUE; + } + n = (int)rangeL; + } + + /** {@inheritDoc} */ + @Override + public void iterationStart(LoopIterationEvent iterEvent) { + Random randGen=null; + if (getPerThread()){ + randGen = perThreadRandom.get(); + } else { + synchronized(this){ + if (globalRandom == null){ + init(); + globalRandom = new Random(getRandomSeedAsLong()); + } + randGen=globalRandom; + } + } + if (n <=0){ + return; + } + long nextRand = minimum + randGen.nextInt(n); + // Cannot use getThreadContext() as we are not cloned per thread + JMeterVariables variables = JMeterContextService.getContext().getVariables(); + variables.put(getVariableName(), formatNumber(nextRand)); + } + + // Use format to create number; if it fails, use the default + private String formatNumber(long value){ + String format = getOutputFormat(); + if (format != null && format.length() > 0) { + try { + DecimalFormat myFormatter = new DecimalFormat(format); + return myFormatter.format(value); + } catch (NumberFormatException ignored) { + log.warn("Exception formatting value:"+value + " at format:"+format+", using default"); + } catch (IllegalArgumentException ignored) { + log.warn("Exception formatting value:"+value + " at format:"+format+", using default"); + } + } + return Long.toString(value); + } + + /** + * @return the minValue + */ + public synchronized String getMinimumValue() { + return minimumValue; + } + + /** + * @param minValue the minValue to set + */ + public synchronized void setMinimumValue(String minValue) { + this.minimumValue = minValue; + } + + /** + * @return the maxvalue + */ + public synchronized String getMaximumValue() { + return maximumValue; + } + + /** + * @param maxvalue the maxvalue to set + */ + public synchronized void setMaximumValue(String maxvalue) { + this.maximumValue = maxvalue; + } + + /** + * @return the variableName + */ + public synchronized String getVariableName() { + return variableName; + } + + /** + * @param variableName the variableName to set + */ + public synchronized void setVariableName(String variableName) { + this.variableName = variableName; + } + + /** + * @return the randomSeed + */ + public synchronized String getRandomSeed() { + return randomSeed; + } + + /** + * @return the randomSeed as a long + */ + private synchronized long getRandomSeedAsLong() { + long seed = 0; + if (randomSeed.length()==0){ + seed = System.currentTimeMillis(); + } else { + try { + seed = Long.parseLong(randomSeed); + } catch (NumberFormatException e) { + seed = System.currentTimeMillis(); + log.warn("Cannot parse seed "+e.getLocalizedMessage()); + } + } + return seed; + } + + /** + * @param randomSeed the randomSeed to set + */ + public synchronized void setRandomSeed(String randomSeed) { + this.randomSeed = randomSeed; + } + + /** + * @return the perThread + */ + public synchronized boolean getPerThread() { + return perThread; + } + + /** + * @param perThread the perThread to set + */ + public synchronized void setPerThread(boolean perThread) { + this.perThread = perThread; + } + /** + * @return the outputFormat + */ + public synchronized String getOutputFormat() { + return outputFormat; + } + /** + * @param outputFormat the outputFormat to set + */ + public synchronized void setOutputFormat(String outputFormat) { + this.outputFormat = outputFormat; + } + +} diff --git a/src/components/org/apache/jmeter/config/RandomVariableConfigBeanInfo.java b/src/components/org/apache/jmeter/config/RandomVariableConfigBeanInfo.java new file mode 100644 index 00000000000..a307ceb99d8 --- /dev/null +++ b/src/components/org/apache/jmeter/config/RandomVariableConfigBeanInfo.java @@ -0,0 +1,78 @@ +/* + * 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.config; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class RandomVariableConfigBeanInfo extends BeanInfoSupport { + + // These group names must have .displayName properties + private static final String VARIABLE_GROUP = "variable"; // $NON-NLS-1$ + private static final String OPTIONS_GROUP = "options"; // $NON-NLS-1$ + private static final String RANDOM_GROUP = "random"; // $NON-NLS-1$ + + // These variable names must have .displayName properties and agree with the getXXX()/setXXX() methods + private static final String PER_THREAD = "perThread"; // $NON-NLS-1$ + private static final String RANDOM_SEED = "randomSeed"; // $NON-NLS-1$ + private static final String MAXIMUM_VALUE = "maximumValue"; // $NON-NLS-1$ + private static final String MINIMUM_VALUE = "minimumValue"; // $NON-NLS-1$ + private static final String OUTPUT_FORMAT = "outputFormat"; // $NON-NLS-1$ + private static final String VARIABLE_NAME = "variableName"; // $NON-NLS-1$ + + public RandomVariableConfigBeanInfo() { + super(RandomVariableConfig.class); + + PropertyDescriptor p; + + createPropertyGroup(VARIABLE_GROUP, new String[] { VARIABLE_NAME, OUTPUT_FORMAT, }); + + p = property(VARIABLE_NAME); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property(OUTPUT_FORMAT); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + createPropertyGroup(RANDOM_GROUP, + new String[] { MINIMUM_VALUE, MAXIMUM_VALUE, RANDOM_SEED, }); + + p = property(MINIMUM_VALUE); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "1"); // $NON-NLS-1$ + + p = property(MAXIMUM_VALUE); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property(RANDOM_SEED); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + createPropertyGroup(OPTIONS_GROUP, new String[] { PER_THREAD, }); + + p = property(PER_THREAD); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + } +} diff --git a/src/components/org/apache/jmeter/config/RandomVariableConfigResources.properties b/src/components/org/apache/jmeter/config/RandomVariableConfigResources.properties new file mode 100644 index 00000000000..4967cccb03c --- /dev/null +++ b/src/components/org/apache/jmeter/config/RandomVariableConfigResources.properties @@ -0,0 +1,33 @@ +# 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. + +displayName=Random Variable +# Groups +variable.displayName=Output variable +random.displayName=Configure the Random generator +options.displayName=Options +# fields +minimumValue.displayName=Minimum Value +minimumValue.shortDescription=Minimum Value +maximumValue.displayName=Maximum Value +maximumValue.shortDescription=Maximum Value +variableName.displayName=Variable Name +variableName.shortDescription=Variable Name +outputFormat.displayName=Output Format +outputFormat.shortDescription=Output Format, e.g. #### +randomSeed.displayName=Seed for Random function +randomSeed.shortDescription=Seed for Random function - long number (defaults to current time) +perThread.displayName=Per Thread(User) ? +perThread.shortDescription=Use independent random generators for each thread(user) ? diff --git a/src/components/org/apache/jmeter/config/RandomVariableConfigResources_es.properties b/src/components/org/apache/jmeter/config/RandomVariableConfigResources_es.properties new file mode 100644 index 00000000000..e9d900f8ab3 --- /dev/null +++ b/src/components/org/apache/jmeter/config/RandomVariableConfigResources_es.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Variable aleatoria +maximumValue.displayName=Valor m\u00E1ximo +maximumValue.shortDescription=Valor m\u00E1ximo +minimumValue.displayName=Valor m\u00EDnimo +minimumValue.shortDescription=Valor m\u00EDnimo +options.displayName=Opciones +outputFormat.displayName=Formato de salida +outputFormat.shortDescription=Formato de salida, e.g. \#\#\#\# +perThread.displayName=\u00BFPor hilo(Usuario)? +perThread.shortDescription=\u00BFUsar generadores aleatorios independientes para cada hilo (usuario)? +random.displayName=Configurar el generador aleatorio +randomSeed.displayName=Semilla para la funci\u00F3n aleatoria +randomSeed.shortDescription=Semilla para la funci\u00F3n aleatoria - n\u00FAmero long (por defecto para la hora actual) +variable.displayName=Variable de salida +variableName.displayName=Nombre de variable +variableName.shortDescription=Nombre de variable diff --git a/src/components/org/apache/jmeter/config/RandomVariableConfigResources_fr.properties b/src/components/org/apache/jmeter/config/RandomVariableConfigResources_fr.properties new file mode 100644 index 00000000000..dccc2ae2077 --- /dev/null +++ b/src/components/org/apache/jmeter/config/RandomVariableConfigResources_fr.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Variable al\u00E9atoire +maximumValue.displayName=Valeur maximum +maximumValue.shortDescription=Valeur maximum +minimumValue.displayName=Valeur minimum +minimumValue.shortDescription=Valeur minimum +options.displayName=Options +outputFormat.displayName=Format de sortie +outputFormat.shortDescription=Format de sortie, i.e. \#\#\#\# +perThread.displayName=Par unit\u00E9 (utilisateur) ? +perThread.shortDescription=Utiliser des g\u00E9n\u00E9rateurs al\u00E9atoires ind\u00E9pendants pour chaque unit\u00E9 (utilisateur) ? +random.displayName=Param\u00E9trer le g\u00E9n\u00E9rateur al\u00E9atoire +randomSeed.displayName=Sels pour la fonction al\u00E9atoire +randomSeed.shortDescription=Sels pour la fonction al\u00E9atoire - chiffre long (par d\u00E9faut \u00E0 l'heure courante) +variable.displayName=Variable de sortie +variableName.displayName=Nom de variable +variableName.shortDescription=Nom de variable diff --git a/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pl.properties b/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pl.properties new file mode 100644 index 00000000000..b68cc7be81d --- /dev/null +++ b/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pl.properties @@ -0,0 +1,34 @@ +# 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. +# Uwagi do tlumaczenia: mr0vek@o2.pl + +displayName=Random Variable +# Groups +variable.displayName=Output variable +random.displayName=Configure the Random generator +options.displayName=Options +# fields +minimumValue.displayName=Minimum Value +minimumValue.shortDescription=Minimum Value +maximumValue.displayName=Maximum Value +maximumValue.shortDescription=Maximum Value +variableName.displayName=Variable Name +variableName.shortDescription=Variable Name +outputFormat.displayName=Output Format +outputFormat.shortDescription=Output Format, e.g. #### +randomSeed.displayName=Seed for Random function +randomSeed.shortDescription=Seed for Random function - long number (defaults to current time) +perThread.displayName=Per Thread(User) ? +perThread.shortDescription=Use independent random generators for each thread(user) ? diff --git a/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pt_BR.properties b/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pt_BR.properties new file mode 100644 index 00000000000..ac3b13d7c1b --- /dev/null +++ b/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pt_BR.properties @@ -0,0 +1,31 @@ +# 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. + +displayName=Vari\u00E1vel Aleat\u00F3ria +maximumValue.displayName=Valor M\u00E1ximo +maximumValue.shortDescription=Valor M\u00E1ximo +minimumValue.displayName=Valor M\u00EDnimo +minimumValue.shortDescription=Valor M\u00EDnimo +options.displayName=Op\u00E7\u00F5es +outputFormat.displayName=Formato de sa\u00EDda +outputFormat.shortDescription=Formato de sa\u00EDda, ex\: \#\#\#\# +perThread.displayName=Por usu\u00E1rio virtual (thread)? +perThread.shortDescription=Utilizar geradores aleat\u00F3rios independentes para cada usu\u00E1rio virtual (thread)? +random.displayName=Configurar o gerador aleat\u00F3rio +randomSeed.displayName=Semente para a fun\u00E7\u00E3o aleat\u00F3ria +randomSeed.shortDescription=Semente para a fun\u00E7\u00E3o aleat\u00F3ria - n\u00FAmero do tipo long (valor padr\u00E3o para o tempo atual) +variable.displayName=Vari\u00E1vel de sa\u00EDda +variableName.displayName=Nome da Vari\u00E1vel +variableName.shortDescription=Nome da Vari\u00E1vel diff --git a/src/components/org/apache/jmeter/control/CriticalSectionController.java b/src/components/org/apache/jmeter/control/CriticalSectionController.java new file mode 100644 index 00000000000..7714fbc343b --- /dev/null +++ b/src/components/org/apache/jmeter/control/CriticalSectionController.java @@ -0,0 +1,205 @@ +/* + * 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.control; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This is a Critical Section Controller; it will execute the set of statements + * (samplers/controllers, etc) under named lock. + *

+ * In a programming world - this is equivalent of : + * + *

+ * try {
+ *          named_lock.lock();
+ *          statements ....
+ * } finally {
+ *          named_lock.unlock();
+ * }
+ * 
+ * + * In JMeter you may have : + * + *
+ * Thread-Group (set to loop a number of times or indefinitely,
+ *    ... Samplers ... (e.g. Counter )
+ *    ... Other Controllers ....
+ *    ... CriticalSectionController ( lock name like "foobar" )
+ *       ... statements to perform when lock acquired
+ *       ...
+ *    ... Other Controllers /Samplers }
+ * 
+ * + * @since 2.12 + */ +public class CriticalSectionController extends GenericController implements + ThreadListener, TestStateListener { + + /** + * + */ + private static final long serialVersionUID = 4362876132435968088L; + + private static final Logger logger = LoggingManager.getLoggerForClass(); + + private static final String LOCK_NAME = "CriticalSectionController.lockName"; //$NON-NLS-1$ + + private static final ConcurrentHashMap LOCK_MAP = new ConcurrentHashMap(); + + private transient volatile ReentrantLock currentLock; + + /** + * constructor + */ + public CriticalSectionController() { + super(); + } + + /** + * constructor + * @param name The name of this controller + */ + public CriticalSectionController(String name) { + super(); + this.setName(name); + } + + /** + * Condition Accessor - this is gonna be any string value + * @param name The name of the lock for this controller + */ + public void setLockName(String name) { + setProperty(new StringProperty(LOCK_NAME, name)); + } + + /** + * If lock exists returns it, otherwise creates one, puts it in LOCK_MAP + * then returns it + * + * @return {@link ReentrantLock} + */ + private ReentrantLock getOrCreateLock() { + String lockName = getLockName(); + ReentrantLock lock = LOCK_MAP.get(lockName); + ReentrantLock prev = null; + if (lock != null) { + return lock; + } + lock = new ReentrantLock(); + prev = LOCK_MAP.putIfAbsent(lockName, lock); + return prev == null ? lock : prev; + } + + /** + * @return String lock name + */ + public String getLockName() { + return getPropertyAsString(LOCK_NAME); + } + + /** + * @see org.apache.jmeter.control.Controller#next() + */ + @Override + public Sampler next() { + if (StringUtils.isEmpty(getLockName())) { + logger.warn("Empty lock name in Critical Section Controller:" + + getName()); + return super.next(); + } + if (isFirst()) { + // Take the lock for first child element + long startTime = System.currentTimeMillis(); + if (this.currentLock == null) { + this.currentLock = getOrCreateLock(); + } + this.currentLock.lock(); + long endTime = System.currentTimeMillis(); + if (logger.isDebugEnabled()) { + logger.debug(Thread.currentThread().getName() + + " acquired lock:'" + getLockName() + + "' in Critical Section Controller " + getName() + + " in:" + (endTime - startTime) + " ms"); + } + } + return super.next(); + } + + /** + * Called after execution of last child of the controller We release lock + * + * @see org.apache.jmeter.control.GenericController#reInitialize() + */ + @Override + protected void reInitialize() { + if (this.currentLock != null) { + if (currentLock.isHeldByCurrentThread()) { + this.currentLock.unlock(); + } + this.currentLock = null; + } + super.reInitialize(); + } + + @Override + public void threadStarted() { + this.currentLock = null; + } + + @Override + public void threadFinished() { + if (this.currentLock != null + && this.currentLock.isHeldByCurrentThread()) { + logger.warn("Lock " + getLockName() + " not released in:" + + getName() + ", releasing in threadFinished"); + this.currentLock.unlock(); + } + this.currentLock = null; + } + + @Override + public void testStarted() { + // NOOP + } + + @Override + public void testStarted(String host) { + // NOOP + } + + @Override + public void testEnded() { + LOCK_MAP.clear(); + } + + @Override + public void testEnded(String host) { + testEnded(); + } +} diff --git a/src/components/org/apache/jmeter/control/ForeachController.java b/src/components/org/apache/jmeter/control/ForeachController.java new file mode 100644 index 00000000000..54cb0311a5a --- /dev/null +++ b/src/components/org/apache/jmeter/control/ForeachController.java @@ -0,0 +1,284 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * ForeachController that iterates over a list of variables named XXXX_NN stored in {@link JMeterVariables} + * where NN is a number starting from 1 to number of occurences. + * This list of variable is usually set by PostProcessor (Regexp PostProcessor or {@link org.apache.jmeter.extractor.HtmlExtractor}) + * Iteration can take the full list or only a subset (configured through indexes) + * + */ +public class ForeachController extends GenericController implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final String INPUTVAL = "ForeachController.inputVal";// $NON-NLS-1$ + + private static final String START_INDEX = "ForeachController.startIndex";// $NON-NLS-1$ + + private static final String END_INDEX = "ForeachController.endIndex";// $NON-NLS-1$ + + private static final String RETURNVAL = "ForeachController.returnVal";// $NON-NLS-1$ + + private static final String USE_SEPARATOR = "ForeachController.useSeparator";// $NON-NLS-1$ + + private static final String INDEX_DEFAULT_VALUE = ""; // start/end index default value for string getters and setters + + private int loopCount = 0; + + private static final String DEFAULT_SEPARATOR = "_";// $NON-NLS-1$ + + public ForeachController() { + } + + + /** + * @param startIndex Start index of loop + */ + public void setStartIndex(String startIndex) { + setProperty(START_INDEX, startIndex, INDEX_DEFAULT_VALUE); + } + + /** + * @return start index of loop + */ + private int getStartIndex() { + // Although the default is not the same as for the string value, it is only used internally + return getPropertyAsInt(START_INDEX, 0); + } + + + /** + * @return start index of loop as String + */ + public String getStartIndexAsString() { + return getPropertyAsString(START_INDEX, INDEX_DEFAULT_VALUE); + } + + /** + * @param endIndex End index of loop + */ + public void setEndIndex(String endIndex) { + setProperty(END_INDEX, endIndex, INDEX_DEFAULT_VALUE); + } + + /** + * @return end index of loop + */ + private int getEndIndex() { + // Although the default is not the same as for the string value, it is only used internally + return getPropertyAsInt(END_INDEX, Integer.MAX_VALUE); + } + + /** + * @return end index of loop + */ + public String getEndIndexAsString() { + return getPropertyAsString(END_INDEX, INDEX_DEFAULT_VALUE); + } + + public void setInputVal(String inputValue) { + setProperty(new StringProperty(INPUTVAL, inputValue)); + } + + private String getInputVal() { + getProperty(INPUTVAL).recoverRunningVersion(null); + return getInputValString(); + } + + public String getInputValString() { + return getPropertyAsString(INPUTVAL); + } + + public void setReturnVal(String inputValue) { + setProperty(new StringProperty(RETURNVAL, inputValue)); + } + + private String getReturnVal() { + getProperty(RETURNVAL).recoverRunningVersion(null); + return getReturnValString(); + } + + public String getReturnValString() { + return getPropertyAsString(RETURNVAL); + } + + private String getSeparator() { + return getUseSeparator() ? DEFAULT_SEPARATOR : "";// $NON-NLS-1$ + } + + public void setUseSeparator(boolean b) { + setProperty(new BooleanProperty(USE_SEPARATOR, b)); + } + + public boolean getUseSeparator() { + return getPropertyAsBoolean(USE_SEPARATOR, true); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDone() { + if (loopCount >= getEndIndex()) { + return true; + } + JMeterContext context = getThreadContext(); + StringBuilder builder = new StringBuilder( + getInputVal().length()+getSeparator().length()+3); + String inputVariable = + builder.append(getInputVal()) + .append(getSeparator()) + .append(Integer.toString(loopCount+1)).toString(); + final JMeterVariables variables = context.getVariables(); + final Object currentVariable = variables.getObject(inputVariable); + if (currentVariable != null) { + variables.putObject(getReturnVal(), currentVariable); + if (log.isDebugEnabled()) { + log.debug("ForEach resultstring isDone=" + variables.get(getReturnVal())); + } + return false; + } + return super.isDone(); + } + + /** + * Tests that JMeterVariables contain inputVal_, if not we can stop iterating + */ + private boolean endOfArguments() { + JMeterContext context = getThreadContext(); + String inputVariable = getInputVal() + getSeparator() + (loopCount + 1); + if (context.getVariables().getObject(inputVariable) != null) { + log.debug("ForEach resultstring eofArgs= false"); + return false; + } + log.debug("ForEach resultstring eofArgs= true"); + return true; + } + + // Prevent entry if nothing to do + @Override + public Sampler next() { + if (emptyList()) { + reInitialize(); + resetLoopCount(); + return null; + } + return super.next(); + } + + /** + * Check if there are any matching entries + * + * @return whether any entries in the list + */ + private boolean emptyList() { + JMeterContext context = getThreadContext(); + + StringBuilder builder = new StringBuilder( + getInputVal().length()+getSeparator().length()+3); + String inputVariable = + builder.append(getInputVal()) + .append(getSeparator()) + .append(Integer.toString(loopCount+1)).toString(); + if (context.getVariables().getObject(inputVariable) != null) { + return false; + } + if (log.isDebugEnabled()) { + log.debug("No entries found - null first entry: " + inputVariable); + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + // Conditions to reset the loop count + if (endOfArguments() // no more variables to iterate + ||loopCount >= getEndIndex() // we reached end index + ) { + // setDone(true); + resetLoopCount(); + return null; + } + return next(); + } + + protected void incrementLoopCount() { + loopCount++; + } + + protected void resetLoopCount() { + loopCount = getStartIndex(); + } + + /** + * {@inheritDoc} + */ + @Override + protected int getIterCount() { + return loopCount + 1; + } + + /** + * {@inheritDoc} + */ + @Override + protected void reInitialize() { + setFirst(true); + resetCurrent(); + incrementLoopCount(); + recoverRunningVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + super.triggerEndOfLoop(); + resetLoopCount(); + } + + + /** + * Reset loopCount to Start index + * @see org.apache.jmeter.control.GenericController#initialize() + */ + @Override + public void initialize() { + super.initialize(); + loopCount = getStartIndex(); + } +} diff --git a/src/components/org/apache/jmeter/control/IncludeController.java b/src/components/org/apache/jmeter/control/IncludeController.java new file mode 100644 index 00000000000..437f6713aab --- /dev/null +++ b/src/components/org/apache/jmeter/control/IncludeController.java @@ -0,0 +1,210 @@ +/* + * 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.control; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; + +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class IncludeController extends GenericController implements ReplaceableController { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final String INCLUDE_PATH = "IncludeController.includepath"; //$NON-NLS-1$ + + private static final String prefix = + JMeterUtils.getPropDefault( + "includecontroller.prefix", //$NON-NLS-1$ + ""); //$NON-NLS-1$ + + private HashTree subtree = null; + private TestElement sub = null; + + /** + * No-arg constructor + * + * @see java.lang.Object#Object() + */ + public IncludeController() { + super(); + } + + @Override + public Object clone() { + // TODO - fix so that this is only called once per test, instead of at every clone + // Perhaps save previous filename, and only load if it has changed? + this.resolveReplacementSubTree(null); + IncludeController clone = (IncludeController) super.clone(); + clone.setIncludePath(this.getIncludePath()); + if (this.subtree != null) { + if (this.subtree.size() == 1) { + Iterator itr = this.subtree.keySet().iterator(); + while (itr.hasNext()) { + this.sub = (TestElement) itr.next(); + } + } + clone.subtree = (HashTree)this.subtree.clone(); + clone.sub = this.sub==null ? null : (TestElement) this.sub.clone(); + } + return clone; + } + + /** + * In the event an user wants to include an external JMX test plan + * the GUI would call this. + * @param jmxfile The path to the JMX test plan to include + */ + public void setIncludePath(String jmxfile) { + this.setProperty(INCLUDE_PATH,jmxfile); + } + + /** + * return the JMX file path. + * @return the JMX file path + */ + public String getIncludePath() { + return this.getPropertyAsString(INCLUDE_PATH); + } + + /** + * The way ReplaceableController works is clone is called first, + * followed by replace(HashTree) and finally getReplacement(). + */ + @Override + public HashTree getReplacementSubTree() { + return subtree; + } + + public TestElement getReplacementElement() { + return sub; + } + + @Override + public void resolveReplacementSubTree(JMeterTreeNode context) { + this.subtree = this.loadIncludedElements(); + } + + /** + * load the included elements using SaveService + * + * @return tree with loaded elements + */ + protected HashTree loadIncludedElements() { + // only try to load the JMX test plan if there is one + final String includePath = getIncludePath(); + HashTree tree = null; + if (includePath != null && includePath.length() > 0) { + try { + String fileName=prefix+includePath; + File file = new File(fileName); + final String absolutePath = file.getAbsolutePath(); + log.info("loadIncludedElements -- try to load included module: "+absolutePath); + if(!file.exists() && !file.isAbsolute()){ + log.info("loadIncludedElements -failed for: "+absolutePath); + file = new File(FileServer.getFileServer().getBaseDir(), includePath); + log.info("loadIncludedElements -Attempting to read it from: "+absolutePath); + if(!file.exists()){ + log.error("loadIncludedElements -failed for: "+absolutePath); + throw new IOException("loadIncludedElements -failed for: "+absolutePath); + } + } + + tree = SaveService.loadTree(file); + // filter the tree for a TestFragment. + tree = getProperBranch(tree); + removeDisabledItems(tree); + return tree; + } catch (NoClassDefFoundError ex) // Allow for missing optional jars + { + String msg = ex.getMessage(); + if (msg == null) { + msg = "Missing jar file - see log for details"; + } + log.warn("Missing jar file", ex); + JMeterUtils.reportErrorToUser(msg); + } catch (FileNotFoundException ex) { + String msg = ex.getMessage(); + JMeterUtils.reportErrorToUser(msg); + log.warn(msg); + } catch (Exception ex) { + String msg = ex.getMessage(); + if (msg == null) { + msg = "Unexpected error - see log for details"; + } + JMeterUtils.reportErrorToUser(msg); + log.warn("Unexpected error", ex); + } + } + return tree; + } + + /** + * Extract from tree (included test plan) all Test Elements located in a Test Fragment + * @param tree HashTree included Test Plan + * @return HashTree Subset within Test Fragment or Empty HashTree + */ + private HashTree getProperBranch(HashTree tree) { + Iterator iter = new LinkedList(tree.list()).iterator(); + while (iter.hasNext()) { + TestElement item = (TestElement) iter.next(); + + //if we found a TestPlan, then we are on our way to the TestFragment + if (item instanceof TestPlan) + { + return getProperBranch(tree.getTree(item)); + } + + if (item instanceof TestFragmentController) + { + return tree.getTree(item); + } + } + log.warn("No Test Fragment was found in included Test Plan, returning empty HashTree"); + return new HashTree(); + } + + + private void removeDisabledItems(HashTree tree) { + Iterator iter = new LinkedList(tree.list()).iterator(); + while (iter.hasNext()) { + TestElement item = (TestElement) iter.next(); + if (!item.isEnabled()) { + //log.info("Removing "+item.toString()); + tree.remove(item); + } else { + //log.info("Keeping "+item.toString()); + removeDisabledItems(tree.getTree(item));// Recursive call + } + } + } + +} diff --git a/src/components/org/apache/jmeter/control/InterleaveControl.java b/src/components/org/apache/jmeter/control/InterleaveControl.java new file mode 100644 index 00000000000..912662b2fca --- /dev/null +++ b/src/components/org/apache/jmeter/control/InterleaveControl.java @@ -0,0 +1,177 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; + +/** + * Alternate among each of the children controllers or samplers for each loop iteration + */ +public class InterleaveControl extends GenericController implements Serializable { + private static final long serialVersionUID = 233L; + + private static final String STYLE = "InterleaveControl.style";// $NON-NLS-1$ + + public static final int IGNORE_SUB_CONTROLLERS = 0; + + public static final int USE_SUB_CONTROLLERS = 1; + + private boolean skipNext; + + private transient TestElement searchStart = null; + + private boolean currentReturnedAtLeastOne; + + private boolean stillSame = true; + + /*************************************************************************** + * Constructor for the InterleaveControl object + **************************************************************************/ + public InterleaveControl() { + } + + /** + * {@inheritDoc} + */ + @Override + public void reInitialize() { + setFirst(true); + currentReturnedAtLeastOne = false; + searchStart = null; + stillSame = true; + skipNext = false; + incrementIterCount(); + recoverRunningVersion(); + } + + public void setStyle(int style) { + setProperty(new IntegerProperty(STYLE, style)); + } + + public int getStyle() { + return getPropertyAsInt(STYLE); + } + + /** + * {@inheritDoc} + */ + @Override + public Sampler next() { + if (isSkipNext()) { + reInitialize(); + return null; + } + return super.next(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsAController(Controller controller) throws NextIsNullException { + Sampler sampler = controller.next(); + if (sampler == null) { + currentReturnedNull(controller); + return next(); + } + currentReturnedAtLeastOne = true; + if (getStyle() == IGNORE_SUB_CONTROLLERS) { + incrementCurrent(); + skipNext = true; + } else { + searchStart = null; + } + return sampler; + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsASampler(Sampler element) throws NextIsNullException { + skipNext = true; + incrementCurrent(); + return element; + } + + /** + * If the current is null, reset and continue searching. The searchStart + * attribute will break us off when we start a repeat. + *

+ * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() { + resetCurrent(); + return next(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setCurrentElement(TestElement currentElement) throws NextIsNullException { + // Set the position when next is first called, and don't overwrite + // until reInitialize is called. + if (searchStart == null) { + searchStart = currentElement; + } else if (searchStart == currentElement && !stillSame) { + // We've gone through the whole list and are now back at the start + // point of our search. + reInitialize(); + throw new NextIsNullException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void currentReturnedNull(Controller c) { + if (c.isDone()) { + removeCurrentElement(); + } else if (getStyle() == USE_SUB_CONTROLLERS) { + incrementCurrent(); + } + } + + protected boolean isSkipNext() { + return skipNext; + } + + protected void setSkipNext(boolean skipNext) { + this.skipNext = skipNext; + } + + /** + * {@inheritDoc} + */ + @Override + protected void incrementCurrent() { + if (currentReturnedAtLeastOne) { + skipNext = true; + } + stillSame = false; + super.incrementCurrent(); + } +} diff --git a/src/components/org/apache/jmeter/control/ModuleController.java b/src/components/org/apache/jmeter/control/ModuleController.java new file mode 100644 index 00000000000..e83e9f8b713 --- /dev/null +++ b/src/components/org/apache/jmeter/control/ModuleController.java @@ -0,0 +1,206 @@ +/* + * 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.control; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.TreeNode; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; + +/** + * The goal of ModuleController is to add modularity to JMeter. The general idea + * is that web applications consist of small units of functionality (i.e. Logon, + * Create Account, Logoff...) which consist of requests that implement the + * functionality. These small units of functionality can be stored in + * SimpleControllers as modules that can be linked together quickly to form + * tests. ModuleController facilitates this by acting as a pointer to any + * controller that sits under the WorkBench. The controller and it's subelements + * will be substituted in place of the ModuleController at runtime. Config + * elements can be attached to the ModuleController to alter the functionality + * (which user logs in, which account is created, etc.) of the module. + * + */ +public class ModuleController extends GenericController implements ReplaceableController { + + private static final long serialVersionUID = 240L; + + private static final String NODE_PATH = "ModuleController.node_path";// $NON-NLS-1$ + + private transient JMeterTreeNode selectedNode = null; + + /** + * No-arg constructor + * + * @see java.lang.Object#Object() + */ + public ModuleController() { + super(); + } + + @Override + public Object clone() { + ModuleController clone = (ModuleController) super.clone(); + if (selectedNode == null) { + this.restoreSelected(); + } + clone.selectedNode = selectedNode; // TODO ?? (JMeterTreeNode) selectedNode.clone(); + return clone; + } + + /** + * Sets the (@link JMeterTreeNode) which represents the controller which + * this object is pointing to. Used for building the test case upon + * execution. + * + * @param tn + * JMeterTreeNode + * @see org.apache.jmeter.gui.tree.JMeterTreeNode + */ + public void setSelectedNode(JMeterTreeNode tn) { + selectedNode = tn; + setNodePath(); + } + + /** + * Gets the (@link JMeterTreeNode) for the Controller + * + * @return JMeterTreeNode + */ + public JMeterTreeNode getSelectedNode() { + if (selectedNode == null){ + restoreSelected(); + } + return selectedNode; + } + + private void setNodePath() { + List nodePath = new ArrayList(); + if (selectedNode != null) { + TreeNode[] path = selectedNode.getPath(); + for (TreeNode node : path) { + nodePath.add(((JMeterTreeNode) node).getName()); + } + // nodePath.add(selectedNode.getName()); + } + setProperty(new CollectionProperty(NODE_PATH, nodePath)); + } + + public List getNodePath() { + JMeterProperty prop = getProperty(NODE_PATH); + if (!(prop instanceof NullProperty)) { + return (List) ((CollectionProperty) prop).getObjectValue(); + } + return null; + } + + private void restoreSelected() { + GuiPackage gp = GuiPackage.getInstance(); + if (gp != null) { + JMeterTreeNode root = (JMeterTreeNode) gp.getTreeModel().getRoot(); + resolveReplacementSubTree(root); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void resolveReplacementSubTree(JMeterTreeNode context) { + if (selectedNode == null) { + List nodePathList = getNodePath(); + if (nodePathList != null && nodePathList.size() > 0) { + traverse(context, nodePathList, 1); + } + } + } + + private void traverse(JMeterTreeNode node, List nodePath, int level) { + if (node != null && nodePath.size() > level) { + for (int i = 0; i < node.getChildCount(); i++) { + JMeterTreeNode cur = (JMeterTreeNode) node.getChildAt(i); + // Bug55375 - don't allow selectedNode to be a ModuleController as can cause recursion + if (!(cur.getTestElement() instanceof ModuleController)) { + if (cur.getName().equals(nodePath.get(level).toString())) { + if (nodePath.size() == (level + 1)) { + selectedNode = cur; + } + traverse(cur, nodePath, level + 1); + } + } + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public HashTree getReplacementSubTree() { + HashTree tree = new ListedHashTree(); + if (selectedNode != null) { + // Use a local variable to avoid replacing reference by modified clone (see Bug 54950) + JMeterTreeNode nodeToReplace = selectedNode; + // We clone to avoid enabling existing node + if (!nodeToReplace.isEnabled()) { + nodeToReplace = cloneTreeNode(selectedNode); + nodeToReplace.setEnabled(true); + } + HashTree subtree = tree.add(nodeToReplace); + createSubTree(subtree, nodeToReplace); + } + return tree; + } + + private void createSubTree(HashTree tree, JMeterTreeNode node) { + Enumeration e = node.children(); + while (e.hasMoreElements()) { + JMeterTreeNode subNode = e.nextElement(); + tree.add(subNode); + createSubTree(tree.getTree(subNode), subNode); + } + } + + private static JMeterTreeNode cloneTreeNode(JMeterTreeNode node) { + JMeterTreeNode treeNode = (JMeterTreeNode) node.clone(); + treeNode.setUserObject(((TestElement) node.getUserObject()).clone()); + cloneChildren(treeNode, node); + return treeNode; + } + + private static void cloneChildren(JMeterTreeNode to, JMeterTreeNode from) { + Enumeration enumr = from.children(); + while (enumr.hasMoreElements()) { + JMeterTreeNode child = enumr.nextElement(); + JMeterTreeNode childClone = (JMeterTreeNode) child.clone(); + childClone.setUserObject(((TestElement) child.getUserObject()).clone()); + to.add(childClone); + cloneChildren((JMeterTreeNode) to.getLastChild(), child); + } + } +} diff --git a/src/components/org/apache/jmeter/control/OnceOnlyController.java b/src/components/org/apache/jmeter/control/OnceOnlyController.java new file mode 100644 index 00000000000..8086cf060b5 --- /dev/null +++ b/src/components/org/apache/jmeter/control/OnceOnlyController.java @@ -0,0 +1,59 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; + +/** + * Controller to run its children once per cycle. + */ +public class OnceOnlyController extends GenericController implements Serializable, LoopIterationListener { + + private static final long serialVersionUID = 240L; + + /** + * Constructor for the OnceOnlyController object. + */ + public OnceOnlyController() { + } + + /** + * @see LoopIterationListener#iterationStart(LoopIterationEvent) + */ + @Override + public void iterationStart(LoopIterationEvent event) { + int numIteration = 1; + // Bug 39509: iteration to 0 for all controller which not LoopController (and TG) + if (!(event.getSource() instanceof LoopController)) { + numIteration = 0; + } + if (event.getIteration() == numIteration) { + reInitialize(); + } + } + + @Override + protected Sampler nextIsNull() throws NextIsNullException { + return null; + } +} diff --git a/src/components/org/apache/jmeter/control/RandomController.java b/src/components/org/apache/jmeter/control/RandomController.java new file mode 100644 index 00000000000..326ef923cc3 --- /dev/null +++ b/src/components/org/apache/jmeter/control/RandomController.java @@ -0,0 +1,55 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.util.ThreadLocalRandom; + +/** + * Controller that rans randomly one of it's children on each iteration + */ +public class RandomController extends InterleaveControl implements Serializable { + private static final long serialVersionUID = 240L; + + public RandomController() { + } + + /** + * @see org.apache.jmeter.control.GenericController#resetCurrent() + */ + @Override + protected void resetCurrent() { + if (getSubControllers().size() > 0) { + current = ThreadLocalRandom.current().nextInt(this.getSubControllers().size()); + } else { + current = 0; + } + } + + /** + * @see org.apache.jmeter.control.GenericController#incrementCurrent() + */ + @Override + protected void incrementCurrent() { + super.incrementCurrent(); + current = ThreadLocalRandom.current().nextInt(this.getSubControllers().size()); + } + +} diff --git a/src/components/org/apache/jmeter/control/RandomOrderController.java b/src/components/org/apache/jmeter/control/RandomOrderController.java new file mode 100644 index 00000000000..50a107c347c --- /dev/null +++ b/src/components/org/apache/jmeter/control/RandomOrderController.java @@ -0,0 +1,88 @@ +/* + * 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.control; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.jmeter.testelement.TestElement; + +/** + * A controller that runs its children each at most once, but in a random order. + * + */ +public class RandomOrderController extends GenericController implements Serializable { + + private static final long serialVersionUID = 240L; + + /** + * Create a new RandomOrderController. + */ + public RandomOrderController() { + } + + /** + * @see GenericController#initialize() + */ + @Override + public void initialize() { + super.initialize(); + this.reorder(); + } + + /** + * @see GenericController#reInitialize() + */ + @Override + protected void reInitialize() { + super.reInitialize(); + this.reorder(); + } + + /** + * Replace the subControllersAndSamplers list with a reordered ArrayList. + */ + private void reorder() { + int numElements = this.subControllersAndSamplers.size(); + + // Create a new list containing numElements null elements. + List reordered = new ArrayList(this.subControllersAndSamplers.size()); + for (int i = 0; i < numElements; i++) { + reordered.add(null); + } + + // Insert the subControllersAndSamplers into random list positions. + for (Iterator i = this.subControllersAndSamplers.iterator(); i.hasNext();) { + int idx = (int) Math.floor(Math.random() * reordered.size()); + while (true) { + if (idx == numElements) { + idx = 0; + } + if (reordered.get(idx) == null) { + reordered.set(idx, i.next()); + break; + } + idx++; + } + } + + // Replace subControllersAndSamplers with reordered copy. + this.subControllersAndSamplers = reordered; + } +} diff --git a/src/components/org/apache/jmeter/control/SwitchController.java b/src/components/org/apache/jmeter/control/SwitchController.java new file mode 100644 index 00000000000..32b48191feb --- /dev/null +++ b/src/components/org/apache/jmeter/control/SwitchController.java @@ -0,0 +1,126 @@ +/* + * 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.control; + +import java.io.Serializable; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.StringProperty; + +// For unit tests @see TestSwitchController + +/** + *

+ * Implements a controller which selects at most one of its children + * based on the condition value, which may be a number or a string. + *

+ *

+ * For numeric input, the controller processes the appropriate child, + * where the numbering starts from 0. + * If the number is out of range, then the first (0th) child is selected. + * If the condition is the empty string, then it is assumed to be 0. + *

+ *

+ * For non-empty non-numeric input, the child is selected by name. + * This may be the name of the controller or a sampler. + * If the string does not match any of the names, then the controller + * with the name "default" (any case) is processed. + * If there is no default entry, then unlike the numeric case, + * no child is selected. + *

+ */ +public class SwitchController extends GenericController implements Serializable { + private static final long serialVersionUID = 240L; + + // Package access for use by Test code + static final String SWITCH_VALUE = "SwitchController.value"; //$NON-NLS-1$ + + public SwitchController() { + super(); + } + + @Override + public Sampler next() { + if (isFirst()) { // Set the selection once per iteration + current = getSelectionAsInt(); + } + return super.next(); + } + + /** + * incrementCurrent is called when the current child (whether sampler or controller) + * has been processed. + *

+ * Setting it to int.max marks the controller as having processed all its + * children. Thus the controller processes one child per iteration. + *

+ * {@inheritDoc} + */ + @Override + protected void incrementCurrent() { + current=Integer.MAX_VALUE; + } + + public void setSelection(String inputValue) { + setProperty(new StringProperty(SWITCH_VALUE, inputValue)); + } + + /* + * Returns the selection value as a int, + * with the value set to zero if it is out of range. + */ + private int getSelectionAsInt() { + int ret; + getProperty(SWITCH_VALUE).recoverRunningVersion(null); + String sel = getSelection(); + try { + ret = Integer.parseInt(sel); + if (ret < 0 || ret >= getSubControllers().size()) { + ret = 0; + } + } catch (NumberFormatException e) { + if (sel.length()==0) { + ret = 0; + } else { + ret = scanControllerNames(sel); + } + } + return ret; + } + + private int scanControllerNames(String sel){ + int i = 0; + int default_pos = Integer.MAX_VALUE; + for(TestElement el : getSubControllers()) { + String name=el.getName(); + if (name.equals(sel)) { + return i; + } + if (name.equalsIgnoreCase("default")) { //$NON-NLS-1$ + default_pos = i; + } + i++; + } + return default_pos; + } + + public String getSelection() { + return getPropertyAsString(SWITCH_VALUE); + } +} diff --git a/src/components/org/apache/jmeter/control/ThroughputController.java b/src/components/org/apache/jmeter/control/ThroughputController.java new file mode 100644 index 00000000000..c87ffe57fb3 --- /dev/null +++ b/src/components/org/apache/jmeter/control/ThroughputController.java @@ -0,0 +1,283 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.FloatProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class represents a controller that can control the number of times that + * it is executed, either by the total number of times the user wants the + * controller executed (BYNUMBER) or by the percentage of time it is called + * (BYPERCENT) + * + * The current implementation executes the first N samples (BYNUMBER) + * or the last N% of samples (BYPERCENT). + */ +public class ThroughputController extends GenericController implements Serializable, LoopIterationListener, + TestStateListener { + + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + public static final int BYNUMBER = 0; + + public static final int BYPERCENT = 1; + + private static final String STYLE = "ThroughputController.style";// $NON-NLS-1$ + + private static final String PERTHREAD = "ThroughputController.perThread";// $NON-NLS-1$ + + private static final String MAXTHROUGHPUT = "ThroughputController.maxThroughput";// $NON-NLS-1$ + + private static final String PERCENTTHROUGHPUT = "ThroughputController.percentThroughput";// $NON-NLS-1$ + + private static class MutableInteger{ + private int integer; + MutableInteger(int value){ + integer=value; + } + int incr(){ + return ++integer; + } + public int intValue() { + return integer; + } + } + + // These items are shared between threads in a group by the clone() method + // They are initialised by testStarted() so don't need to be serialised + private transient MutableInteger globalNumExecutions; + + private transient MutableInteger globalIteration; + + private transient Object counterLock = new Object(); // ensure counts are updated correctly + + /** + * Number of iterations on which we've chosen to deliver samplers. + */ + private int numExecutions = 0; + + /** + * Index of the current iteration. 0-based. + */ + private int iteration = -1; + + /** + * Whether to deliver samplers on this iteration. + */ + private boolean runThisTime; + + public ThroughputController() { + setStyle(BYNUMBER); + setPerThread(true); + setMaxThroughput(1); + setPercentThroughput(100); + runThisTime = false; + } + + public void setStyle(int style) { + setProperty(new IntegerProperty(STYLE, style)); + } + + public int getStyle() { + return getPropertyAsInt(STYLE); + } + + public void setPerThread(boolean perThread) { + setProperty(new BooleanProperty(PERTHREAD, perThread)); + } + + public boolean isPerThread() { + return getPropertyAsBoolean(PERTHREAD); + } + + public void setMaxThroughput(int maxThroughput) { + setProperty(new IntegerProperty(MAXTHROUGHPUT, maxThroughput)); + } + + public void setMaxThroughput(String maxThroughput) { + setProperty(new StringProperty(MAXTHROUGHPUT, maxThroughput)); + } + + public String getMaxThroughput() { + return getPropertyAsString(MAXTHROUGHPUT); + } + + protected int getMaxThroughputAsInt() { + JMeterProperty prop = getProperty(MAXTHROUGHPUT); + int retVal = 1; + if (prop instanceof IntegerProperty) { + retVal = ((IntegerProperty) prop).getIntValue(); + } else { + try { + retVal = Integer.parseInt(prop.getStringValue()); + } catch (NumberFormatException e) { + log.warn("Error parsing "+prop.getStringValue(),e); + } + } + return retVal; + } + + public void setPercentThroughput(float percentThroughput) { + setProperty(new FloatProperty(PERCENTTHROUGHPUT, percentThroughput)); + } + + public void setPercentThroughput(String percentThroughput) { + setProperty(new StringProperty(PERCENTTHROUGHPUT, percentThroughput)); + } + + public String getPercentThroughput() { + return getPropertyAsString(PERCENTTHROUGHPUT); + } + + protected float getPercentThroughputAsFloat() { + JMeterProperty prop = getProperty(PERCENTTHROUGHPUT); + float retVal = 100; + if (prop instanceof FloatProperty) { + retVal = ((FloatProperty) prop).getFloatValue(); + } else { + try { + retVal = Float.parseFloat(prop.getStringValue()); + } catch (NumberFormatException e) { + log.warn("Error parsing "+prop.getStringValue(),e); + } + } + return retVal; + } + + private int getExecutions() { + if (!isPerThread()) { + synchronized (counterLock) { + return globalNumExecutions.intValue(); + } + } + return numExecutions; + } + + /** + * @see org.apache.jmeter.control.Controller#next() + */ + @Override + public Sampler next() { + if (runThisTime) { + return super.next(); + } + return null; + } + + /** + * Decide whether to return any samplers on this iteration. + */ + private boolean decide(int executions, int iterations) { + if (getStyle() == BYNUMBER) { + return executions < getMaxThroughputAsInt(); + } + return (100.0 * executions + 50.0) / (iterations + 1) < getPercentThroughputAsFloat(); + } + + /** + * @see org.apache.jmeter.control.Controller#isDone() + */ + @Override + public boolean isDone() { + if (subControllersAndSamplers.size() == 0) { + return true; + } else if (getStyle() == BYNUMBER && getExecutions() >= getMaxThroughputAsInt() + && current >= getSubControllers().size()) { + return true; + } else { + return false; + } + } + + @Override + public Object clone() { + ThroughputController clone = (ThroughputController) super.clone(); + clone.numExecutions = numExecutions; + clone.iteration = iteration; + clone.runThisTime = false; + // Ensure global counters and lock are shared across threads in the group + clone.globalIteration = globalIteration; + clone.globalNumExecutions = globalNumExecutions; + clone.counterLock = counterLock; + return clone; + } + + @Override + public void iterationStart(LoopIterationEvent iterEvent) { + if (!isPerThread()) { + synchronized (counterLock) { + globalIteration.incr(); + runThisTime = decide(globalNumExecutions.intValue(), globalIteration.intValue()); + if (runThisTime) { + globalNumExecutions.incr(); + } + } + } else { + iteration++; + runThisTime = decide(numExecutions, iteration); + if (runThisTime) { + numExecutions++; + } + } + } + + @Override + public void testStarted() { + synchronized (counterLock) { + globalNumExecutions = new MutableInteger(0); + globalIteration = new MutableInteger(-1); + } + } + + @Override + public void testStarted(String host) { + testStarted(); + } + + @Override + public void testEnded() { + // NOOP + } + + @Override + public void testEnded(String host) { + // NOOP + } + + @Override + protected Object readResolve(){ + super.readResolve(); + counterLock = new Object(); + return this; + } + +} diff --git a/src/components/org/apache/jmeter/control/gui/CriticalSectionControllerGui.java b/src/components/org/apache/jmeter/control/gui/CriticalSectionControllerGui.java new file mode 100644 index 00000000000..c18a487ce61 --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/CriticalSectionControllerGui.java @@ -0,0 +1,182 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.CriticalSectionController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The user interface for a controller which specifies that its subcomponents + * should be executed while a condition holds. This component can be used + * standalone or embedded into some other component. + * + * @since 2.12 + */ +public class CriticalSectionControllerGui extends AbstractControllerGui { + + /** + * + */ + private static final long serialVersionUID = 7177285850634344095L; + + /** + * A field allowing the user to specify the lock name + */ + private JTextField tfLockName; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** + * Create a new CriticalSection Panel as a standalone component. + */ + public CriticalSectionControllerGui() { + this(true); + } + + /** + * Create a new CriticalSectionPanel as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public CriticalSectionControllerGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + if (element instanceof CriticalSectionController) { + CriticalSectionController controller = (CriticalSectionController) element; + tfLockName.setText(controller.getLockName()); + } + + } + + /** + * Implements JMeterGUIComponent.createTestElement() + */ + @Override + public TestElement createTestElement() { + CriticalSectionController controller = new CriticalSectionController(); + modifyTestElement(controller); + return controller; + } + + /** + * Implements JMeterGUIComponent.modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement controller) { + configureTestElement(controller); + if (controller instanceof CriticalSectionController) { + CriticalSectionController csController = (CriticalSectionController) controller; + csController.setLockName(tfLockName.getText()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + tfLockName.setText("global_lock"); // $NON-NLS-1$ + } + + @Override + public String getLabelResource() { + return "critical_section_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + // Standalone + if (displayName) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createCriticalSectionPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + + } else { + // Embedded + setLayout(new BorderLayout()); + add(createCriticalSectionPanel(), BorderLayout.NORTH); + } + } + + /** + * Create a GUI panel containing the lockName + * + * @return a GUI panel containing the lock name components + */ + private JPanel createCriticalSectionPanel() { + JPanel conditionPanel = new JPanel(new BorderLayout(5, 0)); + + // Condition LABEL + JLabel conditionLabel = new JLabel( + JMeterUtils.getResString("critical_section_controller_label")); // $NON-NLS-1$ + conditionPanel.add(conditionLabel, BorderLayout.WEST); + + // TEXT FIELD + tfLockName = new JTextField(""); // $NON-NLS-1$ + conditionLabel.setLabelFor(tfLockName); + conditionPanel.add(tfLockName, BorderLayout.CENTER); + + conditionPanel + .add(Box.createHorizontalStrut(conditionLabel + .getPreferredSize().width + + tfLockName.getPreferredSize().width), + BorderLayout.NORTH); + + return conditionPanel; + } +} diff --git a/src/components/org/apache/jmeter/control/gui/ForeachControlPanel.java b/src/components/org/apache/jmeter/control/gui/ForeachControlPanel.java new file mode 100644 index 00000000000..efd25e5ae8d --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/ForeachControlPanel.java @@ -0,0 +1,253 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.ForeachController; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The user interface for a foreach controller which specifies that its + * subcomponents should be executed some number of times in a loop. This + * component can be used standalone or embedded into some other component. + */ + +public class ForeachControlPanel extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + /** + * A field allowing the user to specify the input variable the controller + * should loop. + */ + private JTextField inputVal; + + /** + * A field allowing the user to specify the indice start of the loop + */ + private JTextField startIndex; + + /** + * A field allowing the user to specify the indice end of the loop + */ + private JTextField endIndex; + + /** + * A field allowing the user to specify output variable the controller + * should return. + */ + private JTextField returnVal; + + // Should we add the "_" separator? + private JCheckBox useSeparator; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** The name of the infinite checkbox component. */ + private static final String INPUTVAL = "Input Field"; // $NON-NLS-1$ + + /** The name of the loops field component. */ + private static final String RETURNVAL = "Return Field"; // $NON-NLS-1$ + + /** The name of the start index field component. */ + private static final String START_INDEX = "Start Index Field"; // $NON-NLS-1$ + + /** The name of the end index field component. */ + private static final String END_INDEX = "End Index Field"; // $NON-NLS-1$ + /** + * Create a new LoopControlPanel as a standalone component. + */ + public ForeachControlPanel() { + this(true); + } + + /** + * Create a new LoopControlPanel as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public ForeachControlPanel(boolean displayName) { + this.displayName = displayName; + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + inputVal.setText(((ForeachController) element).getInputValString()); + startIndex.setText(((ForeachController) element).getStartIndexAsString()); + endIndex.setText(((ForeachController) element).getEndIndexAsString()); + returnVal.setText(((ForeachController) element).getReturnValString()); + useSeparator.setSelected(((ForeachController) element).getUseSeparator()); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + ForeachController lc = new ForeachController(); + modifyTestElement(lc); + return lc; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement lc) { + configureTestElement(lc); + if (lc instanceof ForeachController) { + ForeachController fec = (ForeachController) lc; + fec.setInputVal(inputVal.getText()); + fec.setStartIndex(startIndex.getText()); + fec.setEndIndex(endIndex.getText()); + fec.setReturnVal(returnVal.getText()); + fec.setUseSeparator(useSeparator.isSelected()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + inputVal.setText(""); // $NON-NLS-1$ + startIndex.setText(""); // $NON-NLS-1$ + endIndex.setText(""); // $NON-NLS-1$ + returnVal.setText(""); // $NON-NLS-1$ + useSeparator.setSelected(true); + } + + + @Override + public String getLabelResource() { + return "foreach_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + // The Loop Controller panel can be displayed standalone or inside + // another panel. For standalone, we want to display the TITLE, NAME, + // etc. (everything). However, if we want to display it within another + // panel, we just display the Loop Count fields (not the TITLE and + // NAME). + + // Standalone + if (displayName) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createLoopCountPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } else { + // Embedded + setLayout(new BorderLayout()); + add(createLoopCountPanel(), BorderLayout.NORTH); + } + } + + /** + * Create a GUI panel containing the components related to the number of + * loops which should be executed. + * + * @return a GUI panel containing the loop count components + */ + private JPanel createLoopCountPanel() { + // JPanel loopPanel = new JPanel(new BorderLayout(5, 0)); + VerticalPanel loopPanel = new VerticalPanel(); + + // LOOP LABEL + JLabel inputValLabel = new JLabel(JMeterUtils.getResString("foreach_input")); // $NON-NLS-1$ + JLabel startIndexLabel = new JLabel(JMeterUtils.getResString("foreach_start_index")); // $NON-NLS-1$ + JLabel endIndexLabel = new JLabel(JMeterUtils.getResString("foreach_end_index")); // $NON-NLS-1$ + JLabel returnValLabel = new JLabel(JMeterUtils.getResString("foreach_output")); // $NON-NLS-1$ + + // TEXT FIELD + JPanel inputValSubPanel = new JPanel(new BorderLayout(5, 0)); + inputVal = new JTextField("", 5); // $NON-NLS-1$ + inputVal.setName(INPUTVAL); + inputValLabel.setLabelFor(inputVal); + inputValSubPanel.add(inputValLabel, BorderLayout.WEST); + inputValSubPanel.add(inputVal, BorderLayout.CENTER); + + // TEXT FIELD + JPanel startIndexSubPanel = new JPanel(new BorderLayout(5, 0)); + startIndex = new JTextField("", 5); // $NON-NLS-1$ + startIndex.setName(START_INDEX); + startIndexLabel.setLabelFor(startIndex); + startIndexSubPanel.add(startIndexLabel, BorderLayout.WEST); + startIndexSubPanel.add(startIndex, BorderLayout.CENTER); + + // TEXT FIELD + JPanel endIndexSubPanel = new JPanel(new BorderLayout(5, 0)); + endIndex = new JTextField("", 5); // $NON-NLS-1$ + endIndex.setName(END_INDEX); + endIndexLabel.setLabelFor(endIndex); + endIndexSubPanel.add(endIndexLabel, BorderLayout.WEST); + endIndexSubPanel.add(endIndex, BorderLayout.CENTER); + + // TEXT FIELD + JPanel returnValSubPanel = new JPanel(new BorderLayout(5, 0)); + returnVal = new JTextField("", 5); // $NON-NLS-1$ + returnVal.setName(RETURNVAL); + returnValLabel.setLabelFor(returnVal); + returnValSubPanel.add(returnValLabel, BorderLayout.WEST); + returnValSubPanel.add(returnVal, BorderLayout.CENTER); + + // Checkbox + useSeparator = new JCheckBox(JMeterUtils.getResString("foreach_use_separator"), true); // $NON-NLS-1$ + loopPanel.add(inputValSubPanel); + loopPanel.add(startIndexSubPanel); + loopPanel.add(endIndexSubPanel); + loopPanel.add(returnValSubPanel); + loopPanel.add(useSeparator); + + return loopPanel; + } +} diff --git a/src/components/org/apache/jmeter/control/gui/IncludeControllerGui.java b/src/components/org/apache/jmeter/control/gui/IncludeControllerGui.java new file mode 100644 index 00000000000..ba5edddf564 --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/IncludeControllerGui.java @@ -0,0 +1,115 @@ +/* + * 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.control.gui; + +import javax.swing.JMenu; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.control.IncludeController; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class IncludeControllerGui extends AbstractControllerGui + // implements UnsharedComponent +{ + + private static final long serialVersionUID = 240L; + + private final FilePanel includePanel = + new FilePanel(JMeterUtils.getResString("include_path"), ".jmx"); //$NON-NLS-1$ //$NON-NLS-2$ + + /** + * Initializes the gui panel for the ModuleController instance. + */ + public IncludeControllerGui() { + init(); + } + + @Override + public String getLabelResource() { + return "include_controller";//$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement el) { + super.configure(el); + IncludeController controller = (IncludeController) el; + this.includePanel.setFilename(controller.getIncludePath()); + } + + /** + * {@inheritDoc} + */ + @Override + public TestElement createTestElement() { + IncludeController mc = new IncludeController(); + configureTestElement(mc); + return mc; + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement element) { + configureTestElement(element); + IncludeController controller = (IncludeController)element; + controller.setIncludePath(this.includePanel.getFilename()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + includePanel.clearGui(); + } + + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu menu = new JPopupMenu(); + JMenu addMenu = MenuFactory.makeMenus(new String[] { + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.ASSERTIONS, + MenuFactory.TIMERS, + MenuFactory.LISTENERS, + }, JMeterUtils.getResString("add"), // $NON-NLS-1$ + ActionNames.ADD); + menu.add(addMenu); + MenuFactory.addEditMenu(menu, true); + MenuFactory.addFileMenu(menu); + return menu; + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + add(includePanel); + } +} diff --git a/src/components/org/apache/jmeter/control/gui/InterleaveControlGui.java b/src/components/org/apache/jmeter/control/gui/InterleaveControlGui.java new file mode 100644 index 00000000000..a18b92e4c9f --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/InterleaveControlGui.java @@ -0,0 +1,92 @@ +/* + * 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.control.gui; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.control.InterleaveControl; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class InterleaveControlGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + private JCheckBox style; + + public InterleaveControlGui() { + init(); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (((InterleaveControl) el).getStyle() == InterleaveControl.IGNORE_SUB_CONTROLLERS) { + style.setSelected(true); + } else { + style.setSelected(false); + } + } + + @Override + public TestElement createTestElement() { + InterleaveControl ic = new InterleaveControl(); + modifyTestElement(ic); + return ic; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement ic) { + configureTestElement(ic); + if (style.isSelected()) { + ((InterleaveControl) ic).setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + } else { + ((InterleaveControl) ic).setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + style.setSelected(false); + } + + @Override + public String getLabelResource() { + return "interleave_control_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + + style = new JCheckBox(JMeterUtils.getResString("ignore_subcontrollers")); // $NON-NLS-1$ + add(style); + } +} diff --git a/src/components/org/apache/jmeter/control/gui/ModuleControllerGui.java b/src/components/org/apache/jmeter/control/gui/ModuleControllerGui.java new file mode 100644 index 00000000000..5606b7abe25 --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/ModuleControllerGui.java @@ -0,0 +1,427 @@ +/* + * 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.control.gui; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Iterator; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.ModuleController; +import org.apache.jmeter.control.TestFragmentController; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * ModuleControllerGui provides UI for configuring ModuleController element. + * It contains filtered copy of test plan tree (called Module to run tree) + * in which target element can be specified by selecting node. + * Allowed types of elements in Module to run tree: + * - TestPlan - cannot be referenced + * - AbstractThreadGroup - cannot be referenced + * - Controller (except ModuleController) + * - TestFragmentController + * + */ +public class ModuleControllerGui extends AbstractControllerGui implements ActionListener { + /** + * + */ + private static final long serialVersionUID = -4195441608252523573L; + + private static final String SEPARATOR = " > "; + + private static final TreeNode[] EMPTY_TREE_NODES = new TreeNode[0]; + + private JMeterTreeNode selected = null; + + /** + * Model of a Module to run tree. It is a copy of a test plan tree. + * User object of each element is set to a correlated JMeterTreeNode element of a test plan tree. + */ + private final DefaultTreeModel moduleToRunTreeModel; + + /** + * Module to run tree. Allows to select a module to be executed. Many ModuleControllers can reference + * the same element. + */ + private final JTree moduleToRunTreeNodes; + + /** + * Used in case if referenced element does not exist in test plan tree (after removing it for example) + */ + private final JLabel warningLabel; + + /** + * Helps navigating test plan + */ + private JButton expandButton; + + /** + * Initializes the gui panel for the ModuleController instance. + */ + public ModuleControllerGui() { + moduleToRunTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode()); + moduleToRunTreeNodes = new JTree(moduleToRunTreeModel); + moduleToRunTreeNodes.setCellRenderer(new ModuleControllerCellRenderer()); + + warningLabel = new JLabel(""); // $NON-NLS-1$ + init(); + } + + /** {@inheritDoc}} */ + @Override + public String getLabelResource() { + return "module_controller_title"; // $NON-NLS-1$ + } + /** {@inheritDoc}} */ + @Override + public void configure(TestElement el) { + super.configure(el); + ModuleController controller = (ModuleController) el; + this.selected = controller.getSelectedNode(); + if (selected == null && controller.getNodePath() != null) { + warningLabel.setText(JMeterUtils.getResString("module_controller_warning") // $NON-NLS-1$ + + renderPath(controller.getNodePath())); + } else { + warningLabel.setText(""); // $NON-NLS-1$ + } + reinitialize(); + } + + private String renderPath(Collection path) { + Iterator iter = path.iterator(); + StringBuilder buf = new StringBuilder(); + boolean first = true; + while (iter.hasNext()) { + if (first) { + first = false; + iter.next(); + continue; + } + buf.append(iter.next()); + if (iter.hasNext()) { + buf.append(SEPARATOR); // $NON-NLS-1$ + } + } + return buf.toString(); + } + + /** {@inheritDoc}} */ + @Override + public TestElement createTestElement() { + ModuleController mc = new ModuleController(); + configureTestElement(mc); + if (selected != null) { + mc.setSelectedNode(selected); + } + return mc; + } + + /** {@inheritDoc}} */ + @Override + public void modifyTestElement(TestElement element) { + configureTestElement(element); + JMeterTreeNode tn = null; + DefaultMutableTreeNode lastSelected = (DefaultMutableTreeNode) this.moduleToRunTreeNodes.getLastSelectedPathComponent(); + if (lastSelected != null && lastSelected.getUserObject() instanceof JMeterTreeNode) { + tn = (JMeterTreeNode) lastSelected.getUserObject(); + } + if (tn != null) { + selected = tn; + //prevent from selecting thread group or test plan elements + if (selected != null + && !(selected.getTestElement() instanceof AbstractThreadGroup) + && !(selected.getTestElement() instanceof TestPlan)) { + ((ModuleController) element).setSelectedNode(selected); + } + } + } + + /** {@inheritDoc}} */ + @Override + public void clearGui() { + super.clearGui(); + selected = null; + } + + + /** {@inheritDoc}} */ + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu menu = new JPopupMenu(); + JMenu addMenu = MenuFactory.makeMenus( + new String[] { + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.ASSERTIONS, + MenuFactory.TIMERS, + MenuFactory.LISTENERS, + }, + JMeterUtils.getResString("add"), // $NON-NLS-1$ + ActionNames.ADD); + menu.add(addMenu); + MenuFactory.addEditMenu(menu, true); + MenuFactory.addFileMenu(menu); + return menu; + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + JPanel modulesPanel = new JPanel(); + + expandButton = new JButton(JMeterUtils.getResString("expand")); //$NON-NLS-1$ + expandButton.addActionListener(this); + modulesPanel.add(expandButton); + modulesPanel.setLayout(new BoxLayout(modulesPanel, BoxLayout.Y_AXIS)); + modulesPanel.add(Box.createRigidArea(new Dimension(0,5))); + + JLabel nodesLabel = new JLabel(JMeterUtils.getResString("module_controller_module_to_run")); // $NON-NLS-1$ + modulesPanel.add(nodesLabel); + modulesPanel.add(warningLabel); + add(modulesPanel); + + JPanel treePanel = new JPanel(); + treePanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + treePanel.add(moduleToRunTreeNodes); + add(treePanel); + } + + /** + * Recursively traverse module to run tree in order to find JMeterTreeNode element given by testPlanPath + * in a DefaultMutableTreeNode tree + * + * @param level - current search level + * @param testPlanPath - path of a test plan tree element + * @param parent - module to run tree parent element + * + * @return path of a found element + */ + private TreeNode[] findPathInTreeModel(int level, TreeNode[] testPlanPath, DefaultMutableTreeNode parent) + { + if(level >= testPlanPath.length) { + return EMPTY_TREE_NODES; + } + int childCount = parent.getChildCount(); + JMeterTreeNode searchedTreeNode = + (JMeterTreeNode) testPlanPath[level]; + + for (int i = 0; i < childCount; i++) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) parent.getChildAt(i); + JMeterTreeNode childUserObj = (JMeterTreeNode) child.getUserObject(); + + if(!childUserObj.equals(searchedTreeNode)){ + continue; + } else { + if(level == (testPlanPath.length - 1)){ + return child.getPath(); + } else { + return findPathInTreeModel(level+1, testPlanPath, child); + } + } + } + return EMPTY_TREE_NODES; + } + + /** + * Expand module to run tree to selected JMeterTreeNode and set selection path to it + * @param selected - referenced module to run + */ + private void focusSelectedOnTree(JMeterTreeNode selected) + { + TreeNode[] path = selected.getPath(); + TreeNode[] filteredPath = new TreeNode[path.length-1]; + + //ignore first element of path - WorkBench, (why WorkBench is appearing in the path ???) + for(int i = 1; i < path.length; i++){ + filteredPath[i-1] = path[i]; + } + + DefaultMutableTreeNode root = (DefaultMutableTreeNode) moduleToRunTreeNodes.getModel().getRoot(); + //treepath of test plan tree and module to run tree cannot be compared directly - moduleToRunTreeModel.getPathToRoot() + //custom method for finding an JMeterTreeNode element in DefaultMutableTreeNode have to be used + TreeNode[] dmtnPath = this.findPathInTreeModel(1, filteredPath, root); + if (dmtnPath.length>0) { + TreePath treePath = new TreePath(dmtnPath); + moduleToRunTreeNodes.setSelectionPath(treePath); + moduleToRunTreeNodes.scrollPathToVisible(treePath); + } + } + + /** + * + */ + private void reinitialize() { + ((DefaultMutableTreeNode) moduleToRunTreeModel.getRoot()).removeAllChildren(); + + GuiPackage gp = GuiPackage.getInstance(); + JMeterTreeNode root; + if (gp != null) { + root = (JMeterTreeNode) GuiPackage.getInstance().getTreeModel().getRoot(); + buildTreeNodeModel(root, 0, null); + moduleToRunTreeModel.nodeStructureChanged((TreeNode) moduleToRunTreeModel.getRoot()); + } + if (selected != null) { + //expand Module to run tree to selected node and set selection path to it + this.focusSelectedOnTree(selected); + } + } + + /** + * Recursively build module to run tree. Only 4 types of elements are allowed to be added: + * - All controllers except ModuleController + * - TestPlan + * - TestFragmentController + * - AbstractThreadGroup + * + * @param node - element of test plan tree + * @param level - level of element in a tree + * @param parent + */ + private void buildTreeNodeModel(JMeterTreeNode node, int level, + DefaultMutableTreeNode parent) { + + if (node != null) { + for (int i = 0; i < node.getChildCount(); i++) { + JMeterTreeNode cur = (JMeterTreeNode) node.getChildAt(i); + TestElement te = cur.getTestElement(); + + if (te instanceof Controller + && !(te instanceof ModuleController) && level > 0) { + DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(cur); + parent.add(newNode); + buildTreeNodeModel(cur, level + 1, newNode); + + } else if (te instanceof TestFragmentController) { + DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(cur); + parent.add(newNode); + buildTreeNodeModel(cur, level + 1, newNode); + + } else if (te instanceof AbstractThreadGroup) { + DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(cur); + parent.add(newNode); + buildTreeNodeModel(cur, level + 1, newNode); + } + + else if (te instanceof TestPlan) { + ((DefaultMutableTreeNode) moduleToRunTreeModel.getRoot()) + .setUserObject(cur); + buildTreeNodeModel(cur, level, + (DefaultMutableTreeNode) moduleToRunTreeModel.getRoot()); + } + } + } + } + + /** + * Implementation of Expand button - moves focus to a test plan tree element referenced by + * selected element in Module to run tree + */ + @Override + public void actionPerformed(ActionEvent e) { + if(e.getSource()==expandButton) { + JMeterTreeNode tn = null; + DefaultMutableTreeNode selected = (DefaultMutableTreeNode) + this.moduleToRunTreeNodes.getLastSelectedPathComponent(); + if(selected != null && selected.getUserObject() instanceof JMeterTreeNode){ + tn = (JMeterTreeNode) selected.getUserObject(); + } + if(tn != null){ + TreePath treePath = new TreePath(tn.getPath()); + //changing selection in a test plan tree + GuiPackage.getInstance().getTreeListener().getJTree() + .setSelectionPath(treePath); + //expanding tree to make referenced element visible in test plan tree + GuiPackage.getInstance().getTreeListener().getJTree() + .scrollPathToVisible(treePath); + } + } + } + + + /** + * @param selected JMeterTreeNode tree node to expand + */ + protected void expandToSelectNode(JMeterTreeNode selected) { + GuiPackage guiInstance = GuiPackage.getInstance(); + JTree jTree = guiInstance.getMainFrame().getTree(); + jTree.expandPath(new TreePath(selected.getPath())); + selected.setMarkedBySearch(true); + } + + /** + * Renderer class for printing "module to run" tree + */ + private static class ModuleControllerCellRenderer extends DefaultTreeCellRenderer { + + private static final long serialVersionUID = 1129098620102526299L; + + /** + * @see javax.swing.tree.DefaultTreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree, java.lang.Object, boolean, boolean, boolean, int, boolean) + */ + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, + boolean leaf, int row, boolean hasFocus) { + JMeterTreeNode node = (JMeterTreeNode)((DefaultMutableTreeNode) value).getUserObject(); + if(node != null){ + super.getTreeCellRendererComponent(tree, node.getName(), selected, expanded, leaf, row, + hasFocus); + //print same icon as in test plan tree + boolean enabled = node.isEnabled(); + ImageIcon icon = node.getIcon(enabled); + if (icon != null) { + if (enabled) { + setIcon(icon); + } else { + setDisabledIcon(icon); + } + } + } + return this; + } + } +} diff --git a/src/components/org/apache/jmeter/control/gui/OnceOnlyControllerGui.java b/src/components/org/apache/jmeter/control/gui/OnceOnlyControllerGui.java new file mode 100644 index 00000000000..39b3d13c943 --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/OnceOnlyControllerGui.java @@ -0,0 +1,59 @@ +/* + * 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.control.gui; + +import org.apache.jmeter.control.OnceOnlyController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class OnceOnlyControllerGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + public OnceOnlyControllerGui() { + init(); + } + + @Override + public TestElement createTestElement() { + OnceOnlyController oc = new OnceOnlyController(); + modifyTestElement(oc); + return oc; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement oc) { + configureTestElement(oc); + } + + @Override + public String getLabelResource() { + return "once_only_controller_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + } +} diff --git a/src/components/org/apache/jmeter/control/gui/RandomControlGui.java b/src/components/org/apache/jmeter/control/gui/RandomControlGui.java new file mode 100644 index 00000000000..14d43c55ec0 --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/RandomControlGui.java @@ -0,0 +1,92 @@ +/* + * 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.control.gui; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.control.InterleaveControl; +import org.apache.jmeter.control.RandomController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class RandomControlGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + private JCheckBox style; + + public RandomControlGui() { + init(); + } + + @Override + public TestElement createTestElement() { + RandomController ic = new RandomController(); + modifyTestElement(ic); + return ic; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement ic) { + configureTestElement(ic); + if (style.isSelected()) { + ((RandomController) ic).setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + } else { + ((RandomController) ic).setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + style.setSelected(false); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (((RandomController) el).getStyle() == InterleaveControl.IGNORE_SUB_CONTROLLERS) { + style.setSelected(true); + } else { + style.setSelected(false); + } + } + + @Override + public String getLabelResource() { + return "random_control_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + style = new JCheckBox(JMeterUtils.getResString("ignore_subcontrollers")); // $NON-NLS-1$ + add(style); + } +} diff --git a/src/components/org/apache/jmeter/control/gui/RandomOrderControllerGui.java b/src/components/org/apache/jmeter/control/gui/RandomOrderControllerGui.java new file mode 100644 index 00000000000..fcad38bbab6 --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/RandomOrderControllerGui.java @@ -0,0 +1,53 @@ +/* + * 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.control.gui; + +import org.apache.jmeter.control.RandomOrderController; +import org.apache.jmeter.testelement.TestElement; + +/** + * GUI for RandomOrderController. + * + */ +public class RandomOrderControllerGui extends LogicControllerGui { + + private static final long serialVersionUID = 240L; + + @Override + public String getLabelResource() { + return "random_order_control_title"; // $NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public TestElement createTestElement() { + RandomOrderController ic = new RandomOrderController(); + modifyTestElement(ic); + return ic; + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement ic) { + configureTestElement(ic); + } + +} diff --git a/src/components/org/apache/jmeter/control/gui/SwitchControllerGui.java b/src/components/org/apache/jmeter/control/gui/SwitchControllerGui.java new file mode 100644 index 00000000000..3356c895101 --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/SwitchControllerGui.java @@ -0,0 +1,99 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.SwitchController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class SwitchControllerGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + private static final String SWITCH_LABEL = "switch_controller_label"; // $NON-NLS-1$ + + private JTextField switchValue; + + public SwitchControllerGui() { + init(); + } + + @Override + public TestElement createTestElement() { + SwitchController ic = new SwitchController(); + modifyTestElement(ic); + return ic; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement ic) { + configureTestElement(ic); + ((SwitchController) ic).setSelection(switchValue.getText()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + switchValue.setText(""); // $NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + switchValue.setText(((SwitchController) el).getSelection()); + } + + @Override + public String getLabelResource() { + return "switch_controller_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createSwitchPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } + + private JPanel createSwitchPanel() { + JPanel switchPanel = new JPanel(new BorderLayout(5, 0)); + JLabel selectionLabel = new JLabel(JMeterUtils.getResString(SWITCH_LABEL)); + switchValue = new JTextField(""); // $NON-NLS-1$ + selectionLabel.setLabelFor(switchValue); + switchPanel.add(selectionLabel, BorderLayout.WEST); + switchPanel.add(switchValue, BorderLayout.CENTER); + return switchPanel; + } +} diff --git a/src/components/org/apache/jmeter/control/gui/ThroughputControllerGui.java b/src/components/org/apache/jmeter/control/gui/ThroughputControllerGui.java new file mode 100644 index 00000000000..8cd2ecaa5e8 --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/ThroughputControllerGui.java @@ -0,0 +1,174 @@ +/* + * 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.control.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.ThroughputController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class ThroughputControllerGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + private JComboBox styleBox; + + private int style; + + private JTextField throughput; + + private JCheckBox perthread; + + private boolean isPerThread = true; + + // These must not be static, otherwise Language change does not work + private final String BYNUMBER_LABEL = JMeterUtils.getResString("throughput_control_bynumber_label"); // $NON-NLS-1$ + + private final String BYPERCENT_LABEL = JMeterUtils.getResString("throughput_control_bypercent_label"); // $NON-NLS-1$ + + private final String THROUGHPUT_LABEL = JMeterUtils.getResString("throughput_control_tplabel"); // $NON-NLS-1$ + + private final String PERTHREAD_LABEL = JMeterUtils.getResString("throughput_control_perthread_label"); // $NON-NLS-1$ + + public ThroughputControllerGui() { + init(); + } + + @Override + public TestElement createTestElement() { + ThroughputController tc = new ThroughputController(); + modifyTestElement(tc); + return tc; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement tc) { + configureTestElement(tc); + ((ThroughputController) tc).setStyle(style); + ((ThroughputController) tc).setPerThread(isPerThread); + if (style == ThroughputController.BYNUMBER) { + try { + ((ThroughputController) tc).setMaxThroughput(Integer.parseInt(throughput.getText().trim())); + } catch (NumberFormatException e) { + // In case we are converting back from floating point, drop the decimal fraction + ((ThroughputController) tc).setMaxThroughput(throughput.getText().trim().split("\\.")[0]); // $NON-NLS-1$ + } + } else { + try { + ((ThroughputController) tc).setPercentThroughput(Float.parseFloat(throughput.getText().trim())); + } catch (NumberFormatException e) { + ((ThroughputController) tc).setPercentThroughput(throughput.getText()); + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + styleBox.setSelectedIndex(0); + throughput.setText("1"); // $NON-NLS-1$ + perthread.setSelected(true); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (((ThroughputController) el).getStyle() == ThroughputController.BYNUMBER) { + styleBox.getModel().setSelectedItem(BYNUMBER_LABEL); + throughput.setText(((ThroughputController) el).getMaxThroughput()); + } else { + styleBox.setSelectedItem(BYPERCENT_LABEL); + throughput.setText(((ThroughputController) el).getPercentThroughput()); + } + perthread.setSelected(((ThroughputController) el).isPerThread()); + } + + @Override + public String getLabelResource() { + return "throughput_control_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + DefaultComboBoxModel styleModel = new DefaultComboBoxModel(); + styleModel.addElement(BYNUMBER_LABEL); + styleModel.addElement(BYPERCENT_LABEL); + styleBox = new JComboBox(styleModel); + styleBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (((String) styleBox.getSelectedItem()).equals(BYNUMBER_LABEL)) { + style = ThroughputController.BYNUMBER; + } else { + style = ThroughputController.BYPERCENT; + } + } + }); + add(styleBox); + + // TYPE FIELD + JPanel tpPanel = new JPanel(); + JLabel tpLabel = new JLabel(THROUGHPUT_LABEL); + tpPanel.add(tpLabel); + + // TEXT FIELD + throughput = new JTextField(15); + tpPanel.add(throughput); + throughput.setText("1"); // $NON-NLS-1$ + // throughput.addActionListener(this); + tpPanel.add(throughput); + add(tpPanel); + + // PERTHREAD FIELD + perthread = new JCheckBox(PERTHREAD_LABEL, isPerThread); + perthread.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + isPerThread = true; + } else { + isPerThread = false; + } + } + }); + add(perthread); + } +} diff --git a/src/components/org/apache/jmeter/control/gui/TreeNodeWrapper.java b/src/components/org/apache/jmeter/control/gui/TreeNodeWrapper.java new file mode 100644 index 00000000000..0833d049024 --- /dev/null +++ b/src/components/org/apache/jmeter/control/gui/TreeNodeWrapper.java @@ -0,0 +1,53 @@ +/* + * 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.control.gui; + +import org.apache.jmeter.gui.tree.JMeterTreeNode; + +/** + * Used in JComboBoxes to reference JMeterTreeNodes + */ +public final class TreeNodeWrapper { + + private final JMeterTreeNode tn; + + private final String label; + + public TreeNodeWrapper(JMeterTreeNode tn, String label) { + this.tn = tn; + this.label = label; + } + + public JMeterTreeNode getTreeNode() { + return tn; + } + + /** {@inheritDoc}} */ + @Override + public String toString() { + return label; + } + + /** + * @return the label + */ + public String getLabel() { + return label; + } +} diff --git a/src/components/org/apache/jmeter/extractor/BSFPostProcessor.java b/src/components/org/apache/jmeter/extractor/BSFPostProcessor.java new file mode 100644 index 00000000000..4824aaed48a --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BSFPostProcessor.java @@ -0,0 +1,49 @@ +/* + * 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.extractor; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFPostProcessor extends BSFTestElement implements Cloneable, PostProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + @Override + public void process(){ + BSFManager mgr =null; + try { + mgr = getManager(); + processFileOrScript(mgr); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + } finally { + if (mgr != null) { + mgr.terminate(); + } + } + } +} diff --git a/src/components/org/apache/jmeter/extractor/BSFPostProcessorBeanInfo.java b/src/components/org/apache/jmeter/extractor/BSFPostProcessorBeanInfo.java new file mode 100644 index 00000000000..513a515edd8 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BSFPostProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.extractor; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFPostProcessorBeanInfo extends BSFBeanInfoSupport { + + public BSFPostProcessorBeanInfo() { + super(BSFPostProcessor.class); + } + +} diff --git a/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources.properties b/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources.properties new file mode 100644 index 00000000000..e15f3cd27b7 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources.properties @@ -0,0 +1,28 @@ +# 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. + +displayName=BSF PostProcessor +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_fr.properties b/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_fr.properties new file mode 100644 index 00000000000..44257793daa --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_fr.properties @@ -0,0 +1,29 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Post-Processeur BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables\: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage diff --git a/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pl.properties b/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pl.properties new file mode 100644 index 00000000000..c7ae5ec544d --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pl.properties @@ -0,0 +1,29 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +# 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. + +displayName=Post procesor BSF +filename.displayName=Nazwa pliku +filename.shortDescription=Plik ze skryptem (zast\u0119puje skrypt) +filenameGroup.displayName=Plik ze skryptem (zast\u0119puje skrypt) +parameterGroup.displayName=Parametry do przekazania do skryptu (=> String Parameters and String []args) +parameters.displayName=Parametry +parameters.shortDescription=Parametry do przekazania do pliku lub skryptu +script.displayName=Skrypt +script.shortDescription=Skrypt w jednym z j\u0119zyk\u00F3w BSF +scriptLanguage.displayName=J\u0119zyk +scriptLanguage.shortDescription=Nazwa j\u0119zyka BSF, np. beanshell, javascript, jexl +scripting.displayName=Skrypt (zmienne: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=J\u0119zyk skrytpu (np. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pt_BR.properties b/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pt_BR.properties new file mode 100644 index 00000000000..09aa66ed15a --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pt_BR.properties @@ -0,0 +1,28 @@ +# 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. + +displayName=P\u00F3s-Processador BeanShell +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao arquivo ou script BeanShell +script.displayName=Script +script.shortDescription=Script na linguagem BSF apropriada +scriptLanguage.displayName=Linguagem +scriptLanguage.shortDescription=Nome da linguagem BSF, ex\: beanshell, javascript, jexl +scripting.displayName=Script (vari\u00E1veis\: ctx vars props prev data log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Linguagem do script (ex\: beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/extractor/BeanShellPostProcessor.java b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessor.java new file mode 100644 index 00000000000..01569c6bbe8 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessor.java @@ -0,0 +1,69 @@ +/* + * 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.extractor; + +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +public class BeanShellPostProcessor extends BeanShellTestElement + implements Cloneable, PostProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + // can be specified in jmeter.properties + private static final String INIT_FILE = "beanshell.postprocessor.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + @Override + public void process() { + JMeterContext jmctx = JMeterContextService.getContext(); + + SampleResult prev = jmctx.getPreviousResult(); + if (prev == null) { + return; // TODO - should we skip processing here? + } + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + log.error("BeanShell not found"); + return; + } + + try { + // Add variables for access to context and variables + bshInterpreter.set("data", prev.getResponseData());//$NON-NLS-1$ + processFileOrScript(bshInterpreter); + } catch (JMeterException e) { + log.warn("Problem in BeanShell script "+e); + } + } +} diff --git a/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorBeanInfo.java b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorBeanInfo.java new file mode 100644 index 00000000000..9dc87e9465b --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.extractor; + +import org.apache.jmeter.util.BeanShellBeanInfoSupport; + +public class BeanShellPostProcessorBeanInfo extends BeanShellBeanInfoSupport { + + public BeanShellPostProcessorBeanInfo() { + super(BeanShellPostProcessor.class); + } + +} diff --git a/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources.properties b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources.properties new file mode 100644 index 00000000000..c058d454680 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=BeanShell PostProcessor +filename.displayName=File Name +filename.shortDescription=BeanShell script file (overrides script) +filenameGroup.displayName=Script file (overrides script) +parameterGroup.displayName=Parameters to be passed to BeanShell (=> String Parameters and String []bsh.args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to BeanShell (file or script) +resetGroup.displayName=Reset bsh.Interpreter before each call +resetInterpreter.displayName=Reset Interpreter +script.displayName=Script +script.shortDescription=Beanshell script +scripting.displayName=Script (variables: ctx vars props prev data log) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_de.properties b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_de.properties new file mode 100644 index 00000000000..9ff806779c6 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_de.properties @@ -0,0 +1,24 @@ +# 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. + +filename.displayName=Dateiname +filename.shortDescription=BeanShell Script Datei (Vorrang vor Script) +filenameGroup.displayName=Script Datei (Vorrang vor Script) +parameterGroup.displayName=Parameter die der BeanShell \u00FCbergeben werden sollen ("String parameter, String []bsh.args") +parameters.displayName=Parameter +parameters.shortDescription=Parameter die der BeanShell \u00FCbergeben werden sollen (Datei oder Script) +script.displayName=BeanShell Script +script.shortDescription=BeanShell Script +scripting.displayName=Script (Variablen\: ctx vars props prev data log) diff --git a/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_fr.properties b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_fr.properties new file mode 100644 index 00000000000..2b2f75fb2e4 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_fr.properties @@ -0,0 +1,28 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Post-Processeur BeanShell +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BeanShell (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au BeanShell (\=> String Parameters and String []bsh.args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BeanShell (fichier ou script) +resetGroup.displayName=R\u00E9initialiser l'interpr\u00E9teur BeanShell avant chaque appel +resetInterpreter.displayName=R\u00E9initialiser l'interpr\u00E9teur +script.displayName=Script +script.shortDescription=Script BeanShell +scripting.displayName=Script (variables\: ctx vars props prev data log) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pl.properties b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pl.properties new file mode 100644 index 00000000000..ae38d11315c --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pl.properties @@ -0,0 +1,29 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +# 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. +# Uwagi co do tlumaczenia: mr0vek@o2.pl + +displayName=Post-procesor BeanShell +filename.displayName=Nazwa pliku +filename.shortDescription=Plik ze skryptem BeanShell (zast\u0119puje skrypt) +filenameGroup.displayName=Plik ze skryptem (zast\u0119puje skrypt) +parameterGroup.displayName=Parametry dla BeanShella (=> String Parameters and String []bsh.args) +parameters.displayName=Parametry +parameters.shortDescription=Parametry dla BeanShell (pliku lub skryptu) +resetGroup.displayName=Resetuj interpreter bsh przed ka\u017Cdym wywo\u0142aniem +resetInterpreter.displayName=Resetuj interpreter +script.displayName=Skrypt BeanShell +script.shortDescription=Skrypt Beanshell +scripting.displayName=Skrypt (zmienne: ctx vars props prev data log) diff --git a/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pt_BR.properties b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pt_BR.properties new file mode 100644 index 00000000000..14456fd3b71 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pt_BR.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=P\u00F3s-Processador BeanShell +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script do BeanShell (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao BeanShell (arquivo ou script) +resetGroup.displayName=Reiniciar bsh.Interpreter antes de cada chamada +resetInterpreter.displayName=Reiniciar Interpretador +script.displayName=\ +script.shortDescription=Script BeanShell +scripting.displayName=Script (vari\u00E1veis\: ctx vars props prev data log) diff --git a/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_tr.properties b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_tr.properties new file mode 100644 index 00000000000..2f13f2f1702 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_tr.properties @@ -0,0 +1,25 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=BeanShell Son \u0130\u015Flemcisi +filename.displayName=Dosya Ad\u0131 +filename.shortDescription=BeanShell beti\u011Fi dosyas\u0131 (bu beti\u011Fin \u00FCzerine yazar) +filenameGroup.displayName=Betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +parameterGroup.displayName=BeanShell'e ge\u00E7ilecek parametreler (\=> Dizgi(String) Parametreler ve String []bsh.args) +parameters.displayName=Parametreler +parameters.shortDescription=BeanShell'e ge\u00E7ilecek parametreler (dosya ya da betik) +script.shortDescription=BeanShell beti\u011Fi +scripting.displayName=Betik (de\u011Fi\u015Fkenler\: ctx vars props prev data log) diff --git a/src/components/org/apache/jmeter/extractor/DebugPostProcessor.java b/src/components/org/apache/jmeter/extractor/DebugPostProcessor.java new file mode 100644 index 00000000000..71f72133ed4 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/DebugPostProcessor.java @@ -0,0 +1,156 @@ +/* + * 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.extractor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.HashMap; +import java.util.Set; + +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Debugging Post-Processor: creates a subSample containing the variables defined in the previous sampler. + */ +public class DebugPostProcessor extends AbstractTestElement implements PostProcessor, TestBean { + + private static final long serialVersionUID = 260L; + + private boolean displaySamplerProperties; + + private boolean displayJMeterVariables; + + private boolean displayJMeterProperties; + + private boolean displaySystemProperties; + + @Override + public void process(){ + StringBuilder sb = new StringBuilder(100); + StringBuilder rd = new StringBuilder(20); // for request Data + SampleResult sr = new SampleResult(); + sr.setSampleLabel(getName()); + sr.sampleStart(); + JMeterContext threadContext = getThreadContext(); + if (isDisplaySamplerProperties()){ + rd.append("SamplerProperties\n"); + sb.append("SamplerProperties:\n"); + formatPropertyIterator(sb, threadContext.getCurrentSampler().propertyIterator()); + sb.append("\n"); + } + + if (isDisplayJMeterVariables()){ + rd.append("JMeterVariables\n"); + sb.append("JMeterVariables:\n"); + formatSet(sb, threadContext.getVariables().entrySet()); + sb.append("\n"); + } + + if (isDisplayJMeterProperties()){ + rd.append("JMeterProperties\n"); + sb.append("JMeterProperties:\n"); + formatSet(sb, JMeterUtils.getJMeterProperties().entrySet()); + sb.append("\n"); + } + + if (isDisplaySystemProperties()){ + rd.append("SystemProperties\n"); + sb.append("SystemProperties:\n"); + formatSet(sb, System.getProperties().entrySet()); + sb.append("\n"); + } + + sr.setResponseData(sb.toString(), null); + sr.setDataType(SampleResult.TEXT); + sr.setSamplerData(rd.toString()); + sr.setResponseOK(); + sr.sampleEnd(); + threadContext.getPreviousResult().addSubResult(sr); + } + + private void formatPropertyIterator(StringBuilder sb, PropertyIterator iter) { + Map map = new HashMap(); + while (iter.hasNext()) { + JMeterProperty item = iter.next(); + map.put(item.getName(), item.getStringValue()); + } + formatSet(sb, map.entrySet()); + } + + private void formatSet(StringBuilder sb, @SuppressWarnings("rawtypes") Set s) { + @SuppressWarnings("unchecked") + ArrayList> al = new ArrayList>(s); + Collections.sort(al, new Comparator>(){ + @Override + public int compare(Map.Entry o1, Map.Entry o2) { + String m1,m2; + m1=(String)o1.getKey(); + m2=(String)o2.getKey(); + return m1.compareTo(m2); + } + }); + for(Map.Entry me : al){ + sb.append(me.getKey()); + sb.append("="); + sb.append(me.getValue()); + sb.append("\n"); + } + } + + public boolean isDisplayJMeterVariables() { + return displayJMeterVariables; + } + + public void setDisplayJMeterVariables(boolean displayJMeterVariables) { + this.displayJMeterVariables = displayJMeterVariables; + } + + public boolean isDisplayJMeterProperties() { + return displayJMeterProperties; + } + + public void setDisplayJMeterProperties(boolean displayJMeterPropterties) { + this.displayJMeterProperties = displayJMeterPropterties; + } + + public boolean isDisplaySamplerProperties() { + return displaySamplerProperties; + } + + public void setDisplaySamplerProperties(boolean displaySamplerProperties) { + this.displaySamplerProperties = displaySamplerProperties; + } + + public boolean isDisplaySystemProperties() { + return displaySystemProperties; + } + + public void setDisplaySystemProperties(boolean displaySystemProperties) { + this.displaySystemProperties = displaySystemProperties; + } +} diff --git a/src/components/org/apache/jmeter/extractor/DebugPostProcessorBeanInfo.java b/src/components/org/apache/jmeter/extractor/DebugPostProcessorBeanInfo.java new file mode 100644 index 00000000000..1b82cbfc31b --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/DebugPostProcessorBeanInfo.java @@ -0,0 +1,57 @@ +/* + * 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.extractor; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class DebugPostProcessorBeanInfo extends BeanInfoSupport { + + public DebugPostProcessorBeanInfo() { + super(DebugPostProcessor.class); + + PropertyDescriptor p; + + p = property("displaySamplerProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + + p = property("displayJMeterVariables"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + + p = property("displayJMeterProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + p = property("displaySystemProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + } + +} diff --git a/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources.properties b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources.properties new file mode 100644 index 00000000000..35af9d9bece --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources.properties @@ -0,0 +1,24 @@ +# 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. + +displayJMeterProperties.displayName=JMeter properties +displayJMeterProperties.shortDescription=Display JMeter properties ? +displayJMeterVariables.displayName=JMeter variables +displayJMeterVariables.shortDescription=Display JMeter variables ? +displayName=Debug PostProcessor +displaySamplerProperties.displayName=Sampler properties +displaySamplerProperties.shortDescription=Display Sampler properties ? +displaySystemProperties.displayName=System properties +displaySystemProperties.shortDescription=Display System properties ? diff --git a/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_de.properties b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_de.properties new file mode 100644 index 00000000000..4bd2c434d66 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_de.properties @@ -0,0 +1,24 @@ +# 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. + +displayJMeterProperties.displayName=JMeter Eigenschaften +displayJMeterProperties.shortDescription=Sollen die JMeter Eigenschaften angezeigt werden? +displayJMeterVariables.displayName=JMeter Variablen +displayJMeterVariables.shortDescription=Sollen die JMeter Variablen angezeigt werden +displayName=Debug Post-Prozessor +displaySamplerProperties.displayName=Sampler Eigenschaften +displaySamplerProperties.shortDescription=Sollen die Sampler Eigenschaften angezeigt werden +displaySystemProperties.displayName=System Eigenschaften +displaySystemProperties.shortDescription=Sollen die System Eigenschaften angezeigt werden diff --git a/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_fr.properties b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_fr.properties new file mode 100644 index 00000000000..866cc3e06e8 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_fr.properties @@ -0,0 +1,25 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayJMeterProperties.displayName=Propri\u00E9t\u00E9s JMeter +displayJMeterProperties.shortDescription=Afficher les propri\u00E9t\u00E9s JMeter ? +displayJMeterVariables.displayName=Variables JMeter +displayJMeterVariables.shortDescription=Afficher les variables JMeter ? +displayName=Post-Processeur D\u00E9bogage +displaySamplerProperties.displayName=Propri\u00E9t\u00E9s Echantillon +displaySamplerProperties.shortDescription=Afficher les propri\u00E9t\u00E9s Echantillon ? +displaySystemProperties.displayName=Propri\u00E9t\u00E9s Syst\u00E8me +displaySystemProperties.shortDescription=Afficher les propri\u00E9t\u00E9s syst\u00E8mes ? diff --git a/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_pt_BR.properties b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_pt_BR.properties new file mode 100644 index 00000000000..e6e43f1da5a --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_pt_BR.properties @@ -0,0 +1,24 @@ +# 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. + +displayJMeterProperties.displayName=Propriedades do JMeter +displayJMeterProperties.shortDescription=Exibir propriedades do JMeter? +displayJMeterVariables.displayName=Vari\u00E1veis do JMeter +displayJMeterVariables.shortDescription=Exibir vari\u00E1veis do JMeter? +displayName=Debug P\u00F3s-Processador +displaySamplerProperties.displayName=Propriedades do testador? +displaySamplerProperties.shortDescription=Exibir propriedades do testador? +displaySystemProperties.displayName=Propriedades do sistema +displaySystemProperties.shortDescription=Exibir propriedades do sistema? diff --git a/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_tr.properties b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_tr.properties new file mode 100644 index 00000000000..275308c70c0 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_tr.properties @@ -0,0 +1,25 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayJMeterProperties.displayName=JMeter ayarlar\u0131 +displayJMeterProperties.shortDescription=JMeter ayarlar\u0131n\u0131 g\u00F6ster ? +displayJMeterVariables.displayName=JMeter de\u011Fi\u015Fkenleri +displayJMeterVariables.shortDescription=JMeter de\u011Fi\u015Fkenlerini g\u00F6ster ? +displayName=Ay\u0131klama Son \u0130\u015Flemcisi +displaySamplerProperties.displayName=\u00D6rnekleyicisi ayarlar\u0131 +displaySamplerProperties.shortDescription=\u00D6rnekleyicisi ayarlar\u0131n\u0131 g\u00F6ster ? +displaySystemProperties.displayName=Sistem ayarlar\u0131 +displaySystemProperties.shortDescription=Sistem ayarlar\u0131n\u0131 g\u00F6ster ? diff --git a/src/components/org/apache/jmeter/extractor/Extractor.java b/src/components/org/apache/jmeter/extractor/Extractor.java new file mode 100644 index 00000000000..ab1e24d968c --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/Extractor.java @@ -0,0 +1,48 @@ +/* + * 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.extractor; + +import java.io.Serializable; +import java.util.List; + +/** + * CSS/JQuery based extractor for HTML pages + * @since 2.9 + */ +public interface Extractor extends Serializable { + /** + * + * @param expression Expression used for extraction of nodes + * @param attribute Attribute name to return + * @param matchNumber Match number + * @param inputString Page or excerpt + * @param result List of results + * @param found current matches found + * @param cacheKey If not null, the implementation is encouraged to cache parsing result and use this key as part of cache key + * @return match found updated + */ + int extract( + String expression, + String attribute, + int matchNumber, + String inputString, + List result, + int found, + String cacheKey); +} diff --git a/src/components/org/apache/jmeter/extractor/HtmlExtractor.java b/src/components/org/apache/jmeter/extractor/HtmlExtractor.java new file mode 100644 index 00000000000..53273c6b904 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/HtmlExtractor.java @@ -0,0 +1,321 @@ +/* + * 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.extractor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * + */ +public class HtmlExtractor extends AbstractScopedTestElement implements PostProcessor, Serializable { + + public static final String EXTRACTOR_JSOUP = "JSOUP"; //$NON-NLS-1$ + + public static final String EXTRACTOR_JODD = "JODD"; //$NON-NLS-1$ + + /** + * Get the possible extractor implementations + * @return Array containing the names of the possible extractors. + */ + public static String[] getImplementations(){ + return new String[]{EXTRACTOR_JSOUP,EXTRACTOR_JODD}; + } + + public static final String DEFAULT_EXTRACTOR = ""; // $NON-NLS-1$ + + /** + * + */ + private static final long serialVersionUID = 3978073849365558131L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String EXPRESSION = "HtmlExtractor.expr"; // $NON-NLS-1$ + + private static final String ATTRIBUTE = "HtmlExtractor.attribute"; // $NON-NLS-1$ + + private static final String REFNAME = "HtmlExtractor.refname"; // $NON-NLS-1$ + + private static final String MATCH_NUMBER = "HtmlExtractor.match_number"; // $NON-NLS-1$ + + private static final String DEFAULT = "HtmlExtractor.default"; // $NON-NLS-1$ + + private static final String EXTRACTOR_IMPL = "HtmlExtractor.extractor_impl"; // $NON-NLS-1$ + + private static final String REF_MATCH_NR = "_matchNr"; // $NON-NLS-1$ + + private static final String UNDERSCORE = "_"; // $NON-NLS-1$ + + private Extractor extractor; + + /** + * Parses the response data using CSS/JQuery expressions and saving the results + * into variables for use later in the test. + * + * @see org.apache.jmeter.processor.PostProcessor#process() + */ + @Override + public void process() { + JMeterContext context = getThreadContext(); + SampleResult previousResult = context.getPreviousResult(); + if (previousResult == null) { + return; + } + log.debug("HtmlExtractor processing result"); + + // Fetch some variables + JMeterVariables vars = context.getVariables(); + + String refName = getRefName(); + String expression = getExpression(); + String attribute = getAttribute(); + int matchNumber = getMatchNumber(); + final String defaultValue = getDefaultValue(); + + if (defaultValue.length() > 0){// Only replace default if it is provided + vars.put(refName, defaultValue); + } + + try { + List matches = + extractMatchingStrings(vars, expression, attribute, matchNumber, previousResult); + int prevCount = 0; + String prevString = vars.get(refName + REF_MATCH_NR); + if (prevString != null) { + vars.remove(refName + REF_MATCH_NR);// ensure old value is not left defined + try { + prevCount = Integer.parseInt(prevString); + } catch (NumberFormatException e1) { + log.warn("Could not parse "+prevString+" "+e1); + } + } + int matchCount=0;// Number of refName_n variable sets to keep + try { + String match; + if (matchNumber >= 0) {// Original match behaviour + match = getCorrectMatch(matches, matchNumber); + if (match != null) { + vars.put(refName, match); + } + } else // < 0 means we save all the matches + { + matchCount = matches.size(); + vars.put(refName + REF_MATCH_NR, Integer.toString(matchCount));// Save the count + for (int i = 1; i <= matchCount; i++) { + match = getCorrectMatch(matches, i); + if (match != null) { + final String refName_n = new StringBuilder(refName).append(UNDERSCORE).append(i).toString(); + vars.put(refName_n, match); + } + } + } + // Remove any left-over variables + for (int i = matchCount + 1; i <= prevCount; i++) { + final String refName_n = new StringBuilder(refName).append(UNDERSCORE).append(i).toString(); + vars.remove(refName_n); + } + } catch (RuntimeException e) { + log.warn("Error while generating result"); + } + + } catch (RuntimeException e) { + log.warn("Error while generating result"); + } + + } + + /** + * Grab the appropriate result from the list. + * + * @param matches + * list of matches + * @param entry + * the entry number in the list + * @return MatchResult + */ + private String getCorrectMatch(List matches, int entry) { + int matchSize = matches.size(); + + if (matchSize <= 0 || entry > matchSize){ + return null; + } + + if (entry == 0) // Random match + { + return matches.get(JMeterUtils.getRandomInt(matchSize)); + } + + return matches.get(entry - 1); + } + + private List extractMatchingStrings(JMeterVariables vars, + String expression, String attribute, int matchNumber, + SampleResult previousResult) { + int found = 0; + List result = new ArrayList(); + if (isScopeVariable()){ + String inputString=vars.get(getVariableName()); + if(!StringUtils.isEmpty(inputString)) { + getExtractorImpl().extract(expression, attribute, matchNumber, inputString, result, found, "-1"); + } else { + if(inputString==null) { + log.warn("No variable '"+getVariableName()+"' found to process by Css/JQuery Extractor '"+getName()+"', skipping processing"); + } + return Collections.emptyList(); + } + } else { + List sampleList = getSampleList(previousResult); + int i=0; + for (SampleResult sr : sampleList) { + String inputString = sr.getResponseDataAsString(); + found = getExtractorImpl().extract(expression, attribute, matchNumber, inputString, result, found, + i>0 ? null : Integer.toString(i)); + i++; + if (matchNumber > 0 && found == matchNumber){// no need to process further + break; + } + } + } + return result; + } + + /** + * @param impl Extractor implementation + * @return Extractor + */ + public static final Extractor getExtractorImpl(String impl) { + boolean useDefaultExtractor = DEFAULT_EXTRACTOR.equals(impl); + if (useDefaultExtractor || EXTRACTOR_JSOUP.equals(impl)) { + return new JSoupExtractor(); + } else if (EXTRACTOR_JODD.equals(impl)) { + return new JoddExtractor(); + } else { + throw new IllegalArgumentException("Extractor implementation:"+ impl+" is unknown"); + } + } + + /** + * + * @return Extractor + */ + private Extractor getExtractorImpl() { + if (extractor == null) { + extractor = getExtractorImpl(getExtractor()); + } + return extractor; + } + + + /** + * Set the extractor. Has to be one of the list that can be obtained by + * {@link HtmlExtractor#getImplementations()} + * + * @param attribute + * The name of the extractor to be used + */ + public void setExtractor(String attribute) { + setProperty(EXTRACTOR_IMPL, attribute); + } + + /** + * Get the name of the currently configured extractor + * @return The name of the extractor currently used + */ + public String getExtractor() { + return getPropertyAsString(EXTRACTOR_IMPL); // $NON-NLS-1$ + } + + + public void setAttribute(String attribute) { + setProperty(ATTRIBUTE, attribute); + } + + public String getAttribute() { + return getPropertyAsString(ATTRIBUTE, ""); // $NON-NLS-1$ + } + + public void setExpression(String regex) { + setProperty(EXPRESSION, regex); + } + + public String getExpression() { + return getPropertyAsString(EXPRESSION); + } + + public void setRefName(String refName) { + setProperty(REFNAME, refName); + } + + public String getRefName() { + return getPropertyAsString(REFNAME); + } + + /** + * Set which Match to use. This can be any positive number, indicating the + * exact match to use, or 0, which is interpreted as meaning random. + * + * @param matchNumber The number of the match to be used + */ + public void setMatchNumber(int matchNumber) { + setProperty(new IntegerProperty(MATCH_NUMBER, matchNumber)); + } + + public void setMatchNumber(String matchNumber) { + setProperty(MATCH_NUMBER, matchNumber); + } + + public int getMatchNumber() { + return getPropertyAsInt(MATCH_NUMBER); + } + + public String getMatchNumberAsString() { + return getPropertyAsString(MATCH_NUMBER); + } + + /** + * Sets the value of the variable if no matches are found + * + * @param defaultValue The default value for the variable + */ + public void setDefaultValue(String defaultValue) { + setProperty(DEFAULT, defaultValue); + } + + /** + * Get the default value for the variable if no matches are found + * @return The default value for the variable + */ + public String getDefaultValue() { + return getPropertyAsString(DEFAULT); + } +} diff --git a/src/components/org/apache/jmeter/extractor/JSR223PostProcessor.java b/src/components/org/apache/jmeter/extractor/JSR223PostProcessor.java new file mode 100644 index 00000000000..2fdc4b9b99d --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/JSR223PostProcessor.java @@ -0,0 +1,49 @@ +/* + * 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.extractor; + +import java.io.IOException; + +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223PostProcessor extends JSR223TestElement implements Cloneable, PostProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + @Override + public void process() { + try { + ScriptEngine scriptEngine = getScriptEngine(); + processFileOrScript(scriptEngine, null); + } catch (ScriptException e) { + log.error("Problem in JSR223 script "+getName(), e); + } catch (IOException e) { + log.error("Problem in JSR223 script "+getName(), e); + } + } +} diff --git a/src/components/org/apache/jmeter/extractor/JSR223PostProcessorBeanInfo.java b/src/components/org/apache/jmeter/extractor/JSR223PostProcessorBeanInfo.java new file mode 100644 index 00000000000..2b976b6793e --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/JSR223PostProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.extractor; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223PostProcessorBeanInfo extends JSR223BeanInfoSupport { + + public JSR223PostProcessorBeanInfo() { + super(JSR223PostProcessor.class); + } + +} diff --git a/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources.properties b/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources.properties new file mode 100644 index 00000000000..2a964e9d35b --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources.properties @@ -0,0 +1,31 @@ +# 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. + +displayName=JSR223 PostProcessor +cacheKey.displayName=Compilation cache key +cacheKey.shortDescription=If Cache key is not empty, script will be compiled if JSR223 underlying script language supports it and CompiledScript will be cached, ensure script does not use any variable before making it cacheable +cacheKey_group.displayName=Script compilation caching +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources_fr.properties b/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources_fr.properties new file mode 100644 index 00000000000..34a1427d2e3 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources_fr.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Post-Processeur JSR223 +cacheKey.displayName=Clef de caching +cacheKey.shortDescription=Si la clef de caching n'est pas vide, le script sera compil\u00E9 si le language sous-jacent JSR223 fournit cette fonctionnalit\u00E9 et l'objet CompiledScript sera mis en cache, assurez vous avant d'utiliser ce caching que le script n'utilise pas de variables JMeter +cacheKey_group.displayName=Param\u00E8tres de caching du Script compil\u00E9 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/extractor/JSoupExtractor.java b/src/components/org/apache/jmeter/extractor/JSoupExtractor.java new file mode 100644 index 00000000000..19742b126bc --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/JSoupExtractor.java @@ -0,0 +1,97 @@ +/* + * 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.extractor; + +import java.util.List; + +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.util.JOrphanUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +/** + * JSoup based CSS/JQuery extractor + * see http://jsoup.org/cookbook/extracting-data/selector-syntax + * @since 2.9 + */ +public class JSoupExtractor implements Extractor { + + /** + * + */ + private static final long serialVersionUID = -6308012192067714191L; + + private static final String CACHE_KEY_PREFIX = JSoupExtractor.class.getName()+"_PARSED_BODY"; + + /** + * + */ + public JSoupExtractor() { + super(); + } + + /** + * @see org.apache.jmeter.extractor.Extractor#extract(String, String, int, String, List, int, String) + */ + @Override + public int extract(String expression, String attribute, int matchNumber, + String inputString, List result, int found, + String cacheKey) { + Document document = null; + if (cacheKey != null) { + document = (Document) + JMeterContextService.getContext().getSamplerContext().get(CACHE_KEY_PREFIX+cacheKey); + if(document==null) { + document = Jsoup.parse(inputString); + JMeterContextService.getContext().getSamplerContext().put(CACHE_KEY_PREFIX+cacheKey, document); + } + } else { + document = Jsoup.parse(inputString); + } + Elements elements = document.select(expression); + int size = elements.size(); + for (int i = 0; i < size; i++) { + Element element = elements.get(i); + if (matchNumber <=0 || found != matchNumber) { + result.add(extractValue(attribute, element)); + found++; + } else { + break; + } + } + return found; + } + + + /** + * + * @param attribute Attribute to extract + * @param element Element + * @return String value + */ + private String extractValue(String attribute, Element element) { + if (!JOrphanUtils.isBlank(attribute)) { + return element.attr(attribute); + } else { + return element.text().trim(); + } + } +} diff --git a/src/components/org/apache/jmeter/extractor/JoddExtractor.java b/src/components/org/apache/jmeter/extractor/JoddExtractor.java new file mode 100644 index 00000000000..1c0e957f0fc --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/JoddExtractor.java @@ -0,0 +1,102 @@ +/* + * 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.extractor; + +import java.util.List; + +import jodd.lagarto.dom.LagartoDOMBuilder; +import jodd.lagarto.dom.Node; +import jodd.lagarto.dom.NodeSelector; +import jodd.log.LoggerFactory; +import jodd.log.impl.Slf4jLoggerFactory; + +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Jodd-Lagerto based CSS/JQuery extractor + * see http://jodd.org/doc/csselly/ + * @since 2.9 + */ +public class JoddExtractor implements Extractor { + + /** + * + */ + private static final long serialVersionUID = -7235814605293262972L; + + private static final String CACHE_KEY_PREFIX = JoddExtractor.class.getName()+"_PARSED_BODY"; + + static { + LoggerFactory.setLoggerFactory(new Slf4jLoggerFactory()); + } + + /** + * + */ + public JoddExtractor() { + super(); + } + + /** + * @see org.apache.jmeter.extractor.Extractor#extract(String, String, int, String, List, int, String) + */ + @Override + public int extract(String expression, String attribute, int matchNumber, + String inputString, List result, int found, + String cacheKey) { + NodeSelector nodeSelector = null; + if (cacheKey != null) { + nodeSelector = (NodeSelector) + JMeterContextService.getContext().getSamplerContext().get(CACHE_KEY_PREFIX+cacheKey); + if(nodeSelector==null) { + LagartoDOMBuilder domBuilder = new LagartoDOMBuilder(); + jodd.lagarto.dom.Document doc = domBuilder.parse(inputString); + nodeSelector = new NodeSelector(doc); + JMeterContextService.getContext().getSamplerContext().put(CACHE_KEY_PREFIX+cacheKey, nodeSelector); + } + } else { + LagartoDOMBuilder domBuilder = new LagartoDOMBuilder(); + jodd.lagarto.dom.Document doc = domBuilder.parse(inputString); + nodeSelector = new NodeSelector(doc); + } + List elements = nodeSelector.select(expression); + int size = elements.size(); + for (int i = 0; i < size; i++) { + Node element = elements.get(i); + if (matchNumber <=0 || found != matchNumber) { + result.add(extractValue(attribute, element)); + found++; + } else { + break; + } + } + + return found; + } + + + private String extractValue(String attribute, Node element) { + if (!JOrphanUtils.isBlank(attribute)) { + return element.getAttribute(attribute); + } else { + return element.getTextContent().trim(); + } + } +} diff --git a/src/components/org/apache/jmeter/extractor/RegexExtractor.java b/src/components/org/apache/jmeter/extractor/RegexExtractor.java new file mode 100644 index 00000000000..41aa61e7045 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/RegexExtractor.java @@ -0,0 +1,503 @@ +/* + * 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.extractor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.Document; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcher; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +// @see org.apache.jmeter.extractor.TestRegexExtractor for unit tests + +public class RegexExtractor extends AbstractScopedTestElement implements PostProcessor, Serializable { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // What to match against. N.B. do not change the string value or test plans will break! + private static final String MATCH_AGAINST = "RegexExtractor.useHeaders"; // $NON-NLS-1$ + /* + * Permissible values: + * true - match against headers + * false or absent - match against body (this was the original default) + * URL - match against URL + * These are passed to the setUseField() method + * + * Do not change these values! + */ + public static final String USE_HDRS = "true"; // $NON-NLS-1$ + public static final String USE_REQUEST_HDRS = "request_headers"; // $NON-NLS-1$ + public static final String USE_BODY = "false"; // $NON-NLS-1$ + public static final String USE_BODY_UNESCAPED = "unescaped"; // $NON-NLS-1$ + public static final String USE_BODY_AS_DOCUMENT = "as_document"; // $NON-NLS-1$ + public static final String USE_URL = "URL"; // $NON-NLS-1$ + public static final String USE_CODE = "code"; // $NON-NLS-1$ + public static final String USE_MESSAGE = "message"; // $NON-NLS-1$ + + + private static final String REGEX = "RegexExtractor.regex"; // $NON-NLS-1$ + + private static final String REFNAME = "RegexExtractor.refname"; // $NON-NLS-1$ + + private static final String MATCH_NUMBER = "RegexExtractor.match_number"; // $NON-NLS-1$ + + private static final String DEFAULT = "RegexExtractor.default"; // $NON-NLS-1$ + + private static final String TEMPLATE = "RegexExtractor.template"; // $NON-NLS-1$ + + private static final String REF_MATCH_NR = "_matchNr"; // $NON-NLS-1$ + + private static final String UNDERSCORE = "_"; // $NON-NLS-1$ + + private transient List template; + + /** + * Parses the response data using regular expressions and saving the results + * into variables for use later in the test. + * + * @see org.apache.jmeter.processor.PostProcessor#process() + */ + @Override + public void process() { + initTemplate(); + JMeterContext context = getThreadContext(); + SampleResult previousResult = context.getPreviousResult(); + if (previousResult == null) { + return; + } + log.debug("RegexExtractor processing result"); + + // Fetch some variables + JMeterVariables vars = context.getVariables(); + String refName = getRefName(); + int matchNumber = getMatchNumber(); + + final String defaultValue = getDefaultValue(); + if (defaultValue.length() > 0){// Only replace default if it is provided + vars.put(refName, defaultValue); + } + Perl5Matcher matcher = JMeterUtils.getMatcher(); + String regex = getRegex(); + Pattern pattern = null; + try { + pattern = JMeterUtils.getPatternCache().getPattern(regex, Perl5Compiler.READ_ONLY_MASK); + List matches = processMatches(pattern, regex, previousResult, matchNumber, vars); + int prevCount = 0; + String prevString = vars.get(refName + REF_MATCH_NR); + if (prevString != null) { + vars.remove(refName + REF_MATCH_NR);// ensure old value is not left defined + try { + prevCount = Integer.parseInt(prevString); + } catch (NumberFormatException e1) { + log.warn("Could not parse "+prevString+" "+e1); + } + } + int matchCount=0;// Number of refName_n variable sets to keep + try { + MatchResult match; + if (matchNumber >= 0) {// Original match behaviour + match = getCorrectMatch(matches, matchNumber); + if (match != null) { + vars.put(refName, generateResult(match)); + saveGroups(vars, refName, match); + } else { + // refname has already been set to the default (if present) + removeGroups(vars, refName); + } + } else // < 0 means we save all the matches + { + removeGroups(vars, refName); // remove any single matches + matchCount = matches.size(); + vars.put(refName + REF_MATCH_NR, Integer.toString(matchCount));// Save the count + for (int i = 1; i <= matchCount; i++) { + match = getCorrectMatch(matches, i); + if (match != null) { + final String refName_n = new StringBuilder(refName).append(UNDERSCORE).append(i).toString(); + vars.put(refName_n, generateResult(match)); + saveGroups(vars, refName_n, match); + } + } + } + // Remove any left-over variables + for (int i = matchCount + 1; i <= prevCount; i++) { + final String refName_n = new StringBuilder(refName).append(UNDERSCORE).append(i).toString(); + vars.remove(refName_n); + removeGroups(vars, refName_n); + } + } catch (RuntimeException e) { + log.warn("Error while generating result"); + } + } catch (MalformedCachePatternException e) { + log.error("Error in pattern: " + regex); + } finally { + JMeterUtils.clearMatcherMemory(matcher, pattern); + } + } + + private String getInputString(SampleResult result) { + String inputString = useUrl() ? result.getUrlAsString() // Bug 39707 + : useHeaders() ? result.getResponseHeaders() + : useRequestHeaders() ? result.getRequestHeaders() + : useCode() ? result.getResponseCode() // Bug 43451 + : useMessage() ? result.getResponseMessage() // Bug 43451 + : useUnescapedBody() ? StringEscapeUtils.unescapeHtml4(result.getResponseDataAsString()) + : useBodyAsDocument() ? Document.getTextFromDocument(result.getResponseData()) + : result.getResponseDataAsString() // Bug 36898 + ; + if (log.isDebugEnabled()) { + log.debug("Input = " + inputString); + } + return inputString; + } + + private List processMatches(Pattern pattern, String regex, SampleResult result, int matchNumber, JMeterVariables vars) { + if (log.isDebugEnabled()) { + log.debug("Regex = " + regex); + } + + Perl5Matcher matcher = JMeterUtils.getMatcher(); + List matches = new ArrayList(); + int found = 0; + + if (isScopeVariable()){ + String inputString=vars.get(getVariableName()); + if(inputString == null) { + log.warn("No variable '"+getVariableName()+"' found to process by RegexExtractor '"+getName()+"', skipping processing"); + return Collections.emptyList(); + } + matchStrings(matchNumber, matcher, pattern, matches, found, + inputString); + } else { + List sampleList = getSampleList(result); + for (SampleResult sr : sampleList) { + String inputString = getInputString(sr); + found = matchStrings(matchNumber, matcher, pattern, matches, found, + inputString); + if (matchNumber > 0 && found == matchNumber){// no need to process further + break; + } + } + } + return matches; + } + + private int matchStrings(int matchNumber, Perl5Matcher matcher, + Pattern pattern, List matches, int found, + String inputString) { + PatternMatcherInput input = new PatternMatcherInput(inputString); + while (matchNumber <=0 || found != matchNumber) { + if (matcher.contains(input, pattern)) { + log.debug("RegexExtractor: Match found!"); + matches.add(matcher.getMatch()); + found++; + } else { + break; + } + } + return found; + } + + /** + * Creates the variables:
+ * basename_gn, where n=0...# of groups
+ * basename_g = number of groups (apart from g0) + */ + private void saveGroups(JMeterVariables vars, String basename, MatchResult match) { + StringBuilder buf = new StringBuilder(); + buf.append(basename); + buf.append("_g"); // $NON-NLS-1$ + int pfxlen=buf.length(); + String prevString=vars.get(buf.toString()); + int previous=0; + if (prevString!=null){ + try { + previous=Integer.parseInt(prevString); + } catch (NumberFormatException e) { + log.warn("Could not parse "+prevString+" "+e); + } + } + //Note: match.groups() includes group 0 + final int groups = match.groups(); + for (int x = 0; x < groups; x++) { + buf.append(x); + vars.put(buf.toString(), match.group(x)); + buf.setLength(pfxlen); + } + vars.put(buf.toString(), Integer.toString(groups-1)); + for (int i = groups; i <= previous; i++){ + buf.append(i); + vars.remove(buf.toString());// remove the remaining _gn vars + buf.setLength(pfxlen); + } + } + + /** + * Removes the variables:
+ * basename_gn, where n=0...# of groups
+ * basename_g = number of groups (apart from g0) + */ + private void removeGroups(JMeterVariables vars, String basename) { + StringBuilder buf = new StringBuilder(); + buf.append(basename); + buf.append("_g"); // $NON-NLS-1$ + int pfxlen=buf.length(); + // How many groups are there? + int groups; + try { + groups=Integer.parseInt(vars.get(buf.toString())); + } catch (NumberFormatException e) { + groups=0; + } + vars.remove(buf.toString());// Remove the group count + for (int i = 0; i <= groups; i++) { + buf.append(i); + vars.remove(buf.toString());// remove the g0,g1...gn vars + buf.setLength(pfxlen); + } + } + + private String generateResult(MatchResult match) { + StringBuilder result = new StringBuilder(); + for (Object obj : template) { + if (log.isDebugEnabled()) { + log.debug("RegexExtractor: Template piece " + obj + " (" + obj.getClass().getSimpleName() + ")"); + } + if (obj instanceof Integer) { + result.append(match.group(((Integer) obj).intValue())); + } else { + result.append(obj); + } + } + if (log.isDebugEnabled()) { + log.debug("Regex Extractor result = " + result.toString()); + } + return result.toString(); + } + + private void initTemplate() { + if (template != null) { + return; + } + // Contains Strings and Integers + List combined = new ArrayList(); + String rawTemplate = getTemplate(); + PatternMatcher matcher = JMeterUtils.getMatcher(); + Pattern templatePattern = JMeterUtils.getPatternCache().getPattern("\\$(\\d+)\\$" // $NON-NLS-1$ + , Perl5Compiler.READ_ONLY_MASK + & Perl5Compiler.SINGLELINE_MASK); + if (log.isDebugEnabled()) { + log.debug("Pattern = " + templatePattern.getPattern()); + log.debug("template = " + rawTemplate); + } + int beginOffset = 0; + MatchResult currentResult; + PatternMatcherInput pinput = new PatternMatcherInput(rawTemplate); + while(matcher.contains(pinput, templatePattern)) { + currentResult = matcher.getMatch(); + final int beginMatch = currentResult.beginOffset(0); + if (beginMatch > beginOffset) { // string is not empty + combined.add(rawTemplate.substring(beginOffset, beginMatch)); + } + combined.add(Integer.valueOf(currentResult.group(1)));// add match as Integer + beginOffset = currentResult.endOffset(0); + } + + if (beginOffset < rawTemplate.length()) { // trailing string is not empty + combined.add(rawTemplate.substring(beginOffset, rawTemplate.length())); + } + if (log.isDebugEnabled()){ + log.debug("Template item count: "+combined.size()); + for(Object o : combined){ + log.debug(o.getClass().getSimpleName()+" '"+o.toString()+"'"); + } + } + template = combined; + } + + /** + * Grab the appropriate result from the list. + * + * @param matches + * list of matches + * @param entry + * the entry number in the list + * @return MatchResult + */ + private MatchResult getCorrectMatch(List matches, int entry) { + int matchSize = matches.size(); + + if (matchSize <= 0 || entry > matchSize){ + return null; + } + + if (entry == 0) // Random match + { + return matches.get(JMeterUtils.getRandomInt(matchSize)); + } + + return matches.get(entry - 1); + } + + /** + * Set the regex to be used + * @param regex The string representation of the regex + */ + public void setRegex(String regex) { + setProperty(REGEX, regex); + } + + /** + * Get the regex which is to be used + * @return string representing the regex + */ + public String getRegex() { + return getPropertyAsString(REGEX); + } + + /** + * Set the prefix name of the variable to be used to store the regex matches + * @param refName prefix of the variables to be used + */ + public void setRefName(String refName) { + setProperty(REFNAME, refName); + } + + /** + * Get the prefix name of the variable to be used to store the regex matches + * @return The prefix of the variables to be used + */ + public String getRefName() { + return getPropertyAsString(REFNAME); + } + + /** + * Set which Match to use. This can be any positive number, indicating the + * exact match to use, or 0, which is interpreted as meaning + * random. + * + * @param matchNumber + * The number of the match to be used, or 0 if a + * random match should be used. + */ + public void setMatchNumber(int matchNumber) { + setProperty(new IntegerProperty(MATCH_NUMBER, matchNumber)); + } + + public void setMatchNumber(String matchNumber) { + setProperty(MATCH_NUMBER, matchNumber); + } + + public int getMatchNumber() { + return getPropertyAsInt(MATCH_NUMBER); + } + + public String getMatchNumberAsString() { + return getPropertyAsString(MATCH_NUMBER); + } + + /** + * Sets the value of the variable if no matches are found + * + * @param defaultValue The default value for the variable + */ + public void setDefaultValue(String defaultValue) { + setProperty(DEFAULT, defaultValue); + } + + /** + * Get the default value for the variable, which should be used, if no + * matches are found + * + * @return The default value for the variable + */ + public String getDefaultValue() { + return getPropertyAsString(DEFAULT); + } + + public void setTemplate(String template) { + setProperty(TEMPLATE, template); + } + + public String getTemplate() { + return getPropertyAsString(TEMPLATE); + } + + public boolean useHeaders() { + return USE_HDRS.equalsIgnoreCase( getPropertyAsString(MATCH_AGAINST)); + } + + public boolean useRequestHeaders() { + return USE_REQUEST_HDRS.equalsIgnoreCase(getPropertyAsString(MATCH_AGAINST)); + } + + // Allow for property not yet being set (probably only applies to Test cases) + public boolean useBody() { + String prop = getPropertyAsString(MATCH_AGAINST); + return prop.length()==0 || USE_BODY.equalsIgnoreCase(prop);// $NON-NLS-1$ + } + + public boolean useUnescapedBody() { + String prop = getPropertyAsString(MATCH_AGAINST); + return USE_BODY_UNESCAPED.equalsIgnoreCase(prop);// $NON-NLS-1$ + } + + public boolean useBodyAsDocument() { + String prop = getPropertyAsString(MATCH_AGAINST); + return USE_BODY_AS_DOCUMENT.equalsIgnoreCase(prop);// $NON-NLS-1$ + } + + public boolean useUrl() { + String prop = getPropertyAsString(MATCH_AGAINST); + return USE_URL.equalsIgnoreCase(prop); + } + + public boolean useCode() { + String prop = getPropertyAsString(MATCH_AGAINST); + return USE_CODE.equalsIgnoreCase(prop); + } + + public boolean useMessage() { + String prop = getPropertyAsString(MATCH_AGAINST); + return USE_MESSAGE.equalsIgnoreCase(prop); + } + + public void setUseField(String actionCommand) { + setProperty(MATCH_AGAINST,actionCommand); + } +} diff --git a/src/components/org/apache/jmeter/extractor/XPathExtractor.java b/src/components/org/apache/jmeter/extractor/XPathExtractor.java new file mode 100644 index 00000000000..a767dcb3ccb --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/XPathExtractor.java @@ -0,0 +1,342 @@ +/* + * 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.extractor; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.TidyException; +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +//@see org.apache.jmeter.extractor.TestXPathExtractor for unit tests + +/** + * Extracts text from (X)HTML response using XPath query language + * Example XPath queries: + *
+ *
/html/head/title
+ *
extracts Title from HTML response
+ *
//form[@name='countryForm']//select[@name='country']/option[text()='Czech Republic'])/@value + *
extracts value attribute of option element that match text 'Czech Republic' + * inside of select element with name attribute 'country' inside of + * form with name attribute 'countryForm'
+ *
//head
+ *
extracts the XML fragment for head node.
+ *
//head/text()
+ *
extracts the text content for head node.
+ *
+ */ + /* This file is inspired by RegexExtractor. + * author Henryk Paluch + * of Gitus a.s. + * + * See Bugzilla: 37183 + */ +public class XPathExtractor extends AbstractScopedTestElement implements + PostProcessor, Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final String MATCH_NR = "matchNr"; // $NON-NLS-1$ + + //+ JMX file attributes + private static final String XPATH_QUERY = "XPathExtractor.xpathQuery"; // $NON-NLS-1$ + private static final String REFNAME = "XPathExtractor.refname"; // $NON-NLS-1$ + private static final String DEFAULT = "XPathExtractor.default"; // $NON-NLS-1$ + private static final String TOLERANT = "XPathExtractor.tolerant"; // $NON-NLS-1$ + private static final String NAMESPACE = "XPathExtractor.namespace"; // $NON-NLS-1$ + private static final String QUIET = "XPathExtractor.quiet"; // $NON-NLS-1$ + private static final String REPORT_ERRORS = "XPathExtractor.report_errors"; // $NON-NLS-1$ + private static final String SHOW_WARNINGS = "XPathExtractor.show_warnings"; // $NON-NLS-1$ + private static final String DOWNLOAD_DTDS = "XPathExtractor.download_dtds"; // $NON-NLS-1$ + private static final String WHITESPACE = "XPathExtractor.whitespace"; // $NON-NLS-1$ + private static final String VALIDATE = "XPathExtractor.validate"; // $NON-NLS-1$ + private static final String FRAGMENT = "XPathExtractor.fragment"; // $NON-NLS-1$ + //- JMX file attributes + + + private String concat(String s1,String s2){ + return new StringBuilder(s1).append("_").append(s2).toString(); // $NON-NLS-1$ + } + + private String concat(String s1, int i){ + return new StringBuilder(s1).append("_").append(i).toString(); // $NON-NLS-1$ + } + + /** + * Do the job - extract value from (X)HTML response using XPath Query. + * Return value as variable defined by REFNAME. Returns DEFAULT value + * if not found. + */ + @Override + public void process() { + JMeterContext context = getThreadContext(); + final SampleResult previousResult = context.getPreviousResult(); + if (previousResult == null){ + return; + } + JMeterVariables vars = context.getVariables(); + String refName = getRefName(); + vars.put(refName, getDefaultValue()); + final String matchNR = concat(refName,MATCH_NR); + int prevCount=0; // number of previous matches + try { + prevCount=Integer.parseInt(vars.get(matchNR)); + } catch (NumberFormatException e) { + // ignored + } + vars.put(matchNR, "0"); // In case parse fails // $NON-NLS-1$ + vars.remove(concat(refName,"1")); // In case parse fails // $NON-NLS-1$ + + List matches = new ArrayList(); + try{ + if (isScopeVariable()){ + String inputString=vars.get(getVariableName()); + if(inputString != null) { + if(inputString.length()>0) { + Document d = parseResponse(inputString); + getValuesForXPath(d,getXPathQuery(),matches); + } + } else { + log.warn("No variable '"+getVariableName()+"' found to process by XPathExtractor '"+getName()+"', skipping processing"); + } + } else { + List samples = getSampleList(previousResult); + for (SampleResult res : samples) { + Document d = parseResponse(res.getResponseDataAsString()); + getValuesForXPath(d,getXPathQuery(),matches); + } + } + final int matchCount = matches.size(); + vars.put(matchNR, String.valueOf(matchCount)); + if (matchCount > 0){ + String value = matches.get(0); + if (value != null) { + vars.put(refName, value); + } + for(int i=0; i < matchCount; i++){ + value = matches.get(i); + if (value != null) { + vars.put(concat(refName,i+1),matches.get(i)); + } + } + } + vars.remove(concat(refName,matchCount+1)); // Just in case + // Clear any other remaining variables + for(int i=matchCount+2; i <= prevCount; i++) { + vars.remove(concat(refName,i)); + } + }catch(IOException e){// e.g. DTD not reachable + final String errorMessage = "IOException on ("+getXPathQuery()+")"; + log.error(errorMessage,e); + AssertionResult ass = new AssertionResult(getName()); + ass.setError(true); + ass.setFailureMessage(new StringBuilder("IOException: ").append(e.getLocalizedMessage()).toString()); + previousResult.addAssertionResult(ass); + previousResult.setSuccessful(false); + } catch (ParserConfigurationException e) {// Should not happen + final String errrorMessage = "ParserConfigurationException while processing ("+getXPathQuery()+")"; + log.error(errrorMessage,e); + throw new JMeterError(errrorMessage,e); + } catch (SAXException e) {// Can happen for bad input document + log.warn("SAXException while processing ("+getXPathQuery()+") "+e.getLocalizedMessage()); + addAssertionFailure(previousResult, e, false); // Should this also fail the sample? + } catch (TransformerException e) {// Can happen for incorrect XPath expression + log.warn("TransformerException while processing ("+getXPathQuery()+") "+e.getLocalizedMessage()); + addAssertionFailure(previousResult, e, false); + } catch (TidyException e) { + // Will already have been logged by XPathUtil + addAssertionFailure(previousResult, e, true); // fail the sample + } + } + + private void addAssertionFailure(final SampleResult previousResult, + final Throwable thrown, final boolean setFailed) { + AssertionResult ass = new AssertionResult(thrown.getClass().getSimpleName()); // $NON-NLS-1$ + ass.setFailure(true); + ass.setFailureMessage(thrown.getLocalizedMessage()+"\nSee log file for further details."); + previousResult.addAssertionResult(ass); + if (setFailed){ + previousResult.setSuccessful(false); + } + } + + /*============= object properties ================*/ + public void setXPathQuery(String val){ + setProperty(XPATH_QUERY,val); + } + + public String getXPathQuery(){ + return getPropertyAsString(XPATH_QUERY); + } + + public void setRefName(String refName) { + setProperty(REFNAME, refName); + } + + public String getRefName() { + return getPropertyAsString(REFNAME); + } + + public void setDefaultValue(String val) { + setProperty(DEFAULT, val); + } + + public String getDefaultValue() { + return getPropertyAsString(DEFAULT); + } + + public void setTolerant(boolean val) { + setProperty(new BooleanProperty(TOLERANT, val)); + } + + public boolean isTolerant() { + return getPropertyAsBoolean(TOLERANT); + } + + public void setNameSpace(boolean val) { + setProperty(new BooleanProperty(NAMESPACE, val)); + } + + public boolean useNameSpace() { + return getPropertyAsBoolean(NAMESPACE); + } + + public void setReportErrors(boolean val) { + setProperty(REPORT_ERRORS, val, false); + } + + public boolean reportErrors() { + return getPropertyAsBoolean(REPORT_ERRORS, false); + } + + public void setShowWarnings(boolean val) { + setProperty(SHOW_WARNINGS, val, false); + } + + public boolean showWarnings() { + return getPropertyAsBoolean(SHOW_WARNINGS, false); + } + + public void setQuiet(boolean val) { + setProperty(QUIET, val, true); + } + + public boolean isQuiet() { + return getPropertyAsBoolean(QUIET, true); + } + + /** + * Should we return fragment as text, rather than text of fragment? + * @return true if we should return fragment rather than text + */ + public boolean getFragment() { + return getPropertyAsBoolean(FRAGMENT, false); + } + + /** + * Should we return fragment as text, rather than text of fragment? + * @param selected true to return fragment. + */ + public void setFragment(boolean selected) { + setProperty(FRAGMENT, selected, false); + } + + /*================= internal business =================*/ + /** + * Converts (X)HTML response to DOM object Tree. + * This version cares of charset of response. + * @param unicodeData + * @return the parsed document + * + */ + private Document parseResponse(String unicodeData) + throws UnsupportedEncodingException, IOException, ParserConfigurationException,SAXException,TidyException + { + //TODO: validate contentType for reasonable types? + + // NOTE: responseData encoding is server specific + // Therefore we do byte -> unicode -> byte conversion + // to ensure UTF-8 encoding as required by XPathUtil + // convert unicode String -> UTF-8 bytes + byte[] utf8data = unicodeData.getBytes("UTF-8"); // $NON-NLS-1$ + ByteArrayInputStream in = new ByteArrayInputStream(utf8data); + boolean isXML = JOrphanUtils.isXML(utf8data); + // this method assumes UTF-8 input data + return XPathUtil.makeDocument(in,false,false,useNameSpace(),isTolerant(),isQuiet(),showWarnings(),reportErrors() + ,isXML, isDownloadDTDs()); + } + + /** + * Extract value from Document d by XPath query. + * @param d the document + * @param query the query to execute + * @param matchStrings list of matched strings (may include nulls) + * + * @throws TransformerException + */ + private void getValuesForXPath(Document d,String query, List matchStrings) + throws TransformerException { + XPathUtil.putValuesForXPathInList(d, query, matchStrings, getFragment()); + } + + public void setWhitespace(boolean selected) { + setProperty(WHITESPACE, selected, false); + } + + public boolean isWhitespace() { + return getPropertyAsBoolean(WHITESPACE, false); + } + + public void setValidating(boolean selected) { + setProperty(VALIDATE, selected); + } + + public boolean isValidating() { + return getPropertyAsBoolean(VALIDATE, false); + } + + public void setDownloadDTDs(boolean selected) { + setProperty(DOWNLOAD_DTDS, selected, false); + } + + public boolean isDownloadDTDs() { + return getPropertyAsBoolean(DOWNLOAD_DTDS, false); + } +} diff --git a/src/components/org/apache/jmeter/extractor/gui/HtmlExtractorGui.java b/src/components/org/apache/jmeter/extractor/gui/HtmlExtractorGui.java new file mode 100644 index 00000000000..48a2607c01f --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/gui/HtmlExtractorGui.java @@ -0,0 +1,224 @@ +/* + * 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.extractor.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import org.apache.jmeter.extractor.HtmlExtractor; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * CSS/JQuery Expression Extractor Post-Processor GUI + * @since 2.9 + */ +public class HtmlExtractorGui extends AbstractPostProcessorGui { + private static final long serialVersionUID = 240L; + + /** + * This choice means don't explicitly set Implementation and rely on default + */ + private static final String USE_DEFAULT_EXTRACTOR_IMPL = ""; // $NON-NLS-1$ + + private JLabeledTextField expressionField; + + private JLabeledTextField attributeField; + + private JLabeledTextField defaultField; + + private JLabeledTextField matchNumberField; + + private JLabeledTextField refNameField; + + private JComboBox extractorImplName; + + + public HtmlExtractorGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "html_extractor_title"; //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof HtmlExtractor){ + HtmlExtractor htmlExtractor = (HtmlExtractor) el; + showScopeSettings(htmlExtractor, true); + expressionField.setText(htmlExtractor.getExpression()); + attributeField.setText(htmlExtractor.getAttribute()); + defaultField.setText(htmlExtractor.getDefaultValue()); + matchNumberField.setText(htmlExtractor.getMatchNumberAsString()); + refNameField.setText(htmlExtractor.getRefName()); + extractorImplName.setSelectedItem(htmlExtractor.getExtractor()); + } + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + AbstractScopedTestElement extractor = new HtmlExtractor(); + modifyTestElement(extractor); + return extractor; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement extractor) { + super.configureTestElement(extractor); + if (extractor instanceof HtmlExtractor) { + HtmlExtractor htmlExtractor = (HtmlExtractor) extractor; + saveScopeSettings(htmlExtractor); + htmlExtractor.setRefName(refNameField.getText()); + htmlExtractor.setExpression(expressionField.getText()); + htmlExtractor.setAttribute(attributeField.getText()); + htmlExtractor.setDefaultValue(defaultField.getText()); + htmlExtractor.setMatchNumber(matchNumberField.getText()); + if(extractorImplName.getSelectedIndex()< HtmlExtractor.getImplementations().length) { + htmlExtractor.setExtractor(HtmlExtractor.getImplementations()[extractorImplName.getSelectedIndex()]); + } else { + htmlExtractor.setExtractor(USE_DEFAULT_EXTRACTOR_IMPL); + } + + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + extractorImplName.setSelectedItem(HtmlExtractor.DEFAULT_EXTRACTOR); + expressionField.setText(""); //$NON-NLS-1$ + attributeField.setText(""); //$NON-NLS-1$ + defaultField.setText(""); //$NON-NLS-1$ + refNameField.setText(""); //$NON-NLS-1$ + matchNumberField.setText(""); //$NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createScopePanel(true)); + box.add(makeExtractorPanel()); + add(box, BorderLayout.NORTH); + add(makeParameterPanel(), BorderLayout.CENTER); + } + + + + private Component makeExtractorPanel() { + JPanel panel = new HorizontalPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("html_extractor_type"))); //$NON-NLS-1$ + + DefaultComboBoxModel m = new DefaultComboBoxModel(); + for (String s : HtmlExtractor.getImplementations()){ + m.addElement(s); + } + m.addElement(USE_DEFAULT_EXTRACTOR_IMPL); + extractorImplName = new JComboBox(m); + extractorImplName.setSelectedItem(HtmlExtractor.DEFAULT_EXTRACTOR); + JLabel label2 = new JLabel(JMeterUtils.getResString("html_extractor_type")); // $NON-NLS-1$ + label2.setLabelFor(extractorImplName); + panel.add(label2); + panel.add(extractorImplName); + return panel; + } + + private JPanel makeParameterPanel() { + expressionField = new JLabeledTextField(JMeterUtils.getResString("expression_field")); //$NON-NLS-1$ + attributeField = new JLabeledTextField(JMeterUtils.getResString("attribute_field")); //$NON-NLS-1$ + defaultField = new JLabeledTextField(JMeterUtils.getResString("default_value_field")); //$NON-NLS-1$ + refNameField = new JLabeledTextField(JMeterUtils.getResString("ref_name_field")); //$NON-NLS-1$ + matchNumberField = new JLabeledTextField(JMeterUtils.getResString("match_num_field")); //$NON-NLS-1$ + + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + initConstraints(gbc); + addField(panel, refNameField, gbc); + resetContraints(gbc); + addField(panel, expressionField, gbc); + resetContraints(gbc); + addField(panel, attributeField, gbc); + resetContraints(gbc); + addField(panel, matchNumberField, gbc); + resetContraints(gbc); + gbc.weighty = 1; + addField(panel, defaultField, gbc); + return panel; + } + + private void addField(JPanel panel, JLabeledTextField field, GridBagConstraints gbc) { + List item = field.getComponentList(); + panel.add(item.get(0), gbc.clone()); + gbc.gridx++; + gbc.weightx = 1; + gbc.fill=GridBagConstraints.HORIZONTAL; + panel.add(item.get(1), gbc.clone()); + } + + // Next line + private void resetContraints(GridBagConstraints gbc) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0; + gbc.fill=GridBagConstraints.NONE; + } + + private void initConstraints(GridBagConstraints gbc) { + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.NONE; + gbc.gridheight = 1; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0; + gbc.weighty = 0; + } +} diff --git a/src/components/org/apache/jmeter/extractor/gui/RegexExtractorGui.java b/src/components/org/apache/jmeter/extractor/gui/RegexExtractorGui.java new file mode 100644 index 00000000000..3c72109b0e4 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/gui/RegexExtractorGui.java @@ -0,0 +1,261 @@ +/* + * 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.extractor.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import org.apache.jmeter.extractor.RegexExtractor; +import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * Regular Expression Extractor Post-Processor GUI + */ +public class RegexExtractorGui extends AbstractPostProcessorGui { + private static final long serialVersionUID = 240L; + + private JLabeledTextField regexField; + + private JLabeledTextField templateField; + + private JLabeledTextField defaultField; + + private JLabeledTextField matchNumberField; + + private JLabeledTextField refNameField; + + private JRadioButton useBody; + + private JRadioButton useUnescapedBody; + + private JRadioButton useBodyAsDocument; + + private JRadioButton useHeaders; + + private JRadioButton useRequestHeaders; + + private JRadioButton useURL; + + private JRadioButton useCode; + + private JRadioButton useMessage; + + private ButtonGroup group; + + public RegexExtractorGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "regex_extractor_title"; //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof RegexExtractor){ + RegexExtractor re = (RegexExtractor) el; + showScopeSettings(re, true); + useHeaders.setSelected(re.useHeaders()); + useRequestHeaders.setSelected(re.useRequestHeaders()); + useBody.setSelected(re.useBody()); + useUnescapedBody.setSelected(re.useUnescapedBody()); + useBodyAsDocument.setSelected(re.useBodyAsDocument()); + useURL.setSelected(re.useUrl()); + useCode.setSelected(re.useCode()); + useMessage.setSelected(re.useMessage()); + regexField.setText(re.getRegex()); + templateField.setText(re.getTemplate()); + defaultField.setText(re.getDefaultValue()); + matchNumberField.setText(re.getMatchNumberAsString()); + refNameField.setText(re.getRefName()); + } + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + AbstractScopedTestElement extractor = new RegexExtractor(); + modifyTestElement(extractor); + return extractor; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement extractor) { + super.configureTestElement(extractor); + if (extractor instanceof RegexExtractor) { + RegexExtractor regex = (RegexExtractor) extractor; + saveScopeSettings(regex); + regex.setUseField(group.getSelection().getActionCommand()); + regex.setRefName(refNameField.getText()); + regex.setRegex(regexField.getText()); + regex.setTemplate(templateField.getText()); + regex.setDefaultValue(defaultField.getText()); + regex.setMatchNumber(matchNumberField.getText()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + useBody.setSelected(true); + + regexField.setText(""); //$NON-NLS-1$ + templateField.setText(""); //$NON-NLS-1$ + defaultField.setText(""); //$NON-NLS-1$ + refNameField.setText(""); //$NON-NLS-1$ + matchNumberField.setText(""); //$NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createScopePanel(true)); + box.add(makeSourcePanel()); + add(box, BorderLayout.NORTH); + add(makeParameterPanel(), BorderLayout.CENTER); + } + + private JPanel makeSourcePanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("regex_source"))); //$NON-NLS-1$ + + useBody = new JRadioButton(JMeterUtils.getResString("regex_src_body")); //$NON-NLS-1$ + useUnescapedBody = new JRadioButton(JMeterUtils.getResString("regex_src_body_unescaped")); //$NON-NLS-1$ + useBodyAsDocument = new JRadioButton(JMeterUtils.getResString("regex_src_body_as_document")); //$NON-NLS-1$ + useHeaders = new JRadioButton(JMeterUtils.getResString("regex_src_hdrs")); //$NON-NLS-1$ + useRequestHeaders = new JRadioButton(JMeterUtils.getResString("regex_src_hdrs_req")); //$NON-NLS-1$ + useURL = new JRadioButton(JMeterUtils.getResString("regex_src_url")); //$NON-NLS-1$ + useCode = new JRadioButton(JMeterUtils.getResString("assertion_code_resp")); //$NON-NLS-1$ + useMessage = new JRadioButton(JMeterUtils.getResString("assertion_message_resp")); //$NON-NLS-1$ + + group = new ButtonGroup(); + group.add(useBody); + group.add(useUnescapedBody); + group.add(useBodyAsDocument); + group.add(useHeaders); + group.add(useRequestHeaders); + group.add(useURL); + group.add(useCode); + group.add(useMessage); + + panel.add(useBody); + panel.add(useUnescapedBody); + panel.add(useBodyAsDocument); + panel.add(useHeaders); + panel.add(useRequestHeaders); + panel.add(useURL); + panel.add(useCode); + panel.add(useMessage); + + useBody.setSelected(true); + + // So we know which button is selected + useBody.setActionCommand(RegexExtractor.USE_BODY); + useUnescapedBody.setActionCommand(RegexExtractor.USE_BODY_UNESCAPED); + useBodyAsDocument.setActionCommand(RegexExtractor.USE_BODY_AS_DOCUMENT); + useHeaders.setActionCommand(RegexExtractor.USE_HDRS); + useRequestHeaders.setActionCommand(RegexExtractor.USE_REQUEST_HDRS); + useURL.setActionCommand(RegexExtractor.USE_URL); + useCode.setActionCommand(RegexExtractor.USE_CODE); + useMessage.setActionCommand(RegexExtractor.USE_MESSAGE); + + return panel; + } + + private JPanel makeParameterPanel() { + regexField = new JLabeledTextField(JMeterUtils.getResString("regex_field")); //$NON-NLS-1$ + templateField = new JLabeledTextField(JMeterUtils.getResString("template_field")); //$NON-NLS-1$ + defaultField = new JLabeledTextField(JMeterUtils.getResString("default_value_field")); //$NON-NLS-1$ + refNameField = new JLabeledTextField(JMeterUtils.getResString("ref_name_field")); //$NON-NLS-1$ + matchNumberField = new JLabeledTextField(JMeterUtils.getResString("match_num_field")); //$NON-NLS-1$ + + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + initConstraints(gbc); + addField(panel, refNameField, gbc); + resetContraints(gbc); + addField(panel, regexField, gbc); + resetContraints(gbc); + addField(panel, templateField, gbc); + resetContraints(gbc); + addField(panel, matchNumberField, gbc); + resetContraints(gbc); + gbc.weighty = 1; + addField(panel, defaultField, gbc); + return panel; + } + + private void addField(JPanel panel, JLabeledTextField field, GridBagConstraints gbc) { + List item = field.getComponentList(); + panel.add(item.get(0), gbc.clone()); + gbc.gridx++; + gbc.weightx = 1; + gbc.fill=GridBagConstraints.HORIZONTAL; + panel.add(item.get(1), gbc.clone()); + } + + // Next line + private void resetContraints(GridBagConstraints gbc) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0; + gbc.fill=GridBagConstraints.NONE; + } + + private void initConstraints(GridBagConstraints gbc) { + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.NONE; + gbc.gridheight = 1; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0; + gbc.weighty = 0; + } +} diff --git a/src/components/org/apache/jmeter/extractor/gui/XPathExtractorGui.java b/src/components/org/apache/jmeter/extractor/gui/XPathExtractorGui.java new file mode 100644 index 00000000000..1befd621a19 --- /dev/null +++ b/src/components/org/apache/jmeter/extractor/gui/XPathExtractorGui.java @@ -0,0 +1,174 @@ +/* + * 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.extractor.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JPanel; + +import org.apache.jmeter.assertions.gui.XMLConfPanel; +import org.apache.jmeter.extractor.XPathExtractor; +import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; +/** + * GUI for XPathExtractor class. + */ + /* This file is inspired by RegexExtractor. + * See Bugzilla: 37183 + */ +public class XPathExtractorGui extends AbstractPostProcessorGui { + + private static final long serialVersionUID = 240L; + + private final JLabeledTextField defaultField = + new JLabeledTextField(JMeterUtils.getResString("default_value_field"));//$NON-NLS-1$ + + private final JLabeledTextField xpathQueryField = + new JLabeledTextField(JMeterUtils.getResString("xpath_extractor_query"));//$NON-NLS-1$ + + private final JLabeledTextField refNameField = + new JLabeledTextField(JMeterUtils.getResString("ref_name_field"));//$NON-NLS-1$ + + // Should we return fragment as text, rather than text of fragment? + private final JCheckBox getFragment = + new JCheckBox(JMeterUtils.getResString("xpath_extractor_fragment"));//$NON-NLS-1$ + + private final XMLConfPanel xml = new XMLConfPanel(); + + @Override + public String getLabelResource() { + return "xpath_extractor_title"; //$NON-NLS-1$ + } + + public XPathExtractorGui(){ + super(); + init(); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + XPathExtractor xpe = (XPathExtractor) el; + showScopeSettings(xpe,true); + xpathQueryField.setText(xpe.getXPathQuery()); + defaultField.setText(xpe.getDefaultValue()); + refNameField.setText(xpe.getRefName()); + getFragment.setSelected(xpe.getFragment()); + xml.configure(xpe); + } + + + @Override + public TestElement createTestElement() { + XPathExtractor extractor = new XPathExtractor(); + modifyTestElement(extractor); + return extractor; + } + + @Override + public void modifyTestElement(TestElement extractor) { + super.configureTestElement(extractor); + if ( extractor instanceof XPathExtractor){ + XPathExtractor xpath = (XPathExtractor)extractor; + saveScopeSettings(xpath); + xpath.setDefaultValue(defaultField.getText()); + xpath.setRefName(refNameField.getText()); + xpath.setXPathQuery(xpathQueryField.getText()); + xpath.setFragment(getFragment.isSelected()); + xml.modifyTestElement(xpath); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + xpathQueryField.setText(""); // $NON-NLS-1$ + defaultField.setText(""); // $NON-NLS-1$ + refNameField.setText(""); // $NON-NLS-1$ + xml.setDefaultValues(); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createScopePanel(true, true, true)); + xml.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("xpath_assertion_option"))); //$NON-NLS-1$ + box.add(xml); + box.add(getFragment); + box.add(makeParameterPanel()); + add(box, BorderLayout.NORTH); + } + + + private JPanel makeParameterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + initConstraints(gbc); + addField(panel, refNameField, gbc); + resetContraints(gbc); + addField(panel, xpathQueryField, gbc); + resetContraints(gbc); + gbc.weighty = 1; + addField(panel, defaultField, gbc); + return panel; + } + + private void addField(JPanel panel, JLabeledTextField field, GridBagConstraints gbc) { + List item = field.getComponentList(); + panel.add(item.get(0), gbc.clone()); + gbc.gridx++; + gbc.weightx = 1; + gbc.fill=GridBagConstraints.HORIZONTAL; + panel.add(item.get(1), gbc.clone()); + } + + private void resetContraints(GridBagConstraints gbc) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0; + gbc.fill=GridBagConstraints.NONE; + } + + private void initConstraints(GridBagConstraints gbc) { + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.NONE; + gbc.gridheight = 1; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0; + gbc.weighty = 0; + } +} diff --git a/src/components/org/apache/jmeter/modifiers/BSFPreProcessor.java b/src/components/org/apache/jmeter/modifiers/BSFPreProcessor.java new file mode 100644 index 00000000000..5c2c0f38c64 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BSFPreProcessor.java @@ -0,0 +1,50 @@ +/* + * 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.modifiers; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFPreProcessor extends BSFTestElement implements Cloneable, PreProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + @Override + public void process(){ + BSFManager mgr =null; + try { + mgr = getManager(); + if (mgr == null) { return; } + processFileOrScript(mgr); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + } finally { + if (mgr != null) { + mgr.terminate(); + } + } + } +} diff --git a/src/components/org/apache/jmeter/modifiers/BSFPreProcessorBeanInfo.java b/src/components/org/apache/jmeter/modifiers/BSFPreProcessorBeanInfo.java new file mode 100644 index 00000000000..6f9b728534b --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BSFPreProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.modifiers; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFPreProcessorBeanInfo extends BSFBeanInfoSupport { + + public BSFPreProcessorBeanInfo() { + super(BSFPreProcessor.class); + } + +} diff --git a/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources.properties b/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources.properties new file mode 100644 index 00000000000..1167fe90eb4 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources.properties @@ -0,0 +1,28 @@ +# 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. + +displayName=BSF PreProcessor +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_fr.properties b/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_fr.properties new file mode 100644 index 00000000000..7900ec0455b --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_fr.properties @@ -0,0 +1,29 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Pr\u00E9-Processeur BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables\: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_pt_BR.properties b/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_pt_BR.properties new file mode 100644 index 00000000000..297a497379f --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_pt_BR.properties @@ -0,0 +1,28 @@ +# 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. + +displayName=Pr\u00E9-Processador BSF +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao script (\=> String Parameters e String []args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao arquivo ou script +script.displayName=Script +script.shortDescription=Script na linguagem BSF apropriada +scriptLanguage.displayName=Linguagem +scriptLanguage.shortDescription=Nome da linguagem BSF, ex\: beanshell, javascript, jexl +scripting.displayName=Script (vari\u00E1veis\: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Linguagem de script (ex\: beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessor.java b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessor.java new file mode 100644 index 00000000000..4aafb7f8970 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessor.java @@ -0,0 +1,64 @@ +/* + * 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.modifiers; + +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +public class BeanShellPreProcessor extends BeanShellTestElement + implements Cloneable, PreProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + // can be specified in jmeter.properties + private static final String INIT_FILE = "beanshell.preprocessor.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + @Override + public void process(){ + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + log.error("BeanShell not found"); + return; + } + JMeterContext jmctx = JMeterContextService.getContext(); + Sampler sam = jmctx.getCurrentSampler(); + try { + // Add variables for access to context and variables + bshInterpreter.set("sampler", sam);//$NON-NLS-1$ + processFileOrScript(bshInterpreter); + } catch (JMeterException e) { + log.warn("Problem in BeanShell script "+e); + } + } +} diff --git a/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorBeanInfo.java b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorBeanInfo.java new file mode 100644 index 00000000000..cc1f7239179 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.modifiers; + +import org.apache.jmeter.util.BeanShellBeanInfoSupport; + +public class BeanShellPreProcessorBeanInfo extends BeanShellBeanInfoSupport { + + public BeanShellPreProcessorBeanInfo() { + super(BeanShellPreProcessor.class); + } + +} diff --git a/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources.properties b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources.properties new file mode 100644 index 00000000000..01e4750caf2 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=BeanShell PreProcessor +filename.displayName=File Name +filename.shortDescription=BeanShell script file (overrides script) +filenameGroup.displayName=Script file (overrides script) +parameterGroup.displayName=Parameters to be passed to BeanShell (=> String Parameters and String []bsh.args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to BeanShell (file or script) +resetGroup.displayName=Reset bsh.Interpreter before each call +resetInterpreter.displayName=Reset Interpreter +script.displayName=Script +script.shortDescription=Beanshell script +scripting.displayName=Script (variables: ctx vars props prev sampler log) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_de.properties b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_de.properties new file mode 100644 index 00000000000..f4a23706397 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_de.properties @@ -0,0 +1,25 @@ +# 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. + +displayName=BeanShell Pre-Prozessor +filename.displayName=Dateiname +filename.shortDescription=BeanShell Script Datei (Vorrang vor Script) +filenameGroup.displayName=Script Datei (Vorrang vor Script) +parameterGroup.displayName=Parameter die der BeanShell \u00FCbergeben werden (String Parameters, String []bsh.args) +parameters.displayName=Parameter +parameters.shortDescription=Parameter die der BeanShell \u00FCbergeben werden (Datei oder Script) +script.displayName=BeanShell Script +script.shortDescription=BeanShell Script +scripting.displayName=Script (Variablen\: ctx vars props prev sampler log) diff --git a/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_fr.properties b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_fr.properties new file mode 100644 index 00000000000..dc266abf324 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_fr.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=Pr\u00E9-Processeur BeanShell +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BeanShell (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au BeanShell (\=> String Parameters and String []bsh.args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BeanShell (fichier ou script) +resetGroup.displayName=R\u00E9initialiser l'interpr\u00E9teur bsh avant chaque appel +resetInterpreter.displayName=R\u00E9initialiser l'interpr\u00E9teur +script.displayName=Script +script.shortDescription=Script BeanShell +scripting.displayName=Script (variables \: ctx vars props prev sampler log) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_pt_BR.properties b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_pt_BR.properties new file mode 100644 index 00000000000..22b3d0da18f --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_pt_BR.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=Pr\u00E9-Processador BeanShell +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script do BeanShell (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao BeanShell (arquivo ou script) +resetGroup.displayName=Reiniciar bsh.Interpreter antes de cada chamada +resetInterpreter.displayName=Reiniciar interpretador +script.displayName=\ +script.shortDescription=Script Beanshell +scripting.displayName=Script (var\u00E1veis\: ctx vars props prev sampler log) diff --git a/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_tr.properties b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_tr.properties new file mode 100644 index 00000000000..2b4b34e8344 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_tr.properties @@ -0,0 +1,25 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=BeanShell \u00D6n \u0130\u015Flemcisi +filename.displayName=Dosya Ad\u0131 +filename.shortDescription=BeanShell betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +filenameGroup.displayName=Betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +parameterGroup.displayName=BeanShell'e ge\u00E7ilecek parametreler (\=> Dizgi (string) parametreler ve String [] bsh.args) +parameters.displayName=Parametreler +parameters.shortDescription=BeanShell'e ge\u00E7ilecek parametreler (dosya ya da betik) +script.shortDescription=Beanshell beti\u011Fi +scripting.displayName=Betik (de\u011Fi\u015Fkenler\: ctx vars props prev sampler log) diff --git a/src/components/org/apache/jmeter/modifiers/CounterConfig.java b/src/components/org/apache/jmeter/modifiers/CounterConfig.java new file mode 100644 index 00000000000..36424485737 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/CounterConfig.java @@ -0,0 +1,243 @@ +/* + * 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.modifiers; + +import java.io.Serializable; +import java.text.DecimalFormat; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Provides a counter per-thread(user) or per-thread group. + */ +public class CounterConfig extends AbstractTestElement + implements Serializable, LoopIterationListener, NoThreadClone { + + private static final long serialVersionUID = 233L; + + private static final String START = "CounterConfig.start"; // $NON-NLS-1$ + + private static final String END = "CounterConfig.end"; // $NON-NLS-1$ + + private static final String INCREMENT = "CounterConfig.incr"; // $NON-NLS-1$ + + private static final String FORMAT = "CounterConfig.format"; // $NON-NLS-1$ + + private static final String PER_USER = "CounterConfig.per_user"; // $NON-NLS-1$ + + private static final String VAR_NAME = "CounterConfig.name"; // $NON-NLS-1$ + + private static final String RESET_ON_THREAD_GROUP_ITERATION = "CounterConfig.reset_on_tg_iteration"; // $NON-NLS-1$ + + private static final boolean RESET_ON_THREAD_GROUP_ITERATION_DEFAULT = false; + + // This class is not cloned per thread, so this is shared + //@GuardedBy("this") + private long globalCounter = Long.MIN_VALUE; + + // Used for per-thread/user numbers + private transient ThreadLocal perTheadNumber; + + // Used for per-thread/user storage of increment in Thread Group Main loop + private transient ThreadLocal perTheadLastIterationNumber; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private void init() { + perTheadNumber = new ThreadLocal() { + @Override + protected Long initialValue() { + return Long.valueOf(getStart()); + } + }; + perTheadLastIterationNumber = new ThreadLocal() { + @Override + protected Long initialValue() { + return Long.valueOf(1); + } + }; + } + + + public CounterConfig() { + super(); + init(); + } + + private Object readResolve(){ + init(); + return this; + } + /** + * @see LoopIterationListener#iterationStart(LoopIterationEvent) + */ + @Override + public void iterationStart(LoopIterationEvent event) { + // Cannot use getThreadContext() as not cloned per thread + JMeterVariables variables = JMeterContextService.getContext().getVariables(); + long start = getStart(); + long end = getEnd(); + long increment = getIncrement(); + if (!isPerUser()) { + synchronized (this) { + if (globalCounter == Long.MIN_VALUE || globalCounter > end) { + globalCounter = start; + } + variables.put(getVarName(), formatNumber(globalCounter)); + globalCounter += increment; + } + } else { + long current = perTheadNumber.get().longValue(); + if(isResetOnThreadGroupIteration()) { + int iteration = variables.getIteration(); + Long lastIterationNumber = perTheadLastIterationNumber.get(); + if(iteration != lastIterationNumber.longValue()) { + // reset + current = getStart(); + } + perTheadLastIterationNumber.set(Long.valueOf(iteration)); + } + variables.put(getVarName(), formatNumber(current)); + current += increment; + if (current > end) { + current = start; + } + perTheadNumber.set(Long.valueOf(current)); + } + } + + // Use format to create number; if it fails, use the default + private String formatNumber(long value){ + String format = getFormat(); + if (format != null && format.length() > 0) { + try { + DecimalFormat myFormatter = new DecimalFormat(format); + return myFormatter.format(value); + } catch (NumberFormatException ignored) { + log.warn("Error formating "+value + " at format:"+format+", using default"); + } catch (IllegalArgumentException ignored) { + log.warn("Error formating "+value + " at format:"+format+", using default"); + } + } + return Long.toString(value); + } + + public void setStart(long start) { + setProperty(new LongProperty(START, start)); + } + + public void setStart(String start) { + setProperty(START, start); + } + + public long getStart() { + return getPropertyAsLong(START); + } + + public String getStartAsString() { + return getPropertyAsString(START); + } + + public void setEnd(long end) { + setProperty(new LongProperty(END, end)); + } + + public void setEnd(String end) { + setProperty(END, end); + } + + /** + * @param value boolean indicating if counter must be reset on Thread Group Iteration + */ + public void setResetOnThreadGroupIteration(boolean value) { + setProperty(RESET_ON_THREAD_GROUP_ITERATION, value, RESET_ON_THREAD_GROUP_ITERATION_DEFAULT); + } + + /** + * @return true if counter must be reset on Thread Group Iteration + */ + public boolean isResetOnThreadGroupIteration() { + return getPropertyAsBoolean(RESET_ON_THREAD_GROUP_ITERATION, RESET_ON_THREAD_GROUP_ITERATION_DEFAULT); + } + + /** + * + * @return counter upper limit (default Long.MAX_VALUE) + */ + public long getEnd() { + long propertyAsLong = getPropertyAsLong(END); + if (propertyAsLong == 0 && "".equals(getProperty(END).getStringValue())) { + propertyAsLong = Long.MAX_VALUE; + } + return propertyAsLong; + } + + public String getEndAsString(){ + return getPropertyAsString(END); + } + + public void setIncrement(long inc) { + setProperty(new LongProperty(INCREMENT, inc)); + } + + public void setIncrement(String incr) { + setProperty(INCREMENT, incr); + } + + public long getIncrement() { + return getPropertyAsLong(INCREMENT); + } + + public String getIncrementAsString() { + return getPropertyAsString(INCREMENT); + } + + public void setIsPerUser(boolean isPer) { + setProperty(new BooleanProperty(PER_USER, isPer)); + } + + public boolean isPerUser() { + return getPropertyAsBoolean(PER_USER); + } + + public void setVarName(String name) { + setProperty(VAR_NAME, name); + } + + public String getVarName() { + return getPropertyAsString(VAR_NAME); + } + + public void setFormat(String format) { + setProperty(FORMAT, format); + } + + public String getFormat() { + return getPropertyAsString(FORMAT); + } +} diff --git a/src/components/org/apache/jmeter/modifiers/JSR223PreProcessor.java b/src/components/org/apache/jmeter/modifiers/JSR223PreProcessor.java new file mode 100644 index 00000000000..00b26ca6ece --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/JSR223PreProcessor.java @@ -0,0 +1,49 @@ +/* + * 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.modifiers; + +import java.io.IOException; + +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223PreProcessor extends JSR223TestElement implements Cloneable, PreProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + @Override + public void process() { + try { + ScriptEngine scriptEngine = getScriptEngine(); + processFileOrScript(scriptEngine, null); + } catch (ScriptException e) { + log.error("Problem in JSR223 script "+getName(), e); + } catch (IOException e) { + log.error("Problem in JSR223 script "+getName(), e); + } + } +} diff --git a/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorBeanInfo.java b/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorBeanInfo.java new file mode 100644 index 00000000000..3273b14cd3a --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.modifiers; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223PreProcessorBeanInfo extends JSR223BeanInfoSupport { + + public JSR223PreProcessorBeanInfo() { + super(JSR223PreProcessor.class); + } + +} diff --git a/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources.properties b/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources.properties new file mode 100644 index 00000000000..d5ce2443bfa --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources.properties @@ -0,0 +1,31 @@ +# 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. + +displayName=JSR223 PreProcessor +cacheKey.displayName=Compilation cache key +cacheKey.shortDescription=If Cache key is not empty, script will be compiled if JSR223 underlying script language supports it and CompiledScript will be cached, ensure script does not use any variable before making it cacheable +cacheKey_group.displayName=Script compilation caching +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources_fr.properties b/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources_fr.properties new file mode 100644 index 00000000000..7a04d75de2e --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources_fr.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Pr\u00E9-Processeur JSR223 +cacheKey.displayName=Clef de caching +cacheKey.shortDescription=Si la clef de caching n'est pas vide, le script sera compil\u00E9 si le language sous-jacent JSR223 fournit cette fonctionnalit\u00E9 et l'objet CompiledScript sera mis en cache, assurez vous avant d'utiliser ce caching que le script n'utilise pas de variables JMeter +cacheKey_group.displayName=Param\u00E8tres de caching du Script compil\u00E9 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/modifiers/UserParameters.java b/src/components/org/apache/jmeter/modifiers/UserParameters.java new file mode 100644 index 00000000000..1510d6dc77c --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/UserParameters.java @@ -0,0 +1,202 @@ +/* + * 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.modifiers; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class UserParameters extends AbstractTestElement implements Serializable, PreProcessor, LoopIterationListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + public static final String NAMES = "UserParameters.names";// $NON-NLS-1$ + + public static final String THREAD_VALUES = "UserParameters.thread_values";// $NON-NLS-1$ + + public static final String PER_ITERATION = "UserParameters.per_iteration";// $NON-NLS-1$ + + /* + * Although the lock appears to be an instance lock, in fact the lock is + * shared between all threads in a thread group, but different thread groups + * have different locks - see the clone() method below + * + * The lock ensures that all the variables are processed together, which is + * important for functions such as __CSVRead and _StringFromFile. + */ + private transient Object lock = new Object(); + + private Object readResolve(){ // Lock object must exist + lock = new Object(); + return this; + } + + public CollectionProperty getNames() { + return (CollectionProperty) getProperty(NAMES); + } + + public CollectionProperty getThreadLists() { + return (CollectionProperty) getProperty(THREAD_VALUES); + } + + /** + * The list of names of the variables to hold values. This list must come in + * the same order as the sub lists that are given to + * {@link #setThreadLists(Collection)}. + * + * @param list + * The ordered list of names + */ + public void setNames(Collection list) { + setProperty(new CollectionProperty(NAMES, list)); + } + + /** + * The list of names of the variables to hold values. This list must come in + * the same order as the sub lists that are given to + * {@link #setThreadLists(CollectionProperty)}. + * + * @param list + * The ordered list of names + */ + public void setNames(CollectionProperty list) { + setProperty(list); + } + + /** + * The thread list is a list of lists. Each list within the parent list is a + * collection of values for a simulated user. As many different sets of + * values can be supplied in this fashion to cause JMeter to set different + * values to variables for different test threads. + * + * @param threadLists + * The list of lists of values for each user thread + */ + public void setThreadLists(Collection threadLists) { + setProperty(new CollectionProperty(THREAD_VALUES, threadLists)); + } + + /** + * The thread list is a list of lists. Each list within the parent list is a + * collection of values for a simulated user. As many different sets of + * values can be supplied in this fashion to cause JMeter to set different + * values to variables for different test threads. + * + * @param threadLists + * The list of lists of values for each user thread + */ + public void setThreadLists(CollectionProperty threadLists) { + setProperty(threadLists); + } + + private CollectionProperty getValues() { + CollectionProperty threadValues = (CollectionProperty) getProperty(THREAD_VALUES); + if (threadValues.size() > 0) { + return (CollectionProperty) threadValues.get(getThreadContext().getThreadNum() % threadValues.size()); + } + return new CollectionProperty("noname", new LinkedList()); + } + + public boolean isPerIteration() { + return getPropertyAsBoolean(PER_ITERATION); + } + + public void setPerIteration(boolean perIter) { + setProperty(new BooleanProperty(PER_ITERATION, perIter)); + } + + @Override + public void process() { + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " process " + isPerIteration());//$NON-NLS-1$ + } + if (!isPerIteration()) { + setValues(); + } + } + + private void setValues() { + synchronized (lock) { + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " Running up named: " + getName());//$NON-NLS-1$ + } + PropertyIterator namesIter = getNames().iterator(); + PropertyIterator valueIter = getValues().iterator(); + JMeterVariables jmvars = getThreadContext().getVariables(); + while (namesIter.hasNext() && valueIter.hasNext()) { + String name = namesIter.next().getStringValue(); + String value = valueIter.next().getStringValue(); + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " saving variable: " + name + "=" + value);//$NON-NLS-1$ + } + jmvars.put(name, value); + } + } + } + + /** + * @see LoopIterationListener#iterationStart(LoopIterationEvent) + */ + @Override + public void iterationStart(LoopIterationEvent event) { + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " iteration start " + isPerIteration());//$NON-NLS-1$ + } + if (isPerIteration()) { + setValues(); + } + } + + /* + * (non-Javadoc) A new instance is created for each thread group, and the + * clone() method is then called to create copies for each thread in a + * thread group. This means that the lock object is common to a thread + * group; separate thread groups have separate locks. If this is not + * intended, the lock object could be made static. + * + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + UserParameters up = (UserParameters) super.clone(); + up.lock = lock; // ensure that clones share the same lock object + return up; + } + + /** + * {@inheritDoc} + */ + @Override + protected void mergeIn(TestElement element) { + // super.mergeIn(element); + } +} diff --git a/src/components/org/apache/jmeter/modifiers/gui/CounterConfigGui.java b/src/components/org/apache/jmeter/modifiers/gui/CounterConfigGui.java new file mode 100644 index 00000000000..39a8947bbb1 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/gui/CounterConfigGui.java @@ -0,0 +1,155 @@ +/* + * 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.modifiers.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.modifiers.CounterConfig; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class CounterConfigGui extends AbstractConfigGui implements ActionListener { + private static final long serialVersionUID = 240L; + + private JLabeledTextField startField; + private JLabeledTextField incrField; + private JLabeledTextField endField; + private JLabeledTextField varNameField; + private JLabeledTextField formatField; + private JCheckBox resetCounterOnEachThreadGroupIteration; + + private JCheckBox perUserField; + + public CounterConfigGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "counter_config_title";//$NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + CounterConfig config = new CounterConfig(); + modifyTestElement(config); + return config; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement c) { + if (c instanceof CounterConfig) { + CounterConfig config = (CounterConfig) c; + config.setStart(startField.getText()); + // Bug 22820 if (endField.getText().length() > 0) + { + config.setEnd(endField.getText()); + } + config.setIncrement(incrField.getText()); + config.setVarName(varNameField.getText()); + config.setFormat(formatField.getText()); + config.setIsPerUser(perUserField.isSelected()); + config.setResetOnThreadGroupIteration(resetCounterOnEachThreadGroupIteration.isEnabled() + && resetCounterOnEachThreadGroupIteration.isSelected()); + } + super.configureTestElement(c); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + startField.setText(""); //$NON-NLS-1$ + incrField.setText(""); //$NON-NLS-1$ + endField.setText(""); //$NON-NLS-1$ + varNameField.setText(""); //$NON-NLS-1$ + formatField.setText(""); //$NON-NLS-1$ + perUserField.setSelected(false); + resetCounterOnEachThreadGroupIteration.setEnabled(false); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + CounterConfig config = (CounterConfig) element; + startField.setText(config.getStartAsString()); + endField.setText(config.getEndAsString()); + incrField.setText(config.getIncrementAsString()); + formatField.setText(config.getFormat()); + varNameField.setText(config.getVarName()); + perUserField.setSelected(config.isPerUser()); + if(config.isPerUser()) { + resetCounterOnEachThreadGroupIteration.setEnabled(true); + resetCounterOnEachThreadGroupIteration.setSelected(config.isResetOnThreadGroupIteration()); + } else { + resetCounterOnEachThreadGroupIteration.setEnabled(false); + } + } + + private void init() { + setBorder(makeBorder()); + setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + + startField = new JLabeledTextField(JMeterUtils.getResString("start"));//$NON-NLS-1$ + incrField = new JLabeledTextField(JMeterUtils.getResString("increment"));//$NON-NLS-1$ + endField = new JLabeledTextField(JMeterUtils.getResString("max"));//$NON-NLS-1$ + varNameField = new JLabeledTextField(JMeterUtils.getResString("var_name"));//$NON-NLS-1$ + formatField = new JLabeledTextField(JMeterUtils.getResString("format"));//$NON-NLS-1$ + perUserField = new JCheckBox(JMeterUtils.getResString("counter_per_user"));//$NON-NLS-1$ + resetCounterOnEachThreadGroupIteration = new JCheckBox(JMeterUtils.getResString("counter_reset_per_tg_iteration"));//$NON-NLS-1$ + add(makeTitlePanel()); + add(startField); + add(incrField); + add(endField); + add(formatField); + add(varNameField); + add(perUserField); + add(resetCounterOnEachThreadGroupIteration); + + perUserField.addActionListener(this); + } + + /** + * Disable/Enable resetCounterOnEachThreadGroupIteration when perUserField is disabled / enabled + */ + @Override + public void actionPerformed(ActionEvent e) { + if(e.getSource() == perUserField) { + resetCounterOnEachThreadGroupIteration.setEnabled(perUserField.isSelected()); + } + } +} diff --git a/src/components/org/apache/jmeter/modifiers/gui/UserParametersGui.java b/src/components/org/apache/jmeter/modifiers/gui/UserParametersGui.java new file mode 100644 index 00000000000..50b3c9a1879 --- /dev/null +++ b/src/components/org/apache/jmeter/modifiers/gui/UserParametersGui.java @@ -0,0 +1,402 @@ +/* + * 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.modifiers.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FontMetrics; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.modifiers.UserParameters; +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class UserParametersGui extends AbstractPreProcessorGui { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String NAME_COL_RESOURCE = "name"; // $NON-NLS-1$ + private static final String USER_COL_RESOURCE = "user"; // $NON-NLS-1$ + private static final String UNDERSCORE = "_"; // $NON-NLS-1$ + + private JTable paramTable; + + private PowerTableModel tableModel; + + private int numUserColumns = 1; + + private JButton addParameterButton, addUserButton, deleteRowButton, deleteColumnButton; + + private JCheckBox perIterationCheck; + + private JPanel paramPanel; + + public UserParametersGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "user_parameters_title"; // $NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + initTableModel(); + paramTable.setModel(tableModel); + UserParameters params = (UserParameters) el; + CollectionProperty names = params.getNames(); + CollectionProperty threadValues = params.getThreadLists(); + tableModel.setColumnData(0, (List) names.getObjectValue()); + PropertyIterator iter = threadValues.iterator(); + if (iter.hasNext()) { + tableModel.setColumnData(1, (List) iter.next().getObjectValue()); + } + int count = 2; + while (iter.hasNext()) { + String colName = getUserColName(count); + tableModel.addNewColumn(colName, String.class); + tableModel.setColumnData(count, (List) iter.next().getObjectValue()); + count++; + } + setColumnWidths(); + perIterationCheck.setSelected(params.isPerIteration()); + super.configure(el); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + UserParameters params = new UserParameters(); + modifyTestElement(params); + return params; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement params) { + GuiUtils.stopTableEditing(paramTable); + UserParameters userParams = ((UserParameters) params); + userParams.setNames(new CollectionProperty(UserParameters.NAMES, tableModel.getColumnData(NAME_COL_RESOURCE))); + CollectionProperty threadLists = new CollectionProperty(UserParameters.THREAD_VALUES, new ArrayList()); + log.debug("making threadlists from gui"); + for (int col = 1; col < tableModel.getColumnCount(); col++) { + threadLists.addItem(tableModel.getColumnData(getUserColName(col))); + if (log.isDebugEnabled()) { + log.debug("Adding column to threadlist: " + tableModel.getColumnData(getUserColName(col))); + log.debug("Threadlists now = " + threadLists); + } + } + if (log.isDebugEnabled()) { + log.debug("In the end, threadlists = " + threadLists); + } + userParams.setThreadLists(threadLists); + userParams.setPerIteration(perIterationCheck.isSelected()); + super.configureTestElement(params); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + initTableModel(); + paramTable.setModel(tableModel); + HeaderAsPropertyRenderer defaultRenderer = new HeaderAsPropertyRenderer(){ + private static final long serialVersionUID = 240L; + + @Override + protected String getText(Object value, int row, int column) { + if (column >= 1){ // Don't process the NAME column + String val = value.toString(); + if (val.startsWith(USER_COL_RESOURCE+UNDERSCORE)){ + return JMeterUtils.getResString(USER_COL_RESOURCE)+val.substring(val.indexOf(UNDERSCORE)); + } + } + return super.getText(value, row, column); + } + }; + paramTable.getTableHeader().setDefaultRenderer(defaultRenderer); + perIterationCheck.setSelected(false); + } + + private String getUserColName(int user){ + return USER_COL_RESOURCE+UNDERSCORE+user; + } + + private void init() { + setBorder(makeBorder()); + setLayout(new BorderLayout()); + JPanel vertPanel = new VerticalPanel(); + vertPanel.add(makeTitlePanel()); + + perIterationCheck = new JCheckBox(JMeterUtils.getResString("update_per_iter"), true); // $NON-NLS-1$ + Box perIterationPanel = Box.createHorizontalBox(); + perIterationPanel.add(perIterationCheck); + perIterationPanel.add(Box.createHorizontalGlue()); + vertPanel.add(perIterationPanel); + add(vertPanel, BorderLayout.NORTH); + + add(makeParameterPanel(), BorderLayout.CENTER); + } + + private JPanel makeParameterPanel() { + JLabel tableLabel = new JLabel(JMeterUtils.getResString("user_parameters_table")); // $NON-NLS-1$ + initTableModel(); + paramTable = new JTable(tableModel); + // paramTable.setRowSelectionAllowed(true); + // paramTable.setColumnSelectionAllowed(true); + paramTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + paramTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + // paramTable.setCellSelectionEnabled(true); + // paramTable.setPreferredScrollableViewportSize(new Dimension(100, + // 70)); + + paramPanel = new JPanel(new BorderLayout()); + paramPanel.add(tableLabel, BorderLayout.NORTH); + JScrollPane scroll = new JScrollPane(paramTable); + scroll.setPreferredSize(scroll.getMinimumSize()); + paramPanel.add(scroll, BorderLayout.CENTER); + paramPanel.add(makeButtonPanel(), BorderLayout.SOUTH); + return paramPanel; + } + + protected void initTableModel() { + tableModel = new PowerTableModel(new String[] { NAME_COL_RESOURCE, // $NON-NLS-1$ + getUserColName(numUserColumns) }, new Class[] { String.class, String.class }); + } + + private JPanel makeButtonPanel() { + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new GridLayout(2, 2)); + addParameterButton = new JButton(JMeterUtils.getResString("add_parameter")); // $NON-NLS-1$ + addUserButton = new JButton(JMeterUtils.getResString("add_user")); // $NON-NLS-1$ + deleteRowButton = new JButton(JMeterUtils.getResString("delete_parameter")); // $NON-NLS-1$ + deleteColumnButton = new JButton(JMeterUtils.getResString("delete_user")); // $NON-NLS-1$ + buttonPanel.add(addParameterButton); + buttonPanel.add(deleteRowButton); + buttonPanel.add(addUserButton); + buttonPanel.add(deleteColumnButton); + addParameterButton.addActionListener(new AddParamAction()); + addUserButton.addActionListener(new AddUserAction()); + deleteRowButton.addActionListener(new DeleteRowAction()); + deleteColumnButton.addActionListener(new DeleteColumnAction()); + return buttonPanel; + } + + /** + * Set Column size + */ + private void setColumnWidths() { + int margin = 10; + int minwidth = 150; + + JTableHeader tableHeader = paramTable.getTableHeader(); + FontMetrics headerFontMetrics = tableHeader.getFontMetrics(tableHeader.getFont()); + + for (int i = 0; i < tableModel.getColumnCount(); i++) { + int headerWidth = headerFontMetrics.stringWidth(paramTable.getColumnName(i)); + int maxWidth = getMaximalRequiredColumnWidth(i, headerWidth); + + paramTable.getColumnModel().getColumn(i).setPreferredWidth(Math.max(maxWidth + margin, minwidth)); + } + } + + /** + * Compute max width between width of the largest column at columnIndex and headerWidth + * @param columnIndex Column index + * @param headerWidth Header width based on Font + */ + private int getMaximalRequiredColumnWidth(int columnIndex, int headerWidth) { + int maxWidth = headerWidth; + + TableColumn column = paramTable.getColumnModel().getColumn(columnIndex); + + TableCellRenderer cellRenderer = column.getCellRenderer(); + + if(cellRenderer == null) { + cellRenderer = new DefaultTableCellRenderer(); + } + + for(int row = 0; row < paramTable.getModel().getRowCount(); row++) { + Component rendererComponent = cellRenderer.getTableCellRendererComponent(paramTable, + paramTable.getModel().getValueAt(row, columnIndex), + false, + false, + row, + columnIndex); + + double valueWidth = rendererComponent.getPreferredSize().getWidth(); + + maxWidth = (int) Math.max(maxWidth, valueWidth); + } + + return maxWidth; + } + + private class AddParamAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + GuiUtils.stopTableEditing(paramTable); + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable DELETE (which may already be enabled, but it won't hurt) + deleteRowButton.setEnabled(true); + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + paramTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + + private class AddUserAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + + GuiUtils.stopTableEditing(paramTable); + + tableModel.addNewColumn(getUserColName(tableModel.getColumnCount()), String.class); + tableModel.fireTableDataChanged(); + + setColumnWidths(); + // Enable DELETE (which may already be enabled, but it won't hurt) + deleteColumnButton.setEnabled(true); + + // Highlight (select) the appropriate row. + int colToSelect = tableModel.getColumnCount() - 1; + paramTable.setColumnSelectionInterval(colToSelect, colToSelect); + } + } + + private class DeleteRowAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + if (paramTable.isEditing()) { + TableCellEditor cellEditor = paramTable.getCellEditor(paramTable.getEditingRow(), paramTable + .getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = paramTable.getSelectedRow(); + if (rowSelected >= 0) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + deleteRowButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + paramTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } + + private class DeleteColumnAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + if (paramTable.isEditing()) { + TableCellEditor cellEditor = paramTable.getCellEditor(paramTable.getEditingRow(), paramTable + .getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int colSelected = paramTable.getSelectedColumn(); + if (colSelected == 0 || colSelected == 1) { + JOptionPane.showMessageDialog(null, + JMeterUtils.getResString("column_delete_disallowed"), // $NON-NLS-1$ + "Error", + JOptionPane.ERROR_MESSAGE); + return; + } + if (colSelected >= 0) { + tableModel.removeColumn(colSelected); + tableModel.fireTableDataChanged(); + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getColumnCount() == 0) { + deleteColumnButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + else { + + if (colSelected >= tableModel.getColumnCount()) { + colSelected = colSelected - 1; + } + + paramTable.setColumnSelectionInterval(colSelected, colSelected); + } + setColumnWidths(); + } + } + } +} diff --git a/src/components/org/apache/jmeter/reporters/MailerModel.java b/src/components/org/apache/jmeter/reporters/MailerModel.java new file mode 100644 index 00000000000..048e6544c57 --- /dev/null +++ b/src/components/org/apache/jmeter/reporters/MailerModel.java @@ -0,0 +1,529 @@ +/* + * 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.reporters; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.StringTokenizer; + +import javax.mail.Authenticator; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The model for a MailerVisualizer. + * + */ +public class MailerModel extends AbstractTestElement implements Serializable { + public static enum MailAuthType { + SSL, + TLS, + NONE; + } + + private static final long serialVersionUID = 270L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String MAIL_SMTP_HOST = "mail.smtp.host"; //$NON-NLS-1$ + + private static final String MAIL_SMTP_PORT = "mail.smtp.port"; //$NON-NLS-1$ + + private static final String MAIL_SMTP_AUTH = "mail.smtp.auth"; //$NON-NLS-1$ + + private static final String MAIL_SMTP_SOCKETFACTORY_CLASS = "mail.smtp.socketFactory.class"; //$NON-NLS-1$ + + private static final String MAIL_SMTP_STARTTLS = "mail.smtp.starttls.enable"; //$NON-NLS-1$ + + private long failureCount = 0; + + private long successCount = 0; + + private boolean failureMsgSent = false; + + private boolean siteDown = false; + + private boolean successMsgSent = false; + + private static final String FROM_KEY = "MailerModel.fromAddress"; //$NON-NLS-1$ + + private static final String TO_KEY = "MailerModel.addressie"; //$NON-NLS-1$ + + private static final String HOST_KEY = "MailerModel.smtpHost"; //$NON-NLS-1$ + + private static final String PORT_KEY = "MailerModel.smtpPort"; //$NON-NLS-1$ + + private static final String SUCCESS_SUBJECT = "MailerModel.successSubject"; //$NON-NLS-1$ + + private static final String FAILURE_SUBJECT = "MailerModel.failureSubject"; //$NON-NLS-1$ + + private static final String FAILURE_LIMIT_KEY = "MailerModel.failureLimit"; //$NON-NLS-1$ + + private static final String SUCCESS_LIMIT_KEY = "MailerModel.successLimit"; //$NON-NLS-1$ + + private static final String LOGIN = "MailerModel.login"; //$NON-NLS-1$ + + private static final String PASSWORD = "MailerModel.password"; //$NON-NLS-1$ + + private static final String MAIL_AUTH_TYPE = "MailerModel.authType"; //$NON-NLS-1$ + + private static final String DEFAULT_LIMIT = "2"; //$NON-NLS-1$ + + private static final String DEFAULT_SMTP_PORT = "25"; + + private static final String DEFAULT_PASSWORD_VALUE = ""; //$NON-NLS-1$ + + private static final String DEFAULT_MAIL_AUTH_TYPE_VALUE = MailAuthType.NONE.toString(); //$NON-NLS-1$ + + private static final String DEFAULT_LOGIN_VALUE = ""; //$NON-NLS-1$ + + /** The listener for changes. */ + private transient ChangeListener changeListener; + + /** + * Constructs a MailerModel. + */ + public MailerModel() { + super(); + + setProperty(SUCCESS_LIMIT_KEY, JMeterUtils.getPropDefault("mailer.successlimit", DEFAULT_LIMIT)); //$NON-NLS-1$ + setProperty(FAILURE_LIMIT_KEY, JMeterUtils.getPropDefault("mailer.failurelimit", DEFAULT_LIMIT)); //$NON-NLS-1$ + } + + public void addChangeListener(ChangeListener list) { + changeListener = list; + } + + /** {@inheritDoc} */ + @Override + public Object clone() { + MailerModel m = (MailerModel) super.clone(); + m.changeListener = changeListener; + return m; + } + + public void notifyChangeListeners() { + if (changeListener != null) { + changeListener.stateChanged(new ChangeEvent(this)); + } + } + + /** + * Gets a List of String-objects. Each String is one mail-address of the + * addresses-String set by setToAddress(str). The addresses + * must be seperated by commas. Only String-objects containing a "@" are + * added to the returned List. + * + * @return a List of String-objects wherein each String represents a + * mail-address. + */ + public List getAddressList() { + String addressees = getToAddress(); + List addressList = new ArrayList(); + + if (addressees != null) { + + StringTokenizer next = new StringTokenizer(addressees, ","); //$NON-NLS-1$ + + while (next.hasMoreTokens()) { + String theToken = next.nextToken().trim(); + + if (theToken.indexOf('@') > 0) { //$NON-NLS-1$ + addressList.add(theToken); + } else { + log.warn("Ignored unexpected e-mail address: "+theToken); + } + } + } + + return addressList; + } + + /** + * Adds a SampleResult for display in the Visualizer. + * + * @param sample + * the SampleResult encapsulating informations about the last + * sample. + */ + public void add(SampleResult sample) { + add(sample, false); + } + + /** + * Adds a SampleResult. If SampleResult represents a change concerning the + * failure/success of the sampling a message might be sent to the addressies + * according to the settings of successCount and + * failureCount. + * + * @param sample + * the SampleResult encapsulating information about the last + * sample. + * @param sendMails whether or not to send e-mails + */ + public synchronized void add(SampleResult sample, boolean sendMails) { + + // -1 is the code for a failed sample. + // + if (!sample.isSuccessful()) { + failureCount++; + successCount = 0; + } else { + successCount++; + } + + if (sendMails && (failureCount > getFailureLimit()) && !siteDown && !failureMsgSent) { + // Send the mail ... + List addressList = getAddressList(); + + if (addressList.size() != 0) { + try { + sendMail(getFromAddress(), addressList, getFailureSubject(), "URL Failed: " + + sample.getSampleLabel(), getSmtpHost(), + getSmtpPort(), getLogin(), getPassword(), + getMailAuthType(), false); + } catch (Exception e) { + log.error("Problem sending mail: "+e); + } + siteDown = true; + failureMsgSent = true; + successCount = 0; + successMsgSent = false; + } + } + + if (sendMails && siteDown && (sample.getTime() != -1) && !successMsgSent) { + // Send the mail ... + if (successCount > getSuccessLimit()) { + List addressList = getAddressList(); + + try { + sendMail(getFromAddress(), addressList, getSuccessSubject(), "URL Restarted: " + + sample.getSampleLabel(), getSmtpHost(), + getSmtpPort(), getLogin(), getPassword(), + getMailAuthType(), false); + } catch (Exception e) { + log.error("Problem sending mail", e); + } + siteDown = false; + successMsgSent = true; + failureCount = 0; + failureMsgSent = false; + } + } + + if (successMsgSent && failureMsgSent) { + clear(); + } + notifyChangeListeners(); + } + + + + /** + * Resets the state of this object to its default. But: This method does not + * reset any mail-specific attributes (like sender, mail-subject...) since + * they are independent of the sampling. + */ + @Override + public synchronized void clear() {// TODO: should this be clearData()? + failureCount = 0; + successCount = 0; + siteDown = false; + successMsgSent = false; + failureMsgSent = false; + notifyChangeListeners(); + } + + /** + * Returns a String-representation of this object. Returns always + * "E-Mail-Notification". Might be enhanced in future versions to return + * some kind of String-representation of the mail-parameters (like sender, + * addressies, smtpHost...). + * + * @return A String-representation of this object. + */ + @Override + public String toString() { + return "E-Mail Notification"; + } + + /** + * Sends a mail with the given parameters using SMTP. + * + * @param from + * the sender of the mail as shown in the mail-client. + * @param vEmails + * all receivers of the mail. The receivers are seperated by + * commas. + * @param subject + * the subject of the mail. + * @param attText + * the message-body. + * @param smtpHost + * the smtp-server used to send the mail. + * @throws MessagingException + * if the building of the message fails + * @throws AddressException + * if any of the addresses is wrong + */ + public void sendMail(String from, List vEmails, String subject, String attText, String smtpHost) + throws AddressException, MessagingException { + sendMail(from, vEmails, subject, attText, smtpHost, DEFAULT_SMTP_PORT, null, null, null, false); + } + + /** + * Sends a mail with the given parameters using SMTP. + * + * @param from + * the sender of the mail as shown in the mail-client. + * @param vEmails + * all receivers of the mail. The receivers are seperated by + * commas. + * @param subject + * the subject of the mail. + * @param attText + * the message-body. + * @param smtpHost + * the smtp-server used to send the mail. + * @param smtpPort the smtp-server port used to send the mail. + * @param user the login used to authenticate + * @param password the password used to authenticate + * @param mailAuthType {@link MailAuthType} Security policy + * @param debug Flag whether debug messages for the mail session should be generated + * @throws AddressException If mail address is wrong + * @throws MessagingException If building MimeMessage fails + */ + public void sendMail(String from, List vEmails, String subject, + String attText, String smtpHost, + String smtpPort, + final String user, + final String password, + MailAuthType mailAuthType, + boolean debug) + throws AddressException, MessagingException{ + + InternetAddress[] address = new InternetAddress[vEmails.size()]; + + for (int k = 0; k < vEmails.size(); k++) { + address[k] = new InternetAddress(vEmails.get(k)); + } + + // create some properties and get the default Session + Properties props = new Properties(); + + props.put(MAIL_SMTP_HOST, smtpHost); + props.put(MAIL_SMTP_PORT, smtpPort); // property values are strings + Authenticator authenticator = null; + if(mailAuthType != MailAuthType.NONE) { + props.put(MAIL_SMTP_AUTH, "true"); + switch (mailAuthType) { + case SSL: + props.put(MAIL_SMTP_SOCKETFACTORY_CLASS, + "javax.net.ssl.SSLSocketFactory"); + break; + case TLS: + props.put(MAIL_SMTP_STARTTLS, + "true"); + break; + + default: + break; + } + } + + if(!StringUtils.isEmpty(user)) { + authenticator = + new javax.mail.Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(user,password); + } + }; + } + Session session = Session.getInstance(props, authenticator); + session.setDebug(debug); + + // create a message + Message msg = new MimeMessage(session); + + msg.setFrom(new InternetAddress(from)); + msg.setRecipients(Message.RecipientType.TO, address); + msg.setSubject(subject); + msg.setText(attText); + Transport.send(msg); + } + + /** + * Send a Test Mail to check configuration + * @throws AddressException If mail address is wrong + * @throws MessagingException If building MimeMessage fails + */ + public synchronized void sendTestMail() throws AddressException, MessagingException { + String to = getToAddress(); + String from = getFromAddress(); + String subject = "Testing mail-addresses"; + String smtpHost = getSmtpHost(); + String attText = "JMeter-Testmail" + "\n" + "To: " + to + "\n" + "From: " + from + "\n" + "Via: " + smtpHost + + "\n" + "Fail Subject: " + getFailureSubject() + "\n" + "Success Subject: " + getSuccessSubject(); + + log.info(attText); + + sendMail(from, getAddressList(), subject, attText, smtpHost, + getSmtpPort(), + getLogin(), + getPassword(), + getMailAuthType(), + true); + log.info("Test mail sent successfully!!"); + } + + // //////////////////////////////////////////////////////////// + // + // setter/getter - JavaDoc-Comments not needed... + // + // //////////////////////////////////////////////////////////// + + public void setToAddress(String str) { + setProperty(TO_KEY, str); + } + + public void setFromAddress(String str) { + setProperty(FROM_KEY, str); + } + + public void setSmtpHost(String str) { + setProperty(HOST_KEY, str); + } + + public void setSmtpPort(String value) { + if(StringUtils.isEmpty(value)) { + value = DEFAULT_SMTP_PORT; + } + setProperty(PORT_KEY, value, DEFAULT_SMTP_PORT); + } + + public void setLogin(String login) { + setProperty(LOGIN, login, DEFAULT_LOGIN_VALUE); + } + + public void setPassword(String password) { + setProperty(PASSWORD, password, DEFAULT_PASSWORD_VALUE); + } + + public void setMailAuthType(String value) { + setProperty(MAIL_AUTH_TYPE, value, DEFAULT_MAIL_AUTH_TYPE_VALUE); + } + + public void setFailureSubject(String str) { + setProperty(FAILURE_SUBJECT, str); + } + + public void setSuccessSubject(String str) { + setProperty(SUCCESS_SUBJECT, str); + } + + public void setSuccessLimit(String limit) { + setProperty(SUCCESS_LIMIT_KEY, limit); + } + + // private void setSuccessCount(long count) + // { + // this.successCount = count; + // } + + public void setFailureLimit(String limit) { + setProperty(FAILURE_LIMIT_KEY, limit); + } + + // private void setFailureCount(long count) + // { + // this.failureCount = count; + // } + + public String getToAddress() { + return getPropertyAsString(TO_KEY); + } + + public String getFromAddress() { + return getPropertyAsString(FROM_KEY); + } + + public String getSmtpHost() { + return getPropertyAsString(HOST_KEY); + } + + public String getSmtpPort() { + return getPropertyAsString(PORT_KEY, DEFAULT_SMTP_PORT); + } + + public String getFailureSubject() { + return getPropertyAsString(FAILURE_SUBJECT); + } + + public String getSuccessSubject() { + return getPropertyAsString(SUCCESS_SUBJECT); + } + + public long getSuccessLimit() { + return getPropertyAsLong(SUCCESS_LIMIT_KEY); + } + + public long getSuccessCount() { + return successCount; + } + + public long getFailureLimit() { + return getPropertyAsLong(FAILURE_LIMIT_KEY); + } + + public long getFailureCount() { + return this.failureCount; + } + + public String getLogin() { + return getPropertyAsString(LOGIN, DEFAULT_LOGIN_VALUE); + } + + public String getPassword() { + return getPropertyAsString(PASSWORD, DEFAULT_PASSWORD_VALUE); + } + + public MailAuthType getMailAuthType() { + String authType = getPropertyAsString(MAIL_AUTH_TYPE, DEFAULT_MAIL_AUTH_TYPE_VALUE); + return MailAuthType.valueOf(authType); + } +} diff --git a/src/components/org/apache/jmeter/reporters/MailerResultCollector.java b/src/components/org/apache/jmeter/reporters/MailerResultCollector.java new file mode 100644 index 00000000000..6d72c52a2ec --- /dev/null +++ b/src/components/org/apache/jmeter/reporters/MailerResultCollector.java @@ -0,0 +1,54 @@ +/* + * 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.reporters; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.testelement.property.TestElementProperty; + + +public class MailerResultCollector extends ResultCollector implements Serializable { + private static final long serialVersionUID = 240L; + + public static final String MAILER_MODEL = "MailerResultCollector.mailer_model"; //$NON-NLS-1$ + + public MailerResultCollector() { + super(); + setProperty(new TestElementProperty(MAILER_MODEL, new MailerModel())); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + super.clear(); + setProperty(new TestElementProperty(MAILER_MODEL, new MailerModel())); + } + + /** {@inheritDoc} */ + @Override + public void sampleOccurred(SampleEvent e) { + super.sampleOccurred(e); // sends the result to the visualiser + getMailerModel().add(e.getResult(), true); // updates the model used for sending e-mails + } + + public MailerModel getMailerModel() { + return (MailerModel) getProperty(MAILER_MODEL).getObjectValue(); + } +} diff --git a/src/components/org/apache/jmeter/sampler/DebugSampler.java b/src/components/org/apache/jmeter/sampler/DebugSampler.java new file mode 100644 index 00000000000..2a6490cbe6f --- /dev/null +++ b/src/components/org/apache/jmeter/sampler/DebugSampler.java @@ -0,0 +1,144 @@ +/* + * 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.sampler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The Debug Sampler can be used to "sample" JMeter variables, JMeter properties and System Properties. + * + */ +public class DebugSampler extends AbstractSampler implements TestBean { + + private static final long serialVersionUID = 232L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private boolean displayJMeterVariables; + + private boolean displayJMeterProperties; + + private boolean displaySystemProperties; + + @Override + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.sampleStart(); + StringBuilder sb = new StringBuilder(100); + StringBuilder rd = new StringBuilder(20); // for request Data + if (isDisplayJMeterVariables()){ + rd.append("JMeterVariables\n"); + sb.append("JMeterVariables:\n"); + formatSet(sb, JMeterContextService.getContext().getVariables().entrySet()); + sb.append("\n"); + } + + if (isDisplayJMeterProperties()){ + rd.append("JMeterProperties\n"); + sb.append("JMeterProperties:\n"); + formatSet(sb, JMeterUtils.getJMeterProperties().entrySet()); + sb.append("\n"); + } + + if (isDisplaySystemProperties()){ + rd.append("SystemProperties\n"); + sb.append("SystemProperties:\n"); + formatSet(sb, System.getProperties().entrySet()); + sb.append("\n"); + } + + res.setResponseData(sb.toString(), null); + res.setDataType(SampleResult.TEXT); + res.setSamplerData(rd.toString()); + res.setResponseOK(); + res.sampleEnd(); + return res; + } + + private void formatSet(StringBuilder sb, @SuppressWarnings("rawtypes") Set s) { + @SuppressWarnings("unchecked") + ArrayList> al = new ArrayList>(s); + Collections.sort(al, new Comparator>(){ + @Override + public int compare(Map.Entry o1, Map.Entry o2) { + String m1,m2; + m1=(String)o1.getKey(); + m2=(String)o2.getKey(); + return m1.compareTo(m2); + } + }); + for(Map.Entry me : al){ + sb.append(me.getKey()); + sb.append("="); + sb.append(me.getValue()); + sb.append("\n"); + } + } + + public boolean isDisplayJMeterVariables() { + return displayJMeterVariables; + } + + public void setDisplayJMeterVariables(boolean displayJMeterVariables) { + this.displayJMeterVariables = displayJMeterVariables; + } + + public boolean isDisplayJMeterProperties() { + return displayJMeterProperties; + } + + public void setDisplayJMeterProperties(boolean displayJMeterPropterties) { + this.displayJMeterProperties = displayJMeterPropterties; + } + + public boolean isDisplaySystemProperties() { + return displaySystemProperties; + } + + public void setDisplaySystemProperties(boolean displaySystemProperties) { + this.displaySystemProperties = displaySystemProperties; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/components/org/apache/jmeter/sampler/DebugSamplerBeanInfo.java b/src/components/org/apache/jmeter/sampler/DebugSamplerBeanInfo.java new file mode 100644 index 00000000000..ce8082623b3 --- /dev/null +++ b/src/components/org/apache/jmeter/sampler/DebugSamplerBeanInfo.java @@ -0,0 +1,48 @@ +/* + * 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.sampler; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class DebugSamplerBeanInfo extends BeanInfoSupport { + public DebugSamplerBeanInfo() { + super(DebugSampler.class); + PropertyDescriptor p; + + p = property("displayJMeterVariables"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + + p = property("displayJMeterProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + p = property("displaySystemProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + } +} diff --git a/src/components/org/apache/jmeter/sampler/DebugSamplerResources.properties b/src/components/org/apache/jmeter/sampler/DebugSamplerResources.properties new file mode 100644 index 00000000000..98b3caf20bf --- /dev/null +++ b/src/components/org/apache/jmeter/sampler/DebugSamplerResources.properties @@ -0,0 +1,22 @@ +# 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. + +displayName=Debug Sampler +displayJMeterVariables.displayName=JMeter variables +displayJMeterProperties.displayName=JMeter properties +displaySystemProperties.displayName=System properties +displayJMeterVariables.shortDescription=Display JMeter variables ? +displayJMeterProperties.shortDescription=Display JMeter properties ? +displaySystemProperties.shortDescription=Display System properties ? \ No newline at end of file diff --git a/src/components/org/apache/jmeter/sampler/DebugSamplerResources_de.properties b/src/components/org/apache/jmeter/sampler/DebugSamplerResources_de.properties new file mode 100644 index 00000000000..8e4ebf65390 --- /dev/null +++ b/src/components/org/apache/jmeter/sampler/DebugSamplerResources_de.properties @@ -0,0 +1,22 @@ +# 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. + +displayJMeterProperties.displayName=JMeter Eigenschaften +displayJMeterProperties.shortDescription=Sollen die JMeter Eigenschaften angezeigt werden? +displayJMeterVariables.displayName=JMeter Variablen +displayJMeterVariables.shortDescription=Sollen die JMeter Variablen angezeigt werden +displayName=Debug Sampler +displaySystemProperties.displayName=System Eigenschaften +displaySystemProperties.shortDescription=Sollen die System Eigenschaften angezeigt werden diff --git a/src/components/org/apache/jmeter/sampler/DebugSamplerResources_fr.properties b/src/components/org/apache/jmeter/sampler/DebugSamplerResources_fr.properties new file mode 100644 index 00000000000..2a6b1059d50 --- /dev/null +++ b/src/components/org/apache/jmeter/sampler/DebugSamplerResources_fr.properties @@ -0,0 +1,23 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayJMeterProperties.displayName=Propri\u00E9t\u00E9s JMeter +displayJMeterProperties.shortDescription=Afficher les propri\u00E9t\u00E9s JMeter ? +displayJMeterVariables.displayName=Variables JMeter +displayJMeterVariables.shortDescription=Afficher les variables JMeter ? +displayName=Echantillon D\u00E9bogage +displaySystemProperties.displayName=Propri\u00E9t\u00E9s Syst\u00E8me +displaySystemProperties.shortDescription=Afficher les propri\u00E9t\u00E9s syst\u00E8mes ? diff --git a/src/components/org/apache/jmeter/sampler/DebugSamplerResources_pt_BR.properties b/src/components/org/apache/jmeter/sampler/DebugSamplerResources_pt_BR.properties new file mode 100644 index 00000000000..b99feac467f --- /dev/null +++ b/src/components/org/apache/jmeter/sampler/DebugSamplerResources_pt_BR.properties @@ -0,0 +1,22 @@ +# 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. + +displayJMeterProperties.displayName=Propriedades do JMeter +displayJMeterProperties.shortDescription=Exibir propriedades do JMeter? +displayJMeterVariables.displayName=Vari\u00E1veis do JMeter +displayJMeterVariables.shortDescription=Exibir vari\u00E1veis do JMeter? +displayName=Debug testador +displaySystemProperties.displayName=Propriedades do sistema +displaySystemProperties.shortDescription=Exibir propriedades do sistema? diff --git a/src/components/org/apache/jmeter/sampler/DebugSamplerResources_tr.properties b/src/components/org/apache/jmeter/sampler/DebugSamplerResources_tr.properties new file mode 100644 index 00000000000..71276933fb3 --- /dev/null +++ b/src/components/org/apache/jmeter/sampler/DebugSamplerResources_tr.properties @@ -0,0 +1,23 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayJMeterProperties.displayName=JMeter ayarlar\u0131 +displayJMeterProperties.shortDescription=JMeter ayarlar\u0131n\u0131 g\u00F6ster ? +displayJMeterVariables.displayName=JMeter de\u011Fi\u015Fkenleri +displayJMeterVariables.shortDescription=JMeter de\u011Fi\u015Fkenlerini g\u00F6ster ? +displayName=Ay\u0131klama \u00D6rnekleyicisi +displaySystemProperties.displayName=Sistem ayarlar\u0131 +displaySystemProperties.shortDescription=Sistem ayarlar\u0131n\u0131 g\u00F6ster ? diff --git a/src/components/org/apache/jmeter/sampler/TestAction.java b/src/components/org/apache/jmeter/sampler/TestAction.java new file mode 100644 index 00000000000..49f0285dd10 --- /dev/null +++ b/src/components/org/apache/jmeter/sampler/TestAction.java @@ -0,0 +1,175 @@ +/* + * 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.sampler; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Dummy Sampler used to pause or stop a thread or the test; + * intended for use in Conditional Controllers. + * + */ +public class TestAction extends AbstractSampler implements Interruptible { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + // Actions + public static final int STOP = 0; + public static final int PAUSE = 1; + public static final int STOP_NOW = 2; + public static final int RESTART_NEXT_LOOP = 3; + + // Action targets + public static final int THREAD = 0; + // public static final int THREAD_GROUP = 1; + public static final int TEST = 2; + + // Identifiers + private static final String TARGET = "ActionProcessor.target"; //$NON-NLS-1$ + private static final String ACTION = "ActionProcessor.action"; //$NON-NLS-1$ + private static final String DURATION = "ActionProcessor.duration"; //$NON-NLS-1$ + + private volatile transient Thread pauseThread; + + public TestAction() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public SampleResult sample(Entry e) { + JMeterContext context = JMeterContextService.getContext(); + + int target = getTarget(); + int action = getAction(); + if (action == PAUSE) { + pause(getDurationAsString()); + } else if (action == STOP || action == STOP_NOW || action == RESTART_NEXT_LOOP) { + if (target == THREAD) { + if(action == STOP || action == STOP_NOW) { + log.info("Stopping current thread"); + context.getThread().stop(); + } else { + log.info("Restarting next loop"); + context.setRestartNextLoop(true); + } +// //Not yet implemented +// } else if (target==THREAD_GROUP) { + } else if (target == TEST) { + if (action == STOP_NOW) { + log.info("Stopping all threads now"); + context.getEngine().stopTest(); + } else { + log.info("Stopping all threads"); + context.getEngine().askThreadsToStop(); + } + } + } + + return null; // This means no sample is saved + } + + private void pause(String mili_s) { + int milis; + try { + milis=Integer.parseInt(mili_s); + } catch (NumberFormatException e){ + log.warn("Could not create number from "+mili_s); + milis=0; + } + try { + pauseThread = Thread.currentThread(); + if(milis>0) { + TimeUnit.MILLISECONDS.sleep(milis); + } else if(milis<0) { + throw new IllegalArgumentException("Configured sleep is negative:"+milis); + } // else == 0 we do nothing + } catch (InterruptedException e) { + // NOOP + } finally { + pauseThread = null; + } + } + + public void setTarget(int target) { + setProperty(new IntegerProperty(TARGET, target)); + } + + public int getTarget() { + return getPropertyAsInt(TARGET); + } + + public void setAction(int action) { + setProperty(new IntegerProperty(ACTION, action)); + } + + public int getAction() { + return getPropertyAsInt(ACTION); + } + + public void setDuration(String duration) { + setProperty(new StringProperty(DURATION, duration)); + } + + public String getDurationAsString() { + return getPropertyAsString(DURATION); + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } + + @Override + public boolean interrupt() { + Thread thrd = pauseThread; // take copy so cannot get NPE + if (thrd!= null) { + thrd.interrupt(); + return true; + } + return false; + } +} diff --git a/src/components/org/apache/jmeter/sampler/gui/TestActionGui.java b/src/components/org/apache/jmeter/sampler/gui/TestActionGui.java new file mode 100644 index 00000000000..546da485737 --- /dev/null +++ b/src/components/org/apache/jmeter/sampler/gui/TestActionGui.java @@ -0,0 +1,255 @@ +/* + * 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.sampler.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.ButtonGroup; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.sampler.TestAction; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class TestActionGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + // Gui components + private JComboBox targetBox; + + // private ButtonGroup actionButtons; + private JRadioButton pauseButton; + + private JRadioButton stopButton; + + private JRadioButton stopNowButton; + + private JRadioButton restartNextLoopButton; + + private JTextField durationField; + + // State variables + private int target; + + private int action; + + // String in the panel + // Do not make these static, otherwise language changes don't work + private final String targetLabel = JMeterUtils.getResString("test_action_target"); // $NON-NLS-1$ + + private final String threadTarget = JMeterUtils.getResString("test_action_target_thread"); // $NON-NLS-1$ + + private final String testTarget = JMeterUtils.getResString("test_action_target_test"); // $NON-NLS-1$ + + private final String actionLabel = JMeterUtils.getResString("test_action_action"); // $NON-NLS-1$ + + private final String pauseAction = JMeterUtils.getResString("test_action_pause"); // $NON-NLS-1$ + + private final String stopAction = JMeterUtils.getResString("test_action_stop"); // $NON-NLS-1$ + + private final String stopNowAction = JMeterUtils.getResString("test_action_stop_now"); // $NON-NLS-1$ + + private final String restartNextLoopAction = JMeterUtils.getResString("test_action_restart_next_loop"); // $NON-NLS-1$ + + private final String durationLabel = JMeterUtils.getResString("test_action_duration"); // $NON-NLS-1$ + + public TestActionGui() { + super(); + target = TestAction.THREAD; + action = TestAction.PAUSE; + init(); + } + + @Override + public String getLabelResource() { + return "test_action_title"; // $NON-NLS-1$ + } + + @Override + public void configure(TestElement element) { + super.configure(element); + TestAction ta = (TestAction) element; + + target = ta.getTarget(); + if (target == TestAction.THREAD) { + targetBox.setSelectedItem(threadTarget); + } else { + targetBox.setSelectedItem(testTarget); + } + action = ta.getAction(); + if (action == TestAction.PAUSE) { + pauseButton.setSelected(true); + } else if (action == TestAction.STOP_NOW) { + stopNowButton.setSelected(true); + } else if(action == TestAction.STOP ){ + stopButton.setSelected(true); + } else { + restartNextLoopButton.setSelected(true); + } + + durationField.setText(ta.getDurationAsString()); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + TestAction ta = new TestAction(); + modifyTestElement(ta); + return ta; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement element) { + super.configureTestElement(element); + TestAction ta = (TestAction) element; + ta.setAction(action); + ta.setTarget(target); + ta.setDuration(durationField.getText()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + targetBox.setSelectedIndex(0); + durationField.setText(""); //$NON-NLS-1$ + pauseButton.setSelected(true); + action = TestAction.PAUSE; + target = TestAction.THREAD; + + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + // Target + HorizontalPanel targetPanel = new HorizontalPanel(); + targetPanel.add(new JLabel(targetLabel)); + DefaultComboBoxModel targetModel = new DefaultComboBoxModel(); + targetModel.addElement(threadTarget); + targetModel.addElement(testTarget); + targetBox = new JComboBox(targetModel); + targetBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (((String) targetBox.getSelectedItem()).equals(threadTarget)) { + target = TestAction.THREAD; + } else { + target = TestAction.TEST; + } + } + }); + targetPanel.add(targetBox); + add(targetPanel); + + // Action + HorizontalPanel actionPanel = new HorizontalPanel(); + ButtonGroup actionButtons = new ButtonGroup(); + pauseButton = new JRadioButton(pauseAction, true); + pauseButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (pauseButton.isSelected()) { + action = TestAction.PAUSE; + durationField.setEnabled(true); + targetBox.setEnabled(true); + } + + } + }); + stopButton = new JRadioButton(stopAction, false); + stopButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (stopButton.isSelected()) { + action = TestAction.STOP; + durationField.setEnabled(false); + targetBox.setEnabled(true); + } + } + }); + stopNowButton = new JRadioButton(stopNowAction, false); + stopNowButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (stopNowButton.isSelected()) { + action = TestAction.STOP_NOW; + durationField.setEnabled(false); + targetBox.setEnabled(true); + } + } + }); + + restartNextLoopButton = new JRadioButton(restartNextLoopAction, false); + restartNextLoopButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (restartNextLoopButton.isSelected()) { + action = TestAction.RESTART_NEXT_LOOP; + durationField.setEnabled(false); + targetBox.setSelectedIndex(TestAction.THREAD); + targetBox.setEnabled(false); + } + } + }); + + actionButtons.add(pauseButton); + actionButtons.add(stopButton); + actionButtons.add(stopNowButton); + actionButtons.add(restartNextLoopButton); + + actionPanel.add(new JLabel(actionLabel)); + actionPanel.add(pauseButton); + actionPanel.add(stopButton); + actionPanel.add(stopNowButton); + actionPanel.add(restartNextLoopButton); + add(actionPanel); + + // Duration + HorizontalPanel durationPanel = new HorizontalPanel(); + durationField = new JTextField(15); + durationField.setText(""); // $NON-NLS-1$ + durationPanel.add(new JLabel(durationLabel)); + durationPanel.add(durationField); + add(durationPanel); + } + +} diff --git a/src/components/org/apache/jmeter/timers/BSFTimer.java b/src/components/org/apache/jmeter/timers/BSFTimer.java new file mode 100644 index 00000000000..b1d2d197ed4 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BSFTimer.java @@ -0,0 +1,57 @@ +/* + * 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.timers; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFTimer extends BSFTestElement implements Cloneable, Timer, TestBean { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + /** {@inheritDoc} */ + @Override + public long delay() { + long delay = 0; + BSFManager mgr = null; + try { + mgr = getManager(); + Object o = evalFileOrScript(mgr); + if (o == null) { + log.warn("Script did not return a value"); + return 0; + } + delay = Long.parseLong(o.toString()); + } catch (NumberFormatException e) { + log.warn("Problem in BSF script "+e); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + } finally { + if(mgr != null) { + mgr.terminate(); + } + } + return delay; + } +} diff --git a/src/components/org/apache/jmeter/timers/BSFTimerBeanInfo.java b/src/components/org/apache/jmeter/timers/BSFTimerBeanInfo.java new file mode 100644 index 00000000000..ed8ee6a8fad --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BSFTimerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.timers; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFTimerBeanInfo extends BSFBeanInfoSupport { + + public BSFTimerBeanInfo() { + super(BSFTimer.class); + } + +} diff --git a/src/components/org/apache/jmeter/timers/BSFTimerResources.properties b/src/components/org/apache/jmeter/timers/BSFTimerResources.properties new file mode 100644 index 00000000000..b01e3a7f868 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BSFTimerResources.properties @@ -0,0 +1,28 @@ +# 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. + +displayName=BSF Timer +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/timers/BSFTimerResources_fr.properties b/src/components/org/apache/jmeter/timers/BSFTimerResources_fr.properties new file mode 100644 index 00000000000..45173be6838 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BSFTimerResources_fr.properties @@ -0,0 +1,29 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Compteur de temps BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BSF (remplace le code script) +filenameGroup.displayName=Fichier script (remplace le code script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script BSF (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BSF (fichier ou script) +script.displayName=Script +script.shortDescription=Script dans le langage BSF cible +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script BSF (ex. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/timers/BeanShellTimer.java b/src/components/org/apache/jmeter/timers/BeanShellTimer.java new file mode 100644 index 00000000000..54405b89ac4 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BeanShellTimer.java @@ -0,0 +1,65 @@ +/* + * 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.timers; + +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +public class BeanShellTimer extends BeanShellTestElement implements Cloneable, Timer, TestBean { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + // can be specified in jmeter.properties + private static final String INIT_FILE = "beanshell.timer.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + /** + * {@inheritDoc} + */ + @Override + public long delay() { + String ret="0"; + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + log.error("BeanShell not found"); + return 0; + } + try { + Object o = processFileOrScript(bshInterpreter); + if (o != null) { ret=o.toString(); } + } catch (JMeterException e) { + log.warn("Problem in BeanShell script "+e); + } + try { + return Long.decode(ret).longValue(); + } catch (NumberFormatException e){ + log.warn(e.getLocalizedMessage()); + return 0; + } + } +} diff --git a/src/components/org/apache/jmeter/timers/BeanShellTimerBeanInfo.java b/src/components/org/apache/jmeter/timers/BeanShellTimerBeanInfo.java new file mode 100644 index 00000000000..de56065cc49 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BeanShellTimerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.timers; + +import org.apache.jmeter.util.BeanShellBeanInfoSupport; + +public class BeanShellTimerBeanInfo extends BeanShellBeanInfoSupport { + + public BeanShellTimerBeanInfo() { + super(BeanShellTimer.class); + } + +} diff --git a/src/components/org/apache/jmeter/timers/BeanShellTimerResources.properties b/src/components/org/apache/jmeter/timers/BeanShellTimerResources.properties new file mode 100644 index 00000000000..15a8108c883 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BeanShellTimerResources.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=BeanShell Timer +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=BeanShell script file (overrides script) +parameterGroup.displayName=Parameters to be passed to BeanShell (=> String Parameters and String []bsh.args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to BeanShell (file or script) +resetGroup.displayName=Reset bsh.Interpreter before each call +resetInterpreter.displayName=Reset Interpreter +script.displayName=Script +script.shortDescription=Beanshell script to generate delay +scripting.displayName=Script (variables: ctx vars props log prev) diff --git a/src/components/org/apache/jmeter/timers/BeanShellTimerResources_de.properties b/src/components/org/apache/jmeter/timers/BeanShellTimerResources_de.properties new file mode 100644 index 00000000000..87efd6de751 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BeanShellTimerResources_de.properties @@ -0,0 +1,25 @@ +# 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. + +displayName=BeanShell Timer +filename.displayName=Dateiname +filename.shortDescription=BeanShell Script Datei (Vorrang vor Script) +filenameGroup.displayName=Script Datei (Vorrang vor Script) +parameterGroup.displayName=Parameter die der BeanShell \u00FCbergeben werden (String Parameters, String []bsh.args) +parameters.displayName=Parameter +parameters.shortDescription=Parameter die der BeanShell \u00FCbergeben werden (Datei oder Script) +script.displayName=Script +script.shortDescription=BeanShell Script zur Erzeugung der Pause +scripting.displayName=Script (Variablen\: ctv vars props log prev) diff --git a/src/components/org/apache/jmeter/timers/BeanShellTimerResources_fr.properties b/src/components/org/apache/jmeter/timers/BeanShellTimerResources_fr.properties new file mode 100644 index 00000000000..58a2963a741 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BeanShellTimerResources_fr.properties @@ -0,0 +1,28 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Compteur de temps BeanShell +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BeanShell (remplace le code script) +filenameGroup.displayName=Fichier script (remplace le code script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au BeanShell (\=> String Parameters and String []bsh.args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BeanShell (fichier ou script) +resetGroup.displayName=Reinitialiser l'interpr\u00E9teur bsh avant chaque appel +resetInterpreter.displayName=R\u00E9initialiser l'interpr\u00E9teur +script.displayName=Script +script.shortDescription=Script BeanShell pour g\u00E9n\u00E9rer le d\u00E9calage +scripting.displayName=Script (variables\: ctx vars props log prev) diff --git a/src/components/org/apache/jmeter/timers/BeanShellTimerResources_pt_BR.properties b/src/components/org/apache/jmeter/timers/BeanShellTimerResources_pt_BR.properties new file mode 100644 index 00000000000..3136e1e2e56 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BeanShellTimerResources_pt_BR.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=Temporizador BeanShell +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script do BeanShell (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao BeanShell (arquivo ou script) +resetGroup.displayName=Reiniciar bsh.Interpreter antes de cada chamada. +resetInterpreter.displayName=Reiniciar Interpretador +script.displayName=\ +script.shortDescription=Script BeanShell que ir\u00E1 gerar o atraso +scripting.displayName=Script (vari\u00E1veis\: ctx vars props log prev) diff --git a/src/components/org/apache/jmeter/timers/BeanShellTimerResources_tr.properties b/src/components/org/apache/jmeter/timers/BeanShellTimerResources_tr.properties new file mode 100644 index 00000000000..a06aefe4fc4 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/BeanShellTimerResources_tr.properties @@ -0,0 +1,25 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=BeanShell Zamanlay\u0131c\u0131 +filename.displayName=Dosya Ad\u0131 +filename.shortDescription=BeanShell beti\u011Fi dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +filenameGroup.displayName=Betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +parameterGroup.displayName=BeanShell'e ge\u00E7ilecek parametreler (\=> Dizgi(string) Parametreler ve String []bsh.args) +parameters.displayName=Parametreler +parameters.shortDescription=BeanShell'e ge\u00E7ilecek parametreler (dosya ya da betik) +script.shortDescription=Gecikmeyi yaratacak BeanShell beti\u011Fi +scripting.displayName=Betik (de\u011Fi\u015Fkenler\: ctx vars props log prev) diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimer.java b/src/components/org/apache/jmeter/timers/ConstantThroughputTimer.java new file mode 100644 index 00000000000..002a36cb62a --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimer.java @@ -0,0 +1,333 @@ +/* + * 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.timers; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class implements a constant throughput timer. A Constant Throughtput + * Timer paces the samplers under its influence so that the total number of + * samples per unit of time approaches a given constant as much as possible. + * + * There are two different ways of pacing the requests: + * - delay each thread according to when it last ran + * - delay each thread according to when any thread last ran + */ +public class ConstantThroughputTimer extends AbstractTestElement implements Timer, TestStateListener, TestBean { + private static final long serialVersionUID = 3; + + private static class ThroughputInfo{ + final Object MUTEX = new Object(); + long lastScheduledTime = 0; + } + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final double MILLISEC_PER_MIN = 60000.0; + + /** + * This enum defines the calculation modes used by the ConstantThroughputTimer. + */ + public enum Mode { + ThisThreadOnly("calcMode.1"), + AllActiveThreads("calcMode.2"), + AllActiveThreadsInCurrentThreadGroup("calcMode.3"), + AllActiveThreads_Shared("calcMode.4"), + AllActiveThreadsInCurrentThreadGroup_Shared("calcMode.5"), + ; + + private final String propertyName; // The property name to be used to look up the display string + + Mode(String name) { + this.propertyName = name; + } + + @Override + public String toString() { + return propertyName; + } + } + + /** + * Target time for the start of the next request. The delay provided by the + * timer will be calculated so that the next request happens at this time. + */ + private long previousTime = 0; + + private Mode mode = Mode.ThisThreadOnly; + + /** + * Desired throughput, in samples per minute. + */ + private double throughput; + + //For calculating throughput across all threads + private static final ThroughputInfo allThreadsInfo = new ThroughputInfo(); + + //For holding the ThrougputInfo objects for all ThreadGroups. Keyed by AbstractThreadGroup objects + private static final ConcurrentMap threadGroupsInfoMap = + new ConcurrentHashMap(); + + + /** + * Constructor for a non-configured ConstantThroughputTimer. + */ + public ConstantThroughputTimer() { + } + + /** + * Sets the desired throughput. + * + * @param throughput + * Desired sampling rate, in samples per minute. + */ + public void setThroughput(double throughput) { + this.throughput = throughput; + } + + /** + * Gets the configured desired throughput. + * + * @return the rate at which samples should occur, in samples per minute. + */ + public double getThroughput() { + return throughput; + } + + public int getCalcMode() { + return mode.ordinal(); + } + + public void setCalcMode(int mode) { + this.mode = Mode.values()[mode]; + } + + /** + * Retrieve the delay to use during test execution. + * + * @see org.apache.jmeter.timers.Timer#delay() + */ + @Override + public long delay() { + long currentTime = System.currentTimeMillis(); + + /* + * If previous time is zero, then target will be in the past. + * This is what we want, so first sample is run without a delay. + */ + long currentTarget = previousTime + calculateDelay(); + if (currentTime > currentTarget) { + // We're behind schedule -- try to catch up: + previousTime = currentTime; // assume the sample will run immediately + return 0; + } + previousTime = currentTarget; // assume the sample will run as soon as the delay has expired + return currentTarget - currentTime; + } + + /** + * Calculate the target time by adding the result of private method + * calculateDelay() to the given currentTime + * + * @param currentTime + * time in ms + * @return new Target time + */ + // TODO - is this used? (apart from test code) + protected long calculateCurrentTarget(long currentTime) { + return currentTime + calculateDelay(); + } + + // Calculate the delay based on the mode + private long calculateDelay() { + long delay = 0; + // N.B. we fetch the throughput each time, as it may vary during a test + double msPerRequest = (MILLISEC_PER_MIN / getThroughput()); + switch (mode) { + case AllActiveThreads: // Total number of threads + delay = Math.round(JMeterContextService.getNumberOfThreads() * msPerRequest); + break; + + case AllActiveThreadsInCurrentThreadGroup: // Active threads in this group + delay = Math.round(JMeterContextService.getContext().getThreadGroup().getNumberOfThreads() * msPerRequest); + break; + + case AllActiveThreads_Shared: // All threads - alternate calculation + delay = calculateSharedDelay(allThreadsInfo,Math.round(msPerRequest)); + break; + + case AllActiveThreadsInCurrentThreadGroup_Shared: //All threads in this group - alternate calculation + final org.apache.jmeter.threads.AbstractThreadGroup group = + JMeterContextService.getContext().getThreadGroup(); + ThroughputInfo groupInfo = threadGroupsInfoMap.get(group); + if (groupInfo == null) { + groupInfo = new ThroughputInfo(); + ThroughputInfo previous = threadGroupsInfoMap.putIfAbsent(group, groupInfo); + if (previous != null) { // We did not replace the entry + groupInfo = previous; // so use the existing one + } + } + delay = calculateSharedDelay(groupInfo,Math.round(msPerRequest)); + break; + + case ThisThreadOnly: + default: // e.g. 0 + delay = Math.round(msPerRequest); // i.e. * 1 + break; + } + return delay; + } + + private long calculateSharedDelay(ThroughputInfo info, long milliSecPerRequest) { + final long now = System.currentTimeMillis(); + final long calculatedDelay; + + //Synchronize on the info object's MUTEX to ensure + //Multiple threads don't update the scheduled time simultaneously + synchronized (info.MUTEX) { + final long nextRequstTime = info.lastScheduledTime + milliSecPerRequest; + info.lastScheduledTime = Math.max(now, nextRequstTime); + calculatedDelay = info.lastScheduledTime - now; + } + + return Math.max(calculatedDelay, 0); + } + + private void reset() { + synchronized (allThreadsInfo.MUTEX) { + allThreadsInfo.lastScheduledTime = 0; + } + threadGroupsInfoMap.clear(); + // no need to sync as one per instance + previousTime = 0; + } + + /** + * Provide a description of this timer class. + * + * TODO: Is this ever used? I can't remember where. Remove if it isn't -- + * TODO: or obtain text from bean's displayName or shortDescription. + * + * @return the description of this timer class. + */ + @Override + public String toString() { + return JMeterUtils.getResString("constant_throughput_timer_memo"); //$NON-NLS-1$ + } + + /** + * Get the timer ready to compute delays for a new test. + *

+ * {@inheritDoc} + */ + @Override + public void testStarted() + { + log.debug("Test started - reset throughput calculation."); + reset(); + } + + /** + * Override the setProperty method in order to convert + * the original String calcMode property. + * This used the locale-dependent display value, so caused + * problems when the language was changed. + * Note that the calcMode StringProperty is replaced with an IntegerProperty + * so the conversion only needs to happen once. + */ + @Override + public void setProperty(JMeterProperty property) { + if (property instanceof StringProperty) { + final String pn = property.getName(); + if (pn.equals("calcMode")) { + final Object objectValue = property.getObjectValue(); + try { + final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass()); + final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE); + for(Enum e : Mode.values()) { + final String propName = e.toString(); + if (objectValue.equals(rb.getObject(propName))) { + final int tmpMode = e.ordinal(); + if (log.isDebugEnabled()) { + log.debug("Converted " + pn + "=" + objectValue + " to mode=" + tmpMode + " using Locale: " + rb.getLocale()); + } + super.setProperty(pn, tmpMode); + return; + } + } + log.warn("Could not convert " + pn + "=" + objectValue + " using Locale: " + rb.getLocale()); + } catch (IntrospectionException e) { + log.error("Could not find BeanInfo", e); + } + } + } + super.setProperty(property); + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded() { + //NOOP + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted(String host) { + testStarted(); + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded(String host) { + //NOOP + } + + // For access from test code + Mode getMode() { + return mode; + } + + // For access from test code + void setMode(Mode newMode) { + mode = newMode; + } + +} diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java new file mode 100644 index 00000000000..520121024b3 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java @@ -0,0 +1,46 @@ +/* + * 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.timers; + +import java.beans.PropertyDescriptor; +import org.apache.jmeter.testbeans.BeanInfoSupport; + +/** + * BeanInfo for the ConstantThroughputTimer. + * + */ +public class ConstantThroughputTimerBeanInfo extends BeanInfoSupport { + + public ConstantThroughputTimerBeanInfo() { + super(ConstantThroughputTimer.class); + + createPropertyGroup("delay", //$NON-NLS-1$ + new String[] { "throughput", //$NON-NLS-1$ + "calcMode" }); //$NON-NLS-1$ + + PropertyDescriptor p = property("throughput"); //$NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Double.valueOf(0.0)); + + p = property("calcMode", ConstantThroughputTimer.Mode.class); //$NON-NLS-1$ + p.setValue(DEFAULT, Integer.valueOf(ConstantThroughputTimer.Mode.ThisThreadOnly.ordinal())); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); // must be defined + } + +} diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources.properties b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources.properties new file mode 100644 index 00000000000..f631b11ef64 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources.properties @@ -0,0 +1,26 @@ +# 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. + +calcMode.1=this thread only +calcMode.2=all active threads +calcMode.3=all active threads in current thread group +calcMode.4=all active threads (shared) +calcMode.5=all active threads in current thread group (shared) +calcMode.displayName=Calculate Throughput based on +calcMode.shortDescription=The Constant Throughput Timer used to delay each thread as though it was the only thread in the test. Now, it calculates the delay taking into account the number of active threads in the test or the thread group. +delay.displayName=Delay before each affected sampler +displayName=Constant Throughput Timer +throughput.displayName=Target throughput (in samples per minute) +throughput.shortDescription=Maximum number of samples you want to obtain per minute, per thread, from all affected samplers. diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_de.properties b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_de.properties new file mode 100644 index 00000000000..03f9ab6267c --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_de.properties @@ -0,0 +1,26 @@ +# 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. + +calcMode.1=Nur dieser Thread +calcMode.2=Alle aktiven Threads +calcMode.3=Alle aktiven Threads in der aktuellen Thread-Gruppe +calcMode.4=Alle aktiven Threads (Gemeinsam) +calcMode.5=Alle aktiven Threads in der aktuellen Thread-Gruppe (gemeinsam) +calcMode.displayName=Berechne Durchsatz basierend auf +calcMode.shortDescription=Es war der einzige Thread im Test. Nun wird die Pause unter Ber\u00FCcksichtigung der aktiven Threads oder der Thread-Gruppe berechnet. +delay.displayName=Pause bevor eine Probe genommen wird +displayName=Konstanter Durchsatz-Timer (Zeitgeber) +throughput.displayName=Gew\u00FCnschter Proben-Durchsatz (pro Minute) +throughput.shortDescription=Maximale Anzahl an Proben die pro Minute und pro Thread von allen betroffenen Samplern diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_es.properties b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_es.properties new file mode 100644 index 00000000000..d1db1c60e0b --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_es.properties @@ -0,0 +1,25 @@ +# 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. + +#Stored by I18NEdit, may be edited! +calcMode.1=solamente este hilo +calcMode.2=todos los hilos activos +calcMode.3=todos los hilos activos en el grupo de hilos actual +calcMode.displayName=Calcular el rendimiento basado en +calcMode.shortDescription=El Temporizador para Rendimiento Constante introduc\u00EDa un retardo como si este fuera el unico hilo en la prueba. Ahora calcula el retardo teniendo en cuenta el n\u00FAmero de hilos activos en la prueba o el grupo de hilos. +delay.displayName=Retardo antes de cada muestreador afectado +displayName=Temporizador de Rendimiento Constante +throughput.displayName=Rendimiento objetivo (en muestras por minuto) +throughput.shortDescription=N\u00FAmero m\u00E1ximo de muestras que quiere obtener por minuto, por hilo, desde todos los muestreadores afectados. diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_fr.properties b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_fr.properties new file mode 100644 index 00000000000..f9e087f518d --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_fr.properties @@ -0,0 +1,26 @@ +# 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. + +calcMode.1=cette unit\u00E9 seulement +calcMode.2=toutes les unit\u00E9s actives +calcMode.3=toutes les unit\u00E9s actives dans le groupe d'unit\u00E9s courant +calcMode.4=toutes les unit\u00E9s actives (partag\u00E9) +calcMode.5=toutes les unit\u00E9s actives dans le groupe d'unit\u00E9s courant (partag\u00E9) +calcMode.displayName=Calculer le d\u00E9bit sur la base de +calcMode.shortDescription=Compteur de temps utilis\u00E9 par le Compteur de d\u00E9bit constant pour d\u00E9caler chaque thread comme s'il \u00E9tait le seul dans le test. Maintenant, le d\u00E9lai est calcul\u00E9 en prenant en compte le nombre de threads actifs dans le test ou le groupe d'unit\u00E9s. +delay.displayName=D\u00E9lai avant chaque \u00E9chantillon affect\u00E9 +displayName=Compteur de d\u00E9bit constant +throughput.displayName=D\u00E9bit cibl\u00E9 (en \u00E9chantillons par minute) +throughput.shortDescription=Nombre maximum d'\u00E9chantillon \u00E0 obtenir par minute, par unit\u00E9, pour tous les \u00E9chantillons affect\u00E9s diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_ja.properties b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_ja.properties new file mode 100644 index 00000000000..7fb5c08f45d --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_ja.properties @@ -0,0 +1,18 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=\u5B9A\u6570\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8\u30BF\u30A4\u30DE +throughput.displayName=\u30BF\u30FC\u30B2\u30C3\u30C8\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8(\u30B5\u30F3\u30D7\u30EB\u6570/\u5206) diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_pt_BR.properties b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_pt_BR.properties new file mode 100644 index 00000000000..b198b40845d --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_pt_BR.properties @@ -0,0 +1,26 @@ +# 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. + +calcMode.1=este usu\u00E1rio virtual (thread) somente +calcMode.2=todos usu\u00E1rios virtuais (threads) ativos +calcMode.3=todos usu\u00E1rios virtuais ativos no grupo de usu\u00E1rios corrente +calcMode.4=todos usu\u00E1rios virtuais ativos (compartilhado) +calcMode.5=todos usu\u00E1rios virtuais no grupo de usu\u00E1rios atual (compartillhado) +calcMode.displayName=Calcular Vaz\u00E3o baseada em +calcMode.shortDescription=O Temporizador de Vaz\u00E3o Constante era usado para atrasar cada usu\u00E1rio virtual como se ele fosse o \u00FAnico usu\u00E1rio virtual no teste. Agora, ele calcula o atraso levando em considera\u00E7\u00E3o o n\u00FAmero de usu\u00E1rios virtuais ativos no teste ou no grupo de usu\u00E1rios. +delay.displayName=Atraso antes de cada testador afetado +displayName=Temporizador de Vaz\u00E3o Constante +throughput.displayName=Vaz\u00E3o alvo (em amostras por minuto) +throughput.shortDescription=N\u00FAmero m\u00E1ximo de amostras que voc\u00EA quer obter por minuto, por usu\u00E1rio virtual, de todos os testadores afetados. diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_tr.properties b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_tr.properties new file mode 100644 index 00000000000..67cfa92444b --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_tr.properties @@ -0,0 +1,27 @@ +# 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. + +#Stored by I18NEdit, may be edited! +calcMode.1=sadece bu i\u015F par\u00E7ac\u0131\u011F\u0131 +calcMode.2=b\u00FCt\u00FCn aktif i\u015F par\u00E7ac\u0131klar\u0131 +calcMode.3=bu i\u015F par\u00E7ac\u0131\u011F\u0131 grubundaki t\u00FCm aktif i\u015F par\u00E7ac\u0131klar\u0131 +calcMode.4=t\u00FCm i\u015F par\u00E7ac\u0131klar\u0131 (payla\u015F\u0131ml\u0131) +calcMode.5=bu i\u015F pa\u00E7ac\u0131\u011F\u0131 grubundaki t\u00FCm aktif i\u015F par\u00E7ac\u0131klar\u0131 (payla\u015F\u0131ml\u0131) +calcMode.displayName=transfer oran\u0131 hesab\u0131n\u0131n yap\u0131laca\u011F\u0131 temel +calcMode.shortDescription=Sabit Transfer Oran\u0131 Zamanlay\u0131c\u0131 eskiden her bir i\u015F par\u00E7ac\u0131\u011F\u0131 i\u00E7in, testteki tek i\u015F par\u00E7ac\u0131\u011F\u0131ym\u0131\u015Fcas\u0131na gecikirken; \u015Fimdi gecikme hesab\u0131 testteki veya i\u015F par\u00E7ac\u0131\u011F\u0131 grubundaki aktif i\u015F par\u00E7ac\u0131\u011F\u0131 say\u0131s\u0131na g\u00F6re yap\u0131lmakta. +delay.displayName=Etkilenen her \u00F6rnekleyiciden \u00F6nce gecikme +displayName=Sabit Transfer Oran\u0131 Zamanlay\u0131c\u0131 +throughput.displayName=Hedeflenen transfer oran\u0131 (\u00F6rnek/dak.) +throughput.shortDescription=Dakika, i\u015F par\u00E7ac\u0131\u011F\u0131 ve etkilenen \u00F6rnekleyici ba\u015F\u0131na istedi\u011Finiz en fazla \u00F6rnek say\u0131s\u0131. diff --git a/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_zh_TW.properties b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_zh_TW.properties new file mode 100644 index 00000000000..47c67566f65 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_zh_TW.properties @@ -0,0 +1,20 @@ +# 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. + +#Stored by I18NEdit, may be edited! +delay.displayName=\u6BCF\u500B\u53D6\u6A23\u9593\u7684\u5EF6\u9072\u6642\u9593 +displayName=\u56FA\u5B9A\u6642\u9694 +throughput.displayName=\u76EE\u6A19\u8655\u7406\u91CF(\u6BCF\u5206\u9418\u53D6\u6A23\u6578) +throughput.shortDescription=\u5728\u6240\u6709\u53D6\u6A23\u4E2D\uFF0C\u6BCF\u57F7\u884C\u7DD2\u6BCF\u5206\u9418\u5167\u7684\u6700\u5927\u53D6\u6A23\u6578 diff --git a/src/components/org/apache/jmeter/timers/ConstantTimer.java b/src/components/org/apache/jmeter/timers/ConstantTimer.java new file mode 100644 index 00000000000..443fcf908b8 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/ConstantTimer.java @@ -0,0 +1,112 @@ +/* + * 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.timers; + +import java.io.Serializable; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements a constant timer with its own panel and fields for + * value update and user interaction. + * + */ +public class ConstantTimer extends AbstractTestElement implements Timer, Serializable, LoopIterationListener { + + private static final long serialVersionUID = 240L; + + public static final String DELAY = "ConstantTimer.delay"; //$NON-NLS-1$ + + private long delay = 0; + + /** + * No-arg constructor. + */ + public ConstantTimer() { + } + + /** + * Set the delay for this timer. + * @param delay The delay for this timer + */ + public void setDelay(String delay) { + setProperty(DELAY, delay); + } + + /** + * Set the range (not used for this timer). + * @param range Not used + * + */ + public void setRange(double range) { + // NOOP + } + + /** + * Get the delay value for display. + * + * @return the delay value for display. + */ + public String getDelay() { + return getPropertyAsString(DELAY); + } + + /** + * Retrieve the range (not used for this timer). + * + * @return the range (always zero for this timer). + */ + public double getRange() { + return 0; + } + + /** + * Retrieve the delay to use during test execution. + * + * @return the delay. + */ + @Override + public long delay() { + return delay; + } + + /** + * Provide a description of this timer class. + * + * @return the description of this timer class. + */ + @Override + public String toString() { + return JMeterUtils.getResString("constant_timer_memo"); //$NON-NLS-1$ + } + + /** + * Gain access to any variables that have been defined. + * + * @see LoopIterationListener#iterationStart(LoopIterationEvent) + */ + @Override + public void iterationStart(LoopIterationEvent event) { + delay = getPropertyAsLong(DELAY); + + } +} diff --git a/src/components/org/apache/jmeter/timers/GaussianRandomTimer.java b/src/components/org/apache/jmeter/timers/GaussianRandomTimer.java new file mode 100644 index 00000000000..52f125e89f9 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/GaussianRandomTimer.java @@ -0,0 +1,43 @@ +/* + * 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.timers; + +import java.io.Serializable; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements those methods needed by RandomTimer to be instantiable + * and implements a random delay with an average value and a gaussian + * distributed variation. + * + */ +public class GaussianRandomTimer extends RandomTimer implements Serializable { + private static final long serialVersionUID = 240L; + + @Override + public long delay() { + return (long) Math.abs((this.random.nextGaussian() * getRange()) + super.delay()); + } + + @Override + public String toString() { + return JMeterUtils.getResString("gaussian_timer_memo"); //$NON-NLS-1$ + } +} diff --git a/src/components/org/apache/jmeter/timers/JSR223Timer.java b/src/components/org/apache/jmeter/timers/JSR223Timer.java new file mode 100644 index 00000000000..b5196b69ae5 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/JSR223Timer.java @@ -0,0 +1,57 @@ +/* + * 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.timers; + +import java.io.IOException; + +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223Timer extends JSR223TestElement implements Cloneable, Timer, TestBean { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + /** {@inheritDoc} */ + @Override + public long delay() { + long delay = 0; + try { + ScriptEngine scriptEngine = getScriptEngine(); + Object o = processFileOrScript(scriptEngine, null); + if (o == null) { + log.warn("Script did not return a value"); + return 0; + } + delay = Long.parseLong(o.toString()); + } catch (NumberFormatException e) { + log.error("Problem in JSR223 script "+getName(), e); + } catch (IOException e) { + log.error("Problem in JSR223 script "+getName(), e); + } catch (ScriptException e) { + log.error("Problem in JSR223 script "+getName(), e); + } + return delay; + } +} diff --git a/src/components/org/apache/jmeter/timers/JSR223TimerBeanInfo.java b/src/components/org/apache/jmeter/timers/JSR223TimerBeanInfo.java new file mode 100644 index 00000000000..0eafdaec81f --- /dev/null +++ b/src/components/org/apache/jmeter/timers/JSR223TimerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.timers; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223TimerBeanInfo extends JSR223BeanInfoSupport { + + public JSR223TimerBeanInfo() { + super(JSR223Timer.class); + } + +} diff --git a/src/components/org/apache/jmeter/timers/JSR223TimerResources.properties b/src/components/org/apache/jmeter/timers/JSR223TimerResources.properties new file mode 100644 index 00000000000..9f2d51c86db --- /dev/null +++ b/src/components/org/apache/jmeter/timers/JSR223TimerResources.properties @@ -0,0 +1,31 @@ +# 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. + +displayName=JSR223 Timer +cacheKey.displayName=Compilation cache key +cacheKey.shortDescription=If Cache key is not empty, script will be compiled if JSR223 underlying script language supports it and CompiledScript will be cached, ensure script does not use any variable before making it cacheable +cacheKey_group.displayName=Script compilation caching +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/timers/JSR223TimerResources_fr.properties b/src/components/org/apache/jmeter/timers/JSR223TimerResources_fr.properties new file mode 100644 index 00000000000..5e04ab44dce --- /dev/null +++ b/src/components/org/apache/jmeter/timers/JSR223TimerResources_fr.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Compteur de temps JSR223 +cacheKey.displayName=Clef de caching +cacheKey.shortDescription=Si la clef de caching n'est pas vide, le script sera compil\u00E9 si le language sous-jacent JSR223 fournit cette fonctionnalit\u00E9 et l'objet CompiledScript sera mis en cache, assurez vous avant d'utiliser ce caching que le script n'utilise pas de variables JMeter +cacheKey_group.displayName=Param\u00E8tres de caching du Script compil\u00E9 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le code script) +filenameGroup.displayName=Fichier script (remplace le code script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. groovy, beanshell, jexl) diff --git a/src/components/org/apache/jmeter/timers/PoissonRandomTimer.java b/src/components/org/apache/jmeter/timers/PoissonRandomTimer.java new file mode 100644 index 00000000000..dab58f0fbaf --- /dev/null +++ b/src/components/org/apache/jmeter/timers/PoissonRandomTimer.java @@ -0,0 +1,390 @@ +/* + * 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.timers; + +import java.io.Serializable; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements those methods needed by RandomTimer to be instantiable + * and implements a random delay with an average value and a Poisson + * distributed variation. + * + */ +public class PoissonRandomTimer extends RandomTimer implements Serializable { + /** + * + */ + private static final long serialVersionUID = 3514708226113231004L; + /** + * + */ + private static final double[] LOG_FACTORIAL = + { + 0.000000000000000, + 0.000000000000000, + 0.693147180559945, + 1.791759469228055, + 3.178053830347946, + 4.787491742782046, + 6.579251212010101, + 8.525161361065415, + 10.604602902745251, + 12.801827480081469, + 15.104412573075516, + 17.502307845873887, + 19.987214495661885, + 22.552163853123421, + 25.191221182738683, + 27.899271383840894, + 30.671860106080675, + 33.505073450136891, + 36.395445208033053, + 39.339884187199495, + 42.335616460753485, + 45.380138898476908, + 48.471181351835227, + 51.606675567764377, + 54.784729398112319, + 58.003605222980518, + 61.261701761002001, + 64.557538627006323, + 67.889743137181526, + 71.257038967168000, + 74.658236348830158, + 78.092223553315307, + 81.557959456115029, + 85.054467017581516, + 88.580827542197682, + 92.136175603687079, + 95.719694542143202, + 99.330612454787428, + 102.968198614513810, + 106.631760260643450, + 110.320639714757390, + 114.034211781461690, + 117.771881399745060, + 121.533081515438640, + 125.317271149356880, + 129.123933639127240, + 132.952575035616290, + 136.802722637326350, + 140.673923648234250, + 144.565743946344900, + 148.477766951773020, + 152.409592584497350, + 156.360836303078800, + 160.331128216630930, + 164.320112263195170, + 168.327445448427650, + 172.352797139162820, + 176.395848406997370, + 180.456291417543780, + 184.533828861449510, + 188.628173423671600, + 192.739047287844900, + 196.866181672889980, + 201.009316399281570, + 205.168199482641200, + 209.342586752536820, + 213.532241494563270, + 217.736934113954250, + 221.956441819130360, + 226.190548323727570, + 230.439043565776930, + 234.701723442818260, + 238.978389561834350, + 243.268849002982730, + 247.572914096186910, + 251.890402209723190, + 256.221135550009480, + 260.564940971863220, + 264.921649798552780, + 269.291097651019810, + 273.673124285693690, + 278.067573440366120, + 282.474292687630400, + 286.893133295426990, + 291.323950094270290, + 295.766601350760600, + 300.220948647014100, + 304.686856765668720, + 309.164193580146900, + 313.652829949878990, + 318.152639620209300, + 322.663499126726210, + 327.185287703775200, + 331.717887196928470, + 336.261181979198450, + 340.815058870798960, + 345.379407062266860, + 349.954118040770250, + 354.539085519440790, + 359.134205369575340, + 363.739375555563470, + 368.354496072404690, + 372.979468885689020, + 377.614197873918670, + 382.258588773060010, + 386.912549123217560, + 391.575988217329610, + 396.248817051791490, + 400.930948278915760, + 405.622296161144900, + 410.322776526937280, + 415.032306728249580, + 419.750805599544780, + 424.478193418257090, + 429.214391866651570, + 433.959323995014870, + 438.712914186121170, + 443.475088120918940, + 448.245772745384610, + 453.024896238496130, + 457.812387981278110, + 462.608178526874890, + 467.412199571608080, + 472.224383926980520, + 477.044665492585580, + 481.872979229887900, + 486.709261136839360, + 491.553448223298010, + 496.405478487217580, + 501.265290891579240, + 506.132825342034830, + 511.008022665236070, + 515.890824587822520, + 520.781173716044240, + 525.679013515995050, + 530.584288294433580, + 535.496943180169520, + 540.416924105997740, + 545.344177791154950, + 550.278651724285620, + 555.220294146894960, + 560.169054037273100, + 565.124881094874350, + 570.087725725134190, + 575.057539024710200, + 580.034272767130800, + 585.017879388839220, + 590.008311975617860, + 595.005524249382010, + 600.009470555327430, + 605.020105849423770, + 610.037385686238740, + 615.061266207084940, + 620.091704128477430, + 625.128656730891070, + 630.172081847810200, + 635.221937855059760, + 640.278183660408100, + 645.340778693435030, + 650.409682895655240, + 655.484856710889060, + 660.566261075873510, + 665.653857411105950, + 670.747607611912710, + 675.847474039736880, + 680.953419513637530, + 686.065407301994010, + 691.183401114410800, + 696.307365093814040, + 701.437263808737160, + 706.573062245787470, + 711.714725802289990, + 716.862220279103440, + 722.015511873601330, + 727.174567172815840, + 732.339353146739310, + 737.509837141777440, + 742.685986874351220, + 747.867770424643370, + 753.055156230484160, + 758.248113081374300, + 763.446610112640200, + 768.650616799717000, + 773.860102952558460, + 779.075038710167410, + 784.295394535245690, + 789.521141208958970, + 794.752249825813460, + 799.988691788643450, + 805.230438803703120, + 810.477462875863580, + 815.729736303910160, + 820.987231675937890, + 826.249921864842800, + 831.517780023906310, + 836.790779582469900, + 842.068894241700490, + 847.352097970438420, + 852.640365001133090, + 857.933669825857460, + 863.231987192405430, + 868.535292100464630, + 873.843559797865740, + 879.156765776907600, + 884.474885770751830, + 889.797895749890240, + 895.125771918679900, + 900.458490711945270, + 905.796028791646340, + 911.138363043611210, + 916.485470574328820, + 921.837328707804890, + 927.193914982476710, + 932.555207148186240, + 937.921183163208070, + 943.291821191335660, + 948.667099599019820, + 954.046996952560450, + 959.431492015349480, + 964.820563745165940, + 970.214191291518320, + 975.612353993036210, + 981.015031374908400, + 986.422203146368590, + 991.833849198223450, + 997.249949600427840, + 1002.670484599700300, + 1008.095434617181700, + 1013.524780246136200, + 1018.958502249690200, + 1024.396581558613400, + 1029.838999269135500, + 1035.285736640801600, + 1040.736775094367400, + 1046.192096209724900, + 1051.651681723869200, + 1057.115513528895000, + 1062.583573670030100, + 1068.055844343701400, + 1073.532307895632800, + 1079.012946818975000, + 1084.497743752465600, + 1089.986681478622400, + 1095.479742921962700, + 1100.976911147256000, + 1106.478169357800900, + 1111.983500893733000, + 1117.492889230361000, + 1123.006317976526100, + 1128.523770872990800, + 1134.045231790853000, + 1139.570684729984800, + 1145.100113817496100, + 1150.633503306223700, + 1156.170837573242400, + }; + + + /** + * {@inheritDoc} + */ + @Override + public long delay() { + return Math.abs(randomPoisson((int)Math.round(getRange())) + super.delay()); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return JMeterUtils.getResString("poisson_timer_memo"); //$NON-NLS-1$ + } + + /** + * Generate Poisson random based using + * @param lambda Lambda in Poisson + * @return random + */ + private static int randomPoisson(int lambda) { + if(lambda <= 30) { + return poissonRandomNumberLowEq30(lambda); + } else { + return poissonRandomNumberSup30(lambda); + } + } + /** + * see http://en.wikipedia.org/wiki/Poisson_distribution + * @param lambda Lambda in Poisson + * @return random + */ + private static final int poissonRandomNumberLowEq30(int lambda) { + double L = Math.exp(-lambda); + int k = 0; + double p = 1; + do { + k = k + 1; + double u = Math.random(); + p = p * u; + } while (p > L); + return k - 1; + } + + /** + * http://www.johndcook.com/blog/2010/06/14/generating-poisson-random-values/ + * @param lambda Lambda in Poisson + * @return random + */ + private static final int poissonRandomNumberSup30(int lambda) { + double c = 0.767 - 3.36/lambda; + double beta = Math.PI/Math.sqrt(3.0*lambda); + double alpha = beta*lambda; + double k = Math.log(c) - lambda - Math.log(beta); + while(true) { + double u = Math.random(); + double x = (alpha - Math.log((1.0 - u)/u))/beta; + int n = (int)Math.floor(x + 0.5); + if (n < 0){ + continue; + } + double v = Math.random(); + double y = alpha - beta*x; + double lhs = y + Math.log(v/Math.pow((1.0 + Math.exp(y)),2)); + double rhs = k + n*Math.log(lambda) -logFactorial(n); + if (lhs <= rhs) { + return n; + } + } + } + + /** + * Compute log factorial + * http://www.johndcook.com/blog/2010/08/16/how-to-compute-log-factorial/ + * @param n Number for which we want log(n!) + * @return Log factorial + */ + private static final double logFactorial(int n) + { + if (n < 0) { + throw new IllegalArgumentException(); + } + else if (n > 254) { + double x = n + 1; + return (x - 0.5)*Math.log(x) - x + 0.5*Math.log(2*Math.PI) + 1.0/(12.0*x); + } + else { + return LOG_FACTORIAL[n]; + } + } +} diff --git a/src/components/org/apache/jmeter/timers/RandomTimer.java b/src/components/org/apache/jmeter/timers/RandomTimer.java new file mode 100644 index 00000000000..644db0c80d1 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/RandomTimer.java @@ -0,0 +1,69 @@ +/* + * 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.timers; + +import java.io.Serializable; +import java.util.Random; + +import org.apache.jmeter.testelement.property.DoubleProperty; +import org.apache.jmeter.testelement.property.StringProperty; + +/** + * This class implements a random timer with its own panel and fields for value + * update and user interaction. Since this class does not define the delay() + * method, is abstract and must be extended to provide full functionality. + * + */ +public abstract class RandomTimer extends ConstantTimer implements Timer, Serializable { + private static final long serialVersionUID = 240L; + + public static final String RANGE = "RandomTimer.range"; + + protected final Random random; + + /** + * No-arg constructor. + */ + public RandomTimer() { + this.random = new Random(); + } + + /** + * Set the range value. + */ + @Override + public void setRange(double range) { + setProperty(new DoubleProperty(RANGE, range)); + } + + public void setRange(String range) { + setProperty(new StringProperty(RANGE, range)); + } + + /** + * Get the range value. + * + * @return double + */ + @Override + public double getRange() { + return this.getPropertyAsDouble(RANGE); + } + +} diff --git a/src/components/org/apache/jmeter/timers/SyncTimer.java b/src/components/org/apache/jmeter/timers/SyncTimer.java new file mode 100644 index 00000000000..ed7b2d4d4a7 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/SyncTimer.java @@ -0,0 +1,280 @@ +/* + * 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.timers; + +import java.io.Serializable; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The purpose of the SyncTimer is to block threads until X number of threads + * have been blocked, and then they are all released at once. A SyncTimer can + * thus create large instant loads at various points of the test plan. + * + */ +public class SyncTimer extends AbstractTestElement implements Timer, Serializable, TestBean, TestStateListener, ThreadListener { + private static final Logger LOGGER = LoggingManager.getLoggerForClass(); + + /** + * Wrapper to {@link CyclicBarrier} to allow lazy init of CyclicBarrier when SyncTimer is configured with 0 + */ + private static final class BarrierWrapper implements Cloneable { + + private CyclicBarrier barrier; + + /** + * + */ + public BarrierWrapper() { + this.barrier = null; + } + + /** + * @param parties Number of parties + */ + public BarrierWrapper(int parties) { + this.barrier = new CyclicBarrier(parties); + } + + /** + * Synchronized is required to ensure CyclicBarrier is initialized only once per Thread Group + * @param parties Number of parties + */ + public synchronized void setup(int parties) { + if(this.barrier== null) { + this.barrier = new CyclicBarrier(parties); + } + } + + + /** + * Wait until all threads called await on this timer + * + * @return The arrival index of the current thread + * @throws InterruptedException + * when interrupted while waiting, or the interrupted status + * is set on entering this method + * @throws BrokenBarrierException + * if the barrier is reset while waiting or broken on + * entering or while waiting + * @see java.util.concurrent.CyclicBarrier#await() + */ + public int await() throws InterruptedException, BrokenBarrierException{ + return barrier.await(); + } + + /** + * Wait until all threads called await on this timer + * + * @param timeout + * The timeout in timeUnit units + * @param timeUnit + * The time unit for the timeout + * @return The arrival index of the current thread + * @throws InterruptedException + * when interrupted while waiting, or the interrupted status + * is set on entering this method + * @throws BrokenBarrierException + * if the barrier is reset while waiting or broken on + * entering or while waiting + * @throws TimeoutException + * if the specified time elapses + * @see java.util.concurrent.CyclicBarrier#await() + */ + public int await(long timeout, TimeUnit timeUnit) throws InterruptedException, BrokenBarrierException, TimeoutException { + return barrier.await(timeout, timeUnit); + } + + /** + * @see java.util.concurrent.CyclicBarrier#reset() + */ + public void reset() { + barrier.reset(); + } + + /** + * @see java.lang.Object#clone() + */ + @Override + protected Object clone() { + BarrierWrapper barrierWrapper= null; + try { + barrierWrapper = (BarrierWrapper) super.clone(); + barrierWrapper.barrier = this.barrier; + } catch (CloneNotSupportedException e) { + //Cannot happen + } + return barrierWrapper; + } + } + + private static final long serialVersionUID = 2; + + private transient BarrierWrapper barrier; + + private int groupSize; + + private long timeoutInMs; + + // Ensure transient object is created by the server + private Object readResolve(){ + createBarrier(); + return this; + } + + /** + * @return Returns the numThreads. + */ + public int getGroupSize() { + return groupSize; + } + + /** + * @param numThreads + * The numThreads to set. + */ + public void setGroupSize(int numThreads) { + this.groupSize = numThreads; + } + + /** + * {@inheritDoc} + */ + @Override + public long delay() { + if(getGroupSize()>=0) { + int arrival = 0; + try { + if(timeoutInMs==0) { + arrival = this.barrier.await(); + } else if(timeoutInMs > 0){ + arrival = this.barrier.await(timeoutInMs, TimeUnit.MILLISECONDS); + } else { + throw new IllegalArgumentException("Negative value for timeout:"+timeoutInMs+" in Synchronizing Timer "+getName()); + } + } catch (InterruptedException e) { + return 0; + } catch (BrokenBarrierException e) { + return 0; + } catch (TimeoutException e) { + LOGGER.warn("SyncTimer "+ getName() + " timeouted waiting for users after:"+getTimeoutInMs()+"ms"); + return 0; + } finally { + if(arrival == 0) { + barrier.reset(); + } + } + } + return 0; + } + + /** + * We have to control the cloning process because we need some cross-thread + * communication if our synctimers are to be able to determine when to block + * and when to release. + */ + @Override + public Object clone() { + SyncTimer newTimer = (SyncTimer) super.clone(); + newTimer.barrier = barrier; + return newTimer; + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded() { + this.testEnded(null); + } + + /** + * Reset timerCounter + */ + @Override + public void testEnded(String host) { + createBarrier(); + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted() { + testStarted(null); + } + + /** + * Reset timerCounter + */ + @Override + public void testStarted(String host) { + createBarrier(); + } + + /** + * + */ + private void createBarrier() { + if(getGroupSize() == 0) { + // Lazy init + this.barrier = new BarrierWrapper(); + } else { + this.barrier = new BarrierWrapper(getGroupSize()); + } + } + + @Override + public void threadStarted() { + if(getGroupSize() == 0) { + int numThreadsInGroup = JMeterContextService.getContext().getThreadGroup().getNumThreads(); + // Unique Barrier creation ensured by synchronized setup + this.barrier.setup(numThreadsInGroup); + } + } + + @Override + public void threadFinished() { + // NOOP + } + + /** + * @return the timeoutInMs + */ + public long getTimeoutInMs() { + return timeoutInMs; + } + + /** + * @param timeoutInMs the timeoutInMs to set + */ + public void setTimeoutInMs(long timeoutInMs) { + this.timeoutInMs = timeoutInMs; + } +} diff --git a/src/components/org/apache/jmeter/timers/SyncTimerBeanInfo.java b/src/components/org/apache/jmeter/timers/SyncTimerBeanInfo.java new file mode 100644 index 00000000000..bfd10339c8a --- /dev/null +++ b/src/components/org/apache/jmeter/timers/SyncTimerBeanInfo.java @@ -0,0 +1,42 @@ +/* + * 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.timers; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class SyncTimerBeanInfo extends BeanInfoSupport { + + public SyncTimerBeanInfo() { + super(SyncTimer.class); + + createPropertyGroup("grouping", new String[] { "groupSize", "timeoutInMs" }); + + PropertyDescriptor p = property("groupSize"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Integer.valueOf(0)); + + p = property("timeoutInMs"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Long.valueOf(0)); + + } + +} diff --git a/src/components/org/apache/jmeter/timers/SyncTimerResources.properties b/src/components/org/apache/jmeter/timers/SyncTimerResources.properties new file mode 100644 index 00000000000..97463dd9ded --- /dev/null +++ b/src/components/org/apache/jmeter/timers/SyncTimerResources.properties @@ -0,0 +1,21 @@ +# 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. + +displayName=Synchronizing Timer +grouping.displayName=Grouping +groupSize.displayName=Number of Simulated Users to Group by +groupSize.shortDescription=Define how many simulated users trigger the release of the synchronizing block (default value of '0' means all users) +timeoutInMs.displayName=Timeout in milliseconds +timeoutInMs.shortDescription=If set to 0, not timeout will occurs, if superior to 0, then if ater the timeout interval the number of users waiting is not reached, timer will stop waiting \ No newline at end of file diff --git a/src/components/org/apache/jmeter/timers/SyncTimerResources_de.properties b/src/components/org/apache/jmeter/timers/SyncTimerResources_de.properties new file mode 100644 index 00000000000..f91a0987c24 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/SyncTimerResources_de.properties @@ -0,0 +1,18 @@ +# 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. + +groupSize.displayName=Anzahl der gruppierten, Simulations-Benutzer +groupSize.shortDescription=Geben sie die Anzahl der Simulations-Benutzer an, die den synchronisierten Block ausl\u00F6sen (Vorgabe 0 \= alle Benutzer) +grouping.displayName=Gruppierung diff --git a/src/components/org/apache/jmeter/timers/SyncTimerResources_es.properties b/src/components/org/apache/jmeter/timers/SyncTimerResources_es.properties new file mode 100644 index 00000000000..bdd99c45b9b --- /dev/null +++ b/src/components/org/apache/jmeter/timers/SyncTimerResources_es.properties @@ -0,0 +1,20 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Temporizador sincronizado +groupSize.displayName=N\u00FAmero de Usuarios Simulados para agrupar +groupSize.shortDescription=Defina cuantos usuarios simulados disparan la liberaci\u00F3n del bloque sincronizado (el valor por defecto '0' significa todos los usuarios) +grouping.displayName=Agrupaci\u00F3n diff --git a/src/components/org/apache/jmeter/timers/SyncTimerResources_fr.properties b/src/components/org/apache/jmeter/timers/SyncTimerResources_fr.properties new file mode 100644 index 00000000000..8b24303bb73 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/SyncTimerResources_fr.properties @@ -0,0 +1,22 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Compteur de synchronisation +groupSize.displayName=Nombre d'utilisateurs simul\u00E9s \u00E0 grouper +groupSize.shortDescription=D\u00E9fini combien d'utilisateurs simul\u00E9s d\u00E9clenchent la lib\u00E9ration synchronis\u00E9e du bloc (d\u00E9faut \: la valeur '0' signifie tous les utilisateurs) +grouping.displayName=Regroupement +timeoutInMs.displayName=Timeout en millisecondes +timeoutInMs.shortDescription=Si \u00E9gal \u00E0 0, aucun timeout n'aura lieu, sinon les threads en attente sur le Compteur de synchronisation attendront au max la valeur de timeout \ No newline at end of file diff --git a/src/components/org/apache/jmeter/timers/SyncTimerResources_pt_BR.properties b/src/components/org/apache/jmeter/timers/SyncTimerResources_pt_BR.properties new file mode 100644 index 00000000000..1bf6dff6456 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/SyncTimerResources_pt_BR.properties @@ -0,0 +1,19 @@ +# 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. + +displayName=Temporizador de sincroniza\u00E7\u00E3o +groupSize.displayName=N\u00FAmero de Usu\u00E1rios Simulados por Grupo por +groupSize.shortDescription=Define quantos usu\u00E1rios simulados ativam a libera\u00E7\u00E3o do bloco de sincroniza\u00E7\u00E3o (valor padr\u00E3o de '0' significa todos usu\u00E1rios) +grouping.displayName=Agrupamento diff --git a/src/components/org/apache/jmeter/timers/SyncTimerResources_tr.properties b/src/components/org/apache/jmeter/timers/SyncTimerResources_tr.properties new file mode 100644 index 00000000000..f8e6a473e76 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/SyncTimerResources_tr.properties @@ -0,0 +1,20 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=E\u015Fzamanl\u0131 Zamanlay\u0131c\u0131 +groupSize.displayName=Grup ba\u015F\u0131na d\u00FC\u015Fen sahte kullan\u0131c\u0131 say\u0131s\u0131 +groupSize.shortDescription=E\u015F zamanl\u0131 blo\u011Fun sal\u0131verilmesini tetikleyen sahte kullan\u0131c\u0131 say\u0131s\u0131n\u0131 belirtin (\u00F6n tan\u0131ml\u0131 olarak '0' t\u00FCm kullan\u0131c\u0131lar anlam\u0131na gelmektedir) +grouping.displayName=Gruplama diff --git a/src/components/org/apache/jmeter/timers/UniformRandomTimer.java b/src/components/org/apache/jmeter/timers/UniformRandomTimer.java new file mode 100644 index 00000000000..f9df6725941 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/UniformRandomTimer.java @@ -0,0 +1,44 @@ +/* + * 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.timers; + +import java.io.Serializable; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements those methods needed by RandomTimer to be instantiable + * and implements a random delay with an average value and a uniformly + * distributed variation. + * + */ +public class UniformRandomTimer extends RandomTimer implements Serializable { + private static final long serialVersionUID = 240L; + + @Override + public long delay() { + return (long) Math.abs((this.random.nextDouble() * getRange()) + super.delay()); + } + + @Override + public String toString() { + return JMeterUtils.getResString("uniform_timer_memo"); //$NON-NLS-1$ + } + +} diff --git a/src/components/org/apache/jmeter/timers/gui/AbstractRandomTimerGui.java b/src/components/org/apache/jmeter/timers/gui/AbstractRandomTimerGui.java new file mode 100644 index 00000000000..869b307edcc --- /dev/null +++ b/src/components/org/apache/jmeter/timers/gui/AbstractRandomTimerGui.java @@ -0,0 +1,195 @@ +/* + * 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.timers.gui; + +import java.awt.Dimension; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.timers.ConstantTimer; +import org.apache.jmeter.timers.RandomTimer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * Abstract Random timer GUI. + * + */ +public abstract class AbstractRandomTimerGui extends AbstractTimerGui { + + /** + * + */ + private static final long serialVersionUID = -322164502276145504L; + + private static final String DELAY_FIELD = "Delay Field"; + + private static final String RANGE_FIELD = "Range Field"; + + private JTextField delayField; + + private JTextField rangeField; + + /** + * No-arg constructor. + */ + public AbstractRandomTimerGui() { + init(); + } + + /** + * Handle an error. + * + * @param e + * the Exception that was thrown. + * @param thrower + * the JComponent that threw the Exception. + */ + public static void error(Exception e, JComponent thrower) { + JOptionPane.showMessageDialog(thrower, e, "Error", JOptionPane.ERROR_MESSAGE); + } + + /** + * Create the test element underlying this GUI component. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + RandomTimer timer = createRandomTimer(); + modifyTestElement(timer); + return timer; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement timer) { + this.configureTestElement(timer); + ((RandomTimer) timer).setDelay(delayField.getText()); + ((RandomTimer) timer).setRange(rangeField.getText()); + } + + /** + * Configure this GUI component from the underlying TestElement. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + delayField.setText(el.getPropertyAsString(ConstantTimer.DELAY)); + rangeField.setText(el.getPropertyAsString(RandomTimer.RANGE)); + } + + + /** + * Initialize this component. + */ + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + + JPanel threadDelayPropsPanel = new JPanel(); + threadDelayPropsPanel.setLayout(new VerticalLayout(5, VerticalLayout.LEFT)); + threadDelayPropsPanel.setBorder(BorderFactory.createTitledBorder( + JMeterUtils.getResString("thread_delay_properties")));//$NON-NLS-1$ + + // DELAY DEVIATION + Box delayDevPanel = Box.createHorizontalBox(); + delayDevPanel.add(new JLabel(getTimerRangeLabelKey()));//$NON-NLS-1$ + delayDevPanel.add(Box.createHorizontalStrut(5)); + + rangeField = new JTextField(20); + rangeField.setText(getDefaultRange()); + rangeField.setName(RANGE_FIELD); + delayDevPanel.add(rangeField); + + threadDelayPropsPanel.add(delayDevPanel); + + // AVG DELAY + Box avgDelayPanel = Box.createHorizontalBox(); + avgDelayPanel.add(new JLabel(getTimerDelayLabelKey()));//$NON-NLS-1$ + avgDelayPanel.add(Box.createHorizontalStrut(5)); + + delayField = new JTextField(20); + delayField.setText(getDefaultDelay()); + delayField.setName(DELAY_FIELD); + avgDelayPanel.add(delayField); + + threadDelayPropsPanel.add(avgDelayPanel); + threadDelayPropsPanel.setMaximumSize(new Dimension(threadDelayPropsPanel.getMaximumSize().width, + threadDelayPropsPanel.getPreferredSize().height)); + add(threadDelayPropsPanel); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + rangeField.setText(getDefaultRange()); + delayField.setText(getDefaultDelay()); + super.clearGui(); + } + + /** + * {@inheritDoc} + */ + @Override + abstract public String getLabelResource(); + + /** + * Create implementation of RandomTimer + * @return {@link RandomTimer} + */ + protected abstract RandomTimer createRandomTimer(); + + /** + * @return String timer delay label key + */ + abstract protected String getTimerDelayLabelKey(); + + /** + * @return String timer range label key + */ + abstract protected String getTimerRangeLabelKey(); + + /** + * @return String default delay value + */ + abstract protected String getDefaultDelay(); + + /** + * @return String default range value + */ + abstract protected String getDefaultRange(); +} diff --git a/src/components/org/apache/jmeter/timers/gui/ConstantTimerGui.java b/src/components/org/apache/jmeter/timers/gui/ConstantTimerGui.java new file mode 100644 index 00000000000..5e42f596d90 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/gui/ConstantTimerGui.java @@ -0,0 +1,134 @@ +/* + * 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.timers.gui; + +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JTextField; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.timers.ConstantTimer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * The GUI for ConstantTimer. + * + */ +public class ConstantTimerGui extends AbstractTimerGui { + private static final long serialVersionUID = 240L; + + /** + * The default value for the delay. + */ + private static final String DEFAULT_DELAY = "300"; + + private static final String DELAY_FIELD = "Delay Field"; + + private JTextField delayField; + + /** + * No-arg constructor. + */ + public ConstantTimerGui() { + init(); + } + + /** + * Handle an error. + * + * @param e + * the Exception that was thrown. + * @param thrower + * the JComponent that threw the Exception. + */ + public static void error(Exception e, JComponent thrower) { + JOptionPane.showMessageDialog(thrower, e, "Error", JOptionPane.ERROR_MESSAGE); + } + + @Override + public String getLabelResource() { + return "constant_timer_title"; // $NON-NLS-1$ + } + + /** + * Create the test element underlying this GUI component. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + ConstantTimer timer = new ConstantTimer(); + modifyTestElement(timer); + return timer; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement timer) { + this.configureTestElement(timer); + ((ConstantTimer) timer).setDelay(delayField.getText()); + } + + /** + * Configure this GUI component from the underlying TestElement. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + delayField.setText(((ConstantTimer) el).getDelay()); + } + + /** + * Initialize this component. + */ + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + + setBorder(makeBorder()); + add(makeTitlePanel()); + + Box delayPanel = Box.createHorizontalBox(); + JLabel delayLabel = new JLabel(JMeterUtils.getResString("constant_timer_delay"));//$NON-NLS-1$ + delayPanel.add(delayLabel); + + delayField = new JTextField(6); + delayField.setText(DEFAULT_DELAY); + delayField.setName(DELAY_FIELD); + delayPanel.add(delayField); + add(delayPanel); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + delayField.setText(DEFAULT_DELAY); + super.clearGui(); + } +} diff --git a/src/components/org/apache/jmeter/timers/gui/GaussianRandomTimerGui.java b/src/components/org/apache/jmeter/timers/gui/GaussianRandomTimerGui.java new file mode 100644 index 00000000000..d1aedbfec24 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/gui/GaussianRandomTimerGui.java @@ -0,0 +1,91 @@ +/* + * 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.timers.gui; + +import org.apache.jmeter.timers.GaussianRandomTimer; +import org.apache.jmeter.timers.RandomTimer; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implementation of a gaussian random timer. + */ +public class GaussianRandomTimerGui extends AbstractRandomTimerGui { + + private static final long serialVersionUID = 240L; + + private static final String DEFAULT_DELAY = "300"; // $NON-NLS-1$ + + private static final String DEFAULT_RANGE = "100.0"; // $NON-NLS-1$ + + + /** + * No-arg constructor. + */ + public GaussianRandomTimerGui() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getLabelResource() { + return "gaussian_timer_title";//$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + protected RandomTimer createRandomTimer() { + return new GaussianRandomTimer(); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getTimerDelayLabelKey() { + return JMeterUtils.getResString("gaussian_timer_delay"); //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + protected String getTimerRangeLabelKey() { + return JMeterUtils.getResString("gaussian_timer_range"); //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + protected String getDefaultDelay() { + return DEFAULT_DELAY; + } + + /** + * {@inheritDoc} + */ + @Override + protected String getDefaultRange() { + return DEFAULT_RANGE; + } +} diff --git a/src/components/org/apache/jmeter/timers/gui/PoissonRandomTimerGui.java b/src/components/org/apache/jmeter/timers/gui/PoissonRandomTimerGui.java new file mode 100644 index 00000000000..e83f226c9ec --- /dev/null +++ b/src/components/org/apache/jmeter/timers/gui/PoissonRandomTimerGui.java @@ -0,0 +1,69 @@ +/* + * 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.timers.gui; + +import org.apache.jmeter.timers.PoissonRandomTimer; +import org.apache.jmeter.timers.RandomTimer; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implementation of a Poisson random timer. + */ +public class PoissonRandomTimerGui extends AbstractRandomTimerGui { + + private static final long serialVersionUID = -3218002787832805275L; + + private static final String DEFAULT_DELAY = "300"; // $NON-NLS-1$ + + private static final String DEFAULT_RANGE = "100"; // $NON-NLS-1$ + + public PoissonRandomTimerGui() { + super(); + } + + @Override + public String getLabelResource() { + return "poisson_timer_title";//$NON-NLS-1$ + } + + @Override + protected RandomTimer createRandomTimer() { + return new PoissonRandomTimer(); + } + + @Override + protected String getTimerDelayLabelKey() { + return JMeterUtils.getResString("poisson_timer_delay"); //$NON-NLS-1$ + } + + @Override + protected String getTimerRangeLabelKey() { + return JMeterUtils.getResString("poisson_timer_range"); //$NON-NLS-1$ + } + + @Override + protected String getDefaultDelay() { + return DEFAULT_DELAY; + } + + @Override + protected String getDefaultRange() { + return DEFAULT_RANGE; + } +} diff --git a/src/components/org/apache/jmeter/timers/gui/UniformRandomTimerGui.java b/src/components/org/apache/jmeter/timers/gui/UniformRandomTimerGui.java new file mode 100644 index 00000000000..736aabadb07 --- /dev/null +++ b/src/components/org/apache/jmeter/timers/gui/UniformRandomTimerGui.java @@ -0,0 +1,92 @@ +/* + * 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.timers.gui; + +import org.apache.jmeter.timers.RandomTimer; +import org.apache.jmeter.timers.UniformRandomTimer; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implementation of a uniform random timer. + * + */ +public class UniformRandomTimerGui extends AbstractRandomTimerGui { + + private static final long serialVersionUID = 240L; + + private static final String DEFAULT_DELAY = "0"; // $NON-NLS-1$ + + private static final String DEFAULT_RANGE = "100.0";// $NON-NLS-1$ + + /** + * No-arg constructor. + */ + public UniformRandomTimerGui() { + super(); + } + + + /** + * {@inheritDoc} + */ + @Override + public String getLabelResource() { + return "uniform_timer_title";//$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + protected RandomTimer createRandomTimer() { + return new UniformRandomTimer(); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getTimerDelayLabelKey() { + return JMeterUtils.getResString("uniform_timer_delay"); //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + protected String getTimerRangeLabelKey() { + return JMeterUtils.getResString("uniform_timer_range"); //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + protected String getDefaultDelay() { + return DEFAULT_DELAY; + } + + /** + * {@inheritDoc} + */ + @Override + protected String getDefaultRange() { + return DEFAULT_RANGE; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/AccumListener.java b/src/components/org/apache/jmeter/visualizers/AccumListener.java new file mode 100644 index 00000000000..56749d6caf4 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/AccumListener.java @@ -0,0 +1,24 @@ +/* + * 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.visualizers; + +public interface AccumListener { + + void updateGui(RunningSample s); +} diff --git a/src/components/org/apache/jmeter/visualizers/AssertionVisualizer.java b/src/components/org/apache/jmeter/visualizers/AssertionVisualizer.java new file mode 100644 index 00000000000..f19c31761f7 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/AssertionVisualizer.java @@ -0,0 +1,122 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; + +import javax.swing.Box; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.ScrollPaneConstants; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +public class AssertionVisualizer extends AbstractVisualizer implements Clearable { + + private static final long serialVersionUID = 240L; + + private JTextArea textArea; + + public AssertionVisualizer() { + init(); + setName(getStaticLabel()); + } + + @Override + public String getLabelResource() { + return "assertion_visualizer_title"; // $NON-NLS-1$ + } + + @Override + public void add(SampleResult sample) { + final StringBuilder sb = new StringBuilder(100); + sb.append(sample.getSampleLabel()); + sb.append(getAssertionResult(sample)); + sb.append("\n"); // $NON-NLS-1$ + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + synchronized (textArea) { + textArea.append(sb.toString()); + textArea.setCaretPosition(textArea.getText().length()); + } + } + }); + } + + @Override + public void clearData() { + textArea.setText(""); // $NON-NLS-1$ + } + + private String getAssertionResult(SampleResult res) { + if (res != null) { + StringBuilder display = new StringBuilder(); + AssertionResult assertionResults[] = res.getAssertionResults(); + for (AssertionResult item : assertionResults) { + if (item.isFailure() || item.isError()) { + display.append("\n\t"); // $NON-NLS-1$ + display.append(item.getName() != null ? item.getName() + " : " : "");// $NON-NLS-1$ + display.append(item.getFailureMessage()); + } + } + return display.toString(); + } + return ""; + } + + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + Border margin = new EmptyBorder(10, 10, 5, 10); + + this.setBorder(margin); + + // NAME + this.add(makeTitlePanel(), BorderLayout.NORTH); + + // TEXTAREA LABEL + JLabel textAreaLabel = + new JLabel(JMeterUtils.getResString("assertion_textarea_label")); // $NON-NLS-1$ + Box mainPanel = Box.createVerticalBox(); + mainPanel.add(textAreaLabel); + + // TEXTAREA + textArea = new JTextArea(); + textArea.setEditable(false); + textArea.setLineWrap(false); + JScrollPane areaScrollPane = new JScrollPane(textArea); + + areaScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + areaScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + + areaScrollPane.setPreferredSize(new Dimension(mainPanel.getWidth(),mainPanel.getHeight())); + mainPanel.add(areaScrollPane); + this.add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/src/components/org/apache/jmeter/visualizers/AxisGraph.java b/src/components/org/apache/jmeter/visualizers/AxisGraph.java new file mode 100644 index 00000000000..e20f5d67adc --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/AxisGraph.java @@ -0,0 +1,436 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.LayoutManager; +import java.awt.Paint; +import java.math.BigDecimal; + +import javax.swing.JPanel; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.jCharts.axisChart.AxisChart; +import org.jCharts.axisChart.customRenderers.axisValue.renderers.ValueLabelPosition; +import org.jCharts.axisChart.customRenderers.axisValue.renderers.ValueLabelRenderer; +import org.jCharts.chartData.AxisChartDataSet; +import org.jCharts.chartData.ChartDataException; +import org.jCharts.chartData.DataSeries; +import org.jCharts.properties.AxisProperties; +import org.jCharts.properties.ChartProperties; +import org.jCharts.properties.ClusteredBarChartProperties; +import org.jCharts.properties.DataAxisProperties; +import org.jCharts.properties.LabelAxisProperties; +import org.jCharts.properties.LegendAreaProperties; +import org.jCharts.properties.LegendProperties; +import org.jCharts.properties.PropertyException; +import org.jCharts.properties.util.ChartFont; +import org.jCharts.types.ChartType; + +/** + * + * Axis graph is used by StatGraphVisualizer, which generates bar graphs + * from the statistical data. + */ +public class AxisGraph extends JPanel { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ELLIPSIS = "..."; //$NON-NLS-1$ + private static final int ELLIPSIS_LEN = ELLIPSIS.length(); + + protected double[][] data = null; + protected String title; + protected String xAxisTitle; + protected String yAxisTitle; + protected String yAxisLabel; + protected int maxLength; + protected String[] xAxisLabels; + protected int width, height; + + protected String[] legendLabels = { JMeterUtils.getResString("aggregate_graph_legend") }; // $NON-NLS-1$ + + protected int maxYAxisScale; + + protected Font titleFont; + + protected Font legendFont; + + protected Font valueFont = new Font("SansSerif", Font.PLAIN, 8); + + protected Color[] color = { Color.YELLOW }; + + protected Color foreColor = Color.BLACK; + + protected boolean outlinesBarFlag = false; + + protected boolean showGrouping = true; + + protected boolean valueOrientation = true; + + protected int legendPlacement = LegendAreaProperties.BOTTOM; + + /** + * + */ + public AxisGraph() { + super(); + } + + /** + * @param layout The {@link LayoutManager} to use + */ + public AxisGraph(LayoutManager layout) { + super(layout); + } + + /** + * @param layout The {@link LayoutManager} to use + * @param isDoubleBuffered Flag whether double buffering should be used + */ + public AxisGraph(LayoutManager layout, boolean isDoubleBuffered) { + super(layout, isDoubleBuffered); + } + + /** + * Expects null array when no data not empty array + * @param data The data to be drawn + */ + public void setData(double[][] data) { + this.data = data; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + public void setXAxisTitle(String title) { + this.xAxisTitle = title; + } + + public void setYAxisTitle(String title) { + this.yAxisTitle = title; + } + + /** + * Expects null array when no labels not empty array + * @param labels The labels for the x axis + */ + public void setXAxisLabels(String[] labels) { + this.xAxisLabels = labels; + } + + public void setYAxisLabels(String label) { + this.yAxisLabel = label; + } + + public void setLegendLabels(String[] labels) { + this.legendLabels = labels; + } + + public void setWidth(int w) { + this.width = w; + } + + public void setHeight(int h) { + this.height = h; + } + + /** + * @return the maxYAxisScale + */ + public int getMaxYAxisScale() { + return maxYAxisScale; + } + + /** + * @param maxYAxisScale the maxYAxisScale to set + */ + public void setMaxYAxisScale(int maxYAxisScale) { + this.maxYAxisScale = maxYAxisScale; + } + + /** + * @return the color + */ + public Color[] getColor() { + return color; + } + + /** + * @param color the color to set + */ + public void setColor(Color[] color) { + this.color = color; + } + + /** + * @return the foreColor + */ + public Color getForeColor() { + return foreColor; + } + + /** + * @param foreColor the foreColor to set + */ + public void setForeColor(Color foreColor) { + this.foreColor = foreColor; + } + + /** + * @return the titleFont + */ + public Font getTitleFont() { + return titleFont; + } + + /** + * @param titleFont the titleFont to set + */ + public void setTitleFont(Font titleFont) { + this.titleFont = titleFont; + } + + /** + * @return the legendFont + */ + public Font getLegendFont() { + return legendFont; + } + + /** + * @param legendFont the legendFont to set + */ + public void setLegendFont(Font legendFont) { + this.legendFont = legendFont; + } + + /** + * @return the valueFont + */ + public Font getValueFont() { + return valueFont; + } + + /** + * @param valueFont the valueFont to set + */ + public void setValueFont(Font valueFont) { + this.valueFont = valueFont; + } + + /** + * @return the legendPlacement + */ + public int getLegendPlacement() { + return legendPlacement; + } + + /** + * @param legendPlacement the legendPlacement to set + */ + public void setLegendPlacement(int legendPlacement) { + this.legendPlacement = legendPlacement; + } + + /** + * @return the outlinesBarFlag + */ + public boolean isOutlinesBarFlag() { + return outlinesBarFlag; + } + + /** + * @param outlinesBarFlag the outlinesBarFlag to set + */ + public void setOutlinesBarFlag(boolean outlinesBarFlag) { + this.outlinesBarFlag = outlinesBarFlag; + } + + /** + * @return the valueOrientation + */ + public boolean isValueOrientation() { + return valueOrientation; + } + + /** + * @param valueOrientation the valueOrientation to set + */ + public void setValueOrientation(boolean valueOrientation) { + this.valueOrientation = valueOrientation; + } + + /** + * @return the showGrouping + */ + public boolean isShowGrouping() { + return showGrouping; + } + + /** + * @param showGrouping the showGrouping to set + */ + public void setShowGrouping(boolean showGrouping) { + this.showGrouping = showGrouping; + } + + @Override + public void paintComponent(Graphics graphics) { + if (data != null && this.title != null && this.xAxisLabels != null && + this.yAxisLabel != null && + this.yAxisTitle != null) { + drawSample(this.title, this.maxLength, this.xAxisLabels, + this.yAxisTitle, this.legendLabels, + this.data, this.width, this.height, this.color, + this.legendFont, graphics); + } + } + + private double findMax(double _data[][]) { + double max = 0; + max = _data[0][0]; + for (int i = 0; i < _data.length; i++) { + for (int j = 0; j < _data[i].length; j++) { + if (_data[i][j] > max) { + max = _data[i][j]; + } + } + } + return max; + } + + private String squeeze (String input, int _maxLength){ + if (input.length()>_maxLength){ + return input.substring(0,_maxLength-ELLIPSIS_LEN)+ELLIPSIS; + } + return input; + } + + private void drawSample(String _title, int _maxLength, String[] _xAxisLabels, + String _yAxisTitle, String[] _legendLabels, double[][] _data, + int _width, int _height, Color[] _color, + Font legendFont, Graphics g) { + double max = maxYAxisScale > 0 ? maxYAxisScale : findMax(_data); // define max scale y axis + try { + /** These controls are already done in StatGraphVisualizer + if (_width == 0) { + _width = 450; + } + if (_height == 0) { + _height = 250; + } + **/ + if (_maxLength < 3) { + _maxLength = 3; + } + // if the "Title of Graph" is empty, we can assume some default + if (_title.length() == 0 ) { + _title = JMeterUtils.getResString("aggregate_graph_title"); //$NON-NLS-1$ + } + // if the labels are too long, they'll be "squeezed" to make the chart viewable. + for (int i = 0; i < _xAxisLabels.length; i++) { + String label = _xAxisLabels[i]; + _xAxisLabels[i]=squeeze(label, _maxLength); + } + this.setPreferredSize(new Dimension(_width,_height)); + DataSeries dataSeries = new DataSeries( _xAxisLabels, null, _yAxisTitle, _title ); // replace _xAxisTitle to null (don't display x axis title) + + ClusteredBarChartProperties clusteredBarChartProperties= new ClusteredBarChartProperties(); + clusteredBarChartProperties.setShowOutlinesFlag(outlinesBarFlag); + ValueLabelRenderer valueLabelRenderer = new ValueLabelRenderer(false, false, showGrouping, 0); + valueLabelRenderer.setValueLabelPosition(ValueLabelPosition.AT_TOP); + + valueLabelRenderer.setValueChartFont(new ChartFont(valueFont, foreColor)); + valueLabelRenderer.useVerticalLabels(valueOrientation); + + clusteredBarChartProperties.addPostRenderEventListener(valueLabelRenderer); + + Paint[] paints = new Paint[_color.length]; + System.arraycopy(_color, 0, paints, 0, paints.length); + + AxisChartDataSet axisChartDataSet = + new AxisChartDataSet( + _data, _legendLabels, paints, ChartType.BAR_CLUSTERED, clusteredBarChartProperties ); + dataSeries.addIAxisPlotDataSet( axisChartDataSet ); + + ChartProperties chartProperties= new ChartProperties(); + LabelAxisProperties xaxis = new LabelAxisProperties(); + DataAxisProperties yaxis = new DataAxisProperties(); + yaxis.setUseCommas(showGrouping); + + if (legendFont != null) { + yaxis.setAxisTitleChartFont(new ChartFont(legendFont, new Color(20))); + yaxis.setScaleChartFont(new ChartFont(legendFont, new Color(20))); + xaxis.setAxisTitleChartFont(new ChartFont(legendFont, new Color(20))); + xaxis.setScaleChartFont(new ChartFont(legendFont, new Color(20))); + } + if (titleFont != null) { + chartProperties.setTitleFont(new ChartFont(titleFont, new Color(0))); + } + + // Y Axis + try { + BigDecimal round = new BigDecimal(max / 1000d); + round = round.setScale(0, BigDecimal.ROUND_UP); + double topValue = round.doubleValue() * 1000; + yaxis.setUserDefinedScale(0, 500); + yaxis.setNumItems((int) (topValue / 500)+1); + yaxis.setShowGridLines(1); + } catch (PropertyException e) { + log.warn("",e); + } + + AxisProperties axisProperties= new AxisProperties(xaxis, yaxis); + axisProperties.setXAxisLabelsAreVertical(true); + LegendProperties legendProperties= new LegendProperties(); + legendProperties.setBorderStroke(null); + legendProperties.setPlacement(legendPlacement); + legendProperties.setIconBorderPaint(Color.WHITE); + if (legendPlacement == LegendAreaProperties.RIGHT || legendPlacement == LegendAreaProperties.LEFT) { + legendProperties.setNumColumns(1); + } + if (legendFont != null) { + legendProperties.setFont(legendFont); //new Font("SansSerif", Font.PLAIN, 10) + } + AxisChart axisChart = new AxisChart( + dataSeries, chartProperties, axisProperties, + legendProperties, _width, _height ); + axisChart.setGraphics2D((Graphics2D) g); + axisChart.render(); + } catch (ChartDataException e) { + log.warn("",e); + } catch (PropertyException e) { + log.warn("",e); + } + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/BSFListener.java b/src/components/org/apache/jmeter/visualizers/BSFListener.java new file mode 100644 index 00000000000..93c58298e2b --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BSFListener.java @@ -0,0 +1,80 @@ +/* + * 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.visualizers; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFListener extends BSFTestElement + implements Cloneable, SampleListener, TestBean, Visualizer { +// N.B. Needs to implement Visualizer so that TestBeanGUI can find the correct GUI class + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 234L; + + @Override + public void sampleOccurred(SampleEvent event) { + BSFManager mgr =null; + try { + mgr = getManager(); + if (mgr == null) { + log.error("Problem creating BSF manager"); + return; + } + mgr.declareBean("sampleEvent", event, SampleEvent.class); + SampleResult result = event.getResult(); + mgr.declareBean("sampleResult", result, SampleResult.class); + processFileOrScript(mgr); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + } finally { + if (mgr != null) { + mgr.terminate(); + } + } + } + + @Override + public void sampleStarted(SampleEvent e) { + // NOOP + } + + @Override + public void sampleStopped(SampleEvent e) { + // NOOP + } + + @Override + public void add(SampleResult sample) { + // NOOP + } + + @Override + public boolean isStats() { + return false; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/BSFListenerBeanInfo.java b/src/components/org/apache/jmeter/visualizers/BSFListenerBeanInfo.java new file mode 100644 index 00000000000..7f2551e3f79 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BSFListenerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFListenerBeanInfo extends BSFBeanInfoSupport { + + public BSFListenerBeanInfo() { + super(BSFListener.class); + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/BSFListenerResources.properties b/src/components/org/apache/jmeter/visualizers/BSFListenerResources.properties new file mode 100644 index 00000000000..316756ea1c2 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BSFListenerResources.properties @@ -0,0 +1,28 @@ +# 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. + +displayName=BSF Listener +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampleResult (aka prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/visualizers/BSFListenerResources_fr.properties b/src/components/org/apache/jmeter/visualizers/BSFListenerResources_fr.properties new file mode 100644 index 00000000000..f6f24e3f093 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BSFListenerResources_fr.properties @@ -0,0 +1,29 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=R\u00E9cepteur BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampleResult (aka prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/visualizers/BSFListenerResources_pt_BR.properties b/src/components/org/apache/jmeter/visualizers/BSFListenerResources_pt_BR.properties new file mode 100644 index 00000000000..3da7d50ab67 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BSFListenerResources_pt_BR.properties @@ -0,0 +1,28 @@ +# 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. + +displayName=Ouvinte BSF +filename.displayName=Nome do arquivo +filename.shortDescription=Arquivo de script (substitui o script) +filenameGroup.displayName=Arquivo de script (substitui o script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao script (\=> String Parameters e String []args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros que ser\u00E3o passados ao arquivo ou script +script.displayName=Script +script.shortDescription=Script na linguagem BSF apropriada +scriptLanguage.displayName=Linguagem +scriptLanguage.shortDescription=Nome da linguagem BSF, ex\: beanshell, javascript, jexl +scripting.displayName=Scripts (vari\u00E1veis\: ctx vars props sampleResult (aka prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Linguagem de script (ex\: beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/visualizers/BarGraph.java b/src/components/org/apache/jmeter/visualizers/BarGraph.java new file mode 100644 index 00000000000..34a4650a474 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BarGraph.java @@ -0,0 +1,88 @@ +/* + * 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.visualizers; + +import java.awt.Color; + +import javax.swing.JCheckBox; + +public class BarGraph { + + private String label; + + private JCheckBox chkBox; + + private Color backColor; + + /** + * @param label The label of this component + * @param checked Flag whether the corresponding checkbox should be checked + * @param backColor The color of the background + */ + public BarGraph(String label, boolean checked, Color backColor) { + super(); + this.label = label; + this.chkBox = new JCheckBox(this.label, checked); + this.backColor = backColor; + } + + /** + * @return the label + */ + public String getLabel() { + return label; + } + + /** + * @param label the label to set + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * @return the chkBox + */ + public JCheckBox getChkBox() { + return chkBox; + } + + /** + * @param chkBox the chkBox to set + */ + public void setChkBox(JCheckBox chkBox) { + this.chkBox = chkBox; + } + + /** + * @return the backColor + */ + public Color getBackColor() { + return backColor; + } + + /** + * @param backColor the backColor to set + */ + public void setBackColor(Color backColor) { + this.backColor = backColor; + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/BeanShellListener.java b/src/components/org/apache/jmeter/visualizers/BeanShellListener.java new file mode 100644 index 00000000000..bb0c4424fc6 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BeanShellListener.java @@ -0,0 +1,87 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +public class BeanShellListener extends BeanShellTestElement + implements Cloneable, SampleListener, TestBean, Visualizer, UnsharedComponent { + // N.B. Needs to implement Visualizer so that TestBeanGUI can find the correct GUI class + // TODO - remove UnsharedComponent ? Probably does not make sense for a TestBean. + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + // can be specified in jmeter.properties + private static final String INIT_FILE = "beanshell.listener.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + @Override + public void sampleOccurred(SampleEvent se) { + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + log.error("BeanShell not found"); + return; + } + + SampleResult samp=se.getResult(); + try { + bshInterpreter.set("sampleEvent", se);//$NON-NLS-1$ + bshInterpreter.set("sampleResult", samp);//$NON-NLS-1$ + processFileOrScript(bshInterpreter); + } catch (JMeterException e) { + log.warn("Problem in BeanShell script "+e); + } + } + + @Override + public void sampleStarted(SampleEvent e) { + // NOOP + } + + @Override + public void sampleStopped(SampleEvent e) { + // NOOP + } + + @Override + public void add(SampleResult sample) { + // NOOP + } + + @Override + public boolean isStats() { // Needed by Visualizer interface + return false; + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/BeanShellListenerBeanInfo.java b/src/components/org/apache/jmeter/visualizers/BeanShellListenerBeanInfo.java new file mode 100644 index 00000000000..57fd07b55d4 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BeanShellListenerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.util.BeanShellBeanInfoSupport; + +public class BeanShellListenerBeanInfo extends BeanShellBeanInfoSupport { + + public BeanShellListenerBeanInfo() { + super(BeanShellListener.class); + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources.properties b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources.properties new file mode 100644 index 00000000000..9bb7d34998a --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=BeanShell Listener +filename.displayName=File Name +filename.shortDescription=BeanShell script file (overrides script) +filenameGroup.displayName=Script file (overrides script) +parameterGroup.displayName=Parameters to be passed to BeanShell (=> String Parameters and String []bsh.args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to BeanShell (file or script) +resetGroup.displayName=Reset bsh.Interpreter before each call +resetInterpreter.displayName=Reset Interpreter +script.displayName=Script +script.shortDescription=Beanshell script +scripting.displayName=Script (variables: ctx vars props sampleEvent sampleResult log prev) diff --git a/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_de.properties b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_de.properties new file mode 100644 index 00000000000..1d496a9905c --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_de.properties @@ -0,0 +1,24 @@ +# 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. + +displayName=BeanShell Listener (Lauscher) +filename.displayName=Dateiname +filename.shortDescription=BeanShell Script Datei (Vorrang vor Script) +filenameGroup.displayName=Script Datei (Vorrang vor Script) +parameterGroup.displayName=Parameter die der BeanShell \u00FCbergeben werden sollen (String Parameters, String []bsh.args) +parameters.displayName=Parameter +parameters.shortDescription=Parameter die der BeanShell \u00FCbergeben werden sollen (Datei oder Script) +script.shortDescription=BeanShell Script +scripting.displayName=Script (Variablen\: ctx vars props sampleEvent sampleResult log prev) diff --git a/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_fr.properties b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_fr.properties new file mode 100644 index 00000000000..66fa6cc4385 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_fr.properties @@ -0,0 +1,28 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=R\u00E9cepteur BeanShell +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BeanShell (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au BeanShell (\=> String Parameters and String []bsh.args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BeanShell (fichier ou script) +resetGroup.displayName=R\u00E9initialiser l'interpr\u00E9teur BSH avant chaque appel +resetInterpreter.displayName=R\u00E9initialiser l'interpr\u00E9teur +script.displayName=Script +script.shortDescription=Script BeanShell +scripting.displayName=Script (variables \: ctx vars props sampleEvent sampleResult log prev) diff --git a/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_pt_BR.properties b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_pt_BR.properties new file mode 100644 index 00000000000..09b0f6944d3 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_pt_BR.properties @@ -0,0 +1,27 @@ +# 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. + +displayName=Ouvinte BeanShell +filename.displayName=Nome do arquivo +filename.shortDescription=Arquivo de script do BeanShell (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao BeanShell (arquivo ou script) +resetGroup.displayName=Reiniciar bsh.Interpreter antes de cada chamada +resetInterpreter.displayName=Reiniciar Interpretador +script.displayName= +script.shortDescription=Script BeanShell +scripting.displayName=Script (vari\u00E1veis\: ctx vars props sampleEvent sampleResult log prev) diff --git a/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_tr.properties b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_tr.properties new file mode 100644 index 00000000000..d67b3b4ff95 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_tr.properties @@ -0,0 +1,25 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=BeanShell Alg\u0131lay\u0131c\u0131 +filename.displayName=Dosya Ad\u0131 +filename.shortDescription=BeanShell betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +filenameGroup.displayName=Betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +parameterGroup.displayName=BeanShell'e ge\u00E7ilecek parametreler (\=> Dizgi (String) Parametreler ve String []bsh.args) +parameters.displayName=Parametreler +parameters.shortDescription=BeanShell'e ge\u00E7ilecek parametreler (dosya ya da betik) +script.shortDescription=Beanshell beti\u011Fi +scripting.displayName=Betik (de\u011Fi\u015Fkenler\: ctx vars props sampleEvent sampleResult log prev) diff --git a/src/components/org/apache/jmeter/visualizers/ComparisonVisualizer.java b/src/components/org/apache/jmeter/visualizers/ComparisonVisualizer.java new file mode 100644 index 00000000000..ddcc5963f00 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/ComparisonVisualizer.java @@ -0,0 +1,175 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridLayout; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextPane; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.assertions.CompareAssertionResult; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +public class ComparisonVisualizer extends AbstractVisualizer implements Clearable { + private static final long serialVersionUID = 240L; + + private JTree resultsTree; + + private DefaultTreeModel treeModel; + + private DefaultMutableTreeNode root; + + private JTextPane base, secondary; + + public ComparisonVisualizer() { + super(); + init(); + } + + @Override + public void add(final SampleResult sample) { + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + DefaultMutableTreeNode currNode = new DefaultMutableTreeNode(sample); + treeModel.insertNodeInto(currNode, root, root.getChildCount()); + if (root.getChildCount() == 1) { + resultsTree.expandPath(new TreePath(root)); + } + } + }); + } + + @Override + public String getLabelResource() { + return "comparison_visualizer_title"; //$NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + split.add(getTreePanel()); + split.add(getSideBySidePanel()); + add(split, BorderLayout.CENTER); + } + + private JComponent getSideBySidePanel() { + JPanel main = new JPanel(new GridLayout(1, 2)); + JScrollPane base = new JScrollPane(getBaseTextPane()); + base.setPreferredSize(base.getMinimumSize()); + JScrollPane secondary = new JScrollPane(getSecondaryTextPane()); + secondary.setPreferredSize(secondary.getMinimumSize()); + main.add(base); + main.add(secondary); + main.setPreferredSize(main.getMinimumSize()); + return main; + } + + private JTextPane getBaseTextPane() { + base = new JTextPane(); + base.setEditable(false); + base.setBackground(getBackground()); + return base; + } + + private JTextPane getSecondaryTextPane() { + secondary = new JTextPane(); + secondary.setEditable(false); + return secondary; + } + + private JComponent getTreePanel() { + root = new DefaultMutableTreeNode("Root"); //$NON-NLS-1$ + treeModel = new DefaultTreeModel(root); + resultsTree = new JTree(treeModel); + resultsTree.setCellRenderer(new TreeNodeRenderer()); + resultsTree.setCellRenderer(new TreeNodeRenderer()); + resultsTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + resultsTree.addTreeSelectionListener(new Selector()); + resultsTree.setRootVisible(false); + resultsTree.setShowsRootHandles(true); + + JScrollPane treePane = new JScrollPane(resultsTree); + treePane.setPreferredSize(new Dimension(150, 50)); + JPanel panel = new JPanel(new GridLayout(1, 1)); + panel.add(treePane); + return panel; + } + + private class Selector implements TreeSelectionListener { + /** + * {@inheritDoc} + */ + @Override + public void valueChanged(TreeSelectionEvent e) { + try { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) resultsTree.getLastSelectedPathComponent(); + SampleResult sr = (SampleResult) node.getUserObject(); + AssertionResult[] results = sr.getAssertionResults(); + CompareAssertionResult result = null; + for (AssertionResult r : results) { + if (r instanceof CompareAssertionResult) { + result = (CompareAssertionResult) r; + break; + } + } + if (result == null) { + result = new CompareAssertionResult(getName()); + } + base.setText(result.getBaseResult()); + secondary.setText(result.getSecondaryResult()); + } catch (Exception err) { + base.setText(JMeterUtils.getResString("comparison_invalid_node") + err); //$NON-NLS-1$ + secondary.setText(JMeterUtils.getResString("comparison_invalid_node") + err); //$NON-NLS-1$ + } + base.setCaretPosition(0); + secondary.setCaretPosition(0); + } + } + + @Override + public void clearData() { + while (root.getChildCount() > 0) { + // the child to be removed will always be 0 'cos as the nodes are + // removed the nth node will become (n-1)th + treeModel.removeNodeFromParent((DefaultMutableTreeNode) root.getChildAt(0)); + base.setText(""); //$NON-NLS-1$ + secondary.setText(""); //$NON-NLS-1$ + } + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/DistributionGraph.java b/src/components/org/apache/jmeter/visualizers/DistributionGraph.java new file mode 100644 index 00000000000..71eaa4f6c16 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/DistributionGraph.java @@ -0,0 +1,218 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JComponent; +import javax.swing.Scrollable; + +import org.apache.jmeter.samplers.Clearable; +// import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.math.NumberComparator; + +/** + * New graph for drawing distribution graph of the results. It is intended as a + * way to view the data after the stress has been performed. Although it can be + * used at runtime, it is not recommended, since it is rather intensive. The + * graph will draw a red line at 90% and an orange line at 50%. I like + * distribution graphs because they allow me to see how the data clumps. In + * general, the data will tend to clump in predictable ways when the application + * is well designed and implemented. Data that generates erratic graphs are + * generally not desirable. + * + */ +public class DistributionGraph extends JComponent implements Scrollable, Clearable { + + private static final long serialVersionUID = 240L; + + private SamplingStatCalculator model; + + private static final int xborder = 30; + + /** + * Constructor for the Graph object. + */ + public DistributionGraph() { + init(); + } + + /** + * Constructor for the Graph object. + * @param model The container for the aggregated sample data + */ + public DistributionGraph(SamplingStatCalculator model) { + this(); + setModel(model); + } + + private void init() {// called from ctor, so must not be overridable + repaint(); + } + + /** + * Gets the ScrollableTracksViewportWidth attribute of the Graph object. + * + * @return the ScrollableTracksViewportWidth value + */ + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + + /** + * Gets the ScrollableTracksViewportHeight attribute of the Graph object. + * + * @return the ScrollableTracksViewportHeight value + */ + @Override + public boolean getScrollableTracksViewportHeight() { + return true; + } + + /** + * Sets the Model attribute of the Graph object. + */ + private void setModel(Object model) { + this.model = (SamplingStatCalculator) model; + repaint(); + } + + /** + * Gets the PreferredScrollableViewportSize attribute of the Graph object. + * + * @return the PreferredScrollableViewportSize value + */ + @Override + public Dimension getPreferredScrollableViewportSize() { + return this.getPreferredSize(); + } + + /** + * Gets the ScrollableUnitIncrement attribute of the Graph object. + * + * @return the ScrollableUnitIncrement value + */ + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 5; + } + + /** + * Gets the ScrollableBlockIncrement attribute of the Graph object. + * + * @return the ScrollableBlockIncrement value + */ + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + return (int) (visibleRect.width * .9); + } + + /** + * Clears this graph. + */ + @Override + public void clearData() { + model.clear(); + } + + /** + * Method is responsible for calling drawSample and updating the graph. + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + final SamplingStatCalculator m = this.model; + synchronized (m) { + drawSample(m, g); + } + } + + private void drawSample(SamplingStatCalculator p_model, Graphics g) { + int width = getWidth(); + double height = getHeight() - 1.0; + + // first lets draw the grid + for (int y = 0; y < 4; y++) { + int q1 = (int) (height - (height * 0.25 * y)); + g.setColor(Color.lightGray); + g.drawLine(xborder, q1, width, q1); + g.setColor(Color.black); + g.drawString(String.valueOf((25 * y) + "%"), 0, q1); + } + g.setColor(Color.black); + // draw the X axis + g.drawLine(xborder, (int) height, width, (int) height); + // draw the Y axis + g.drawLine(xborder, 0, xborder, (int) height); + // the test plan has to have more than 200 samples + // for it to generate half way decent distribution + // graph. the larger the sample, the better the + // results. + if (p_model != null && p_model.getCount() > 50) { + // now draw the bar chart + Number ninety = p_model.getPercentPoint(0.90); + Number fifty = p_model.getPercentPoint(0.50); + + long total = p_model.getCount(); + Collection values = p_model.getDistribution().values(); + Number[][] objval = values.toArray(new Number[values.size()][]); + // we sort the objects + Arrays.sort(objval, new NumberComparator()); + int len = objval.length; + for (int count = 0; count < len; count++) { + // calculate the height + Number[] num = objval[count]; + double iper = (double) num[1].intValue() / (double) total; + double iheight = height * iper; + // if the height is less than one, we set it + // to one pixel + if (iheight < 1) { + iheight = 1.0; + } + int ix = (count * 4) + xborder + 5; + int dheight = (int) (height - iheight); + g.setColor(Color.blue); + g.drawLine(ix - 1, (int) height, ix - 1, dheight); + g.drawLine(ix, (int) height, ix, dheight); + g.setColor(Color.black); + // draw a red line for 90% point + if (num[0].longValue() == ninety.longValue()) { + g.setColor(Color.red); + g.drawLine(ix, (int) height, ix, 55); + g.drawLine(ix, 35, ix, 0); + g.drawString("90%", ix - 30, 20); + g.drawString(String.valueOf(num[0].longValue()), ix + 8, 20); + } + // draw an orange line for 50% point + if (num[0].longValue() == fifty.longValue()) { + g.setColor(Color.orange); + g.drawLine(ix, (int) height, ix, 30); + g.drawString("50%", ix - 30, 50); + g.drawString(String.valueOf(num[0].longValue()), ix + 8, 50); + } + } + } + } +} diff --git a/src/components/org/apache/jmeter/visualizers/DistributionGraphVisualizer.java b/src/components/org/apache/jmeter/visualizers/DistributionGraphVisualizer.java new file mode 100644 index 00000000000..c83c441d74e --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/DistributionGraphVisualizer.java @@ -0,0 +1,243 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Image; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +/** + * This class implements the visualizer for displaying the distribution graph. + * Distribution graphs are useful for standard benchmarks and viewing the + * distribution of data points. Results tend to clump together. + * + * Created May 25, 2004 + */ +public class DistributionGraphVisualizer extends AbstractVisualizer implements ImageVisualizer, GraphListener, + Clearable { + private static final long serialVersionUID = 240L; + + private final SamplingStatCalculator model; + + private JPanel graphPanel = null; + + private final DistributionGraph graph; + + private JTextField noteField; + + private static final int DELAY = 10; + + private int counter = 0; + + /** + * Constructor for the GraphVisualizer object. + */ + public DistributionGraphVisualizer() { + model = new SamplingStatCalculator("Distribution"); + graph = new DistributionGraph(model); + graph.setBackground(Color.white); + init(); + } + + /** + * Gets the Image attribute of the GraphVisualizer object. + * + * @return the Image value + */ + @Override + public Image getImage() { + Image result = graph.createImage(graph.getWidth(), graph.getHeight()); + + graph.paintComponent(result.getGraphics()); + + return result; + } + + @Override + public synchronized void updateGui() { + if (graph.getWidth() < 10) { + graph.setPreferredSize(new Dimension(getWidth() - 40, getHeight() - 160)); + } + graphPanel.updateUI(); + graph.repaint(); + } + + @Override + public synchronized void updateGui(Sample s) { + // We have received one more sample + if (DELAY == counter) { + updateGui(); + counter = 0; + } else { + counter++; + } + } + + @Override + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + // made currentSample volatile + model.addSample(res); + updateGui(model.getCurrentSample()); + } + }); + } + + @Override + public String getLabelResource() { + return "distribution_graph_title"; // $NON-NLS-1$ + } + + @Override + public synchronized void clearData() { + this.graph.clearData(); + model.clear(); + repaint(); + } + + @Override + public String toString() { + return "Show the samples in a distribution graph"; + } + + /** + * Initialize the GUI. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + Border margin = new EmptyBorder(10, 10, 5, 10); + + this.setBorder(margin); + + // Set up the graph with header, footer, Y axis and graph display + JPanel lgraphPanel = new JPanel(new BorderLayout()); + lgraphPanel.add(createGraphPanel(), BorderLayout.CENTER); + lgraphPanel.add(createGraphInfoPanel(), BorderLayout.SOUTH); + + // Add the main panel and the graph + this.add(makeTitlePanel(), BorderLayout.NORTH); + this.add(lgraphPanel, BorderLayout.CENTER); + } + + // Methods used in creating the GUI + + /** + * Creates a scroll pane containing the actual graph of the results. + * + * @return a scroll pane containing the graph + */ + private Component createGraphPanel() { + graphPanel = new JPanel(); + graphPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED, Color.lightGray, Color.darkGray)); + graphPanel.add(graph); + graphPanel.setBackground(Color.white); + return graphPanel; + } + + // /** + // * Creates one of the fields used to display the graph's current + // * values. + // * + // * @param color the color used to draw the value. By convention + // * this is the same color that is used to draw the + // * graph for this value and in the choose panel. + // * @param length the number of digits which the field should be + // * able to display + // * + // * @return a text field configured to display one of the + // * current graph values + // */ + // private JTextField createInfoField(Color color, int length) + // { + // JTextField field = new JTextField(length); + // field.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + // field.setEditable(false); + // field.setForeground(color); + // field.setBackground(getBackground()); + // + // // The text field should expand horizontally, but have + // // a fixed height + // field.setMaximumSize(new Dimension( + // field.getMaximumSize().width, + // field.getPreferredSize().height)); + // return field; + // } + + /** + * Creates a label for one of the fields used to display the graph's current + * values. Neither the label created by this method or the + * field passed as a parameter is added to the GUI here. + * + * @param labelResourceName + * the name of the label resource. This is used to look up the + * label text using {@link JMeterUtils#getResString(String)}. + * @param field + * the field this label is being created for. + */ + private JLabel createInfoLabel(String labelResourceName, JTextField field) { + JLabel label = new JLabel(JMeterUtils.getResString(labelResourceName)); + label.setForeground(field.getForeground()); + label.setLabelFor(field); + return label; + } + + /** + * Creates the information Panel at the bottom + * + * @return the box containing the panel + */ + private Box createGraphInfoPanel() { + Box graphInfoPanel = Box.createHorizontalBox(); + this.noteField = new JTextField(); + graphInfoPanel.add(this.createInfoLabel("distribution_note1", this.noteField)); // $NON-NLS-1$ + return graphInfoPanel; + } + + /** + * Method implements Printable, which is suppose to return the correct + * internal component. The Action class can then print or save the graphics + * to a file. + */ + @Override + public JComponent getPrintableComponent() { + return this.graphPanel; + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/Graph.java b/src/components/org/apache/jmeter/visualizers/Graph.java new file mode 100644 index 00000000000..9841ec807b2 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/Graph.java @@ -0,0 +1,263 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.util.Iterator; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.Scrollable; +import javax.swing.SwingUtilities; + +import org.apache.jmeter.gui.util.JMeterColor; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements a simple graph for displaying performance results. + * + */ +public class Graph extends JComponent implements Scrollable, Clearable { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private boolean wantData = true; + + private boolean wantAverage = true; + + private boolean wantDeviation = true; + + private boolean wantThroughput = true; + + private boolean wantMedian = true; + + private CachingStatCalculator model; + + private static final int width = 2000; + + private long graphMax = 1; + + private double throughputMax = 1; + + /** + * Constructor for the Graph object. + */ + public Graph() { + this.setPreferredSize(new Dimension(width, 100)); + } + + /** + * Constructor for the Graph object. + * @param model The container for samples and statistics + */ + public Graph(CachingStatCalculator model) { + this(); + this.model = model; + } + + /** + * Gets the PreferredScrollableViewportSize attribute of the Graph object. + * + * @return the PreferredScrollableViewportSize value + */ + @Override + public Dimension getPreferredScrollableViewportSize() { + return this.getPreferredSize(); + // return new Dimension(width, 400); + } + + /** + * Gets the ScrollableUnitIncrement attribute of the Graph object. + * + * @return the ScrollableUnitIncrement value + */ + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 5; + } + + /** + * Gets the ScrollableBlockIncrement attribute of the Graph object. + * + * @return the ScrollableBlockIncrement value + */ + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + return (int) (visibleRect.width * .9); + } + + /** + * Gets the ScrollableTracksViewportWidth attribute of the Graph object. + * + * @return the ScrollableTracksViewportWidth value + */ + @Override + public boolean getScrollableTracksViewportWidth() { + return false; + } + + /** + * Gets the ScrollableTracksViewportHeight attribute of the Graph object. + * + * @return the ScrollableTracksViewportHeight value + */ + @Override + public boolean getScrollableTracksViewportHeight() { + return true; + } + + /** + * Clears this graph. + */ + @Override + public void clearData() { + graphMax = 1; + throughputMax = 1; + } + + public void enableData(boolean value) { + this.wantData = value; + } + + public void enableAverage(boolean value) { + this.wantAverage = value; + } + + public void enableMedian(boolean value) { + this.wantMedian = value; + } + + public void enableDeviation(boolean value) { + this.wantDeviation = value; + } + + public void enableThroughput(boolean value) { + this.wantThroughput = value; + } + + public void updateGui(final Sample oneSample) { + long h = model.getPercentPoint((float) 0.90).longValue(); + boolean repaint = false; + if ((oneSample.getCount() % 20 == 0 || oneSample.getCount() < 20) && h > (graphMax * 1.2) || graphMax > (h * 1.2)) { + if (h >= 1) { + graphMax = h; + } else { + graphMax = 1; + } + repaint = true; + } + if (model.getMaxThroughput() > throughputMax) { + throughputMax = model.getMaxThroughput() * 1.3; + repaint = true; + } + if (repaint) { + repaint(); + return; + } + final long xPos = model.getCount(); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + Graphics g = getGraphics(); + + if (g != null) { + drawSample(xPos, oneSample, g); + } + } + }); + } + + /** {@inheritDoc}} */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + + List samples = model.getSamples(); + synchronized (samples ) { + Iterator e = samples.iterator(); + + for (int i = 0; e.hasNext(); i++) { + Sample s = e.next(); + + drawSample(i, s, g); + } + } + } + + private void drawSample(long x, Sample oneSample, Graphics g) { + // int width = getWidth(); + int height = getHeight(); + log.debug("Drawing a sample at " + x); + int adjustedWidth = (int)(x % width); // will always be within range of an int: as must be < width + if (wantData) { + int data = (int) (oneSample.getData() * height / graphMax); + + if (oneSample.isSuccess()) { + g.setColor(Color.black); + } else { + g.setColor(Color.YELLOW); + } + g.drawLine(adjustedWidth, height - data, adjustedWidth, height - data - 1); + if (log.isDebugEnabled()) { + log.debug("Drawing coords = " + adjustedWidth + "," + (height - data)); + } + } + + if (wantAverage) { + int average = (int) (oneSample.getAverage() * height / graphMax); + + g.setColor(Color.blue); + g.drawLine(adjustedWidth, height - average, adjustedWidth, (height - average - 1)); + } + + if (wantMedian) { + int median = (int) (oneSample.getMedian() * height / graphMax); + + g.setColor(JMeterColor.purple); + g.drawLine(adjustedWidth, height - median, adjustedWidth, (height - median - 1)); + } + + if (wantDeviation) { + int deviation = (int) (oneSample.getDeviation() * height / graphMax); + + g.setColor(Color.red); + g.drawLine(adjustedWidth, height - deviation, adjustedWidth, (height - deviation - 1)); + } + if (wantThroughput) { + int throughput = (int) (oneSample.getThroughput() * height / throughputMax); + + g.setColor(JMeterColor.dark_green); + g.drawLine(adjustedWidth, height - throughput, adjustedWidth, (height - throughput - 1)); + } + } + + /** + * @return Returns the graphMax. + */ + public long getGraphMax() { + return graphMax; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/GraphListener.java b/src/components/org/apache/jmeter/visualizers/GraphListener.java new file mode 100644 index 00000000000..bc65db83902 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/GraphListener.java @@ -0,0 +1,25 @@ +/* + * 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.visualizers; + +public interface GraphListener { + void updateGui(Sample s); + + void updateGui(); +} diff --git a/src/components/org/apache/jmeter/visualizers/GraphVisualizer.java b/src/components/org/apache/jmeter/visualizers/GraphVisualizer.java new file mode 100644 index 00000000000..e743f445aec --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/GraphVisualizer.java @@ -0,0 +1,464 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Image; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.text.NumberFormat; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.gui.util.JMeterColor; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +/** + * This class implements a statistical analyser that calculates both the average + * and the standard deviation of the sampling process and outputs them as + * autoscaling plots. + * + * Created February 8, 2001 + * + */ +public class GraphVisualizer extends AbstractVisualizer implements ImageVisualizer, ItemListener, Clearable { + + private static final long serialVersionUID = 240L; + + private static final String ZERO = "0"; //$NON-NLS-1$ + + private final NumberFormat nf = NumberFormat.getInstance(); // OK, because used in synchronised method + + private final CachingStatCalculator model; + + private JTextField maxYField = null; + + private JTextField minYField = null; + + private JTextField noSamplesField = null; + + private final String minute = JMeterUtils.getResString("minute"); // $NON-NLS-1$ + + private final Graph graph; + + private JCheckBox data; + + private JCheckBox average; + + private JCheckBox deviation; + + private JCheckBox throughput; + + private JCheckBox median; + + private JTextField dataField; + + private JTextField averageField; + + private JTextField deviationField; + + private JTextField throughputField; + + private JTextField medianField; + + /** + * Constructor for the GraphVisualizer object. + */ + public GraphVisualizer() { + model = new CachingStatCalculator("Graph"); + graph = new Graph(model); + init(); + } + + /** + * Gets the Image attribute of the GraphVisualizer object. + * + * @return the Image value + */ + @Override + public Image getImage() { + Image result = graph.createImage(graph.getWidth(), graph.getHeight()); + + graph.paintComponent(result.getGraphics()); + + return result; + } + + public synchronized void updateGui(Sample s) { + // We have received one more sample + graph.updateGui(s); + noSamplesField.setText(Long.toString(s.getCount())); + dataField.setText(Long.toString(s.getData())); + averageField.setText(Long.toString(s.getAverage())); + deviationField.setText(Long.toString(s.getDeviation())); + throughputField.setText(nf.format(60 * s.getThroughput()) + "/" + minute); // $NON-NLS-1$ + medianField.setText(Long.toString(s.getMedian())); + updateYAxis(); + } + + @Override + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + updateGui(model.addSample(res)); + } + }); + } + + @Override + public String getLabelResource() { + return "graph_results_title"; // $NON-NLS-1$ + } + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getItem() == data) { + this.graph.enableData(e.getStateChange() == ItemEvent.SELECTED); + } else if (e.getItem() == average) { + this.graph.enableAverage(e.getStateChange() == ItemEvent.SELECTED); + } else if (e.getItem() == deviation) { + this.graph.enableDeviation(e.getStateChange() == ItemEvent.SELECTED); + } else if (e.getItem() == throughput) { + this.graph.enableThroughput(e.getStateChange() == ItemEvent.SELECTED); + } else if (e.getItem() == median) { + this.graph.enableMedian(e.getStateChange() == ItemEvent.SELECTED); + } + this.graph.repaint(); + } + + @Override + public void clearData() { + graph.clearData(); + model.clear(); + dataField.setText(ZERO); + averageField.setText(ZERO); + deviationField.setText(ZERO); + throughputField.setText("0/" + minute); //$NON-NLS-1$ + medianField.setText(ZERO); + noSamplesField.setText(ZERO); + updateYAxis(); + repaint(); + } + + @Override + public String toString() { + return "Show the samples analysis as dot plots"; + } + + /** + * Update the max and min value of the Y axis. + */ + private void updateYAxis() { + maxYField.setText(Long.toString(graph.getGraphMax())); + minYField.setText(ZERO); + } + + /** + * Initialize the GUI. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + Border margin = new EmptyBorder(10, 10, 5, 10); + + this.setBorder(margin); + + // Set up the graph with header, footer, Y axis and graph display + JPanel graphPanel = new JPanel(new BorderLayout()); + graphPanel.add(createYAxis(), BorderLayout.WEST); + graphPanel.add(createChoosePanel(), BorderLayout.NORTH); + graphPanel.add(createGraphPanel(), BorderLayout.CENTER); + graphPanel.add(createGraphInfoPanel(), BorderLayout.SOUTH); + + // Add the main panel and the graph + this.add(makeTitlePanel(), BorderLayout.NORTH); + this.add(graphPanel, BorderLayout.CENTER); + } + + // Methods used in creating the GUI + + /** + * Creates the panel containing the graph's Y axis labels. + * + * @return the Y axis panel + */ + private JPanel createYAxis() { + JPanel graphYAxisPanel = new JPanel(); + + graphYAxisPanel.setLayout(new BorderLayout()); + + maxYField = createYAxisField(5); + minYField = createYAxisField(3); + + graphYAxisPanel.add(createYAxisPanel("graph_results_ms", maxYField), BorderLayout.NORTH); // $NON-NLS-1$ + graphYAxisPanel.add(createYAxisPanel("graph_results_ms", minYField), BorderLayout.SOUTH); // $NON-NLS-1$ + + return graphYAxisPanel; + } + + /** + * Creates a text field to be used for the value of a Y axis label. These + * fields hold the minimum and maximum values for the graph. The units are + * kept in a separate label outside of this field. + * + * @param length + * the number of characters which the field will use to calculate + * its preferred width. This should be set to the maximum number + * of digits that are expected to be necessary to hold the label + * value. + * + * @see #createYAxisPanel(String, JTextField) + * + * @return a text field configured to be used in the Y axis + */ + private JTextField createYAxisField(int length) { + JTextField field = new JTextField(length); + field.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); + field.setEditable(false); + field.setForeground(Color.black); + field.setBackground(getBackground()); + field.setHorizontalAlignment(SwingConstants.RIGHT); + return field; + } + + /** + * Creates a panel for an entire Y axis label. This includes the dynamic + * value as well as the unit label. + * + * @param labelResourceName + * the name of the label resource. This is used to look up the + * label text using {@link JMeterUtils#getResString(String)}. + * + * @return a panel containing both the dynamic and static parts of a Y axis + * label + */ + private JPanel createYAxisPanel(String labelResourceName, JTextField field) { + JPanel panel = new JPanel(new FlowLayout()); + JLabel label = new JLabel(JMeterUtils.getResString(labelResourceName)); + + panel.add(field); + panel.add(label); + return panel; + } + + /** + * Creates a panel which allows the user to choose which graphs to display. + * This panel consists of a check box for each type of graph (current + * sample, average, deviation, and throughput). + * + * @return a panel allowing the user to choose which graphs to display + */ + private JPanel createChoosePanel() { + JPanel chooseGraphsPanel = new JPanel(); + + chooseGraphsPanel.setLayout(new FlowLayout()); + JLabel selectGraphsLabel = new JLabel(JMeterUtils.getResString("graph_choose_graphs")); //$NON-NLS-1$ + data = createChooseCheckBox("graph_results_data", Color.black); // $NON-NLS-1$ + average = createChooseCheckBox("graph_results_average", Color.blue); // $NON-NLS-1$ + deviation = createChooseCheckBox("graph_results_deviation", Color.red); // $NON-NLS-1$ + throughput = createChooseCheckBox("graph_results_throughput", JMeterColor.dark_green); // $NON-NLS-1$ + median = createChooseCheckBox("graph_results_median", JMeterColor.purple); // $NON-NLS-1$ + + chooseGraphsPanel.add(selectGraphsLabel); + chooseGraphsPanel.add(data); + chooseGraphsPanel.add(average); + chooseGraphsPanel.add(median); + chooseGraphsPanel.add(deviation); + chooseGraphsPanel.add(throughput); + return chooseGraphsPanel; + } + + /** + * Creates a check box configured to be used to in the choose panel allowing + * the user to select whether or not a particular kind of graph data will be + * displayed. + * + * @param labelResourceName + * the name of the label resource. This is used to look up the + * label text using {@link JMeterUtils#getResString(String)}. + * @param color + * the color used for the checkbox text. By convention this is + * the same color that is used to draw the graph and for the + * corresponding info field. + * + * @return a checkbox allowing the user to select whether or not a kind of + * graph data will be displayed + */ + private JCheckBox createChooseCheckBox(String labelResourceName, Color color) { + JCheckBox checkBox = new JCheckBox(JMeterUtils.getResString(labelResourceName)); + checkBox.setSelected(true); + checkBox.addItemListener(this); + checkBox.setForeground(color); + return checkBox; + } + + /** + * Creates a scroll pane containing the actual graph of the results. + * + * @return a scroll pane containing the graph + */ + private Component createGraphPanel() { + JScrollPane graphScrollPanel = makeScrollPane(graph, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + graphScrollPanel.setViewportBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + graphScrollPanel.setPreferredSize(graphScrollPanel.getMinimumSize()); + + return graphScrollPanel; + } + + /** + * Creates a panel which numerically displays the current graph values. + * + * @return a panel showing the current graph values + */ + private Box createGraphInfoPanel() { + Box graphInfoPanel = Box.createHorizontalBox(); + + noSamplesField = createInfoField(Color.black, 6); + dataField = createInfoField(Color.black, 5); + averageField = createInfoField(Color.blue, 5); + deviationField = createInfoField(Color.red, 5); + throughputField = createInfoField(JMeterColor.dark_green, 15); + medianField = createInfoField(JMeterColor.purple, 5); + + graphInfoPanel.add(createInfoColumn(createInfoLabel("graph_results_no_samples", noSamplesField), // $NON-NLS-1$ + noSamplesField, createInfoLabel("graph_results_deviation", deviationField), deviationField)); // $NON-NLS-1$ + graphInfoPanel.add(Box.createHorizontalGlue()); + + graphInfoPanel.add(createInfoColumn(createInfoLabel("graph_results_latest_sample", dataField), dataField, // $NON-NLS-1$ + createInfoLabel("graph_results_throughput", throughputField), throughputField)); // $NON-NLS-1$ + graphInfoPanel.add(Box.createHorizontalGlue()); + + graphInfoPanel.add(createInfoColumn(createInfoLabel("graph_results_average", averageField), averageField, // $NON-NLS-1$ + createInfoLabel("graph_results_median", medianField), medianField)); // $NON-NLS-1$ + graphInfoPanel.add(Box.createHorizontalGlue()); + return graphInfoPanel; + } + + /** + * Creates one of the fields used to display the graph's current values. + * + * @param color + * the color used to draw the value. By convention this is the + * same color that is used to draw the graph for this value and + * in the choose panel. + * @param length + * the number of digits which the field should be able to display + * + * @return a text field configured to display one of the current graph + * values + */ + private JTextField createInfoField(Color color, int length) { + JTextField field = new JTextField(length); + field.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + field.setEditable(false); + field.setForeground(color); + field.setBackground(getBackground()); + + // The text field should expand horizontally, but have + // a fixed height + field.setMaximumSize(new Dimension(field.getMaximumSize().width, field.getPreferredSize().height)); + return field; + } + + /** + * Creates a label for one of the fields used to display the graph's current + * values. Neither the label created by this method or the + * field passed as a parameter is added to the GUI here. + * + * @param labelResourceName + * the name of the label resource. This is used to look up the + * label text using {@link JMeterUtils#getResString(String)}. + * @param field + * the field this label is being created for. + */ + private JLabel createInfoLabel(String labelResourceName, JTextField field) { + JLabel label = new JLabel(JMeterUtils.getResString(labelResourceName)); + label.setForeground(field.getForeground()); + label.setLabelFor(field); + return label; + } + + /** + * Creates a panel containing two pairs of labels and fields for displaying + * the current graph values. This method exists to help with laying out the + * fields in columns. If one or more components are null then these + * components will be represented by blank space. + * + * @param label1 + * the label for the first field. This label will be placed in + * the upper left section of the panel. If this parameter is + * null, this section of the panel will be left blank. + * @param field1 + * the field corresponding to the first label. This field will be + * placed in the upper right section of the panel. If this + * parameter is null, this section of the panel will be left + * blank. + * @param label2 + * the label for the second field. This label will be placed in + * the lower left section of the panel. If this parameter is + * null, this section of the panel will be left blank. + * @param field2 + * the field corresponding to the second label. This field will + * be placed in the lower right section of the panel. If this + * parameter is null, this section of the panel will be left + * blank. + */ + private Box createInfoColumn(JLabel label1, JTextField field1, JLabel label2, JTextField field2) { + // This column actually consists of a row with two sub-columns + // The first column contains the labels, and the second + // column contains the fields. + Box row = Box.createHorizontalBox(); + Box col = Box.createVerticalBox(); + col.add(label1 != null ? label1 : Box.createVerticalGlue()); + col.add(label2 != null ? label2 : Box.createVerticalGlue()); + row.add(col); + + row.add(Box.createHorizontalStrut(5)); + + col = Box.createVerticalBox(); + col.add(field1 != null ? field1 : Box.createVerticalGlue()); + col.add(field2 != null ? field2 : Box.createVerticalGlue()); + row.add(col); + + row.add(Box.createHorizontalStrut(5)); + + return row; + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/JSR223Listener.java b/src/components/org/apache/jmeter/visualizers/JSR223Listener.java new file mode 100644 index 00000000000..dce4473c7ac --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/JSR223Listener.java @@ -0,0 +1,77 @@ +/* + * 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.visualizers; + +import java.io.IOException; + +import javax.script.Bindings; +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223Listener extends JSR223TestElement + implements Cloneable, SampleListener, TestBean, Visualizer { +// N.B. Needs to implement Visualizer so that TestBeanGUI can find the correct GUI class + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 234L; + + @Override + public void sampleOccurred(SampleEvent event) { + try { + ScriptEngine scriptEngine = getScriptEngine(); + Bindings bindings = scriptEngine.createBindings(); + bindings.put("sampleEvent", event); + bindings.put("sampleResult", event.getResult()); + processFileOrScript(scriptEngine, bindings); + } catch (ScriptException e) { + log.error("Problem in JSR223 script "+getName(), e); + } catch (IOException e) { + log.error("Problem in JSR223 script "+getName(), e); + } + } + + @Override + public void sampleStarted(SampleEvent e) { + // NOOP + } + + @Override + public void sampleStopped(SampleEvent e) { + // NOOP + } + + @Override + public void add(SampleResult sample) { + // NOOP + } + + @Override + public boolean isStats() { + return false; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/JSR223ListenerBeanInfo.java b/src/components/org/apache/jmeter/visualizers/JSR223ListenerBeanInfo.java new file mode 100644 index 00000000000..0be405113dc --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/JSR223ListenerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223ListenerBeanInfo extends JSR223BeanInfoSupport { + + public JSR223ListenerBeanInfo() { + super(JSR223Listener.class); + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources.properties b/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources.properties new file mode 100644 index 00000000000..0a0645d4fb5 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources.properties @@ -0,0 +1,31 @@ +# 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. + +displayName=JSR223 Listener +cacheKey.displayName=Compilation cache key +cacheKey.shortDescription=If Cache key is not empty, script will be compiled if JSR223 underlying script language supports it and CompiledScript will be cached, ensure script does not use any variable before making it cacheable +cacheKey_group.displayName=Script compilation caching +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR 223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampleResult (aka prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR 223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources_fr.properties b/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources_fr.properties new file mode 100644 index 00000000000..18199c80eb5 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources_fr.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=R\u00E9cepteur JSR 223 +cacheKey.displayName=Clef de caching +cacheKey.shortDescription=Si la clef de caching n'est pas vide, le script sera compil\u00E9 si le language sous-jacent JSR223 fournit cette fonctionnalit\u00E9 et l'objet CompiledScript sera mis en cache, assurez vous avant d'utiliser ce caching que le script n'utilise pas de variables JMeter +cacheKey_group.displayName=Param\u00E8tres de caching du Script compil\u00E9 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampleResult (avant prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/src/components/org/apache/jmeter/visualizers/LineGraph.java b/src/components/org/apache/jmeter/visualizers/LineGraph.java new file mode 100644 index 00000000000..83ca6b40f17 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/LineGraph.java @@ -0,0 +1,267 @@ +/* + * 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.visualizers; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.LayoutManager; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; + +import javax.swing.JPanel; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.jCharts.axisChart.AxisChart; +import org.jCharts.chartData.AxisChartDataSet; +import org.jCharts.chartData.DataSeries; +import org.jCharts.properties.AxisProperties; +import org.jCharts.properties.ChartProperties; +import org.jCharts.properties.DataAxisProperties; +import org.jCharts.properties.LegendProperties; +import org.jCharts.properties.LineChartProperties; +import org.jCharts.properties.PointChartProperties; +import org.jCharts.types.ChartType; + +/** + * + * Axis graph is used by StatGraphVisualizer, which generates bar graphs + * from the statistical data. + */ +public class LineGraph extends JPanel { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected double[][] data = null; + protected String title, xAxisTitle, yAxisTitle; + protected String[] xAxisLabels, yAxisLabel; + protected int width, height; + + private static final Shape[] SHAPE_ARRAY = {PointChartProperties.SHAPE_CIRCLE, + PointChartProperties.SHAPE_DIAMOND,PointChartProperties.SHAPE_SQUARE, + PointChartProperties.SHAPE_TRIANGLE}; + + /** + * 12 basic colors for line graphs. If we need more colors than this, + * we can add more. Though more than 12 lines per graph will look + * rather busy and be hard to read. + */ + private static final Paint[] PAINT_ARRAY = {Color.black, + Color.blue,Color.green,Color.magenta,Color.orange, + Color.red,Color.yellow,Color.darkGray,Color.gray,Color.lightGray, + Color.pink,Color.cyan}; + protected int shape_counter = 0; + protected int paint_counter = -1; + + /** + * + */ + public LineGraph() { + super(); + } + + /** + * @param layout The {@link LayoutManager} to be used + */ + public LineGraph(LayoutManager layout) { + super(layout); + } + + /** + * @param layout The {@link LayoutManager} to be used + * @param isDoubleBuffered Flag whether double buffering should be used + */ + public LineGraph(LayoutManager layout, boolean isDoubleBuffered) { + super(layout, isDoubleBuffered); + } + + public void setData(double[][] data) { + this.data = data; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setXAxisTitle(String title) { + this.xAxisTitle = title; + } + + public void setYAxisTitle(String title) { + this.yAxisTitle = title; + } + + public void setXAxisLabels(String[] labels) { + this.xAxisLabels = labels; + } + + public void setYAxisLabels(String[] label) { + this.yAxisLabel = label; + } + + public void setWidth(int w) { + this.width = w; + } + + public void setHeight(int h) { + this.height = h; + } + + @Override + public void paintComponent(Graphics g) { + // reset the paint counter + this.paint_counter = -1; + if (data != null && this.title != null && this.xAxisLabels != null && + this.xAxisTitle != null && this.yAxisLabel != null && + this.yAxisTitle != null) { + drawSample(this.title,this.xAxisLabels,this.xAxisTitle, + this.yAxisTitle,this.data,this.width,this.height,g); + } + } + + private void drawSample(String _title, String[] _xAxisLabels, String _xAxisTitle, + String _yAxisTitle, double[][] _data, int _width, int _height, Graphics g) { + try { + if (_width == 0) { + _width = 450; + } + if (_height == 0) { + _height = 250; + } + this.setPreferredSize(new Dimension(_width,_height)); + DataSeries dataSeries = new DataSeries( _xAxisLabels, _xAxisTitle, _yAxisTitle, _title ); + String[] legendLabels= yAxisLabel; + Paint[] paints = this.createPaint(_data.length); + Shape[] shapes = createShapes(_data.length); + Stroke[] lstrokes = createStrokes(_data.length); + LineChartProperties lineChartProperties= new LineChartProperties(lstrokes,shapes); + AxisChartDataSet axisChartDataSet= new AxisChartDataSet( _data, + legendLabels, + paints, + ChartType.LINE, + lineChartProperties ); + dataSeries.addIAxisPlotDataSet( axisChartDataSet ); + + ChartProperties chartProperties = new ChartProperties(); + AxisProperties axisProperties = new AxisProperties(); + // show the grid lines, to turn it off, set it to zero + axisProperties.getYAxisProperties().setShowGridLines(1); + axisProperties.setXAxisLabelsAreVertical(true); + // set the Y Axis to round + DataAxisProperties daxp = (DataAxisProperties)axisProperties.getYAxisProperties(); + daxp.setRoundToNearest(1); + LegendProperties legendProperties = new LegendProperties(); + AxisChart axisChart = new AxisChart( + dataSeries, chartProperties, axisProperties, + legendProperties, _width, _height ); + axisChart.setGraphics2D((Graphics2D) g); + axisChart.render(); + } catch (Exception e) { + log.error(e.getMessage()); + } + } + + /** + * Since we only have 4 shapes, the method will start with the first shape + * and keep cycling through the shapes in order. + * + * @param count + * The number of shapes to be created + * @return the first n shapes + */ + public Shape[] createShapes(int count) { + Shape[] shapes = new Shape[count]; + for (int idx=0; idx < count; idx++) { + shapes[idx] = nextShape(); + } + return shapes; + } + + /** + * Return the next shape + * @return the next shape + */ + public Shape nextShape() { + this.shape_counter++; + if (shape_counter >= (SHAPE_ARRAY.length - 1)) { + shape_counter = 0; + } + return SHAPE_ARRAY[shape_counter]; + } + + /** + * Create a given number of {@link Stroke}s + * + * @param count + * The number of strokes to be created + * @return the first count strokes + */ + public Stroke[] createStrokes(int count) { + Stroke[] str = new Stroke[count]; + for (int idx=0; idx < count; idx++) { + str[idx] = nextStroke(); + } + return str; + } + + /** + * method always return a new BasicStroke with 1.0f weight + * @return a new BasicStroke with 1.0f weight + */ + public Stroke nextStroke() { + return new BasicStroke(1.0f); + } + + /** + * return an array of Paint with different colors. The current + * implementation will cycle through 12 colors if a line graph has more than + * 12 entries + * + * @param count + * The number of {@link Paint}s to be created + * @return an array of Paint with different colors + */ + public Paint[] createPaint(int count) { + Paint[] pts = new Paint[count]; + for (int idx=0; idx < count; idx++) { + pts[idx] = nextPaint(); + } + return pts; + } + + /** + * The method will return the next paint color in the PAINT_ARRAY. + * Rather than return a random color, we want it to always go through + * the same sequence. This way, the same charts will always use the + * same color and make it easier to compare side by side. + * @return the next paint color in the PAINT_ARRAY + */ + public Paint nextPaint() { + this.paint_counter++; + if (this.paint_counter == (PAINT_ARRAY.length - 1)) { + this.paint_counter = 0; + } + return PAINT_ARRAY[this.paint_counter]; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/MailerVisualizer.java b/src/components/org/apache/jmeter/visualizers/MailerVisualizer.java new file mode 100644 index 00000000000..71ff185b5e7 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/MailerVisualizer.java @@ -0,0 +1,449 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.mail.MessagingException; +import javax.mail.internet.AddressException; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.reporters.MailerModel; +import org.apache.jmeter.reporters.MailerResultCollector; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/* + * TODO : - Create a subpanel for other visualizers - connect to the properties. - + * Get the specific URL that is failing. - add a seperate interface to collect + * the thrown failure messages. - - suggestions ;-) + */ + +/** + * This class implements a visualizer that mails a message when an error occurs. + * + */ +public class MailerVisualizer extends AbstractVisualizer implements ActionListener, Clearable, ChangeListener { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private JButton testerButton; + + private JTextField addressField; + + private JTextField fromField; + + private JTextField smtpHostField; + + private JTextField smtpPortField; + + private JTextField failureSubjectField; + + private JTextField successSubjectField; + + private JTextField failureField; + + private JTextField failureLimitField; + + private JTextField successLimitField; + + private JTextField smtpLoginField; + + private JTextField smtpPasswordField; + + private JComboBox authTypeCombo; + + /** + * Constructs the MailerVisualizer and initializes its GUI. + */ + public MailerVisualizer() { + super(); + setModel(new MailerResultCollector()); + // initialize GUI. + initGui(); + } + + public JPanel getControlPanel() { + return this; + } + + /** + * Clears any stored sampling-informations. + */ + @Override + public synchronized void clearData() { + if (getModel() != null) { + MailerModel model = ((MailerResultCollector) getModel()).getMailerModel(); + model.clear(); + updateVisualizer(model); + } + } + + @Override + public void add(final SampleResult res) { + if (getModel() != null) { + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + MailerModel model = ((MailerResultCollector) getModel()).getMailerModel(); + // method called by add is synchronized + model.add(res);//this is a different model from the one used by the result collector + updateVisualizer(model); + } + }); + } + } + + @Override + public String toString() { + return JMeterUtils.getResString("mailer_string"); // $NON-NLS-1$ + } + + /** + * Initializes the GUI. Lays out components and adds them to the container. + */ + private void initGui() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new VerticalPanel(); + Border margin = new EmptyBorder(5, 10, 5, 10); + this.setBorder(margin); + + mainPanel.add(makeTitlePanel()); + + JPanel attributePane = new VerticalPanel(); + attributePane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("mailer_title_settings"))); // $NON-NLS-1$ + + // Settings panes + attributePane.add(createMailingSettings()); + attributePane.add(createSmtpSettings()); + + // Test mail button + JPanel testerPanel = new JPanel(new BorderLayout()); + testerButton = new JButton(JMeterUtils.getResString("mailer_test_mail")); // $NON-NLS-1$ + testerButton.addActionListener(this); + testerButton.setEnabled(true); + testerPanel.add(testerButton, BorderLayout.EAST); + attributePane.add(testerPanel); + mainPanel.add(attributePane); + mainPanel.add(Box.createRigidArea(new Dimension(0,5))); + + // Failures count + JPanel mailerPanel = new JPanel(new BorderLayout()); + mailerPanel.add(new JLabel(JMeterUtils.getResString("mailer_failures")), BorderLayout.WEST); // $NON-NLS-1$ + failureField = new JTextField(6); + failureField.setEditable(false); + mailerPanel.add(failureField, BorderLayout.CENTER); + mainPanel.add(mailerPanel); + + this.add(mainPanel, BorderLayout.CENTER); + } + + private JPanel createMailingSettings() { + JPanel settingsPane = new JPanel(new BorderLayout()); + settingsPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("mailer_title_message"))); // $NON-NLS-1$ + + JPanel headerPane = new JPanel(new BorderLayout()); + headerPane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel fromPane = new JPanel(new BorderLayout()); + fromPane.add(new JLabel(JMeterUtils.getResString("mailer_from")), BorderLayout.WEST); // $NON-NLS-1$ + fromField = new JTextField(25); + fromField.setEditable(true); + fromPane.add(fromField, BorderLayout.CENTER); + fromPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + headerPane.add(fromPane, BorderLayout.WEST); + JPanel addressPane = new JPanel(new BorderLayout()); + addressPane.add(new JLabel(JMeterUtils.getResString("mailer_addressees")), BorderLayout.WEST); // $NON-NLS-1$ + addressField = new JTextField(10); + addressField.setEditable(true); + addressPane.add(addressField, BorderLayout.CENTER); + headerPane.add(addressPane, BorderLayout.CENTER); + + JPanel successPane = new JPanel(new BorderLayout()); + successPane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel succesSubjectPane = new JPanel(new BorderLayout()); + succesSubjectPane.add(new JLabel(JMeterUtils.getResString("mailer_success_subject")), BorderLayout.WEST); // $NON-NLS-1$ + successSubjectField = new JTextField(10); + successSubjectField.setEditable(true); + succesSubjectPane.add(successSubjectField, BorderLayout.CENTER); + succesSubjectPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + successPane.add(succesSubjectPane, BorderLayout.CENTER); + JPanel successLimitPane = new JPanel(new BorderLayout()); + successLimitPane.add(new JLabel(JMeterUtils.getResString("mailer_success_limit")), BorderLayout.WEST); // $NON-NLS-1$ + successLimitField = new JTextField("2", 5); // $NON-NLS-1$ + successLimitField.setEditable(true); + successLimitPane.add(successLimitField, BorderLayout.CENTER); + successPane.add(successLimitPane, BorderLayout.EAST); + + JPanel failurePane = new JPanel(new BorderLayout()); + failurePane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel failureSubjectPane = new JPanel(new BorderLayout()); + failureSubjectPane.add(new JLabel(JMeterUtils.getResString("mailer_failure_subject")), BorderLayout.WEST); // $NON-NLS-1$ + failureSubjectField = new JTextField(10); + failureSubjectField.setEditable(true); + failureSubjectPane.add(failureSubjectField, BorderLayout.CENTER); + failureSubjectPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + failurePane.add(failureSubjectPane, BorderLayout.CENTER); + JPanel failureLimitPane = new JPanel(new BorderLayout()); + failureLimitPane.add(new JLabel(JMeterUtils.getResString("mailer_failure_limit")), BorderLayout.WEST); // $NON-NLS-1$ + failureLimitField = new JTextField("2", 5); // $NON-NLS-1$ + failureLimitField.setEditable(true); + failureLimitPane.add(failureLimitField, BorderLayout.CENTER); + failurePane.add(failureLimitPane, BorderLayout.EAST); + + settingsPane.add(headerPane, BorderLayout.NORTH); + settingsPane.add(successPane, BorderLayout.CENTER); + settingsPane.add(failurePane, BorderLayout.SOUTH); + + return settingsPane; + } + + private JPanel createSmtpSettings() { + JPanel settingsPane = new JPanel(new BorderLayout()); + settingsPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("mailer_title_smtpserver"))); // $NON-NLS-1$ + + JPanel hostPane = new JPanel(new BorderLayout()); + hostPane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel smtpHostPane = new JPanel(new BorderLayout()); + smtpHostPane.add(new JLabel(JMeterUtils.getResString("mailer_host")), BorderLayout.WEST); // $NON-NLS-1$ + smtpHostField = new JTextField(10); + smtpHostField.setEditable(true); + smtpHostPane.add(smtpHostField, BorderLayout.CENTER); + smtpHostPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + hostPane.add(smtpHostPane, BorderLayout.CENTER); + JPanel smtpPortPane = new JPanel(new BorderLayout()); + smtpPortPane.add(new JLabel(JMeterUtils.getResString("mailer_port")), BorderLayout.WEST); // $NON-NLS-1$ + smtpPortField = new JTextField(10); + smtpPortField.setEditable(true); + smtpPortPane.add(smtpPortField, BorderLayout.CENTER); + hostPane.add(smtpPortPane, BorderLayout.EAST); + + JPanel authPane = new JPanel(new BorderLayout()); + hostPane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel smtpLoginPane = new JPanel(new BorderLayout()); + smtpLoginPane.add(new JLabel(JMeterUtils.getResString("mailer_login")), BorderLayout.WEST); // $NON-NLS-1$ + smtpLoginField = new JTextField(10); + smtpLoginField.setEditable(true); + smtpLoginPane.add(smtpLoginField, BorderLayout.CENTER); + smtpLoginPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + authPane.add(smtpLoginPane, BorderLayout.CENTER); + JPanel smtpPasswordPane = new JPanel(new BorderLayout()); + smtpPasswordPane.add(new JLabel(JMeterUtils.getResString("mailer_password")), BorderLayout.WEST); // $NON-NLS-1$ + smtpPasswordField = new JPasswordField(10); + smtpPasswordField.setEditable(true); + smtpPasswordPane.add(smtpPasswordField, BorderLayout.CENTER); + smtpPasswordPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + authPane.add(smtpPasswordPane, BorderLayout.EAST); + + JPanel authTypePane = new JPanel(new BorderLayout()); + authTypePane.add(new JLabel(JMeterUtils.getResString("mailer_connection_security")), BorderLayout.WEST); // $NON-NLS-1$ + authTypeCombo = new JComboBox(new Object[] { + MailerModel.MailAuthType.NONE.toString(), + MailerModel.MailAuthType.SSL.toString(), + MailerModel.MailAuthType.TLS.toString()}); + authTypeCombo.setFont(new Font("SansSerif", Font.PLAIN, 10)); // $NON-NLS-1$ + authTypePane.add(authTypeCombo, BorderLayout.CENTER); + + JPanel credPane = new JPanel(new BorderLayout()); + credPane.add(authPane, BorderLayout.CENTER); + credPane.add(authTypePane, BorderLayout.EAST); + + settingsPane.add(hostPane, BorderLayout.NORTH); + settingsPane.add(credPane, BorderLayout.CENTER); + + return settingsPane; + } + + @Override + public String getLabelResource() { + return "mailer_visualizer_title"; //$NON-NLS-1$ + } + + /** + * Returns a String for the title of the attributes-panel as set up in the + * properties-file using the lookup-constant "mailer_attributes_panel". + * + * @return The title of the component. + */ + public String getAttributesTitle() { + return JMeterUtils.getResString("mailer_attributes_panel"); //$NON-NLS-1$ + } + + // //////////////////////////////////////////////////////////// + // + // Implementation of the ActionListener-Interface. + // + // //////////////////////////////////////////////////////////// + + /** + * Reacts on an ActionEvent (like pressing a button). + * + * @param e + * The ActionEvent with information about the event and its + * source. + */ + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == testerButton) { + ResultCollector testElement = getModel(); + modifyTestElement(testElement); + try { + MailerModel model = ((MailerResultCollector) testElement).getMailerModel(); + model.sendTestMail(); + displayMessage(JMeterUtils.getResString("mail_sent"), false); //$NON-NLS-1$ + } catch (AddressException ex) { + log.error("Invalid mail address ", ex); + displayMessage(JMeterUtils.getResString("invalid_mail_address") //$NON-NLS-1$ + + "\n" + ex.getMessage(), true); //$NON-NLS-1$ + } catch (MessagingException ex) { + log.error("Couldn't send mail...", ex); + displayMessage(JMeterUtils.getResString("invalid_mail") //$NON-NLS-1$ + + "\n" + ex.getMessage(), true); //$NON-NLS-1$ + } + } + } + + // //////////////////////////////////////////////////////////// + // + // Methods used to store and retrieve the MailerVisualizer. + // + // //////////////////////////////////////////////////////////// + + /** + * Restores MailerVisualizer. + */ + @Override + public void configure(TestElement el) { + super.configure(el); + updateVisualizer(((MailerResultCollector) el).getMailerModel()); + } + + /** + * Makes MailerVisualizer storable. + */ + @Override + public TestElement createTestElement() { + ResultCollector model = getModel(); + if (model == null) { + model = new MailerResultCollector(); + setModel(model); + } + modifyTestElement(model); + return model; + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement c) { + super.modifyTestElement(c); + MailerModel mailerModel = ((MailerResultCollector) c).getMailerModel(); + mailerModel.setFailureLimit(failureLimitField.getText()); + mailerModel.setFailureSubject(failureSubjectField.getText()); + mailerModel.setFromAddress(fromField.getText()); + mailerModel.setSmtpHost(smtpHostField.getText()); + mailerModel.setSmtpPort(smtpPortField.getText()); + mailerModel.setLogin(smtpLoginField.getText()); + mailerModel.setPassword(smtpPasswordField.getText()); + mailerModel.setMailAuthType( + authTypeCombo.getSelectedItem().toString()); + mailerModel.setSuccessLimit(successLimitField.getText()); + mailerModel.setSuccessSubject(successSubjectField.getText()); + mailerModel.setToAddress(addressField.getText()); + } + + /** + * Notifies this Visualizer about model-changes. Causes the Visualizer to + * query the model about its new state. + */ + private void updateVisualizer(MailerModel model) { + addressField.setText(model.getToAddress()); + fromField.setText(model.getFromAddress()); + smtpHostField.setText(model.getSmtpHost()); + smtpPortField.setText(model.getSmtpPort()); + smtpLoginField.setText(model.getLogin()); + smtpPasswordField.setText(model.getPassword()); + authTypeCombo.setSelectedItem(model.getMailAuthType().toString()); + successSubjectField.setText(model.getSuccessSubject()); + failureSubjectField.setText(model.getFailureSubject()); + failureLimitField.setText(String.valueOf(model.getFailureLimit())); + failureField.setText(String.valueOf(model.getFailureCount())); + successLimitField.setText(String.valueOf(model.getSuccessLimit())); + repaint(); + } + + /** + * Shows a message using a DialogBox. + */ + private void displayMessage(String message, boolean isError) { + int type = 0; + + if (isError) { + type = JOptionPane.ERROR_MESSAGE; + } else { + type = JOptionPane.INFORMATION_MESSAGE; + } + JOptionPane.showMessageDialog(null, message, isError ? + JMeterUtils.getResString("mailer_msg_title_error") : // $NON-NLS-1$ + JMeterUtils.getResString("mailer_msg_title_information"), type); // $NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void stateChanged(ChangeEvent e) { + if (e.getSource() instanceof MailerModel) { + MailerModel testModel = (MailerModel) e.getSource(); + updateVisualizer(testModel); + } else { + super.stateChanged(e); + } + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/PropertyControlGui.java b/src/components/org/apache/jmeter/visualizers/PropertyControlGui.java new file mode 100644 index 00000000000..c07fbf959cf --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/PropertyControlGui.java @@ -0,0 +1,256 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +//import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +//import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +public class PropertyControlGui extends AbstractConfigGui implements + ActionListener, UnsharedComponent { + + private static final long serialVersionUID = 1L; + + private static final String COLUMN_NAMES_0 = "name"; // $NON-NLS-1$ + + private static final String COLUMN_NAMES_1 = "value"; // $NON-NLS-1$ + + // TODO: add and delete not currently supported + private static final String ADD = "add"; // $NON-NLS-1$ + + private static final String DELETE = "delete"; // $NON-NLS-1$ + + private static final String SYSTEM = "system"; // $NON-NLS-1$ + + private static final String JMETER = "jmeter"; // $NON-NLS-1$ + + private final JCheckBox systemButton = new JCheckBox("System"); + + private final JCheckBox jmeterButton = new JCheckBox("JMeter"); + + private final JLabel tableLabel = new JLabel("Properties"); + + /** The table containing the list of arguments. */ + private transient JTable table; + + /** The model for the arguments table. */ + protected transient ObjectTableModel tableModel; + +// /** A button for adding new arguments to the table. */ +// private JButton add; +// +// /** A button for removing arguments from the table. */ +// private JButton delete; + + public PropertyControlGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "property_visualiser_title"; // $NON-NLS-1$ + } + + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.NON_TEST_ELEMENTS }); + } + + @Override + public void actionPerformed(ActionEvent action) { + String command = action.getActionCommand(); + if (ADD.equals(command)){ + return; + } + if (DELETE.equals(command)){ + return; + } + if (SYSTEM.equals(command)){ + setUpData(); + return; + } + if (JMETER.equals(command)){ + setUpData(); + return; + } + + } + + @Override + public TestElement createTestElement() { + TestElement el = new ConfigTestElement(); + modifyTestElement(el); + return el; + } + @Override + public void configure(TestElement element) { + super.configure(element); + setUpData(); + } + + private void setUpData(){ + tableModel.clearData(); + Properties p = null; + if (systemButton.isSelected()){ + p = System.getProperties(); + } + if (jmeterButton.isSelected()) { + p = JMeterUtils.getJMeterProperties(); + } + if (p == null) { + return; + } + Set> s = p.entrySet(); + ArrayList> al = new ArrayList>(s); + Collections.sort(al, new Comparator>(){ + @Override + public int compare(Map.Entry o1, Map.Entry o2) { + String m1, m2; + m1 = (String) o1.getKey(); + m2 = (String) o2.getKey(); + return m1.compareTo(m2); + } + }); + Iterator> i = al.iterator(); + while (i.hasNext()) { + tableModel.addRow(i.next()); + } + + } + + @Override + public void modifyTestElement(TestElement element) { + configureTestElement(element); + } + + private Component makeMainPanel() { + initializeTableModel(); + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + return makeScrollPane(table); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + private Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + ButtonGroup bg = new ButtonGroup(); + bg.add(systemButton); + bg.add(jmeterButton); + jmeterButton.setSelected(true); + systemButton.setActionCommand(SYSTEM); + jmeterButton.setActionCommand(JMETER); + systemButton.addActionListener(this); + jmeterButton.addActionListener(this); + + labelPanel.add(systemButton); + labelPanel.add(jmeterButton); + labelPanel.add(tableLabel); + return labelPanel; + } + +// /** +// * Create a panel containing the add and delete buttons. +// * +// * @return a GUI panel containing the buttons +// */ +// private JPanel makeButtonPanel() {// Not currently used +// add = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ +// add.setActionCommand(ADD); +// add.setEnabled(true); +// +// delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ +// delete.setActionCommand(DELETE); +// +// JPanel buttonPanel = new JPanel(); +// buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); +// add.addActionListener(this); +// delete.addActionListener(this); +// buttonPanel.add(add); +// buttonPanel.add(delete); +// return buttonPanel; +// } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel p = new JPanel(); + p.setLayout(new BorderLayout()); + p.add(makeLabelPanel(), BorderLayout.NORTH); + p.add(makeMainPanel(), BorderLayout.CENTER); + // Force a minimum table height of 70 pixels + p.add(Box.createVerticalStrut(70), BorderLayout.WEST); + + add(p, BorderLayout.CENTER); + table.revalidate(); + } + private void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { COLUMN_NAMES_0, COLUMN_NAMES_1 }, + new Functor[] { + new Functor(Map.Entry.class, "getKey"), // $NON-NLS-1$ + new Functor(Map.Entry.class, "getValue") // $NON-NLS-1$ + }, + new Functor[] { + null, //new Functor("setName"), // $NON-NLS-1$ + new Functor(Map.Entry.class,"setValue", new Class[] { Object.class }) // $NON-NLS-1$ + }, + new Class[] { String.class, String.class }); + } +} diff --git a/src/components/org/apache/jmeter/visualizers/RenderAsCssJQuery.java b/src/components/org/apache/jmeter/visualizers/RenderAsCssJQuery.java new file mode 100644 index 00000000000..333957c024e --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RenderAsCssJQuery.java @@ -0,0 +1,292 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.extractor.Extractor; +import org.apache.jmeter.extractor.HtmlExtractor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * Implement ResultsRender for CSS/JQuery tester + * @since 2.10 + */ +public class RenderAsCssJQuery implements ResultRenderer, ActionListener { + + private static final String CSSJQUEY_TESTER_COMMAND = "cssjquery_tester"; // $NON-NLS-1$ + + private JPanel cssJqueryPane; + + private JTextArea cssJqueryDataField; + + private JLabeledTextField cssJqueryField; + + private JTextArea cssJqueryResultField; + + private JLabeledTextField attributeField; + + private JTabbedPane rightSide; + + private JLabeledChoice cssJqueryLabeledChoice; + + private SampleResult sampleResult = null; + + /** {@inheritDoc} */ + @Override + public void clearData() { + this.cssJqueryDataField.setText(""); // $NON-NLS-1$ + // don't set empty to keep cssJquery + // cssJqueryField.setText(""); // $NON-NLS-1$ + this.cssJqueryResultField.setText(""); // $NON-NLS-1$ + // don't set empty to keep attribute + // this.attributeField.setText(""); // $NON-NLS-1$ + // don't change impl + // this.cssJqueryLabeledChoice.setText(HtmlExtractor.DEFAULT_EXTRACTOR); + } + + /** {@inheritDoc} */ + @Override + public void init() { + // Create the panels for the cssJquery tab + cssJqueryPane = createCssJqueryPanel(); + } + + /** + * Display the response as text or as rendered HTML. Change the text on the + * button appropriate to the current display. + * + * @param e the ActionEvent being processed + */ + @Override + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + if ((sampleResult != null) && (CSSJQUEY_TESTER_COMMAND.equals(command))) { + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + executeAndShowCssJqueryTester(response); + } + } + + /** + * Launch cssJquery engine to parse a input text + * @param textToParse + */ + private void executeAndShowCssJqueryTester(String textToParse) { + if (textToParse != null && textToParse.length() > 0 + && this.cssJqueryField.getText().length() > 0) { + this.cssJqueryResultField.setText(process(textToParse)); + this.cssJqueryResultField.setCaretPosition(0); // go to first line + } + } + + private String process(String textToParse) { + try { + List result = new ArrayList(); + Extractor extractor = HtmlExtractor.getExtractorImpl(cssJqueryLabeledChoice.getText()); + final int nbFound = extractor.extract( + cssJqueryField.getText(), attributeField.getText(), -1, textToParse, result, 0, null); + + // Construct a multi-line string with all matches + StringBuilder sb = new StringBuilder(); + sb.append("Match count: ").append(nbFound).append("\n"); + for (int j = 0; j < nbFound; j++) { + String mr = result.get(j); + sb.append("Match[").append(j+1).append("]=").append(mr).append("\n"); + } + return sb.toString(); + } catch (Exception ex) { + StringBuilder sb = new StringBuilder(); + String message = MessageFormat.format( + JMeterUtils.getResString("cssjquery_tester_error") // $NON-NLS-1$ + , new Object[]{cssJqueryField.getText(), ex.getMessage()}); + sb.append(message); + return sb.toString(); + } + + } + /** {@inheritDoc} */ + @Override + public void renderResult(SampleResult sampleResult) { + clearData(); + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + cssJqueryDataField.setText(response); + cssJqueryDataField.setCaretPosition(0); + } + + /** {@inheritDoc} */ + @Override + public void setupTabPane() { + // Add cssJquery tester pane + if (rightSide.indexOfTab(JMeterUtils.getResString("cssjquery_tester_title")) < 0) { // $NON-NLS-1$ + rightSide.addTab(JMeterUtils.getResString("cssjquery_tester_title"), cssJqueryPane); // $NON-NLS-1$ + } + clearData(); + } + + /** + * @return RegExp Tester panel + */ + private JPanel createCssJqueryPanel() { + cssJqueryDataField = new JTextArea(); + cssJqueryDataField.setEditable(false); + cssJqueryDataField.setLineWrap(true); + cssJqueryDataField.setWrapStyleWord(true); + + JScrollPane cssJqueryDataPane = GuiUtils.makeScrollPane(cssJqueryDataField); + cssJqueryDataPane.setMinimumSize(new Dimension(0, 200)); + + JPanel pane = new JPanel(new BorderLayout(0, 5)); + + JSplitPane mainSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + cssJqueryDataPane, createCssJqueryTasksPanel()); + mainSplit.setDividerLocation(300); + pane.add(mainSplit, BorderLayout.CENTER); + return pane; + } + + private static String[] getImplementations() { + return new String[]{ + HtmlExtractor.EXTRACTOR_JSOUP, + HtmlExtractor.EXTRACTOR_JODD, + HtmlExtractor.DEFAULT_EXTRACTOR + }; + } + /** + * Create the CssJquery task pane + * + * @return CssJquery task pane + */ + private JPanel createCssJqueryTasksPanel() { + GridBagLayout g = new GridBagLayout(); + GridBagConstraints c = new GridBagConstraints(); + + JPanel cssJqueryActionPanel = new JPanel(); + cssJqueryActionPanel.setLayout(g); + Border margin = new EmptyBorder(5, 5, 0, 5); + cssJqueryActionPanel.setBorder(margin); + cssJqueryField = new JLabeledTextField(JMeterUtils.getResString("cssjquery_tester_field")); // $NON-NLS-1$ + cssJqueryField.setPreferredSize(new Dimension(300, 30)); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx=0; + c.gridy=0; + cssJqueryActionPanel.add(cssJqueryField, c); + + cssJqueryLabeledChoice = new JLabeledChoice( + JMeterUtils.getResString("cssjquery_impl"), // $NON-NLS-1$ + getImplementations()); + cssJqueryLabeledChoice.setPreferredSize(new Dimension(300, 30)); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx=1; + c.gridy=0; + cssJqueryActionPanel.add(cssJqueryLabeledChoice, c); + + attributeField = new JLabeledTextField(JMeterUtils.getResString("cssjquery_attribute")); // $NON-NLS-1$ + attributeField.setPreferredSize(new Dimension(300, 30)); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx=0; + c.gridy=1; + cssJqueryActionPanel.add(attributeField, c); + + JButton cssJqueryTester = new JButton(JMeterUtils.getResString("cssjquery_tester_button_test")); // $NON-NLS-1$ + cssJqueryTester.setPreferredSize(new Dimension(100, 30)); + cssJqueryTester.setActionCommand(CSSJQUEY_TESTER_COMMAND); + cssJqueryTester.addActionListener(this); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx=1; + c.gridy=1; + cssJqueryActionPanel.add(cssJqueryTester, c); + + + cssJqueryResultField = new JTextArea(); + cssJqueryResultField.setEditable(false); + cssJqueryResultField.setLineWrap(true); + cssJqueryResultField.setWrapStyleWord(true); + + JPanel cssJqueryTasksPanel = new JPanel(new BorderLayout(0, 5)); + cssJqueryTasksPanel.add(cssJqueryActionPanel, BorderLayout.NORTH); + cssJqueryTasksPanel.add(GuiUtils.makeScrollPane(cssJqueryResultField), BorderLayout.CENTER); + + return cssJqueryTasksPanel; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setRightSide(JTabbedPane side) { + rightSide = side; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setSamplerResult(Object userObject) { + if (userObject instanceof SampleResult) { + sampleResult = (SampleResult) userObject; + } + } + + /** {@inheritDoc} */ + @Override + public void setLastSelectedTab(int index) { + // nothing to do + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("cssjquery_tester_title"); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void renderImage(SampleResult sampleResult) { + clearData(); + cssJqueryDataField.setText(JMeterUtils.getResString("cssjquery_render_no_text")); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void setBackgroundColor(Color backGround) { + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RenderAsDocument.java b/src/components/org/apache/jmeter/visualizers/RenderAsDocument.java new file mode 100644 index 00000000000..87a49067128 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RenderAsDocument.java @@ -0,0 +1,58 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.Document; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class RenderAsDocument extends SamplerResultTab implements ResultRenderer { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** {@inheritDoc} */ + @Override + public void renderResult(SampleResult sampleResult) { + try { + showDocumentResponse(sampleResult); + } catch (Exception e) { + results.setText(e.toString()); + log.error("Error:", e); // $NON-NLS-1$ + } + } + + private void showDocumentResponse(SampleResult sampleResult) { + String response = Document.getTextFromDocument(sampleResult.getResponseData()); + + results.setContentType("text/plain"); // $NON-NLS-1$ + results.setText(response); + results.setCaretPosition(0); + resultsScrollPane.setViewportView(results); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_document"); // $NON-NLS-1$ + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RenderAsHTML.java b/src/components/org/apache/jmeter/visualizers/RenderAsHTML.java new file mode 100644 index 00000000000..7185bd6e79a --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RenderAsHTML.java @@ -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.visualizers; + +import javax.swing.JEditorPane; +import javax.swing.text.ComponentView; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; +import javax.swing.text.Element; +import javax.swing.text.StyleConstants; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; +import javax.swing.text.html.HTML; +import javax.swing.text.html.HTMLEditorKit; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class RenderAsHTML extends SamplerResultTab implements ResultRenderer { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String TEXT_HTML = "text/html"; // $NON-NLS-1$ + + // Keep copies of the two editors needed + private static final EditorKit customisedEditor = new LocalHTMLEditorKit(); + + private static final EditorKit defaultHtmlEditor = JEditorPane.createEditorKitForContentType(TEXT_HTML); + + /** {@inheritDoc} */ + @Override + public void renderResult(SampleResult sampleResult) { + // get the text response and image icon + // to determine which is NOT null + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + showRenderedResponse(response, sampleResult); + } + + protected void showRenderedResponse(String response, SampleResult res) { + showRenderedResponse(response, res, false); + } + + protected void showRenderedResponse(String response, SampleResult res, boolean embedded) { + if (response == null) { + results.setText(""); + return; + } + + int htmlIndex = response.indexOf(" // $NON-NLS-1$ + + // Look for a case variation + if (htmlIndex < 0) { + htmlIndex = response.indexOf(" See + * http://bz.apache.org/bugzilla/show_bug.cgi?id=23315 + * + * Is this due to a bug in Java? + */ + results.getDocument().putProperty("IgnoreCharsetDirective", Boolean.TRUE); // $NON-NLS-1$ + + try { + results.setText(html); // Bug can generate RTE + } catch (RuntimeException rte) { + results.setText("Failed to parse HTML: " + rte.getMessage()); + } + results.setCaretPosition(0); + try { + resultsScrollPane.setViewportView(results); + } catch (NumberFormatException e) { + // Java Bug : http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=9001188. + // See https://bz.apache.org/bugzilla/show_bug.cgi?id=54586 + log.warn("An error occured rendering html code", e); + results.setText("Failed to render HTML: " + e.getMessage() +", use Text renderer"); + } + } + + private static class LocalHTMLEditorKit extends HTMLEditorKit { + + private static final long serialVersionUID = -3399554318202905392L; + + private static final ViewFactory defaultFactory = new LocalHTMLFactory(); + + @Override + public ViewFactory getViewFactory() { + return defaultFactory; + } + + private static class LocalHTMLFactory extends javax.swing.text.html.HTMLEditorKit.HTMLFactory { + /* + * Provide dummy implementations to suppress download and display of + * related resources: - FRAMEs - IMAGEs TODO create better dummy + * displays TODO suppress LINK somehow + */ + @Override + public View create(Element elem) { + Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute); + if (o instanceof HTML.Tag) { + HTML.Tag kind = (HTML.Tag) o; + if (kind == HTML.Tag.FRAME) { + return new ComponentView(elem); + } else if (kind == HTML.Tag.IMG) { + return new ComponentView(elem); + } + } + return super.create(elem); + } + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_html"); // $NON-NLS-1$ + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java b/src/components/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java new file mode 100644 index 00000000000..1572433ad4a --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java @@ -0,0 +1,41 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +public class RenderAsHTMLWithEmbedded extends RenderAsHTML + implements ResultRenderer { + + /** {@inheritDoc} */ + @Override + protected void showRenderedResponse(String response, SampleResult res) { + // enable embedded html resources + showRenderedResponse(response, res, true); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_html_embedded"); // $NON-NLS-1$ + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RenderAsJSON.java b/src/components/org/apache/jmeter/visualizers/RenderAsJSON.java new file mode 100644 index 00000000000..20fd4aff847 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RenderAsJSON.java @@ -0,0 +1,118 @@ +/* + * 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.visualizers; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +public class RenderAsJSON extends SamplerResultTab implements ResultRenderer { + + private static final String ESC_CHAR_REGEX = "\\\\[\"\\\\/bfnrt]|\\\\u[0-9A-Fa-f]{4}"; // $NON-NLS-1$ + + private static final String NORMAL_CHARACTER_REGEX = "[^\"\\\\]"; // $NON-NLS-1$ + + private static final String STRING_REGEX = "\"(" + ESC_CHAR_REGEX + "|" + NORMAL_CHARACTER_REGEX + ")*+\""; // $NON-NLS-1$ + + // This 'other value' regex is deliberately weak, even accepting an empty string, to be useful when reporting malformed data. + private static final String OTHER_VALUE_REGEX = "[^\\{\\[\\]\\}\\,]*"; // $NON-NLS-1$ + + private static final String VALUE_OR_PAIR_REGEX = "((" + STRING_REGEX + "\\s*:)?\\s*(" + STRING_REGEX + "|" + OTHER_VALUE_REGEX + ")\\s*,?\\s*)"; // $NON-NLS-1$ + + private static final Pattern VALUE_OR_PAIR_PATTERN = Pattern.compile(VALUE_OR_PAIR_REGEX); + + /** {@inheritDoc} */ + @Override + public void renderResult(SampleResult sampleResult) { + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + showRenderJSONResponse(response); + } + + private void showRenderJSONResponse(String response) { + results.setContentType("text/plain"); // $NON-NLS-1$ + results.setText(response == null ? "" : prettyJSON(response)); + results.setCaretPosition(0); + resultsScrollPane.setViewportView(results); + } + + // It might be useful also to make this available in the 'Request' tab, for + // when posting JSON. + private static String prettyJSON(String json) { + StringBuilder pretty = new StringBuilder(json.length() * 2); // Educated guess + + final String tab = ": "; // $NON-NLS-1$ + StringBuilder index = new StringBuilder(); + String nl = ""; // $NON-NLS-1$ + + Matcher valueOrPair = VALUE_OR_PAIR_PATTERN.matcher(json); + + boolean misparse = false; + + for (int i = 0; i < json.length(); ) { + final char currentChar = json.charAt(i); + if ((currentChar == '{') || (currentChar == '[')) { + pretty.append(nl).append(index).append(currentChar); + i++; + index.append(tab); + misparse = false; + } + else if ((currentChar == '}') || (currentChar == ']')) { + if (index.length() > 0) { + index.delete(0, tab.length()); + } + pretty.append(nl).append(index).append(currentChar); + i++; + int j = i; + while ((j < json.length()) && Character.isWhitespace(json.charAt(j))) { + j++; + } + if ((j < json.length()) && (json.charAt(j) == ',')) { + pretty.append(","); // $NON-NLS-1$ + i=j+1; + } + misparse = false; + } + else if (valueOrPair.find(i) && valueOrPair.group().length() > 0) { + pretty.append(nl).append(index).append(valueOrPair.group()); + i=valueOrPair.end(); + misparse = false; + } + else { + if (!misparse) { + pretty.append(nl).append("- Parse failed from:"); + } + pretty.append(currentChar); + i++; + misparse = true; + } + nl = "\n"; // $NON-NLS-1$ + } + return pretty.toString(); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_json"); // $NON-NLS-1$ + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RenderAsRegexp.java b/src/components/org/apache/jmeter/visualizers/RenderAsRegexp.java new file mode 100644 index 00000000000..11cf1b43385 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RenderAsRegexp.java @@ -0,0 +1,255 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.PatternCacheLRU; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * Implement ResultsRender for Regexp tester + */ +public class RenderAsRegexp implements ResultRenderer, ActionListener { + + private static final String REGEXP_TESTER_COMMAND = "regexp_tester"; // $NON-NLS-1$ + + private JPanel regexpPane; + + private JTextArea regexpDataField; + + private JLabeledTextField regexpField; + + private JTextArea regexpResultField; + + private JTabbedPane rightSide; + + private SampleResult sampleResult = null; + + /** {@inheritDoc} */ + @Override + public void clearData() { + this.regexpDataField.setText(""); // $NON-NLS-1$ + // don't set empty to keep regexp + // regexpField.setText(""); // $NON-NLS-1$ + this.regexpResultField.setText(""); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void init() { + // Create the panels for the regexp tab + regexpPane = createRegexpPanel(); + } + + /** + * Display the response as text or as rendered HTML. Change the text on the + * button appropriate to the current display. + * + * @param e the ActionEvent being processed + */ + @Override + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + if ((sampleResult != null) && (REGEXP_TESTER_COMMAND.equals(command))) { + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + executeAndShowRegexpTester(response); + } + } + + /** + * Launch regexp engine to parse a input text + * @param textToParse + */ + private void executeAndShowRegexpTester(String textToParse) { + if (textToParse != null && textToParse.length() > 0 + && this.regexpField.getText().length() > 0) { + this.regexpResultField.setText(process(textToParse)); + this.regexpResultField.setCaretPosition(0); // go to first line + } + } + + private String process(String textToParse) { + + Perl5Matcher matcher = new Perl5Matcher(); + PatternMatcherInput input = new PatternMatcherInput(textToParse); + + PatternCacheLRU pcLRU = new PatternCacheLRU(); + Pattern pattern; + try { + pattern = pcLRU.getPattern(regexpField.getText(), Perl5Compiler.READ_ONLY_MASK); + } catch (MalformedCachePatternException e) { + return e.toString(); + } + List matches = new LinkedList(); + while (matcher.contains(input, pattern)) { + matches.add(matcher.getMatch()); + } + // Construct a multi-line string with all matches + StringBuilder sb = new StringBuilder(); + final int size = matches.size(); + sb.append("Match count: ").append(size).append("\n"); + for (int j = 0; j < size; j++) { + MatchResult mr = matches.get(j); + final int groups = mr.groups(); + for (int i = 0; i < groups; i++) { + sb.append("Match[").append(j+1).append("][").append(i).append("]=").append(mr.group(i)).append("\n"); + } + } + return sb.toString(); + + } + /** {@inheritDoc} */ + @Override +public void renderResult(SampleResult sampleResult) { + clearData(); + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + regexpDataField.setText(response); + regexpDataField.setCaretPosition(0); + } + + /** {@inheritDoc} */ + @Override + public void setupTabPane() { + // Add regexp tester pane + if (rightSide.indexOfTab(JMeterUtils.getResString("regexp_tester_title")) < 0) { // $NON-NLS-1$ + rightSide.addTab(JMeterUtils.getResString("regexp_tester_title"), regexpPane); // $NON-NLS-1$ + } + clearData(); + } + + /** + * @return RegExp Tester panel + */ + private JPanel createRegexpPanel() { + regexpDataField = new JTextArea(); + regexpDataField.setEditable(false); + regexpDataField.setLineWrap(true); + regexpDataField.setWrapStyleWord(true); + + JScrollPane regexpDataPane = GuiUtils.makeScrollPane(regexpDataField); + regexpDataPane.setMinimumSize(new Dimension(0, 200)); + + JPanel pane = new JPanel(new BorderLayout(0, 5)); + + JSplitPane mainSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + regexpDataPane, createRegexpTasksPanel()); + mainSplit.setDividerLocation(300); + pane.add(mainSplit, BorderLayout.CENTER); + return pane; + } + + /** + * Create the Regexp task pane + * + * @return Regexp task pane + */ + private JPanel createRegexpTasksPanel() { + JPanel regexpActionPanel = new JPanel(); + regexpActionPanel.setLayout(new BoxLayout(regexpActionPanel, BoxLayout.X_AXIS)); + Border margin = new EmptyBorder(5, 5, 0, 5); + regexpActionPanel.setBorder(margin); + regexpField = new JLabeledTextField(JMeterUtils.getResString("regexp_tester_field")); // $NON-NLS-1$ + regexpActionPanel.add(regexpField, BorderLayout.WEST); + + JButton regexpTester = new JButton(JMeterUtils.getResString("regexp_tester_button_test")); // $NON-NLS-1$ + regexpTester.setActionCommand(REGEXP_TESTER_COMMAND); + regexpTester.addActionListener(this); + regexpActionPanel.add(regexpTester, BorderLayout.EAST); + + regexpResultField = new JTextArea(); + regexpResultField.setEditable(false); + regexpResultField.setLineWrap(true); + regexpResultField.setWrapStyleWord(true); + + JPanel regexpTasksPanel = new JPanel(new BorderLayout(0, 5)); + regexpTasksPanel.add(regexpActionPanel, BorderLayout.NORTH); + regexpTasksPanel.add(GuiUtils.makeScrollPane(regexpResultField), BorderLayout.CENTER); + + return regexpTasksPanel; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setRightSide(JTabbedPane side) { + rightSide = side; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setSamplerResult(Object userObject) { + if (userObject instanceof SampleResult) { + sampleResult = (SampleResult) userObject; + } + } + + /** {@inheritDoc} */ + @Override + public void setLastSelectedTab(int index) { + // nothing to do + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("regexp_tester_title"); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void renderImage(SampleResult sampleResult) { + clearData(); + regexpDataField.setText(JMeterUtils.getResString("regexp_render_no_text")); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void setBackgroundColor(Color backGround) { + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RenderAsText.java b/src/components/org/apache/jmeter/visualizers/RenderAsText.java new file mode 100644 index 00000000000..6c8404e067e --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RenderAsText.java @@ -0,0 +1,50 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +public class RenderAsText extends SamplerResultTab implements ResultRenderer { + + /** {@inheritDoc} */ + @Override + public void renderResult(SampleResult sampleResult) { + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + showTextResponse(response); + } + + private void showTextResponse(String response) { + results.setContentType("text/plain"); // $NON-NLS-1$ + results.setText(response == null ? "" : response); // $NON-NLS-1$ + results.setCaretPosition(0); + resultsScrollPane.setViewportView(results); + // Bug 55111 - Refresh JEditor pane size depending on the presence or absence of scrollbars + resultsScrollPane.setPreferredSize(resultsScrollPane.getMinimumSize()); + results.revalidate(); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_text"); // $NON-NLS-1$ + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RenderAsXML.java b/src/components/org/apache/jmeter/visualizers/RenderAsXML.java new file mode 100644 index 00000000000..b56c3c48e05 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RenderAsXML.java @@ -0,0 +1,235 @@ +/* + * 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.visualizers; + +import java.awt.Component; +import java.awt.GridLayout; +import java.io.ByteArrayInputStream; +import java.io.StringWriter; + +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.ToolTipManager; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeSelectionModel; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.tidy.Tidy; +import org.xml.sax.SAXException; + +public class RenderAsXML extends SamplerResultTab + implements ResultRenderer { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final byte[] XML_PFX = {'<','?','x','m','l',' '};//" 0) { + showErrorMessageDialog(sw.toString(), + "Tidy: " + tidy.getParseErrors() + " errors, " + tidy.getParseWarnings() + " warnings", + JOptionPane.WARNING_MESSAGE); + } + + JPanel domTreePanel = new DOMTreePanel(document); + resultsScrollPane.setViewportView(domTreePanel); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.SamplerResultTab#clearData() + */ + @Override + public void clearData() { + super.clearData(); + resultsScrollPane.setViewportView(null); // clear result tab on Ctrl-E + } + + /* + * + * A Dom tree panel for to display response as tree view author Dave Maung + * TODO implement to find any nodes in the tree using TreePath. + * + */ + private static class DOMTreePanel extends JPanel { + + private static final long serialVersionUID = 6871690021183779153L; + + private JTree domJTree; + + public DOMTreePanel(org.w3c.dom.Document document) { + super(new GridLayout(1, 0)); + try { + Node firstElement = getFirstElement(document); + DefaultMutableTreeNode top = new XMLDefaultMutableTreeNode(firstElement); + domJTree = new JTree(top); + + domJTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + domJTree.setShowsRootHandles(true); + JScrollPane domJScrollPane = new JScrollPane(domJTree); + domJTree.setAutoscrolls(true); + this.add(domJScrollPane); + ToolTipManager.sharedInstance().registerComponent(domJTree); + domJTree.setCellRenderer(new DomTreeRenderer()); + } catch (SAXException e) { + log.warn("Error trying to parse document", e); + } + + } + + /** + * Skip all DTD nodes, all prolog nodes. They dont support in tree view + * We let user to insert them however in DOMTreeView, we dont display it + * + * @param root + * @return + */ + private Node getFirstElement(Node parent) { + NodeList childNodes = parent.getChildNodes(); + Node toReturn = parent; // Must return a valid node, or may generate an NPE + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + toReturn = childNode; + if (childNode.getNodeType() == Node.ELEMENT_NODE){ + break; + } + + } + return toReturn; + } + + /** + * This class is to view as tooltext. This is very useful, when the + * contents has long string and does not fit in the view. it will also + * automatically wrap line for each 100 characters since tool tip + * support html. author Dave Maung + */ + private static class DomTreeRenderer extends DefaultTreeCellRenderer { + + private static final long serialVersionUID = 240210061375790195L; + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, + boolean leaf, int row, boolean phasFocus) { + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, phasFocus); + + DefaultMutableTreeNode valueTreeNode = (DefaultMutableTreeNode) value; + setToolTipText(getHTML(valueTreeNode.toString(), "
", 100)); // $NON-NLS-1$ + return this; + } + + /** + * get the html + * + * @param str + * @param separator + * @param maxChar + * @return + */ + private String getHTML(String str, String separator, int maxChar) { + StringBuilder strBuf = new StringBuilder(""); // $NON-NLS-1$ + char[] chars = str.toCharArray(); + for (int i = 0; i < chars.length; i++) { + + if (i % maxChar == 0 && i != 0) { + strBuf.append(separator); + } + strBuf.append(encode(chars[i])); + + } + strBuf.append(""); // $NON-NLS-1$ + return strBuf.toString(); + + } + + private String encode(char c) { + String toReturn = String.valueOf(c); + switch (c) { + case '<': // $NON-NLS-1$ + toReturn = "<"; // $NON-NLS-1$ + break; + case '>': // $NON-NLS-1$ + toReturn = ">"; // $NON-NLS-1$ + break; + case '\'': // $NON-NLS-1$ + toReturn = "'"; // $NON-NLS-1$ + break; + case '\"': // $NON-NLS-1$ + toReturn = """; // $NON-NLS-1$ + break; + default: + // ignored + break; + + } + return toReturn; + } + } + } + + private static void showErrorMessageDialog(String message, String title, int messageType) { + JOptionPane.showMessageDialog(null, message, title, messageType); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_xml"); // $NON-NLS-1$ + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RenderAsXPath.java b/src/components/org/apache/jmeter/visualizers/RenderAsXPath.java new file mode 100644 index 00000000000..7912f518bb4 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RenderAsXPath.java @@ -0,0 +1,301 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.jmeter.assertions.gui.XMLConfPanel; +import org.apache.jmeter.extractor.XPathExtractor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.TidyException; +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + + +/** + * Implement ResultsRender for XPath tester + */ +public class RenderAsXPath implements ResultRenderer, ActionListener { + + private static final Logger logger = LoggingManager.getLoggerForClass(); + + private static final String XPATH_TESTER_COMMAND = "xpath_tester"; // $NON-NLS-1$ + + private JPanel xmlWithXPathPane; + + private JTextArea xmlDataField; + + private JLabeledTextField xpathExpressionField; + + private JTextArea xpathResultField; + + private JTabbedPane rightSide; + + private SampleResult sampleResult = null; + + private JScrollPane xmlDataPane; + + // Should we return fragment as text, rather than text of fragment? + private final JCheckBox getFragment = + new JCheckBox(JMeterUtils.getResString("xpath_tester_fragment"));//$NON-NLS-1$ + + private final XMLConfPanel xmlConfPanel = new XMLConfPanel(); + + /** {@inheritDoc} */ + @Override + public void clearData() { + this.xmlDataField.setText(""); // $NON-NLS-1$ + // don't set empty to keep xpath + // xpathExpressionField.setText(""); // $NON-NLS-1$ + this.xpathResultField.setText(""); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void init() { + // Create the panels for the xpath tab + xmlWithXPathPane = createXpathExtractorPanel(); + } + + /** + * Display the response as text or as rendered HTML. Change the text on the + * button appropriate to the current display. + * + * @param e the ActionEvent being processed + */ + @Override + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + if ((sampleResult != null) && (XPATH_TESTER_COMMAND.equals(command))) { + String response = xmlDataField.getText(); + XPathExtractor extractor = new XPathExtractor(); + xmlConfPanel.modifyTestElement(extractor); + extractor.setFragment(getFragment.isSelected()); + executeAndShowXPathTester(response, extractor); + } + } + + /** + * Launch xpath engine to parse a input text + * @param textToParse + */ + private void executeAndShowXPathTester(String textToParse, XPathExtractor extractor) { + if (textToParse != null && textToParse.length() > 0 + && this.xpathExpressionField.getText().length() > 0) { + this.xpathResultField.setText(process(textToParse, extractor)); + this.xpathResultField.setCaretPosition(0); // go to first line + } + } + + private String process(String textToParse, XPathExtractor extractor) { + try { + Document doc = parseResponse(textToParse, extractor); + List matchStrings = new ArrayList(); + XPathUtil.putValuesForXPathInList(doc, xpathExpressionField.getText(), + matchStrings, extractor.getFragment()); + StringBuilder builder = new StringBuilder(); + int nbFound = matchStrings.size(); + builder.append("Match count: ").append(nbFound).append("\n"); + for (int i = 0; i < nbFound; i++) { + builder.append("Match[").append(i+1).append("]=").append(matchStrings.get(i)).append("\n"); + } + return builder.toString(); + } catch (Exception e) { + return "Exception:"+ ExceptionUtils.getStackTrace(e); + } + } + + /*================= internal business =================*/ + /** + * Converts (X)HTML response to DOM object Tree. + * This version cares of charset of response. + * @param unicodeData + * @return + * + */ + private Document parseResponse(String unicodeData, XPathExtractor extractor) + throws UnsupportedEncodingException, IOException, ParserConfigurationException,SAXException,TidyException + { + //TODO: validate contentType for reasonable types? + + // NOTE: responseData encoding is server specific + // Therefore we do byte -> unicode -> byte conversion + // to ensure UTF-8 encoding as required by XPathUtil + // convert unicode String -> UTF-8 bytes + byte[] utf8data = unicodeData.getBytes("UTF-8"); // $NON-NLS-1$ + ByteArrayInputStream in = new ByteArrayInputStream(utf8data); + boolean isXML = JOrphanUtils.isXML(utf8data); + // this method assumes UTF-8 input data + return XPathUtil.makeDocument(in,false,false,extractor.useNameSpace(), + extractor.isTolerant(),extractor.isQuiet(),extractor.showWarnings(), + extractor.reportErrors(),isXML, extractor.isDownloadDTDs()); + } + + + /** {@inheritDoc} */ + @Override + public void renderResult(SampleResult sampleResult) { + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + try { + xmlDataField.setText(response == null ? "" : response); + xmlDataField.setCaretPosition(0); + } catch (Exception e) { + logger.error("Exception converting to XML:"+response+ ", message:"+e.getMessage(),e); + xmlDataField.setText("Exception converting to XML:"+response+ ", message:"+e.getMessage()); + xmlDataField.setCaretPosition(0); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("xpath_tester"); // $NON-NLS-1$ + } + + + /** {@inheritDoc} */ + @Override + public void setupTabPane() { + // Add xpath tester pane + if (rightSide.indexOfTab(JMeterUtils.getResString("xpath_tester_title")) < 0) { // $NON-NLS-1$ + rightSide.addTab(JMeterUtils.getResString("xpath_tester_title"), xmlWithXPathPane); // $NON-NLS-1$ + } + clearData(); + } + + /** + * @return XPath Tester panel + */ + private JPanel createXpathExtractorPanel() { + + xmlDataField = new JTextArea(); + xmlDataField.setEditable(false); + xmlDataField.setLineWrap(true); + xmlDataField.setWrapStyleWord(true); + + this.xmlDataPane = GuiUtils.makeScrollPane(xmlDataField); + xmlDataPane.setMinimumSize(new Dimension(0, 400)); + + JPanel pane = new JPanel(new BorderLayout(0, 5)); + + JSplitPane mainSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + xmlDataPane, createXpathExtractorTasksPanel()); + mainSplit.setDividerLocation(400); + pane.add(mainSplit, BorderLayout.CENTER); + return pane; + } + + /** + * Create the XPath task pane + * + * @return XPath task pane + */ + private JPanel createXpathExtractorTasksPanel() { + Box xpathActionPanel = Box.createVerticalBox(); + + Box selectorAndButton = Box.createHorizontalBox(); + + Border margin = new EmptyBorder(5, 5, 0, 5); + xpathActionPanel.setBorder(margin); + xpathExpressionField = new JLabeledTextField(JMeterUtils.getResString("xpath_tester_field")); // $NON-NLS-1$ + + JButton xpathTester = new JButton(JMeterUtils.getResString("xpath_tester_button_test")); // $NON-NLS-1$ + xpathTester.setActionCommand(XPATH_TESTER_COMMAND); + xpathTester.addActionListener(this); + + selectorAndButton.add(xpathExpressionField); + selectorAndButton.add(xpathTester); + + xpathActionPanel.add(selectorAndButton); + xpathActionPanel.add(xmlConfPanel); + xpathActionPanel.add(getFragment); + + xpathResultField = new JTextArea(); + xpathResultField.setEditable(false); + xpathResultField.setLineWrap(true); + xpathResultField.setWrapStyleWord(true); + + JPanel xpathTasksPanel = new JPanel(new BorderLayout(0, 5)); + xpathTasksPanel.add(xpathActionPanel, BorderLayout.NORTH); + xpathTasksPanel.add(GuiUtils.makeScrollPane(xpathResultField), BorderLayout.CENTER); + + return xpathTasksPanel; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setRightSide(JTabbedPane side) { + rightSide = side; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setSamplerResult(Object userObject) { + if (userObject instanceof SampleResult) { + sampleResult = (SampleResult) userObject; + } + } + + /** {@inheritDoc} */ + @Override + public void setLastSelectedTab(int index) { + // nothing to do + } + + /** {@inheritDoc} */ + @Override + public void renderImage(SampleResult sampleResult) { + clearData(); + xmlDataField.setText(JMeterUtils.getResString("xpath_tester_no_text")); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void setBackgroundColor(Color backGround) { + } +} diff --git a/src/components/org/apache/jmeter/visualizers/RequestPanel.java b/src/components/org/apache/jmeter/visualizers/RequestPanel.java new file mode 100644 index 00000000000..073ad94576b --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RequestPanel.java @@ -0,0 +1,120 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.SwingConstants; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Manipulate all classes which implements request view panel interface + * and return a super panel with a bottom tab list of this classes + * + */ +public class RequestPanel { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final LinkedList listRequestView; + + private final JPanel panel; + + /** + * Find and instanciate all class that extend RequestView + * and Create Request Panel + */ + public RequestPanel() { + listRequestView = new LinkedList(); + List classesToAdd = Collections. emptyList(); + try { + classesToAdd = JMeterUtils.findClassesThatExtend(RequestView.class); + } catch (IOException e1) { + // ignored + } + String rawTab = JMeterUtils.getResString(RequestViewRaw.KEY_LABEL); // $NON-NLS-1$ + Object rawObject = null; + for (String clazz : classesToAdd) { + try { + // Instantiate requestview classes + final RequestView requestView = (RequestView) Class.forName(clazz).newInstance(); + if (rawTab.equals(requestView.getLabel())) { + rawObject = requestView; // use later + } else { + listRequestView.add(requestView); + } + } catch (Exception e) { + log.warn("Error in load result render:" + clazz, e); // $NON-NLS-1$ + } + } + // place raw tab in first position (first tab) + if (rawObject != null) { + listRequestView.addFirst((RequestView) rawObject); + } + + // Prepare the Request tabbed pane + JTabbedPane tabbedRequest = new JTabbedPane(SwingConstants.BOTTOM); + for (RequestView requestView : listRequestView) { + requestView.init(); + tabbedRequest.addTab(requestView.getLabel(), requestView.getPanel()); + } + + // Hint to background color on bottom tabs (grey, not blue) + panel = new JPanel(new BorderLayout()); + panel.add(tabbedRequest); + } + + /** + * Clear data in all request view + */ + public void clearData() { + for (RequestView requestView : listRequestView) { + requestView.clearData(); + } + } + + /** + * Put SamplerResult in all request view + * + * @param samplerResult The {@link SampleResult} to be put in all {@link RequestView}s + */ + public void setSamplerResult(SampleResult samplerResult) { + for (RequestView requestView : listRequestView) { + requestView.setSamplerResult(samplerResult); + } + } + + /** + * @return a tabbed panel for view request + */ + public JPanel getPanel() { + return panel; + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RequestView.java b/src/components/org/apache/jmeter/visualizers/RequestView.java new file mode 100644 index 00000000000..0ea852f3091 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RequestView.java @@ -0,0 +1,60 @@ +/* + * 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.visualizers; + +import javax.swing.JPanel; + +/** + * Interface for request panel in View Results Tree + * All classes which implements this interface is display + * on bottom tab in request panel + * + */ +public interface RequestView { + + /** + * Init the panel + */ + void init(); + + /** + * Clear all data in panel + */ + void clearData(); + + /** + * Put the result bean to display in panel + * @param userObject result to display + */ + void setSamplerResult(Object userObject); + + /** + * Get the panel + * @return the panel viewer + */ + JPanel getPanel(); + + /** + * Get the label. Use as name for bottom tab + * @return the label's panel + */ + String getLabel(); // return label + +} diff --git a/src/components/org/apache/jmeter/visualizers/RequestViewRaw.java b/src/components/org/apache/jmeter/visualizers/RequestViewRaw.java new file mode 100644 index 00000000000..17cf4d8cc71 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RequestViewRaw.java @@ -0,0 +1,114 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; + +import javax.swing.JPanel; +import javax.swing.JTextArea; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; + +/** + * (historical) Panel to view request data + * + */ +public class RequestViewRaw implements RequestView { + + // Used by Request Panel + static final String KEY_LABEL = "view_results_table_request_tab_raw"; //$NON-NLS-1$ + + private JTextArea sampleDataField; + + private JPanel paneRaw; /** request pane content */ + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#init() + */ + @Override + public void init() { + paneRaw = new JPanel(new BorderLayout(0, 5)); + sampleDataField = new JTextArea(); + sampleDataField.setEditable(false); + sampleDataField.setLineWrap(true); + sampleDataField.setWrapStyleWord(true); + + paneRaw.add(GuiUtils.makeScrollPane(sampleDataField)); + + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#clearData() + */ + @Override + public void clearData() { + sampleDataField.setText(""); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#setSamplerResult(java.lang.Object) + */ + @Override + public void setSamplerResult(Object objectResult) { + + if (objectResult instanceof SampleResult) { + SampleResult sampleResult = (SampleResult) objectResult; + String rh = sampleResult.getRequestHeaders(); + StringBuilder sb = new StringBuilder(); + String sd = sampleResult.getSamplerData(); + if (sd != null) { + sb.append(sd); + sb.append("\n"); //$NON-NLS-1$ + } + // Don't display Request headers label if rh is null or empty + if (rh != null && rh.length() > 0) { + sb.append(JMeterUtils.getResString("view_results_request_headers")); //$NON-NLS-1$ + sb.append("\n"); //$NON-NLS-1$ + sb.append(rh); + sb.append("\n"); //$NON-NLS-1$ + } + if (sb.length() > 0) { + sampleDataField.setText(sb.toString()); + } else { + // add a message when no request data (ex. Java request) + sampleDataField.setText(JMeterUtils + .getResString("view_results_table_request_raw_nodata")); //$NON-NLS-1$ + } + } + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#getPanel() + */ + @Override + public JPanel getPanel() { + return paneRaw; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#getLabel() + */ + @Override + public String getLabel() { + return JMeterUtils.getResString(KEY_LABEL); + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/RespTimeGraphChart.java b/src/components/org/apache/jmeter/visualizers/RespTimeGraphChart.java new file mode 100644 index 00000000000..9ad13ba34cf --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RespTimeGraphChart.java @@ -0,0 +1,417 @@ +/* + * 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.visualizers; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.LayoutManager; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.math.BigDecimal; + +import javax.swing.JPanel; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.jCharts.axisChart.AxisChart; +import org.jCharts.chartData.AxisChartDataSet; +import org.jCharts.chartData.ChartDataException; +import org.jCharts.chartData.DataSeries; +import org.jCharts.properties.AxisProperties; +import org.jCharts.properties.ChartProperties; +import org.jCharts.properties.DataAxisProperties; +import org.jCharts.properties.LabelAxisProperties; +import org.jCharts.properties.LegendAreaProperties; +import org.jCharts.properties.LegendProperties; +import org.jCharts.properties.LineChartProperties; +import org.jCharts.properties.PointChartProperties; +import org.jCharts.properties.PropertyException; +import org.jCharts.properties.util.ChartFont; +import org.jCharts.types.ChartType; + +public class RespTimeGraphChart extends JPanel { + + private static final long serialVersionUID = 280L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected double[][] data; + + protected String title; + + protected String xAxisTitle; + + protected String yAxisTitle; + + protected String yAxisLabel; + + protected String[] xAxisLabels; + + protected int width; + + protected int height; + + protected int incrYAxisScale; + + protected String[] legendLabels = { JMeterUtils.getResString("aggregate_graph_legend") }; // $NON-NLS-1$ + + protected int maxYAxisScale; + + protected Font titleFont; + + protected Font legendFont; + + protected Color[] color; + + protected boolean showGrouping = true; + + protected int legendPlacement = LegendAreaProperties.BOTTOM; + + protected Shape pointShape = PointChartProperties.SHAPE_CIRCLE; + + protected float strokeWidth = 3.5f; + + /** + * Constructor + */ + public RespTimeGraphChart() { + super(); + } + + /** + * Constructor + * + * @param layout + * The {@link LayoutManager} to be used + */ + public RespTimeGraphChart(LayoutManager layout) { + super(layout); + } + + /** + * Constructor + * + * @param layout + * The {@link LayoutManager} to be used + * @param isDoubleBuffered + * Flag whether double buffering should be used + */ + public RespTimeGraphChart(LayoutManager layout, boolean isDoubleBuffered) { + super(layout, isDoubleBuffered); + } + + public void setData(double[][] data) { + this.data = data; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setXAxisTitle(String title) { + this.xAxisTitle = title; + } + + public void setYAxisTitle(String title) { + this.yAxisTitle = title; + } + + public void setXAxisLabels(String[] labels) { + this.xAxisLabels = labels; + } + + public void setYAxisLabels(String label) { + this.yAxisLabel = label; + } + + public void setLegendLabels(String[] labels) { + this.legendLabels = labels; + } + + public void setWidth(int w) { + this.width = w; + } + + public void setHeight(int h) { + this.height = h; + } + + /** + * @param incrYAxisScale the incrYAxisScale to set + */ + public void setIncrYAxisScale(int incrYAxisScale) { + this.incrYAxisScale = incrYAxisScale; + } + + /** + * @return the maxYAxisScale + */ + public int getMaxYAxisScale() { + return maxYAxisScale; + } + + /** + * @param maxYAxisScale the maxYAxisScale to set + */ + public void setMaxYAxisScale(int maxYAxisScale) { + this.maxYAxisScale = maxYAxisScale; + } + + /** + * @return the color + */ + public Color[] getColor() { + return color; + } + + /** + * @param color the color to set + */ + public void setColor(Color[] color) { + this.color = color; + } + + /** + * @return the titleFont + */ + public Font getTitleFont() { + return titleFont; + } + + /** + * @param titleFont the titleFont to set + */ + public void setTitleFont(Font titleFont) { + this.titleFont = titleFont; + } + + /** + * @return the legendFont + */ + public Font getLegendFont() { + return legendFont; + } + + /** + * @param legendFont the legendFont to set + */ + public void setLegendFont(Font legendFont) { + this.legendFont = legendFont; + } + + /** + * @return the legendPlacement + */ + public int getLegendPlacement() { + return legendPlacement; + } + + /** + * @param legendPlacement the legendPlacement to set + */ + public void setLegendPlacement(int legendPlacement) { + this.legendPlacement = legendPlacement; + } + + /** + * @return the pointShape + */ + public Shape getPointShape() { + return pointShape; + } + + /** + * @param pointShape the pointShape to set + */ + public void setPointShape(Shape pointShape) { + this.pointShape = pointShape; + } + + /** + * @return the strokeWidth + */ + public float getStrokeWidth() { + return strokeWidth; + } + + /** + * @param strokeWidth the strokeWidth to set + */ + public void setStrokeWidth(float strokeWidth) { + this.strokeWidth = strokeWidth; + } + + /** + * @return the showGrouping + */ + public boolean isShowGrouping() { + return showGrouping; + } + + /** + * @param showGrouping the showGrouping to set + */ + public void setShowGrouping(boolean showGrouping) { + this.showGrouping = showGrouping; + } + + private void drawSample(String _title, String[] _xAxisLabels, + String _yAxisTitle, String[] _legendLabels, + double[][] _data, int _width, int _height, int _incrScaleYAxis, + Color[] _color, Font legendFont, Graphics g) { + + double max = maxYAxisScale > 0 ? maxYAxisScale : getTopValue(findMax(_data), BigDecimal.ROUND_UP); // define max scale y axis + try { + // if the title graph is empty, we can assume some default + if (_title.length() == 0 ) { + _title = JMeterUtils.getResString("graph_resp_time_title"); //$NON-NLS-1$ + } + this.setPreferredSize(new Dimension(_width,_height)); + DataSeries dataSeries = new DataSeries( _xAxisLabels, null, _yAxisTitle, _title ); // replace _xAxisTitle to null (don't display x axis title) + + // Stroke and shape line settings + Stroke[] strokes = new Stroke[_legendLabels.length]; + for (int i = 0; i < _legendLabels.length; i++) { + strokes[i] = new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 5f); + } + Shape[] shapes = new Shape[_legendLabels.length]; + for (int i = 0; i < _legendLabels.length; i++) { + shapes[i] = pointShape; + } + LineChartProperties lineChartProperties= new LineChartProperties(strokes, shapes); + + // Lines colors + Paint[] paints = new Paint[_color.length]; + System.arraycopy(_color, 0, paints, 0, _color.length); + + // Define chart type (line) + AxisChartDataSet axisChartDataSet = + new AxisChartDataSet( _data, _legendLabels, paints, ChartType.LINE, lineChartProperties ); + dataSeries.addIAxisPlotDataSet(axisChartDataSet); + + ChartProperties chartProperties= new ChartProperties(); + LabelAxisProperties xaxis = new LabelAxisProperties(); + DataAxisProperties yaxis = new DataAxisProperties(); + yaxis.setUseCommas(showGrouping); + + if (legendFont != null) { + yaxis.setAxisTitleChartFont(new ChartFont(legendFont, new Color(20))); + yaxis.setScaleChartFont(new ChartFont(legendFont, new Color(20))); + xaxis.setAxisTitleChartFont(new ChartFont(legendFont, new Color(20))); + xaxis.setScaleChartFont(new ChartFont(legendFont, new Color(20))); + } + if (titleFont != null) { + chartProperties.setTitleFont(new ChartFont(titleFont, new Color(0))); + } + + // Y Axis ruler + try { + double numInterval = _height / 50d; // ~a tic every 50 px + double incrYAxis = max / numInterval; + double incrTopValue = _incrScaleYAxis; + if (_incrScaleYAxis == 0) { + incrTopValue = getTopValue(incrYAxis, BigDecimal.ROUND_HALF_UP); + } + if (incrTopValue < 1) { + incrTopValue = 1.0d; // Increment cannot be < 1 + } + yaxis.setUserDefinedScale(0, incrTopValue); + yaxis.setNumItems((int)(max / incrTopValue) + 1); + yaxis.setShowGridLines(1); + } catch (PropertyException e) { + log.warn("",e); + } + + AxisProperties axisProperties= new AxisProperties(xaxis, yaxis); + axisProperties.setXAxisLabelsAreVertical(true); + LegendProperties legendProperties= new LegendProperties(); + legendProperties.setBorderStroke(null); + legendProperties.setPlacement(legendPlacement); + legendProperties.setIconBorderPaint(Color.WHITE); + legendProperties.setIconBorderStroke(new BasicStroke(0f, BasicStroke.CAP_SQUARE, BasicStroke.CAP_SQUARE)); + // Manage legend placement + legendProperties.setNumColumns(LegendAreaProperties.COLUMNS_FIT_TO_IMAGE); + if (legendPlacement == LegendAreaProperties.RIGHT || legendPlacement == LegendAreaProperties.LEFT) { + legendProperties.setNumColumns(1); + } + if (legendFont != null) { + legendProperties.setFont(legendFont); + } + AxisChart axisChart = new AxisChart( + dataSeries, chartProperties, axisProperties, + legendProperties, _width, _height ); + axisChart.setGraphics2D((Graphics2D) g); + axisChart.render(); + } catch (ChartDataException e) { + log.warn("", e); + } catch (PropertyException e) { + log.warn("", e); + } + } + + private int getTopValue(double value, int roundMode) { + String maxStr = String.valueOf(Math.round(value)); + StringBuilder divValueStr = new StringBuilder(maxStr.length()+1); + divValueStr.append("1"); + for (int i = 1; i < maxStr.length(); i++) { + divValueStr.append("0"); //$NON-NLS-1$ + } + int divValueInt = Integer.parseInt(divValueStr.toString()); + BigDecimal round = new BigDecimal(value / divValueInt); + round = round.setScale(0, roundMode); + int topValue = round.intValue() * divValueInt; + return topValue; + } + + @Override + public void paintComponent(Graphics graphics) { + if (data != null && this.title != null && this.xAxisLabels != null && + this.yAxisLabel != null && this.yAxisTitle != null) { + drawSample(this.title, this.xAxisLabels, + this.yAxisTitle, this.legendLabels, + this.data, this.width, this.height, this.incrYAxisScale, this.color, + this.legendFont, graphics); + } + } + + /** + * Find max in datas + * @param datas array of positive or NaN doubles + * @return double + */ + private double findMax(double datas[][]) { + double max = 0; + for (int i = 0; i < datas.length; i++) { + for (int j = 0; j < datas[i].length; j++) { + final double value = datas[i][j]; + if ((!Double.isNaN(value)) && (value > max)) { + max = value; + } + } + } + return max; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/RespTimeGraphDataBean.java b/src/components/org/apache/jmeter/visualizers/RespTimeGraphDataBean.java new file mode 100644 index 00000000000..892208d2dd1 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RespTimeGraphDataBean.java @@ -0,0 +1,94 @@ +/* + * 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.visualizers; + +/** + * Bean to hold timing information about samples + * + */ +public class RespTimeGraphDataBean { + + private long startTime; + + private long time; + + private String samplerLabel; + + /** + * Constructor + * + * @param startTime + * The start time of this Sample + * @param time + * The time elapsed for this sample + * @param samplerLabel + * The label for this sample + */ + public RespTimeGraphDataBean(long startTime, long time, String samplerLabel) { + super(); + this.startTime = startTime; + this.time = time; + this.samplerLabel = samplerLabel; + } + + /** + * @return the startTime + */ + public long getStartTime() { + return startTime; + } + + /** + * @param startTime the startTime to set + */ + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + /** + * @return the time + */ + public long getTime() { + return time; + } + + /** + * @param time the time to set + */ + public void setTime(long time) { + this.time = time; + } + + /** + * @return the samplerLabel + */ + public String getSamplerLabel() { + return samplerLabel; + } + + /** + * @param samplerLabel the samplerLabel to set + */ + public void setSamplerLabel(String samplerLabel) { + this.samplerLabel = samplerLabel; + } + + +} diff --git a/src/components/org/apache/jmeter/visualizers/RespTimeGraphLineBean.java b/src/components/org/apache/jmeter/visualizers/RespTimeGraphLineBean.java new file mode 100644 index 00000000000..e6f7aa03f30 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RespTimeGraphLineBean.java @@ -0,0 +1,70 @@ +/* + * 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.visualizers; + +import java.awt.Color; + +/** + * Bean to represent information about a graph line + */ +public class RespTimeGraphLineBean { + + private String label; + + private Color lineColor; + + /** + * @param label The label for this line + * @param lineColor The {@link Color} for this line + */ + public RespTimeGraphLineBean(String label, Color lineColor) { + super(); + this.label = label; + this.lineColor = lineColor; + } + + /** + * @return the label + */ + public String getLabel() { + return label; + } + + /** + * @param label the label to set + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * @return the lineColor + */ + public Color getLineColor() { + return lineColor; + } + + /** + * @param lineColor the lineColor to set + */ + public void setLineColor(Color lineColor) { + this.lineColor = lineColor; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/RespTimeGraphVisualizer.java b/src/components/org/apache/jmeter/visualizers/RespTimeGraphVisualizer.java new file mode 100644 index 00000000000..c3b091b18ae --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/RespTimeGraphVisualizer.java @@ -0,0 +1,965 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.SaveGraphics; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jmeter.visualizers.utils.Colors; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.math.StatCalculatorLong; +import org.apache.log.Logger; + +public class RespTimeGraphVisualizer extends AbstractVisualizer implements ActionListener, Clearable { + + private static final long serialVersionUID = 280L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Font FONT_SMALL = new Font("SansSerif", Font.PLAIN, 10); + + //+ JMX property names; do not change + + public static final String INTERVAL = "RespTimeGraph.interval"; // $NON-NLS-1$ + + public static final String SERIES_SELECTION = "RespTimeGraph.seriesselection"; // $NON-NLS-1$ + + public static final String SERIES_SELECTION_MATCH_LABEL = "RespTimeGraph.seriesselectionmatchlabel"; // $NON-NLS-1$ + + public static final String SERIES_SELECTION_CASE_SENSITIVE = "RespTimeGraph.seriesselectioncasesensitive"; // $NON-NLS-1$ + + public static final String SERIES_SELECTION_REGEXP = "RespTimeGraph.seriesselectionregexp"; // $NON-NLS-1$ + + public static final String GRAPH_TITLE = "RespTimeGraph.graphtitle"; // $NON-NLS-1$ + + public static final String GRAPH_TITLE_FONT_NAME = "RespTimeGraph.graphtitlefontname"; // $NON-NLS-1$ + + public static final String GRAPH_TITLE_FONT_SIZE = "RespTimeGraph.graphtitlefondsize"; // $NON-NLS-1$ + + public static final String GRAPH_TITLE_FONT_STYLE = "RespTimeGraph.graphtitlefontstyle"; // $NON-NLS-1$ + + public static final String LINE_STROKE_WIDTH = "RespTimeGraph.linestrockwidth"; // $NON-NLS-1$ + + public static final String LINE_SHAPE_POINT = "RespTimeGraph.lineshapepoint"; // $NON-NLS-1$ + + public static final String GRAPH_SIZE_DYNAMIC = "RespTimeGraph.graphsizedynamic"; // $NON-NLS-1$ + + public static final String GRAPH_SIZE_WIDTH = "RespTimeGraph.graphsizewidth"; // $NON-NLS-1$ + + public static final String GRAPH_SIZE_HEIGHT = "RespTimeGraph.graphsizeheight"; // $NON-NLS-1$ + + public static final String XAXIS_TIME_FORMAT = "RespTimeGraph.xaxistimeformat"; // $NON-NLS-1$ + + public static final String YAXIS_SCALE_MAX_VALUE = "RespTimeGraph.yaxisscalemaxvalue"; // $NON-NLS-1$ + + public static final String YAXIS_INCREMENT_SCALE = "RespTimeGraph.yaxisscaleincrement"; // $NON-NLS-1$ + + public static final String YAXIS_NUMBER_GROUPING = "RespTimeGraph.yaxisnumbergrouping"; // $NON-NLS-1$ + + public static final String LEGEND_PLACEMENT = "RespTimeGraph.legendplacement"; // $NON-NLS-1$ + + public static final String LEGEND_FONT = "RespTimeGraph.legendfont"; // $NON-NLS-1$ + + public static final String LEGEND_SIZE = "RespTimeGraph.legendsize"; // $NON-NLS-1$ + + public static final String LEGEND_STYLE = "RespTimeGraph.legendstyle"; // $NON-NLS-1$ + + //- JMX property names + + public static final int DEFAULT_INTERVAL = 10000; // in milli-seconds // TODO: properties? + + public static final boolean DEFAULT_SERIES_SELECTION = false; + + public static final boolean DEFAULT_CASE_SENSITIVE = false; + + public static final boolean DEFAULT_REGEXP = true; + + public static final int DEFAULT_TITLE_FONT_NAME = 0; // default: sans serif + + public static final int DEFAULT_TITLE_FONT_SIZE = 6; // default: 16 + + public static final int DEFAULT_TITLE_FONT_STYLE = 1; // default: bold + + public static final int DEFAULT_STROKE_WIDTH_LIST = 4; // default: 3.0f + + public static final int DEFAULT_LINE_SHAPE_POINT = 0; // default: circle + + public static final boolean DEFAULT_DYNAMIC_GRAPH_SIZE = true; // default: true + + public static final String DEFAULT_XAXIS_TIME_FORMAT = "HH:mm:ss"; // $NON-NLS-1$ + + public static final boolean DEFAULT_NUMBER_SHOW_GROUPING = true; + + public static final int DEFAULT_LEGEND_PLACEMENT = 0; // default: bottom + + public static final int DEFAULT_LEGEND_FONT = 0; // default: sans serif + + public static final int DEFAULT_LEGEND_SIZE = 2; // default: 10 + + public static final int DEFAULT_LEGEND_STYLE = 0; // default: normal + + /** + * Lock used to protect list update + */ + private final transient Object lock = new Object(); + /** + * Lock used to protect refresh interval + */ + private final transient Object lockInterval = new Object(); + + private static final String Y_AXIS_LABEL = JMeterUtils.getResString("aggregate_graph_response_time");//$NON-NLS-1$ + + private static final String Y_AXIS_TITLE = JMeterUtils.getResString("aggregate_graph_ms"); //$NON-NLS-1$ + + private RespTimeGraphChart graphPanel = null; + + private final JTabbedPane tabbedGraph = new JTabbedPane(SwingConstants.TOP); + + private boolean saveGraphToFile = false; + + private static final int DEFAULT_WIDTH = 400; + + private static final int DEFAULT_HEIGTH = 300; + + private int intervalValue = DEFAULT_INTERVAL; + + private final JLabeledTextField intervalField = + new JLabeledTextField(JMeterUtils.getResString("graph_resp_time_interval_label"), 7); //$NON-NLS-1$ + + private final JButton intervalButton = new JButton(JMeterUtils.getResString("graph_resp_time_interval_reload")); // $NON-NLS-1$ + + private final JButton displayButton = + new JButton(JMeterUtils.getResString("aggregate_graph_display")); //$NON-NLS-1$ + + private final JButton saveGraph = + new JButton(JMeterUtils.getResString("aggregate_graph_save")); //$NON-NLS-1$ + + private final JCheckBox samplerSelection = new JCheckBox(JMeterUtils.getResString("graph_resp_time_series_selection"), false); //$NON-NLS-1$ + + private final JTextField samplerMatchLabel = new JTextField(); + + private final JButton applyFilterBtn = new JButton(JMeterUtils.getResString("graph_apply_filter")); // $NON-NLS-1$ + + private final JCheckBox caseChkBox = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_case"), false); // $NON-NLS-1$ + + private final JCheckBox regexpChkBox = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_regexp"), true); // $NON-NLS-1$ + + private final JComboBox titleFontNameList = new JComboBox(StatGraphProperties.getFontNameMap().keySet().toArray()); + + private final JComboBox titleFontSizeList = new JComboBox(StatGraphProperties.fontSize); + + private final JComboBox titleFontStyleList = new JComboBox(StatGraphProperties.getFontStyleMap().keySet().toArray()); + + private final JComboBox fontNameList = new JComboBox(StatGraphProperties.getFontNameMap().keySet().toArray()); + + private final JComboBox fontSizeList = new JComboBox(StatGraphProperties.fontSize); + + private final JComboBox fontStyleList = new JComboBox(StatGraphProperties.getFontStyleMap().keySet().toArray()); + + private final JComboBox legendPlacementList = new JComboBox(StatGraphProperties.getPlacementNameMap().keySet().toArray()); + + private final JComboBox pointShapeLine = new JComboBox(StatGraphProperties.getPointShapeMap().keySet().toArray()); + + private final JComboBox strokeWidthList = new JComboBox(StatGraphProperties.strokeWidth); + + private final JCheckBox numberShowGrouping = new JCheckBox(JMeterUtils.getResString("aggregate_graph_number_grouping"), // $NON-NLS-1$ + DEFAULT_NUMBER_SHOW_GROUPING); // Default checked + + private final JButton syncWithName = + new JButton(JMeterUtils.getResString("aggregate_graph_sync_with_name")); //$NON-NLS-1$ + + private final JLabeledTextField graphTitle = + new JLabeledTextField(JMeterUtils.getResString("graph_resp_time_title_label")); //$NON-NLS-1$ + + private final JLabeledTextField xAxisTimeFormat = + new JLabeledTextField(JMeterUtils.getResString("graph_resp_time_xaxis_time_format"), 10); //$NON-NLS-1$ $NON-NLS-2$ + + private final JLabeledTextField maxValueYAxisLabel = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_yaxis_max_value"), 5); //$NON-NLS-1$ + + /** + * checkbox for use dynamic graph size + */ + private final JCheckBox dynamicGraphSize = new JCheckBox(JMeterUtils.getResString("aggregate_graph_dynamic_size")); // $NON-NLS-1$ + + private final JLabeledTextField graphWidth = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_width"), 6); //$NON-NLS-1$ + private final JLabeledTextField graphHeight = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_height"), 6); //$NON-NLS-1$ + + private final JLabeledTextField incrScaleYAxis = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_increment_scale"), 5); //$NON-NLS-1$ + + private long minStartTime = Long.MAX_VALUE; + + private long maxStartTime = Long.MIN_VALUE; + + /** + * We want to retain insertion order, so LinkedHashMap is necessary + */ + private final Map seriesNames = new LinkedHashMap(); + + /** + * We want to retain insertion order, so LinkedHashMap is necessary + */ + private final Map> pList = new LinkedHashMap>(); + + private long durationTest = 0; + + private int colorIdx = 0; + + private Pattern pattern = null; + + private transient Matcher matcher = null; + + private final List listColors = Colors.getColors(); + + private final List internalList = new ArrayList(); // internal list of all results + + public RespTimeGraphVisualizer() { + init(); + } + + @Override + public void add(final SampleResult sampleResult) { + final String sampleLabel = sampleResult.getSampleLabel(); + // Make a internal list of all results to allow reload data with filter or interval + synchronized (lockInterval) { + internalList.add(new RespTimeGraphDataBean(sampleResult.getStartTime(), sampleResult.getTime(), sampleLabel)); + } + + // Sampler selection + if (samplerSelection.isSelected() && pattern != null) { + matcher = pattern.matcher(sampleLabel); + } + if ((matcher == null) || (matcher.find())) { + final long startTimeMS = sampleResult.getStartTime(); + final long startTimeInterval = startTimeMS / intervalValue; + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + synchronized (lock) { + // Use for x-axis scale + if (startTimeInterval < minStartTime) { + minStartTime = startTimeInterval; + } else if (startTimeInterval > maxStartTime) { + maxStartTime = startTimeInterval; + } + // Generate x-axis label and associated color + if (!seriesNames.containsKey(sampleLabel)) { + seriesNames.put(sampleLabel, + new RespTimeGraphLineBean(sampleLabel, listColors.get(colorIdx++))); + // reset colors index + if (colorIdx >= listColors.size()) { + colorIdx = 0; + } + } + // List of value by sampler + Map subList = pList.get(sampleLabel); + final Long startTimeIntervalLong = Long.valueOf(startTimeInterval); + if (subList != null) { + long respTime = sampleResult.getTime(); + StatCalculatorLong value = subList.get(startTimeIntervalLong); + if (value==null) { + value = new StatCalculatorLong(); + subList.put(startTimeIntervalLong, value); + } + value.addValue(respTime, 1); + } else { + // We want to retain insertion order, so LinkedHashMap is necessary + Map newSubList = new LinkedHashMap(5); + StatCalculatorLong helper = new StatCalculatorLong(); + helper.addValue(Long.valueOf(sampleResult.getTime()),1); + newSubList.put(startTimeIntervalLong, helper); + pList.put(sampleLabel, newSubList); + } + } + } + }); + } + } + + public void makeGraph() { + Dimension size = graphPanel.getSize(); + // canvas size + int width = (int) size.getWidth(); + int height = (int) size.getHeight(); + if (!dynamicGraphSize.isSelected()) { + String wstr = graphWidth.getText(); + String hstr = graphHeight.getText(); + if (wstr.length() != 0) { + width = Integer.parseInt(wstr); + } + if (hstr.length() != 0) { + height = Integer.parseInt(hstr); + } + } + + String yAxisStr = maxValueYAxisLabel.getText(); + int maxYAxisScale = yAxisStr.length() == 0 ? 0 : Integer.parseInt(yAxisStr); + + graphPanel.setData(this.getData()); + graphPanel.setTitle(graphTitle.getText()); + graphPanel.setMaxYAxisScale(maxYAxisScale); + + graphPanel.setYAxisLabels(Y_AXIS_LABEL); + graphPanel.setYAxisTitle(Y_AXIS_TITLE); + graphPanel.setXAxisLabels(getXAxisLabels()); + graphPanel.setLegendLabels(getLegendLabels()); + graphPanel.setColor(getLinesColors()); + graphPanel.setShowGrouping(numberShowGrouping.isSelected()); + graphPanel.setLegendPlacement(StatGraphProperties.getPlacementNameMap() + .get(legendPlacementList.getSelectedItem()).intValue()); + graphPanel.setPointShape(StatGraphProperties.getPointShapeMap().get(pointShapeLine.getSelectedItem())); + graphPanel.setStrokeWidth(Float.parseFloat((String) strokeWidthList.getSelectedItem())); + + graphPanel.setTitleFont(new Font(StatGraphProperties.getFontNameMap().get(titleFontNameList.getSelectedItem()), + StatGraphProperties.getFontStyleMap().get(titleFontStyleList.getSelectedItem()).intValue(), + Integer.parseInt((String) titleFontSizeList.getSelectedItem()))); + graphPanel.setLegendFont(new Font(StatGraphProperties.getFontNameMap().get(fontNameList.getSelectedItem()), + StatGraphProperties.getFontStyleMap().get(fontStyleList.getSelectedItem()).intValue(), + Integer.parseInt((String) fontSizeList.getSelectedItem()))); + + graphPanel.setHeight(height); + graphPanel.setWidth(width); + graphPanel.setIncrYAxisScale(getIncrScaleYAxis()); + // Draw the graph + graphPanel.repaint(); + } + + /** + * Generate the data for the jChart API + * @return array of array of data to draw + */ + public double[][] getData() { + int size = pList.size(); + int max = (int) durationTest; // Test can't have a duration more than 2^31 secs (cast from long to int) + + double[][] data = new double[size][max]; + + double nanLast = 0; + double nanBegin = 0; + List nanList = new ArrayList(); + int s = 0; + for (Map subList : pList.values()) { + int idx = 0; + while (idx < durationTest) { + long keyShift = minStartTime + idx; + StatCalculatorLong value = subList.get(Long.valueOf(keyShift)); + if (value != null) { + nanLast = value.getMean(); + data[s][idx] = nanLast; + // Calculate intermediate values (if needed) + int nlsize = nanList.size(); + if (nlsize > 0) { + double valPrev = nanBegin; + for (int cnt = 0; cnt < nlsize; cnt++) { + int pos = idx - (nlsize - cnt); + if (pos < 0) { pos = 0; } + valPrev = (valPrev + ((nanLast - nanBegin) / (nlsize + 2))); + data[s][pos] = valPrev; + } + nanList.clear(); + } + } else { + nanList.add(Double.valueOf(Double.NaN)); + nanBegin = nanLast; + data[s][idx] = Double.NaN; + } + // log.debug("data["+s+"]["+idx+"]: " + data[s][idx]); + idx++; + } + s++; + } + return data; + } + + @Override + public String getLabelResource() { + return "graph_resp_time_title"; // $NON-NLS-1$ + } + + @Override + public void clearData() { + synchronized (lock) { + internalList.clear(); + seriesNames.clear(); + pList.clear(); + minStartTime = Long.MAX_VALUE; + maxStartTime = Long.MIN_VALUE; + durationTest = 0; + colorIdx = 0; + } + tabbedGraph.setSelectedIndex(0); + } + + /** + * Initialize the GUI. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + Border margin2 = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.add(makeTitlePanel()); + + JPanel settingsPane = new VerticalPanel(); + settingsPane.setBorder(margin2); + + graphPanel = new RespTimeGraphChart(); + graphPanel.setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGTH)); + + settingsPane.add(createGraphActionsPane()); + settingsPane.add(createGraphSettingsPane()); + settingsPane.add(createGraphTitlePane()); + settingsPane.add(createLinePane()); + settingsPane.add(createGraphDimensionPane()); + JPanel axisPane = new JPanel(new BorderLayout()); + axisPane.add(createGraphXAxisPane(), BorderLayout.WEST); + axisPane.add(createGraphYAxisPane(), BorderLayout.CENTER); + settingsPane.add(axisPane); + settingsPane.add(createLegendPane()); + + tabbedGraph.addTab(JMeterUtils.getResString("aggregate_graph_tab_settings"), settingsPane); //$NON-NLS-1$ + tabbedGraph.addTab(JMeterUtils.getResString("aggregate_graph_tab_graph"), graphPanel); //$NON-NLS-1$ + + // If clic on the Graph tab, make the graph (without apply interval or filter) + ChangeListener changeListener = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent changeEvent) { + JTabbedPane srcTab = (JTabbedPane) changeEvent.getSource(); + int index = srcTab.getSelectedIndex(); + if (srcTab.getTitleAt(index).equals(JMeterUtils.getResString("aggregate_graph_tab_graph"))) { //$NON-NLS-1$ + actionMakeGraph(); + } + } + }; + tabbedGraph.addChangeListener(changeListener); + + this.add(mainPanel, BorderLayout.NORTH); + this.add(tabbedGraph, BorderLayout.CENTER); + + } + + @Override + public void actionPerformed(ActionEvent event) { + boolean forceReloadData = false; + final Object eventSource = event.getSource(); + if (eventSource == displayButton) { + actionMakeGraph(); + } else if (eventSource == saveGraph) { + saveGraphToFile = true; + try { + ActionRouter.getInstance().getAction( + ActionNames.SAVE_GRAPHICS,SaveGraphics.class.getName()).doAction( + new ActionEvent(this,event.getID(),ActionNames.SAVE_GRAPHICS)); + } catch (Exception e) { + log.error(e.getMessage()); + } + } else if (eventSource == syncWithName) { + graphTitle.setText(namePanel.getName()); + } else if (eventSource == dynamicGraphSize) { + enableDynamicGraph(dynamicGraphSize.isSelected()); + } else if (eventSource == samplerSelection) { + enableSamplerSelection(samplerSelection.isSelected()); + if (!samplerSelection.isSelected()) { + // Force reload data + forceReloadData = true; + } + } + // Not 'else if' because forceReloadData + if (eventSource == applyFilterBtn || eventSource == intervalButton || forceReloadData) { + if (eventSource == intervalButton) { + intervalValue = Integer.parseInt(intervalField.getText()); + } + if (eventSource == applyFilterBtn && samplerSelection.isSelected() && samplerMatchLabel.getText() != null + && samplerMatchLabel.getText().length() > 0) { + pattern = createPattern(samplerMatchLabel.getText()); + } else if (forceReloadData) { + pattern = null; + matcher = null; + } + if (getFile() != null && getFile().length() > 0) { + // Reload data from file + clearData(); + FilePanel filePanel = (FilePanel) getFilePanel(); + filePanel.actionPerformed(event); + } else { + // Reload data form internal list of results + synchronized (lockInterval) { + if (internalList.size() >= 2) { + List tempList = new ArrayList(); + tempList.addAll(internalList); + this.clearData(); + for (RespTimeGraphDataBean data : tempList) { + SampleResult sr = new SampleResult(data.getStartTime(), data.getTime()); + sr.setSampleLabel(data.getSamplerLabel()); + this.add(sr); + } + } + } + } + } + } + + private void actionMakeGraph() { + String msgErr = null; + // Calculate the test duration. Needs to xAxis Labels and getData. + durationTest = maxStartTime - minStartTime; + if (seriesNames.size() <= 0) { + msgErr = JMeterUtils.getResString("aggregate_graph_no_values_to_graph"); // $NON-NLS-1$ + } else if (durationTest < 1) { + msgErr = JMeterUtils.getResString("graph_resp_time_not_enough_data"); // $NON-NLS-1$ + } + if (msgErr == null) { + makeGraph(); + tabbedGraph.setSelectedIndex(1); + } else { + tabbedGraph.setSelectedIndex(0); + JOptionPane.showMessageDialog(null, msgErr, msgErr, JOptionPane.WARNING_MESSAGE); + } + } + + @Override + public JComponent getPrintableComponent() { + if (saveGraphToFile == true) { + saveGraphToFile = false; + graphPanel.setBounds(graphPanel.getLocation().x,graphPanel.getLocation().y, + graphPanel.width,graphPanel.height); + return graphPanel; + } + return this; + } + + @Override + public void configure(TestElement te) { + super.configure(te); + intervalField.setText(te.getPropertyAsString(INTERVAL, String.valueOf(DEFAULT_INTERVAL))); + samplerSelection.setSelected(te.getPropertyAsBoolean(SERIES_SELECTION, DEFAULT_SERIES_SELECTION)); + samplerMatchLabel.setText(te.getPropertyAsString(SERIES_SELECTION_MATCH_LABEL, "")); //$NON-NLS-1$ + caseChkBox.setSelected(te.getPropertyAsBoolean(SERIES_SELECTION_CASE_SENSITIVE, DEFAULT_CASE_SENSITIVE)); + regexpChkBox.setSelected(te.getPropertyAsBoolean(SERIES_SELECTION_REGEXP, DEFAULT_REGEXP)); + graphTitle.setText(te.getPropertyAsString(GRAPH_TITLE, "")); //$NON-NLS-1$ + titleFontNameList.setSelectedIndex(te.getPropertyAsInt(GRAPH_TITLE_FONT_NAME, DEFAULT_TITLE_FONT_NAME)); + titleFontSizeList.setSelectedIndex(te.getPropertyAsInt(GRAPH_TITLE_FONT_SIZE, DEFAULT_TITLE_FONT_SIZE)); + titleFontStyleList.setSelectedIndex(te.getPropertyAsInt(GRAPH_TITLE_FONT_STYLE, DEFAULT_TITLE_FONT_STYLE)); + strokeWidthList.setSelectedIndex(te.getPropertyAsInt(LINE_STROKE_WIDTH, DEFAULT_STROKE_WIDTH_LIST)); + pointShapeLine.setSelectedIndex(te.getPropertyAsInt(LINE_SHAPE_POINT, DEFAULT_LINE_SHAPE_POINT)); + dynamicGraphSize.setSelected(te.getPropertyAsBoolean(GRAPH_SIZE_DYNAMIC, DEFAULT_DYNAMIC_GRAPH_SIZE)); + graphWidth.setText(te.getPropertyAsString(GRAPH_SIZE_WIDTH, "")); //$NON-NLS-1$ + graphHeight.setText(te.getPropertyAsString(GRAPH_SIZE_HEIGHT, "")); //$NON-NLS-1$ + xAxisTimeFormat.setText(te.getPropertyAsString(XAXIS_TIME_FORMAT, DEFAULT_XAXIS_TIME_FORMAT)); + maxValueYAxisLabel.setText(te.getPropertyAsString(YAXIS_SCALE_MAX_VALUE, "")); //$NON-NLS-1$ + incrScaleYAxis.setText(te.getPropertyAsString(YAXIS_INCREMENT_SCALE, "")); //$NON-NLS-1$ + numberShowGrouping.setSelected(te.getPropertyAsBoolean(YAXIS_NUMBER_GROUPING, DEFAULT_NUMBER_SHOW_GROUPING)); + legendPlacementList.setSelectedIndex(te.getPropertyAsInt(LEGEND_PLACEMENT, DEFAULT_LEGEND_PLACEMENT)); + fontNameList.setSelectedIndex(te.getPropertyAsInt(LEGEND_FONT, DEFAULT_LEGEND_FONT)); + fontSizeList.setSelectedIndex(te.getPropertyAsInt(LEGEND_SIZE, DEFAULT_LEGEND_SIZE)); + fontStyleList.setSelectedIndex(te.getPropertyAsInt(LEGEND_STYLE, DEFAULT_LEGEND_STYLE)); + + enableSamplerSelection(samplerSelection.isSelected()); + enableDynamicGraph(dynamicGraphSize.isSelected()); + } + + @Override + public void modifyTestElement(TestElement te) { + super.modifyTestElement(te); + te.setProperty(INTERVAL, intervalField.getText(), String.valueOf(DEFAULT_INTERVAL)); + te.setProperty(SERIES_SELECTION, samplerSelection.isSelected(), DEFAULT_SERIES_SELECTION); + te.setProperty(SERIES_SELECTION_MATCH_LABEL, samplerMatchLabel.getText(), ""); //$NON-NLS-1$ + te.setProperty(SERIES_SELECTION_CASE_SENSITIVE, caseChkBox.isSelected(), DEFAULT_CASE_SENSITIVE); + te.setProperty(SERIES_SELECTION_REGEXP, regexpChkBox.isSelected(), DEFAULT_REGEXP); + te.setProperty(GRAPH_TITLE, graphTitle.getText(), ""); //$NON-NLS-1$ + te.setProperty(GRAPH_TITLE_FONT_NAME, titleFontNameList.getSelectedIndex(), DEFAULT_TITLE_FONT_NAME); + te.setProperty(GRAPH_TITLE_FONT_SIZE, titleFontSizeList.getSelectedIndex(), DEFAULT_TITLE_FONT_SIZE); + te.setProperty(GRAPH_TITLE_FONT_STYLE, titleFontStyleList.getSelectedIndex(), DEFAULT_TITLE_FONT_STYLE); + te.setProperty(LINE_STROKE_WIDTH, strokeWidthList.getSelectedIndex(), DEFAULT_STROKE_WIDTH_LIST); + te.setProperty(LINE_SHAPE_POINT, pointShapeLine.getSelectedIndex(), DEFAULT_LINE_SHAPE_POINT); + te.setProperty(GRAPH_SIZE_DYNAMIC, dynamicGraphSize.isSelected(), DEFAULT_DYNAMIC_GRAPH_SIZE); + te.setProperty(GRAPH_SIZE_WIDTH, graphWidth.getText(), ""); //$NON-NLS-1$ + te.setProperty(GRAPH_SIZE_HEIGHT, graphHeight.getText(), ""); //$NON-NLS-1$ + te.setProperty(XAXIS_TIME_FORMAT, xAxisTimeFormat.getText(), DEFAULT_XAXIS_TIME_FORMAT); + te.setProperty(YAXIS_SCALE_MAX_VALUE, maxValueYAxisLabel.getText(), ""); //$NON-NLS-1$ + te.setProperty(YAXIS_INCREMENT_SCALE, incrScaleYAxis.getText(), ""); //$NON-NLS-1$ + te.setProperty(YAXIS_NUMBER_GROUPING, numberShowGrouping.isSelected(), DEFAULT_NUMBER_SHOW_GROUPING); + te.setProperty(LEGEND_PLACEMENT, legendPlacementList.getSelectedIndex(), DEFAULT_LEGEND_PLACEMENT); + te.setProperty(LEGEND_FONT, fontNameList.getSelectedIndex(), DEFAULT_LEGEND_FONT); + te.setProperty(LEGEND_SIZE, fontSizeList.getSelectedIndex(), DEFAULT_LEGEND_SIZE); + te.setProperty(LEGEND_STYLE, fontStyleList.getSelectedIndex(), DEFAULT_LEGEND_STYLE); + + // Update sub-element visibility and data reload if need + enableSamplerSelection(samplerSelection.isSelected()); + enableDynamicGraph(dynamicGraphSize.isSelected()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + intervalField.setText(String.valueOf(DEFAULT_INTERVAL)); + samplerSelection.setSelected(DEFAULT_SERIES_SELECTION); + samplerMatchLabel.setText( ""); //$NON-NLS-1$ + caseChkBox.setSelected(DEFAULT_CASE_SENSITIVE); + regexpChkBox.setSelected(DEFAULT_REGEXP); + graphTitle.setText(""); //$NON-NLS-1$ + titleFontNameList.setSelectedIndex(DEFAULT_TITLE_FONT_NAME); + titleFontSizeList.setSelectedIndex(DEFAULT_TITLE_FONT_SIZE); + titleFontStyleList.setSelectedIndex(DEFAULT_TITLE_FONT_STYLE); + strokeWidthList.setSelectedIndex(DEFAULT_STROKE_WIDTH_LIST); + pointShapeLine.setSelectedIndex(DEFAULT_LINE_SHAPE_POINT); + dynamicGraphSize.setSelected(DEFAULT_DYNAMIC_GRAPH_SIZE); + graphWidth.setText(""); //$NON-NLS-1$ + graphHeight.setText(""); //$NON-NLS-1$ + xAxisTimeFormat.setText(DEFAULT_XAXIS_TIME_FORMAT); + maxValueYAxisLabel.setText(""); //$NON-NLS-1$ + incrScaleYAxis.setText(""); //$NON-NLS-1$ + numberShowGrouping.setSelected(DEFAULT_NUMBER_SHOW_GROUPING); + legendPlacementList.setSelectedIndex(DEFAULT_LEGEND_PLACEMENT); + fontNameList.setSelectedIndex(DEFAULT_LEGEND_FONT); + fontSizeList.setSelectedIndex(DEFAULT_LEGEND_SIZE); + fontStyleList.setSelectedIndex(DEFAULT_LEGEND_STYLE); + } + + private JPanel createGraphActionsPane() { + JPanel buttonPanel = new JPanel(new BorderLayout()); + JPanel displayPane = new JPanel(); + displayPane.add(displayButton); + displayButton.addActionListener(this); + buttonPanel.add(displayPane, BorderLayout.WEST); + + JPanel savePane = new JPanel(); + savePane.add(saveGraph); + saveGraph.addActionListener(this); + syncWithName.addActionListener(this); + buttonPanel.add(savePane, BorderLayout.EAST); + + return buttonPanel; + } + + public String[] getXAxisLabels() { + SimpleDateFormat formatter = new SimpleDateFormat(xAxisTimeFormat.getText()); //$NON-NLS-1$ + String[] xAxisLabels = new String[(int) durationTest]; // Test can't have a duration more than 2^31 secs (cast from long to int) + for (int j = 0; j < durationTest; j++) { + xAxisLabels[j] = formatter.format(new Date((minStartTime + j) * intervalValue)); + } + return xAxisLabels; + } + + private String[] getLegendLabels() { + String[] legends = new String[seriesNames.size()]; + int i = 0; + for (Map.Entry entry : seriesNames.entrySet()) { + RespTimeGraphLineBean val = entry.getValue(); + legends[i] = val.getLabel(); + i++; + } + return legends; + } + + private Color[] getLinesColors() { + Color[] linesColors = new Color[seriesNames.size()]; + int i = 0; + for (Map.Entry entry : seriesNames.entrySet()) { + RespTimeGraphLineBean val = entry.getValue(); + linesColors[i] = val.getLineColor(); + i++; + } + return linesColors; + } + + private int getIncrScaleYAxis() { + int incrYAxisScale = 0; + String iyas = incrScaleYAxis.getText(); + if (iyas.length() != 0) { + incrYAxisScale = Integer.parseInt(iyas); + } + return incrYAxisScale; + } + + private JPanel createGraphSettingsPane() { + JPanel settingsPane = new JPanel(new BorderLayout()); + settingsPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("graph_resp_time_settings_pane"))); // $NON-NLS-1$ + + JPanel intervalPane = new JPanel(); + intervalPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + intervalField.setText(String.valueOf(DEFAULT_INTERVAL)); + intervalPane.add(intervalField); + + // Button + intervalButton.setFont(FONT_SMALL); + intervalButton.addActionListener(this); + intervalPane.add(intervalButton); + + settingsPane.add(intervalPane, BorderLayout.NORTH); + settingsPane.add(createGraphSelectionSubPane(), BorderLayout.SOUTH); + + return settingsPane; + } + + private JPanel createGraphSelectionSubPane() { + // Search field + JPanel searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); + searchPanel.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); + + searchPanel.add(samplerSelection); + samplerMatchLabel.setEnabled(false); + applyFilterBtn.setEnabled(false); + caseChkBox.setEnabled(false); + regexpChkBox.setEnabled(false); + samplerSelection.addActionListener(this); + + searchPanel.add(samplerMatchLabel); + searchPanel.add(Box.createRigidArea(new Dimension(5,0))); + + // Button + applyFilterBtn.setFont(FONT_SMALL); + applyFilterBtn.addActionListener(this); + searchPanel.add(applyFilterBtn); + + // checkboxes + caseChkBox.setFont(FONT_SMALL); + searchPanel.add(caseChkBox); + regexpChkBox.setFont(FONT_SMALL); + searchPanel.add(regexpChkBox); + + return searchPanel; + } + + private JPanel createGraphTitlePane() { + JPanel titleNamePane = new JPanel(new BorderLayout()); + syncWithName.setFont(FONT_SMALL); + titleNamePane.add(graphTitle, BorderLayout.CENTER); + titleNamePane.add(syncWithName, BorderLayout.EAST); + + JPanel titleStylePane = new JPanel(); + titleStylePane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 5)); + titleStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_font"), //$NON-NLS-1$ + titleFontNameList)); + titleFontNameList.setSelectedIndex(DEFAULT_TITLE_FONT_NAME); + titleStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_size"), //$NON-NLS-1$ + titleFontSizeList)); + titleFontSizeList.setSelectedItem(StatGraphProperties.fontSize[DEFAULT_TITLE_FONT_SIZE]); + titleStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_style"), //$NON-NLS-1$ + titleFontStyleList)); + titleFontStyleList.setSelectedIndex(DEFAULT_TITLE_FONT_STYLE); + + JPanel titlePane = new JPanel(new BorderLayout()); + titlePane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_title_group"))); // $NON-NLS-1$ + titlePane.add(titleNamePane, BorderLayout.NORTH); + titlePane.add(titleStylePane, BorderLayout.SOUTH); + return titlePane; + } + + private JPanel createLinePane() { + JPanel lineStylePane = new JPanel(); + lineStylePane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + lineStylePane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("graph_resp_time_settings_line"))); // $NON-NLS-1$ + lineStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("graph_resp_time_stroke_width"), //$NON-NLS-1$ + strokeWidthList)); + strokeWidthList.setSelectedItem(StatGraphProperties.strokeWidth[DEFAULT_STROKE_WIDTH_LIST]); + lineStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("graph_resp_time_shape_label"), //$NON-NLS-1$ + pointShapeLine)); + pointShapeLine.setSelectedIndex(DEFAULT_LINE_SHAPE_POINT); + return lineStylePane; + } + + private JPanel createGraphDimensionPane() { + JPanel dimensionPane = new JPanel(); + dimensionPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + dimensionPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_dimension"))); // $NON-NLS-1$ + + dimensionPane.add(dynamicGraphSize); + dynamicGraphSize.setSelected(DEFAULT_DYNAMIC_GRAPH_SIZE); + graphWidth.setEnabled(false); + graphHeight.setEnabled(false); + dynamicGraphSize.addActionListener(this); + dimensionPane.add(Box.createRigidArea(new Dimension(10,0))); + dimensionPane.add(graphWidth); + dimensionPane.add(Box.createRigidArea(new Dimension(5,0))); + dimensionPane.add(graphHeight); + return dimensionPane; + } + + /** + * Create pane for X Axis options + * @return X Axis pane + */ + private JPanel createGraphXAxisPane() { + JPanel xAxisPane = new JPanel(); + xAxisPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + xAxisPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_xaxis_group"))); // $NON-NLS-1$ + xAxisTimeFormat.setText(DEFAULT_XAXIS_TIME_FORMAT); // $NON-NLS-1$ + xAxisPane.add(xAxisTimeFormat); + return xAxisPane; + } + + /** + * Create pane for Y Axis options + * @return Y Axis pane + */ + private JPanel createGraphYAxisPane() { + JPanel yAxisPane = new JPanel(); + yAxisPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + yAxisPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_yaxis_group"))); // $NON-NLS-1$ + yAxisPane.add(maxValueYAxisLabel); + yAxisPane.add(incrScaleYAxis); + yAxisPane.add(numberShowGrouping); + return yAxisPane; + } + + /** + * Create pane for legend settings + * @return Legend pane + */ + private JPanel createLegendPane() { + JPanel legendPanel = new JPanel(); + legendPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + legendPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_legend"))); // $NON-NLS-1$ + + legendPanel.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_legend_placement"), //$NON-NLS-1$ + legendPlacementList)); + legendPlacementList.setSelectedIndex(DEFAULT_LEGEND_PLACEMENT); + legendPanel.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_font"), //$NON-NLS-1$ + fontNameList)); + fontNameList.setSelectedIndex(DEFAULT_LEGEND_FONT); + legendPanel.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_size"), //$NON-NLS-1$ + fontSizeList)); + fontSizeList.setSelectedItem(StatGraphProperties.fontSize[DEFAULT_LEGEND_SIZE]); + legendPanel.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_style"), //$NON-NLS-1$ + fontStyleList)); + fontStyleList.setSelectedIndex(DEFAULT_LEGEND_STYLE); + + return legendPanel; + } + + /** + * @param textToFind + * @return pattern ready to search + */ + private Pattern createPattern(String textToFind) { + String textToFindQ = Pattern.quote(textToFind); + if (regexpChkBox.isSelected()) { + textToFindQ = textToFind; + } + Pattern pattern = null; + try { + if (caseChkBox.isSelected()) { + pattern = Pattern.compile(textToFindQ); + } else { + pattern = Pattern.compile(textToFindQ, Pattern.CASE_INSENSITIVE); + } + } catch (PatternSyntaxException pse) { + return null; + } + return pattern; + } + + private void enableDynamicGraph(boolean enable) { + // if use dynamic graph size is checked, we disable the dimension fields + if (enable) { + graphWidth.setEnabled(false); + graphHeight.setEnabled(false); + } else { + graphWidth.setEnabled(true); + graphHeight.setEnabled(true); + } + } + + private void enableSamplerSelection(boolean enable) { + if (enable) { + samplerMatchLabel.setEnabled(true); + applyFilterBtn.setEnabled(true); + caseChkBox.setEnabled(true); + regexpChkBox.setEnabled(true); + } else { + samplerMatchLabel.setEnabled(false); + applyFilterBtn.setEnabled(false); + caseChkBox.setEnabled(false); + regexpChkBox.setEnabled(false); + } + } +} diff --git a/src/components/org/apache/jmeter/visualizers/ResultRenderer.java b/src/components/org/apache/jmeter/visualizers/ResultRenderer.java new file mode 100644 index 00000000000..390b13297eb --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/ResultRenderer.java @@ -0,0 +1,62 @@ +/* + * 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.visualizers; + +import java.awt.Color; + +import javax.swing.JTabbedPane; + +import org.apache.jmeter.samplers.SampleResult; + + +/** + * Interface to results render + */ +public interface ResultRenderer { + + void clearData(); + + void init(); + + void setupTabPane(); + + void setLastSelectedTab(int index); + + void setRightSide(JTabbedPane rightSide); + + void setSamplerResult(Object userObject); + + void renderResult(SampleResult sampleResult); + + void renderImage(SampleResult sampleResult); + + /** + * + * @return the string to be displayed by the ComboBox + */ + @Override + String toString(); + + void setBackgroundColor(Color backGround); + +} diff --git a/src/components/org/apache/jmeter/visualizers/SamplerResultTab.java b/src/components/org/apache/jmeter/visualizers/SamplerResultTab.java new file mode 100644 index 00000000000..7b8be3f0562 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/SamplerResultTab.java @@ -0,0 +1,554 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.Set; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JTextPane; +import javax.swing.SwingConstants; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.text.BadLocationException; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.TextBoxDialoger.TextBoxDoubleClick; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.reflect.Functor; + +/** + * Right side in View Results Tree + * + */ +public abstract class SamplerResultTab implements ResultRenderer { + + // N.B. these are not multi-threaded, so don't make it static + private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); // ISO format $NON-NLS-1$ + + private static final String NL = "\n"; // $NON-NLS-1$ + + public static final Color SERVER_ERROR_COLOR = Color.red; + + public static final Color CLIENT_ERROR_COLOR = Color.blue; + + public static final Color REDIRECT_COLOR = Color.green; + + protected static final String TEXT_COMMAND = "text"; // $NON-NLS-1$ + + protected static final String REQUEST_VIEW_COMMAND = "change_request_view"; // $NON-NLS-1$ + + private static final String STYLE_SERVER_ERROR = "ServerError"; // $NON-NLS-1$ + + private static final String STYLE_CLIENT_ERROR = "ClientError"; // $NON-NLS-1$ + + private static final String STYLE_REDIRECT = "Redirect"; // $NON-NLS-1$ + + private JTextPane stats; + + private JPanel resultsPane; /** Response Data pane */ + protected JScrollPane resultsScrollPane; /** Contains results; contained in resultsPane */ + protected JEditorPane results; /** Response Data shown here */ + + private JLabel imageLabel; + + private RequestPanel requestPanel; /** request pane content */ + + protected JTabbedPane rightSide; /** holds the tabbed panes */ + + private int lastSelectedTab; + + private Object userObject = null; // Could be SampleResult or AssertionResult + + private SampleResult sampleResult = null; + + private AssertionResult assertionResult = null; + + protected SearchTextExtension searchTextExtension; + + private JPanel searchPanel = null; + + protected boolean activateSearchExtension = true; // most current subclasses can process text + + private Color backGround; + + private static final String[] COLUMNS_RESULT = new String[] { + " ", // one space for blank header // $NON-NLS-1$ + " " }; // one space for blank header // $NON-NLS-1$ + + private static final String[] COLUMNS_HEADERS = new String[] { + "view_results_table_headers_key", // $NON-NLS-1$ + "view_results_table_headers_value" }; // $NON-NLS-1$ + + private static final String[] COLUMNS_FIELDS = new String[] { + "view_results_table_fields_key", // $NON-NLS-1$ + "view_results_table_fields_value" }; // $NON-NLS-1$ + + private ObjectTableModel resultModel = null; + + private ObjectTableModel resHeadersModel = null; + + private ObjectTableModel resFieldsModel = null; + + private JTable tableResult = null; + + private JTable tableResHeaders = null; + + private JTable tableResFields = null; + + private JTabbedPane tabbedResult = null; + + private JScrollPane paneRaw = null; + + private JSplitPane paneParsed = null; + + // to save last select tab (raw/parsed) + private int lastResultTabIndex= 0; + + // Result column renderers + private static final TableCellRenderer[] RENDERERS_RESULT = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + // Response headers column renderers + private static final TableCellRenderer[] RENDERERS_HEADERS = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + // Response fields column renderers + private static final TableCellRenderer[] RENDERERS_FIELDS = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + public SamplerResultTab() { + // create tables + resultModel = new ObjectTableModel(COLUMNS_RESULT, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + resHeadersModel = new ObjectTableModel(COLUMNS_HEADERS, + RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + resFieldsModel = new ObjectTableModel(COLUMNS_FIELDS, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + } + + @Override + public void clearData() { + results.setText("");// Response Data // $NON-NLS-1$ + requestPanel.clearData();// Request Data // $NON-NLS-1$ + stats.setText(""); // Sampler result // $NON-NLS-1$ + resultModel.clearData(); + resHeadersModel.clearData(); + resFieldsModel.clearData(); + } + + @Override + public void init() { + rightSide.addTab(JMeterUtils.getResString("view_results_tab_sampler"), createResponseMetadataPanel()); // $NON-NLS-1$ + // Create the panels for the other tabs + requestPanel = new RequestPanel(); + resultsPane = createResponseDataPanel(); + } + + @Override + @SuppressWarnings("boxing") + public void setupTabPane() { + // Clear all data before display a new + this.clearData(); + StyledDocument statsDoc = stats.getStyledDocument(); + try { + if (userObject instanceof SampleResult) { + sampleResult = (SampleResult) userObject; + // We are displaying a SampleResult + setupTabPaneForSampleResult(); + requestPanel.setSamplerResult(sampleResult); + + final String samplerClass = sampleResult.getClass().getName(); + String typeResult = samplerClass.substring(1 + samplerClass.lastIndexOf('.')); + + StringBuilder statsBuff = new StringBuilder(200); + statsBuff.append(JMeterUtils.getResString("view_results_thread_name")).append(sampleResult.getThreadName()).append(NL); //$NON-NLS-1$ + String startTime = dateFormat.format(new Date(sampleResult.getStartTime())); + statsBuff.append(JMeterUtils.getResString("view_results_sample_start")).append(startTime).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_load_time")).append(sampleResult.getTime()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_connect_time")).append(sampleResult.getConnectTime()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_latency")).append(sampleResult.getLatency()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_size_in_bytes")).append(sampleResult.getBytes()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_size_headers_in_bytes")).append(sampleResult.getHeadersSize()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_size_body_in_bytes")).append(sampleResult.getBodySize()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_sample_count")).append(sampleResult.getSampleCount()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_error_count")).append(sampleResult.getErrorCount()).append(NL); //$NON-NLS-1$ + statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), null); + statsBuff.setLength(0); // reset for reuse + + String responseCode = sampleResult.getResponseCode(); + + int responseLevel = 0; + if (responseCode != null) { + try { + responseLevel = Integer.parseInt(responseCode) / 100; + } catch (NumberFormatException numberFormatException) { + // no need to change the foreground color + } + } + + Style style = null; + switch (responseLevel) { + case 3: + style = statsDoc.getStyle(STYLE_REDIRECT); + break; + case 4: + style = statsDoc.getStyle(STYLE_CLIENT_ERROR); + break; + case 5: + style = statsDoc.getStyle(STYLE_SERVER_ERROR); + break; + default: // quieten Findbugs + break; // default - do nothing + } + + statsBuff.append(JMeterUtils.getResString("view_results_response_code")).append(responseCode).append(NL); //$NON-NLS-1$ + statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), style); + statsBuff.setLength(0); // reset for reuse + + // response message label + String responseMsgStr = sampleResult.getResponseMessage(); + + statsBuff.append(JMeterUtils.getResString("view_results_response_message")).append(responseMsgStr).append(NL); //$NON-NLS-1$ + statsBuff.append(NL); + statsBuff.append(JMeterUtils.getResString("view_results_response_headers")).append(NL); //$NON-NLS-1$ + statsBuff.append(sampleResult.getResponseHeaders()).append(NL); + statsBuff.append(NL); + statsBuff.append(typeResult + " "+ JMeterUtils.getResString("view_results_fields")).append(NL); //$NON-NLS-1$ $NON-NLS-2$ + statsBuff.append("ContentType: ").append(sampleResult.getContentType()).append(NL); //$NON-NLS-1$ + statsBuff.append("DataEncoding: ").append(sampleResult.getDataEncodingNoDefault()).append(NL); //$NON-NLS-1$ + statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), null); + statsBuff = null; // Done + + // Tabbed results: fill table + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_thread_name"), sampleResult.getThreadName())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_sample_start"), startTime)); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_load_time"), sampleResult.getTime())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_latency"), sampleResult.getLatency())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_size_in_bytes"), sampleResult.getBytes())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_size_headers_in_bytes"), sampleResult.getHeadersSize())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_size_body_in_bytes"), sampleResult.getBodySize())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_sample_count"), sampleResult.getSampleCount())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_error_count"), sampleResult.getErrorCount())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_response_code"), responseCode)); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_response_message"), responseMsgStr)); //$NON-NLS-1$ + + // Parsed response headers + LinkedHashMap lhm = JMeterUtils.parseHeaders(sampleResult.getResponseHeaders()); + Set> keySet = lhm.entrySet(); + for (Entry entry : keySet) { + resHeadersModel.addRow(new RowResult(entry.getKey(), entry.getValue())); + } + + // Fields table + resFieldsModel.addRow(new RowResult("Type Result ", typeResult)); //$NON-NLS-1$ + //not sure needs I18N? + resFieldsModel.addRow(new RowResult("ContentType", sampleResult.getContentType())); //$NON-NLS-1$ + resFieldsModel.addRow(new RowResult("DataEncoding", sampleResult.getDataEncodingNoDefault())); //$NON-NLS-1$ + + // Reset search + if (activateSearchExtension) { + searchTextExtension.resetTextToFind(); + } + + } else if (userObject instanceof AssertionResult) { + assertionResult = (AssertionResult) userObject; + + // We are displaying an AssertionResult + setupTabPaneForAssertionResult(); + + StringBuilder statsBuff = new StringBuilder(100); + statsBuff.append(JMeterUtils.getResString("view_results_assertion_error")).append(assertionResult.isError()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_assertion_failure")).append(assertionResult.isFailure()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_assertion_failure_message")).append(assertionResult.getFailureMessage()).append(NL); //$NON-NLS-1$ + statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), null); + } + } catch (BadLocationException exc) { + stats.setText(exc.getLocalizedMessage()); + } + } + + private void setupTabPaneForSampleResult() { + // restore tabbed pane parsed if needed + if (tabbedResult.getTabCount() < 2) { + tabbedResult.insertTab(JMeterUtils.getResString("view_results_table_result_tab_parsed"), null, paneParsed, null, 1); //$NON-NLS-1$ + tabbedResult.setSelectedIndex(lastResultTabIndex); // select last tab + } + // Set the title for the first tab + rightSide.setTitleAt(0, JMeterUtils.getResString("view_results_tab_sampler")); //$NON-NLS-1$ + // Add the other tabs if not present + if(rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_request")) < 0) { // $NON-NLS-1$ + rightSide.addTab(JMeterUtils.getResString("view_results_tab_request"), requestPanel.getPanel()); // $NON-NLS-1$ + } + if(rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_response")) < 0) { // $NON-NLS-1$ + rightSide.addTab(JMeterUtils.getResString("view_results_tab_response"), resultsPane); // $NON-NLS-1$ + } + // restore last selected tab + if (lastSelectedTab < rightSide.getTabCount()) { + rightSide.setSelectedIndex(lastSelectedTab); + } + } + + private void setupTabPaneForAssertionResult() { + // Remove the other (parsed) tab if present + if (tabbedResult.getTabCount() >= 2) { + lastResultTabIndex = tabbedResult.getSelectedIndex(); + int parsedTabIndex = tabbedResult.indexOfTab(JMeterUtils.getResString("view_results_table_result_tab_parsed")); // $NON-NLS-1$ + if(parsedTabIndex >= 0) { + tabbedResult.removeTabAt(parsedTabIndex); + } + } + // Set the title for the first tab + rightSide.setTitleAt(0, JMeterUtils.getResString("view_results_tab_assertion")); //$NON-NLS-1$ + // Remove the other tabs if present + int requestTabIndex = rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_request")); // $NON-NLS-1$ + if(requestTabIndex >= 0) { + rightSide.removeTabAt(requestTabIndex); + } + int responseTabIndex = rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_response")); // $NON-NLS-1$ + if(responseTabIndex >= 0) { + rightSide.removeTabAt(responseTabIndex); + } + } + + private Component createResponseMetadataPanel() { + stats = new JTextPane(); + stats.setEditable(false); + stats.setBackground(backGround); + + // Add styles to use for different types of status messages + StyledDocument doc = (StyledDocument) stats.getDocument(); + + Style style = doc.addStyle(STYLE_REDIRECT, null); + StyleConstants.setForeground(style, REDIRECT_COLOR); + + style = doc.addStyle(STYLE_CLIENT_ERROR, null); + StyleConstants.setForeground(style, CLIENT_ERROR_COLOR); + + style = doc.addStyle(STYLE_SERVER_ERROR, null); + StyleConstants.setForeground(style, SERVER_ERROR_COLOR); + + paneRaw = GuiUtils.makeScrollPane(stats); + paneRaw.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + + // Set up the 1st table Result with empty headers + tableResult = new JTable(resultModel); + tableResult.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableResult.addMouseListener(new TextBoxDoubleClick(tableResult)); + setFirstColumnPreferredSize(tableResult); + RendererUtils.applyRenderers(tableResult, RENDERERS_RESULT); + + // Set up the 2nd table + tableResHeaders = new JTable(resHeadersModel); + tableResHeaders.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableResHeaders.addMouseListener(new TextBoxDoubleClick(tableResHeaders)); + setFirstColumnPreferredSize(tableResHeaders); + tableResHeaders.getTableHeader().setDefaultRenderer( + new HeaderAsPropertyRenderer()); + RendererUtils.applyRenderers(tableResHeaders, RENDERERS_HEADERS); + + // Set up the 3rd table + tableResFields = new JTable(resFieldsModel); + tableResFields.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableResFields.addMouseListener(new TextBoxDoubleClick(tableResFields)); + setFirstColumnPreferredSize(tableResFields); + tableResFields.getTableHeader().setDefaultRenderer( + new HeaderAsPropertyRenderer()); + RendererUtils.applyRenderers(tableResFields, RENDERERS_FIELDS); + + // Prepare the Results tabbed pane + tabbedResult = new JTabbedPane(SwingConstants.BOTTOM); + + // Create the split pane + JSplitPane topSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + GuiUtils.makeScrollPane(tableResHeaders), + GuiUtils.makeScrollPane(tableResFields)); + topSplit.setOneTouchExpandable(true); + topSplit.setResizeWeight(0.80); // set split ratio + topSplit.setBorder(null); // see bug jdk 4131528 + + paneParsed = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + GuiUtils.makeScrollPane(tableResult), topSplit); + paneParsed.setOneTouchExpandable(true); + paneParsed.setResizeWeight(0.40); // set split ratio + paneParsed.setBorder(null); // see bug jdk 4131528 + + // setup bottom tabs, first Raw, second Parsed + tabbedResult.addTab(JMeterUtils.getResString("view_results_table_result_tab_raw"), paneRaw); //$NON-NLS-1$ + tabbedResult.addTab(JMeterUtils.getResString("view_results_table_result_tab_parsed"), paneParsed); //$NON-NLS-1$ + + // Hint to background color on bottom tabs (grey, not blue) + JPanel panel = new JPanel(new BorderLayout()); + panel.add(tabbedResult); + return panel; + } + + private JPanel createResponseDataPanel() { + results = new JEditorPane(); + results.setEditable(false); + + resultsScrollPane = GuiUtils.makeScrollPane(results); + imageLabel = new JLabel(); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(resultsScrollPane, BorderLayout.CENTER); + + if (activateSearchExtension) { + // Add search text extension + searchTextExtension = new SearchTextExtension(); + searchTextExtension.init(panel); + searchPanel = searchTextExtension.createSearchTextExtensionPane(); + searchTextExtension.setResults(results); + searchPanel.setVisible(true); + panel.add(searchPanel, BorderLayout.PAGE_END); + } + + return panel; + } + + private void showImage(Icon image) { + imageLabel.setIcon(image); + resultsScrollPane.setViewportView(imageLabel); + } + + @Override + public synchronized void setSamplerResult(Object sample) { + userObject = sample; + } + + @Override + public synchronized void setRightSide(JTabbedPane side) { + rightSide = side; + } + + @Override + public void setLastSelectedTab(int index) { + lastSelectedTab = index; + } + + @Override + public void renderImage(SampleResult sampleResult) { + byte[] responseBytes = sampleResult.getResponseData(); + if (responseBytes != null) { + showImage(new ImageIcon(responseBytes)); //TODO implement other non-text types + } + } + + @Override + public void setBackgroundColor(Color backGround){ + this.backGround = backGround; + } + + private void setFirstColumnPreferredSize(JTable table) { + TableColumn column = table.getColumnModel().getColumn(0); + column.setMaxWidth(300); + column.setPreferredWidth(180); + } + + /** + * For model table + */ + public static class RowResult { + private String key; + + private Object value; + + public RowResult(String key, Object value) { + this.key = key; + this.value = value; + } + + /** + * @return the key + */ + public synchronized String getKey() { + return key; + } + + /** + * @param key + * the key to set + */ + public synchronized void setKey(String key) { + this.key = key; + } + + /** + * @return the value + */ + public synchronized Object getValue() { + return value; + } + + /** + * @param value + * the value to set + */ + public synchronized void setValue(Object value) { + this.value = value; + } + } +} diff --git a/src/components/org/apache/jmeter/visualizers/SearchTextExtension.java b/src/components/org/apache/jmeter/visualizers/SearchTextExtension.java new file mode 100644 index 00000000000..1e97f49d8bd --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/SearchTextExtension.java @@ -0,0 +1,295 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultHighlighter; +import javax.swing.text.Document; +import javax.swing.text.Highlighter; + +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class SearchTextExtension implements ActionListener, DocumentListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String SEARCH_TEXT_COMMAND = "search_text"; // $NON-NLS-1$ + + private static volatile int LAST_POSITION_DEFAULT = 0; + + private int lastPosition = LAST_POSITION_DEFAULT; + + private static final Color HILIT_COLOR = Color.LIGHT_GRAY; + + private Highlighter selection; + + private Highlighter.HighlightPainter painter; + + private JLabel label; + + private JButton findButton; + + private JTextField textToFindField; + + private JCheckBox caseChkBox; + + private JCheckBox regexpChkBox; + + private String lastTextTofind; + + private boolean newSearch = false; + + private JEditorPane results; + + private JPanel searchPanel; + + + public void init(JPanel resultsPane) { + } + + public void setResults(JEditorPane results) { + if (this.results != null) { + newSearch = true; + resetTextToFind(); + } + this.results = results; + // prepare highlighter to show text find with search command + selection = new DefaultHighlighter(); + painter = new DefaultHighlighter.DefaultHighlightPainter(HILIT_COLOR); + results.setHighlighter(selection); + } + + /** + * Launch find text engine on response text + */ + private void executeAndShowTextFind() { + String textToFind = textToFindField.getText(); + if (results != null && results.getText().length() > 0 + && textToFind.length() > 0) { + + // new search? + if (lastTextTofind != null && !lastTextTofind.equals(textToFind)) { + lastPosition = LAST_POSITION_DEFAULT; + } + + if (log.isDebugEnabled()) { + log.debug("lastPosition=" + lastPosition); + } + Matcher matcher = null; + try { + Pattern pattern = createPattern(textToFind); + Document contentDoc = results.getDocument(); + String body = contentDoc.getText(lastPosition, + (contentDoc.getLength() - lastPosition)); + matcher = pattern.matcher(body); + + if ((matcher != null) && (matcher.find())) { + selection.removeAllHighlights(); + selection.addHighlight(lastPosition + matcher.start(), + lastPosition + matcher.end(), painter); + results.setCaretPosition(lastPosition + matcher.end()); + + // save search position + lastPosition = lastPosition + matcher.end(); + findButton.setText(JMeterUtils + .getResString("search_text_button_next"));// $NON-NLS-1$ + lastTextTofind = textToFind; + newSearch = true; + } else { + // Display not found message and reset search + JOptionPane.showMessageDialog(null, JMeterUtils + .getResString("search_text_msg_not_found"),// $NON-NLS-1$ + JMeterUtils.getResString("search_text_title_not_found"), // $NON-NLS-1$ + JOptionPane.INFORMATION_MESSAGE); + lastPosition = LAST_POSITION_DEFAULT; + findButton.setText(JMeterUtils + .getResString("search_text_button_find"));// $NON-NLS-1$ + results.setCaretPosition(0); + } + } catch (PatternSyntaxException pse) { + JOptionPane.showMessageDialog(null, + pse.toString(),// $NON-NLS-1$ + JMeterUtils.getResString("error_title"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + } catch (BadLocationException ble) { + log.error("Location exception in text find", ble);// $NON-NLS-1$ + } + } + } + + /** + * Create the text find task pane + * + * @return Text find task pane + */ + private JPanel createSearchTextPanel() { + Font font = new Font("SansSerif", Font.PLAIN, 10); + + // Search field + searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); + searchPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + label = new JLabel(JMeterUtils.getResString("search_text_field")); // $NON-NLS-1$ + label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); + searchPanel.add(label); + textToFindField = new JTextField(); // $NON-NLS-1$ + searchPanel.add(textToFindField); + searchPanel.add(Box.createRigidArea(new Dimension(5,0))); + + // add listener to intercept texttofind changes and reset search + textToFindField.getDocument().addDocumentListener(this); + + // Buttons + findButton = new JButton(JMeterUtils + .getResString("search_text_button_find")); // $NON-NLS-1$ + findButton.setFont(font); + findButton.setActionCommand(SEARCH_TEXT_COMMAND); + findButton.addActionListener(this); + searchPanel.add(findButton); + + // checkboxes + caseChkBox = new JCheckBox(JMeterUtils + .getResString("search_text_chkbox_case"), false); // $NON-NLS-1$ + caseChkBox.setFont(font); + searchPanel.add(caseChkBox); + regexpChkBox = new JCheckBox(JMeterUtils + .getResString("search_text_chkbox_regexp"), false); // $NON-NLS-1$ + regexpChkBox.setFont(font); + searchPanel.add(regexpChkBox); + + // when Enter is pressed, search start + InputMap im = textToFindField + .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + im.put(KeyStrokes.ENTER, SEARCH_TEXT_COMMAND); + ActionMap am = textToFindField.getActionMap(); + am.put(SEARCH_TEXT_COMMAND, new EnterAction()); + + // default not visible + searchPanel.setVisible(true); + return searchPanel; + } + + JPanel createSearchTextExtensionPane() { + JPanel pane = new JPanel(); + pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS)); + pane.add(createSearchTextPanel()); + return pane; + } + + /** + * Display the response as text or as rendered HTML. Change the text on the + * button appropriate to the current display. + * + * @param e + * the ActionEvent being processed + */ + @Override + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + + // Search text in response data + if (SEARCH_TEXT_COMMAND.equals(command)) { + executeAndShowTextFind(); + } + } + + private class EnterAction extends AbstractAction { + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent ev) { + executeAndShowTextFind(); + } + } + + // DocumentListener method + @Override + public void changedUpdate(DocumentEvent e) { + // do nothing + } + + // DocumentListener method + @Override + public void insertUpdate(DocumentEvent e) { + resetTextToFind(); + } + + // DocumentListener method + @Override + public void removeUpdate(DocumentEvent e) { + resetTextToFind(); + } + + void resetTextToFind() { + if (newSearch) { + log.debug("reset pass"); + // Reset search + lastPosition = LAST_POSITION_DEFAULT; + lastTextTofind = null; + findButton.setText(JMeterUtils + .getResString("search_text_button_find"));// $NON-NLS-1$ + selection.removeAllHighlights(); + results.setCaretPosition(0); + newSearch = false; + } + } + + private Pattern createPattern(String textToFind) { + // desactivate or not specials regexp char + String textToFindQ = Pattern.quote(textToFind); + if (regexpChkBox.isSelected()) { + textToFindQ = textToFind; + } + Pattern pattern = null; + if (caseChkBox.isSelected()) { + pattern = Pattern.compile(textToFindQ); + } else { + pattern = Pattern.compile(textToFindQ, Pattern.CASE_INSENSITIVE); + } + return pattern; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/SimpleDataWriter.java b/src/components/org/apache/jmeter/visualizers/SimpleDataWriter.java new file mode 100644 index 00000000000..d24b8fc898b --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/SimpleDataWriter.java @@ -0,0 +1,78 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +/******************************************************************************* + * This listener can record results to a file but not to the UI. It is meant to + * provide an efficient means of recording data by eliminating GUI overhead. + * + ******************************************************************************/ + +public class SimpleDataWriter extends AbstractVisualizer { + private static final long serialVersionUID = 240L; + + /*************************************************************************** + * Create the SimpleDataWriter. + **************************************************************************/ + + public SimpleDataWriter() { + init(); + setName(getStaticLabel()); + } + + @Override + public String getLabelResource() { + return "simple_data_writer_title"; // $NON-NLS-1$ + } + + /** + * Initialize the component in the UI + */ + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + } + + /** + * Does nothing, but required by interface. + */ + + @Override + public void clearData() { + } + + /** + * Does nothing, but required by interface. + * + * @param sample + * ignored + */ + + @Override + public void add(SampleResult sample) { + } +} diff --git a/src/components/org/apache/jmeter/visualizers/Spline3.java b/src/components/org/apache/jmeter/visualizers/Spline3.java new file mode 100644 index 00000000000..ac394ffea9c --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/Spline3.java @@ -0,0 +1,439 @@ +/* + * 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.visualizers; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/* + * TODO : - implement ImageProducer interface - suggestions ;-) + */ + +/** + * This class implements the representation of an interpolated Spline curve. + *

+ * The curve described by such an object interpolates an arbitrary number of + * fixed points called nodes. The distance between two nodes should + * currently be constant. This is about to change in a later version but it can + * last a while as it's not really needed. Nevertheless, if you need the + * feature, just write me + * a note and I'll write it asap. + *

+ * The interpolated Spline curve can't be described by an polynomial analytic + * equation, the degree of which would be as high as the number of nodes, which + * would cause extreme oscillations of the curve on the edges. + *

+ * The solution is to split the curve accross a lot of little intervals : + * an interval starts at one node and ends at the next one. Then, the + * interpolation is done on each interval, according to the following conditions : + *

    + *
  1. the interpolated curve is degree 3 : it's a cubic curve ; + *
  2. the interpolated curve contains the two points delimiting the interval. + * This condition obviously implies the curve is continuous ; + *
  3. the interpolated curve has a smooth slope : the curvature has to be the + * same on the left and the right sides of each node ; + *
  4. the curvature of the global curve is 0 at both edges. + *
+ * Every part of the global curve is represented by a cubic (degree-3) + * polynomial, the coefficients of which have to be computed in order to meet + * the above conditions. + *

+ * This leads to a n-unknow n-equation system to resolve. One can resolve an + * equation system by several manners ; this class uses the Jacobi iterative + * method, particularly well adapted to this situation, as the diagonal of the + * system matrix is strong compared to the other elements. This implies the + * algorithm always converges ! This is not the case of the Gauss-Seidel + * algorithm, which is quite faster (it uses intermediate results of each + * iteration to speed up the convergence) but it doesn't converge in all the + * cases or it converges to a wrong value. This is not acceptable and that's why + * the Jacobi method is safer. Anyway, the gain of speed is about a factor of 3 + * but, for a 100x100 system, it means 10 ms instead of 30 ms, which is a pretty + * good reason not to explore the question any further :) + *

+ * Here is a little piece of code showing how to use this class : + * + *

+ *  // ...
+ *  float[] nodes = {3F, 2F, 4F, 1F, 2.5F, 5F, 3F};
+ *  Spline3 curve = new Spline3(nodes);
+ *  // ...
+ *  public void paint(Graphics g) {
+ *    int[] plot = curve.getPlots();
+ *    for (int i = 1; i < n; i++) {
+ *      g.drawLine(i - 1, plot[i - 1], i, plot[i]);
+ *    }
+ *  }
+ *  // ...
+ * 
+ * + */ +public class Spline3 { + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected float[][] _coefficients; + + protected float[][] _A; + + protected float[] _B; + + protected float[] _r; + + protected float[] _rS; + + protected int _m; // number of nodes + + protected int _n; // number of non extreme nodes (_m-2) + + static final protected float DEFAULT_PRECISION = (float) 1E-1; + + static final protected int DEFAULT_MAX_ITERATIONS = 100; + + protected float _minPrecision = DEFAULT_PRECISION; + + protected int _maxIterations = DEFAULT_MAX_ITERATIONS; + + /** + * Creates a new Spline curve by calculating the coefficients of each part + * of the curve, i.e. by resolving the equation system implied by the + * interpolation condition on every interval. + * + * @param r + * an array of float containing the vertical coordinates of the + * nodes to interpolate ; the vertical coordinates start at 0 and + * are equidistant with a step of 1. + */ + public Spline3(float[] r) { + int n = r.length; + + // the number of nodes is defined by the length of r + this._m = n; + // grab the nodes + this._r = new float[n]; + System.arraycopy(r, 0, _r, 0, _r.length); + // the number of non extreme nodes is the number of intervals + // minus 1, i.e. the length of r minus 2 + this._n = n - 2; + // computes interpolation coefficients + try { + long startTime = System.currentTimeMillis(); + + this.interpolation(); + if (log.isDebugEnabled()) { + long endTime = System.currentTimeMillis(); + long elapsedTime = endTime - startTime; + + if (log.isDebugEnabled()) { + log.debug("New Spline curve interpolated in "); + log.debug(elapsedTime + " ms"); + } + } + } catch (Exception e) { + log.error("Error when interpolating : ", e); + } + + } + + /** + * Computes the coefficients of the Spline interpolated curve, on each + * interval. The matrix system to resolve is AX=B + */ + protected void interpolation() { + // creation of the interpolation structure + _rS = new float[_m]; + _B = new float[_n]; + _A = new float[_n][_n]; + _coefficients = new float[_n + 1][4]; + // local variables + int i = 0, j = 0; + + // initialize system structures (just to be safe) + for (i = 0; i < _n; i++) { + _B[i] = 0; + for (j = 0; j < _n; j++) { + _A[i][j] = 0; + } + for (j = 0; j < 4; j++) { + _coefficients[i][j] = 0; + } + } + for (i = 0; i < _n; i++) { + _rS[i] = 0; + } + // initialize the diagonal of the system matrix (A) to 4 + for (i = 0; i < _n; i++) { + _A[i][i] = 4; + } + // initialize the two minor diagonals of A to 1 + for (i = 1; i < _n; i++) { + _A[i][i - 1] = 1; + _A[i - 1][i] = 1; + } + // initialize B + for (i = 0; i < _n; i++) { + _B[i] = 6 * (_r[i + 2] - 2 * _r[i + 1] + _r[i]); + } + // Jacobi system resolving + this.jacobi(); // results are stored in _rS + // computes the coefficients (di, ci, bi, ai) from the results + for (i = 0; i < _n + 1; i++) { + // di (degree 0) + _coefficients[i][0] = _r[i]; + // ci (degree 1) + _coefficients[i][1] = _r[i + 1] - _r[i] - (_rS[i + 1] + 2 * _rS[i]) / 6; + // bi (degree 2) + _coefficients[i][2] = _rS[i] / 2; + // ai (degree 3) + _coefficients[i][3] = (_rS[i + 1] - _rS[i]) / 6; + } + } + + /** + * Resolves the equation system by a Jacobi algorithm. The use of the slower + * Jacobi algorithm instead of Gauss-Seidel is choosen here because Jacobi + * is assured of to be convergent for this particular equation system, as + * the system matrix has a strong diagonal. + */ + protected void jacobi() { + // local variables + int i = 0, j = 0, iterations = 0; + // intermediate arrays + float[] newX = new float[_n]; + float[] oldX = new float[_n]; + + // Jacobi convergence test + if (!converge()) { + if (log.isDebugEnabled()) { + log.debug("Warning : equation system resolving is unstable"); + } + } + // init newX and oldX arrays to 0 + for (i = 0; i < _n; i++) { + newX[i] = 0; + oldX[i] = 0; + } + // main iteration + while ((this.precision(oldX, newX) > this._minPrecision) && (iterations < this._maxIterations)) { + System.arraycopy(oldX, 0, newX, 0, _n); + for (i = 0; i < _n; i++) { + newX[i] = _B[i]; + for (j = 0; j < i; j++) { + newX[i] = newX[i] - (_A[i][j] * oldX[j]); + } + for (j = i + 1; j < _n; j++) { + newX[i] = newX[i] - (_A[i][j] * oldX[j]); + } + newX[i] = newX[i] / _A[i][i]; + } + iterations++; + } + if (this.precision(oldX, newX) < this._minPrecision) { + if (log.isDebugEnabled()) { + log.debug("Minimal precision ("); + log.debug(this._minPrecision + ") reached after "); + log.debug(iterations + " iterations"); + } + } else if (iterations > this._maxIterations) { + if (log.isDebugEnabled()) { + log.debug("Maximal number of iterations ("); + log.debug(this._maxIterations + ") reached"); + log.debug("Warning : precision is only "); + log.debug("" + this.precision(oldX, newX)); + log.debug(", divergence is possible"); + } + } + System.arraycopy(newX, 0, _rS, 1, _n); + } + + /** + * Test if the Jacobi resolution of the equation system converges. It's OK + * if A has a strong diagonal. + * + * @return true if equation system converges + */ + protected boolean converge() { + boolean converge = true; + int i = 0, j = 0; + float lineSum = 0F; + + for (i = 0; i < _n; i++) { + if (converge) { + lineSum = 0; + for (j = 0; j < _n; j++) { + lineSum = lineSum + Math.abs(_A[i][j]); + } + lineSum = lineSum - Math.abs(_A[i][i]); + if (lineSum > Math.abs(_A[i][i])) { + converge = false; + break; + } + } + } + return converge; + } + + /** + * Computes the current precision reached. + * + * @param oldX + * old values + * @param newX + * new values + * @return indicator of how different the old and new values are (always + * zero or greater, the nearer to zero the more similar) + */ + protected float precision(float[] oldX, float[] newX) { + float N = 0F, D = 0F, erreur = 0F; + int i = 0; + + for (i = 0; i < _n; i++) { + N = N + Math.abs(newX[i] - oldX[i]); + D = D + Math.abs(newX[i]); + } + if (D != 0F) { + erreur = N / D; + } else { + erreur = Float.MAX_VALUE; + } + return erreur; + } + + /** + * Computes a (vertical) Y-axis value of the global curve. + * + * @param t + * abscissa + * @return computed ordinate + */ + public float value(float t) { + int i = 0, splineNumber = 0; + float abscissa = 0F, result = 0F; + + // verify t belongs to the curve (range [0, _m-1]) + if ((t < 0) || (t > (_m - 1))) { + if (log.isDebugEnabled()) { + log.debug("Warning : abscissa " + t + " out of bounds [0, " + (_m - 1) + "]"); + } + // silent error, consider the curve is constant outside its range + if (t < 0) { + t = 0; + } else { + t = _m - 1; + } + } + // seek the good interval for t and get the piece of curve on it + splineNumber = (int) Math.floor(t); + if (t == (_m - 1)) { + // the upper limit of the curve range belongs by definition + // to the last interval + splineNumber--; + } + // computes the value of the curve at the pecified abscissa + // and relative to the beginning of the right piece of Spline curve + abscissa = t - splineNumber; + // the polynomial calculation is done by the (fast) Euler method + for (i = 0; i < 4; i++) { + result = result * abscissa; + result = result + _coefficients[splineNumber][3 - i]; + } + return result; + } + + /** + * Manual check of the curve at the interpolated points. + */ + public void debugCheck() { + int i = 0; + + for (i = 0; i < _m; i++) { + log.info("Point " + i + " : "); + log.info(_r[i] + " =? " + value(i)); + } + } + + /** + * Computes drawable plots from the curve for a given draw space. The values + * returned are drawable vertically and from the bottom of a Panel. + * + * @param width + * width within the plots have to be computed + * @param height + * height within the plots are expected to be drawed + * @return drawable plots within the limits defined, in an array of int (as + * many int as the value of the width parameter) + */ + public int[] getPlots(int width, int height) { + int[] plot = new int[width]; + // computes auto-scaling and absolute plots + float[] y = new float[width]; + float max = java.lang.Integer.MIN_VALUE; + float min = java.lang.Integer.MAX_VALUE; + + for (int i = 0; i < width; i++) { + y[i] = value(((float) i) * (_m - 1) / width); + if (y[i] < min) { + min = y[i]; + } + + if (y[i] > max) { + max = y[i]; + } + } + if (min < 0) { + min = 0; // shouldn't draw negative values + } + // computes relative auto-scaled plots to fit in the specified area + for (int i = 0; i < width; i++) { + plot[i] = Math.round(((y[i] - min) * (height - 1)) / (max - min)); + } + return plot; + } + + public void setPrecision(float precision) { + this._minPrecision = precision; + } + + public float getPrecision() { + return this._minPrecision; + } + + public void setToDefaultPrecision() { + this._minPrecision = DEFAULT_PRECISION; + } + + public float getDefaultPrecision() { + return DEFAULT_PRECISION; + } + + public void setMaxIterations(int iterations) { + this._maxIterations = iterations; + } + + public int getMaxIterations() { + return this._maxIterations; + } + + public void setToDefaultMaxIterations() { + this._maxIterations = DEFAULT_MAX_ITERATIONS; + } + + public int getDefaultMaxIterations() { + return DEFAULT_MAX_ITERATIONS; + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/SplineModel.java b/src/components/org/apache/jmeter/visualizers/SplineModel.java new file mode 100644 index 00000000000..cabcd0171d9 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/SplineModel.java @@ -0,0 +1,136 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; + +public class SplineModel implements Clearable { + public static final int DEFAULT_NUMBER_OF_NODES = 10; + + public static final int DEFAULT_REFRESH_PERIOD = 1; + + protected final boolean SHOW_INCOMING_SAMPLES = true; + + // These are not currently updated + protected int numberOfNodes = DEFAULT_NUMBER_OF_NODES; + + protected int refreshPeriod = DEFAULT_REFRESH_PERIOD; + + /** Current Spline curve. */ + //@GuardedBy("this") + private Spline3 dataCurve = null; + + final CachingStatCalculator samples; + + //@GuardedBy("this") + private GraphListener listener; + + //@GuardedBy("this") + private String name; + + public SplineModel() { + samples = new CachingStatCalculator("Spline"); + } + + public synchronized void setListener(GraphListener vis) { + listener = vis; + } + + public synchronized void setName(String newName) { + name = newName; + } + + public boolean isEditable() { + return true; + } + + public synchronized Spline3 getDataCurve() { + return dataCurve; + } + + public long getMinimum() { + return samples.getMin().longValue(); + } + + public long getMaximum() { + return samples.getMax().longValue(); + } + + public long getAverage() { + return (long) samples.getMean(); + } + + public long getCurrent() { + return samples.getCurrentSample().getData(); + } + + public long getSample(int i) { + return samples.getSample(i).getData(); + } + + public long getNumberOfCollectedSamples() { + return samples.getCount(); + } + + public synchronized String getName() { + return name; + } + + public void uncompile() { + clearData(); + } + + @Override + public synchronized void clearData() { + // this.graph.clear(); + samples.clear(); + + this.dataCurve = null; + + if (listener != null) { + listener.updateGui(); + } + } + + public synchronized void add(SampleResult sampleResult) { + samples.addSample(sampleResult); + long n = samples.getCount(); + + if ((n % (numberOfNodes * refreshPeriod)) == 0) { + float[] floatNode = new float[numberOfNodes]; + // NOTUSED: long[] longSample = getSamples(); + // load each node + long loadFactor = n / numberOfNodes; + + for (int i = 0; i < numberOfNodes; i++) { + for (int j = 0; j < loadFactor; j++) { + floatNode[i] += samples.getSample((int) ((i * loadFactor) + j)).getData(); + } + floatNode[i] = floatNode[i] / loadFactor; + } + // compute the new Spline curve + dataCurve = new Spline3(floatNode); + if (listener != null) { + listener.updateGui(); + } + } else {// do nothing, wait for the next pile to complete + } + } +} diff --git a/src/components/org/apache/jmeter/visualizers/SplineVisualizer.java b/src/components/org/apache/jmeter/visualizers/SplineVisualizer.java new file mode 100644 index 00000000000..1626d74c05e --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/SplineVisualizer.java @@ -0,0 +1,345 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.GridLayout; +import java.awt.Image; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * This class implements a statistical analyser that takes samples to process a + * Spline interpolated curve. Currently, it tries to look mostly like the + * GraphVisualizer. + * + */ +public class SplineVisualizer extends AbstractVisualizer implements ImageVisualizer, GraphListener { + + private static final long serialVersionUID = 240L; + + private static final String SUFFIX_MS = " ms"; //$NON-NLS-1$ + + protected final Color BACKGROUND_COLOR = getBackground(); + + protected final Color MINIMUM_COLOR = new Color(0F, 0.5F, 0F); + + protected final Color MAXIMUM_COLOR = new Color(0.9F, 0F, 0F); + + protected final Color AVERAGE_COLOR = new Color(0F, 0F, 0.75F); + + protected final Color INCOMING_COLOR = Color.black; + + protected final int NUMBERS_TO_DISPLAY = 4; + + protected final boolean FILL_UP_WITH_ZEROS = false; + + private transient SplineGraph graph = null; + + private JLabel minimumLabel = null; + + private JLabel maximumLabel = null; + + private JLabel averageLabel = null; + + private JLabel incomingLabel = null; + + private JLabel minimumNumberLabel = null; + + private JLabel maximumNumberLabel = null; + + private JLabel averageNumberLabel = null; + + private JLabel incomingNumberLabel = null; + + private transient SplineModel model; + + public SplineVisualizer() { + super(); + model = new SplineModel(); + graph = new SplineGraph(); + this.model.setListener(this); + setGUI(); + } + + @Override + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + model.add(res); + } + }); + } + + @Override + public String getLabelResource() { + return "spline_visualizer_title"; //$NON-NLS-1$ + } + + @Override + public void updateGui(Sample s) { + updateGui(); + } + + @Override + public void clearData() { + model.clearData(); + } + + private void setGUI() { + Color backColor = BACKGROUND_COLOR; + + this.setBackground(backColor); + + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + + // NAME + mainPanel.add(makeTitlePanel()); + + maximumLabel = new JLabel(JMeterUtils.getResString("spline_visualizer_maximum")); //$NON-NLS-1$ + maximumLabel.setForeground(MAXIMUM_COLOR); + maximumLabel.setBackground(backColor); + + averageLabel = new JLabel(JMeterUtils.getResString("spline_visualizer_average")); //$NON-NLS-1$ + averageLabel.setForeground(AVERAGE_COLOR); + averageLabel.setBackground(backColor); + + incomingLabel = new JLabel(JMeterUtils.getResString("spline_visualizer_incoming")); //$NON-NLS-1$ + incomingLabel.setForeground(INCOMING_COLOR); + incomingLabel.setBackground(backColor); + + minimumLabel = new JLabel(JMeterUtils.getResString("spline_visualizer_minimum")); //$NON-NLS-1$ + minimumLabel.setForeground(MINIMUM_COLOR); + minimumLabel.setBackground(backColor); + + maximumNumberLabel = new JLabel("0 ms"); //$NON-NLS-1$ + maximumNumberLabel.setHorizontalAlignment(SwingConstants.RIGHT); + maximumNumberLabel.setForeground(MAXIMUM_COLOR); + maximumNumberLabel.setBackground(backColor); + + averageNumberLabel = new JLabel("0 ms"); //$NON-NLS-1$ + averageNumberLabel.setHorizontalAlignment(SwingConstants.RIGHT); + averageNumberLabel.setForeground(AVERAGE_COLOR); + averageNumberLabel.setBackground(backColor); + + incomingNumberLabel = new JLabel("0 ms"); //$NON-NLS-1$ + incomingNumberLabel.setHorizontalAlignment(SwingConstants.RIGHT); + incomingNumberLabel.setForeground(INCOMING_COLOR); + incomingNumberLabel.setBackground(backColor); + + minimumNumberLabel = new JLabel("0 ms"); //$NON-NLS-1$ + minimumNumberLabel.setHorizontalAlignment(SwingConstants.RIGHT); + minimumNumberLabel.setForeground(MINIMUM_COLOR); + minimumNumberLabel.setBackground(backColor); + + // description Panel + JPanel labelPanel = new JPanel(); + + labelPanel.setLayout(new GridLayout(0, 1)); + labelPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 20)); + labelPanel.setBackground(backColor); + labelPanel.add(maximumLabel); + labelPanel.add(averageLabel); + if (model.SHOW_INCOMING_SAMPLES) { + labelPanel.add(incomingLabel); + } + labelPanel.add(minimumLabel); + // number Panel + JPanel numberPanel = new JPanel(); + + numberPanel.setLayout(new GridLayout(0, 1)); + numberPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 20)); + numberPanel.setBackground(backColor); + numberPanel.add(maximumNumberLabel); + numberPanel.add(averageNumberLabel); + if (model.SHOW_INCOMING_SAMPLES) { + numberPanel.add(incomingNumberLabel); + } + numberPanel.add(minimumNumberLabel); + // information display Panel + JPanel infoPanel = new JPanel(); + + infoPanel.setLayout(new BorderLayout()); + infoPanel.add(labelPanel, BorderLayout.CENTER); + infoPanel.add(numberPanel, BorderLayout.EAST); + + this.add(mainPanel, BorderLayout.NORTH); + this.add(infoPanel, BorderLayout.WEST); + this.add(graph, BorderLayout.CENTER); + // everyone is free to swing on its side :) + // add(infoPanel, BorderLayout.EAST); + } + + @Override + public void updateGui() { + repaint(); + synchronized (this) { + setMinimum(model.getMinimum()); + setMaximum(model.getMaximum()); + setAverage(model.getAverage()); + setIncoming(model.getCurrent()); + } + } + + @Override + public String toString() { + return "Show the samples analysis as a Spline curve"; + } + + private String formatMeasureToDisplay(long measure) { + String numberString = String.valueOf(measure); + + if (FILL_UP_WITH_ZEROS) { + for (int i = numberString.length(); i < NUMBERS_TO_DISPLAY; i++) { + numberString = "0" + numberString; //$NON-NLS-1$ + } + } + return numberString; + } + + private void setMinimum(long n) { + String text = this.formatMeasureToDisplay(n) + SUFFIX_MS; + + this.minimumNumberLabel.setText(text); + } + + private void setMaximum(long n) { + String text = this.formatMeasureToDisplay(n) + SUFFIX_MS; + + this.maximumNumberLabel.setText(text); + } + + private void setAverage(long n) { + String text = this.formatMeasureToDisplay(n) + SUFFIX_MS; + + this.averageNumberLabel.setText(text); + } + + private void setIncoming(long n) { + String text = this.formatMeasureToDisplay(n) + SUFFIX_MS; + + this.incomingNumberLabel.setText(text); + } + + public JPanel getControlPanel() {// TODO - is this needed? + return this; + } + + @Override + public Image getImage() { + Image result = graph.createImage(graph.getWidth(), graph.getHeight()); + + graph.paintComponent(result.getGraphics()); + + return result; + } + + /** + * Component showing a Spline curve. + * + */ + public class SplineGraph extends JComponent { + + private static final long serialVersionUID = 240L; + + private final Color WAITING_COLOR = Color.darkGray; + + private int lastWidth = -1; + + private int lastHeight = -1; + + private int[] plot = null; + + public SplineGraph() { + } + + /** + * Clear the Spline graph and get ready for the next wave. + */ + public void clear() { + lastWidth = -1; + lastHeight = -1; + plot = null; + this.repaint(); + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + + Dimension dimension = this.getSize(); + int width = dimension.width; + int height = dimension.height; + + if (model.getDataCurve() == null) { + g.setColor(this.getBackground()); + g.fillRect(0, 0, width, height); + g.setColor(WAITING_COLOR); + g.drawString(JMeterUtils.getResString("spline_visualizer_waitingmessage"), //$NON-NLS-1$ + (width - 120) / 2, height - (height - 12) / 2); + return; + } + + // boolean resized = true; + + if (width == lastWidth && height == lastHeight) { + // dimension of the SplineGraph is the same + // resized = false; + } else { + // dimension changed + // resized = true; + lastWidth = width; + lastHeight = height; + } + + this.plot = model.getDataCurve().getPlots(width, height); // rounds! + + int n = plot.length; + int curY = plot[0]; + + for (int i = 1; i < n; i++) { + g.setColor(Color.black); + g.drawLine(i - 1, height - curY - 1, i, height - plot[i] - 1); + curY = plot[i]; + } + } + } +} diff --git a/src/components/org/apache/jmeter/visualizers/StatGraphProperties.java b/src/components/org/apache/jmeter/visualizers/StatGraphProperties.java new file mode 100644 index 00000000000..e560247d6a1 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/StatGraphProperties.java @@ -0,0 +1,73 @@ +/* + * 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.visualizers; + +import java.awt.Font; +import java.awt.Shape; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.jmeter.util.JMeterUtils; +import org.jCharts.properties.LegendAreaProperties; +import org.jCharts.properties.PointChartProperties; + +public class StatGraphProperties { + + public static final String[] fontSize = { "8", "9", "10", "11", "12", "14", "16", "18", "20", "24", "28", "32"}; + + public static final String[] strokeWidth = { "1.0f", "1.5f", "2.0f", "2.5f", "3.0f", "3.5f", "4.0f", "4.5f", "5.0f", "5.5f", "6.0f", "6.5f"}; + + public static Map getFontNameMap() { + Map fontNameMap = new LinkedHashMap(2); + fontNameMap.put(JMeterUtils.getResString("font.sansserif"), "SansSerif"); //$NON-NLS-1$ //$NON-NLS-1$ + fontNameMap.put(JMeterUtils.getResString("font.serif"), "Serif"); //$NON-NLS-1$ + return fontNameMap; + } + + @SuppressWarnings("boxing") + public static Map getFontStyleMap() { + Map fontStyleMap = new LinkedHashMap(3); + fontStyleMap.put(JMeterUtils.getResString("fontstyle.normal"), Font.PLAIN); //$NON-NLS-1$ + fontStyleMap.put(JMeterUtils.getResString("fontstyle.bold"), Font.BOLD); //$NON-NLS-1$ + fontStyleMap.put(JMeterUtils.getResString("fontstyle.italic"), Font.ITALIC); //$NON-NLS-1$ + return fontStyleMap; + } + + @SuppressWarnings("boxing") + public static Map getPlacementNameMap() { + Map placementNameMap = new LinkedHashMap(4); + placementNameMap.put(JMeterUtils.getResString("aggregate_graph_legend.placement.bottom"), LegendAreaProperties.BOTTOM); //$NON-NLS-1$ + placementNameMap.put(JMeterUtils.getResString("aggregate_graph_legend.placement.right"), LegendAreaProperties.RIGHT); //$NON-NLS-1$ + placementNameMap.put(JMeterUtils.getResString("aggregate_graph_legend.placement.left"), LegendAreaProperties.LEFT); //$NON-NLS-1$ + placementNameMap.put(JMeterUtils.getResString("aggregate_graph_legend.placement.top"), LegendAreaProperties.TOP); //$NON-NLS-1$ + return placementNameMap; + } + + public static Map getPointShapeMap() { + // We want to retain insertion order, so LinkedHashMap is necessary + Map pointShapeMap = new LinkedHashMap(5); + pointShapeMap.put(JMeterUtils.getResString("graph_pointshape_circle"), PointChartProperties.SHAPE_CIRCLE); //$NON-NLS-1$ + pointShapeMap.put(JMeterUtils.getResString("graph_pointshape_diamond"), PointChartProperties.SHAPE_DIAMOND); //$NON-NLS-1$ + pointShapeMap.put(JMeterUtils.getResString("graph_pointshape_square"), PointChartProperties.SHAPE_SQUARE); //$NON-NLS-1$ + pointShapeMap.put(JMeterUtils.getResString("graph_pointshape_triangle"), PointChartProperties.SHAPE_TRIANGLE); //$NON-NLS-1$ + pointShapeMap.put(JMeterUtils.getResString("graph_pointshape_none"), null); //$NON-NLS-1$ + return pointShapeMap; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/StatGraphVisualizer.java b/src/components/org/apache/jmeter/visualizers/StatGraphVisualizer.java new file mode 100644 index 00000000000..023d47fa8d8 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/StatGraphVisualizer.java @@ -0,0 +1,983 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.Format; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JColorChooser; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.table.TableCellRenderer; + +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.SaveGraphics; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.gui.NumberRenderer; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RateRenderer; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Aggregrate Table-Based Reporting Visualizer for JMeter. Props to the people + * who've done the other visualizers ahead of me (Stefano Mazzocchi), who I + * borrowed code from to start me off (and much code may still exist). Thank + * you! + * + */ +public class StatGraphVisualizer extends AbstractVisualizer implements Clearable, ActionListener { + private static final long serialVersionUID = 240L; + + private static final String pct1Label = JMeterUtils.getPropDefault("aggregate_rpt_pct1", "90"); + private static final String pct2Label = JMeterUtils.getPropDefault("aggregate_rpt_pct2", "95"); + private static final String pct3Label = JMeterUtils.getPropDefault("aggregate_rpt_pct3", "99"); + + private static final Float pct1Value = new Float(Float.parseFloat(pct1Label)/100); + private static final Float pct2Value = new Float(Float.parseFloat(pct2Label)/100); + private static final Float pct3Value = new Float(Float.parseFloat(pct3Label)/100); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + static final String[] COLUMNS = { + "sampler_label", //$NON-NLS-1$ + "aggregate_report_count", //$NON-NLS-1$ + "average", //$NON-NLS-1$ + "aggregate_report_median", //$NON-NLS-1$ + "aggregate_report_xx_pct1_line", //$NON-NLS-1$ + "aggregate_report_xx_pct2_line", //$NON-NLS-1$ + "aggregate_report_xx_pct3_line", //$NON-NLS-1$ + "aggregate_report_min", //$NON-NLS-1$ + "aggregate_report_max", //$NON-NLS-1$ + "aggregate_report_error%", //$NON-NLS-1$ + "aggregate_report_rate", //$NON-NLS-1$ + "aggregate_report_bandwidth" }; //$NON-NLS-1$ + + static final Object[][] COLUMNS_MSG_PARAMETERS = { null, //$NON-NLS-1$ + null, //$NON-NLS-1$ + null, //$NON-NLS-1$ + null, //$NON-NLS-1$ + new Object[]{pct1Label}, //$NON-NLS-1$ + new Object[]{pct2Label}, //$NON-NLS-1$ + new Object[]{pct3Label}, //$NON-NLS-1$ + null, //$NON-NLS-1$ + null, //$NON-NLS-1$ + null, //$NON-NLS-1$ + null, //$NON-NLS-1$ + null }; //$NON-NLS-1$ + + private final String[] GRAPH_COLUMNS = {"average",//$NON-NLS-1$ + "aggregate_report_median", //$NON-NLS-1$ + "aggregate_report_xx_pct1_line", //$NON-NLS-1$ + "aggregate_report_xx_pct2_line", //$NON-NLS-1$ + "aggregate_report_xx_pct3_line", //$NON-NLS-1$ + "aggregate_report_min", //$NON-NLS-1$ + "aggregate_report_max"}; //$NON-NLS-1$ + + private final String TOTAL_ROW_LABEL = + JMeterUtils.getResString("aggregate_report_total_label"); //$NON-NLS-1$ + + private Font FONT_SMALL = new Font("SansSerif", Font.PLAIN, 10); // $NON-NLS-1$ + + private JTable myJTable; + + private JScrollPane myScrollPane; + + private transient ObjectTableModel model; + + /** + * Lock used to protect tableRows update + model update + */ + private final transient Object lock = new Object(); + + private final Map tableRows = + new ConcurrentHashMap(); + + private AxisGraph graphPanel = null; + + private JPanel settingsPane = null; + + private JSplitPane spane = null; + + //NOT USED protected double[][] data = null; + + private JTabbedPane tabbedGraph = new JTabbedPane(SwingConstants.TOP); + + private JButton displayButton = + new JButton(JMeterUtils.getResString("aggregate_graph_display")); //$NON-NLS-1$ + + private JButton saveGraph = + new JButton(JMeterUtils.getResString("aggregate_graph_save")); //$NON-NLS-1$ + + private JButton saveTable = + new JButton(JMeterUtils.getResString("aggregate_graph_save_table")); //$NON-NLS-1$ + + private JButton chooseForeColor = + new JButton(JMeterUtils.getResString("aggregate_graph_choose_foreground_color")); //$NON-NLS-1$ + + private JButton syncWithName = + new JButton(JMeterUtils.getResString("aggregate_graph_sync_with_name")); //$NON-NLS-1$ + + private JCheckBox saveHeaders = // should header be saved with the data? + new JCheckBox(JMeterUtils.getResString("aggregate_graph_save_table_header")); //$NON-NLS-1$ + + private JLabeledTextField graphTitle = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_user_title")); //$NON-NLS-1$ + + private JLabeledTextField maxLengthXAxisLabel = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_max_length_xaxis_label"), 8);//$NON-NLS-1$ + + private JLabeledTextField maxValueYAxisLabel = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_yaxis_max_value"), 8);//$NON-NLS-1$ + + /** + * checkbox for use dynamic graph size + */ + private JCheckBox dynamicGraphSize = new JCheckBox(JMeterUtils.getResString("aggregate_graph_dynamic_size")); // $NON-NLS-1$ + + private JLabeledTextField graphWidth = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_width"), 6); //$NON-NLS-1$ + private JLabeledTextField graphHeight = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_height"), 6); //$NON-NLS-1$ + + private String yAxisLabel = JMeterUtils.getResString("aggregate_graph_response_time");//$NON-NLS-1$ + + private String yAxisTitle = JMeterUtils.getResString("aggregate_graph_ms"); //$NON-NLS-1$ + + private boolean saveGraphToFile = false; + + private int defaultWidth = 400; + + private int defaultHeight = 300; + + private JComboBox columnsList = new JComboBox(GRAPH_COLUMNS); + + private List eltList = new ArrayList(); + + private JCheckBox columnSelection = new JCheckBox(JMeterUtils.getResString("aggregate_graph_column_selection"), false); //$NON-NLS-1$ + + private JTextField columnMatchLabel = new JTextField(); + + private JButton applyFilterBtn = new JButton(JMeterUtils.getResString("graph_apply_filter")); // $NON-NLS-1$ + + private JCheckBox caseChkBox = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_case"), false); // $NON-NLS-1$ + + private JCheckBox regexpChkBox = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_regexp"), true); // $NON-NLS-1$ + + private JComboBox titleFontNameList = new JComboBox(StatGraphProperties.getFontNameMap().keySet().toArray()); + + private JComboBox titleFontSizeList = new JComboBox(StatGraphProperties.fontSize); + + private JComboBox titleFontStyleList = new JComboBox(StatGraphProperties.getFontStyleMap().keySet().toArray()); + + private JComboBox valueFontNameList = new JComboBox(StatGraphProperties.getFontNameMap().keySet().toArray()); + + private JComboBox valueFontSizeList = new JComboBox(StatGraphProperties.fontSize); + + private JComboBox valueFontStyleList = new JComboBox(StatGraphProperties.getFontStyleMap().keySet().toArray()); + + private JComboBox fontNameList = new JComboBox(StatGraphProperties.getFontNameMap().keySet().toArray()); + + private JComboBox fontSizeList = new JComboBox(StatGraphProperties.fontSize); + + private JComboBox fontStyleList = new JComboBox(StatGraphProperties.getFontStyleMap().keySet().toArray()); + + private JComboBox legendPlacementList = new JComboBox(StatGraphProperties.getPlacementNameMap().keySet().toArray()); + + private JCheckBox drawOutlinesBar = new JCheckBox(JMeterUtils.getResString("aggregate_graph_draw_outlines"), true); // Default checked // $NON-NLS-1$ + + private JCheckBox numberShowGrouping = new JCheckBox(JMeterUtils.getResString("aggregate_graph_number_grouping"), true); // Default checked // $NON-NLS-1$ + + private JCheckBox valueLabelsVertical = new JCheckBox(JMeterUtils.getResString("aggregate_graph_value_labels_vertical"), true); // Default checked // $NON-NLS-1$ + + private Color colorBarGraph = Color.YELLOW; + + private Color colorForeGraph = Color.BLACK; + + private int nbColToGraph = 1; + + private Pattern pattern = null; + + private transient Matcher matcher = null; + + public StatGraphVisualizer() { + super(); + model = createObjectTableModel(); + eltList.add(new BarGraph(JMeterUtils.getResString("average"), true, new Color(202, 0, 0))); + eltList.add(new BarGraph(JMeterUtils.getResString("aggregate_report_median"), false, new Color(49, 49, 181))); + eltList.add(new BarGraph(MessageFormat.format(JMeterUtils.getResString("aggregate_report_xx_pct1_line"),new Object[]{pct1Label}), false, new Color(42, 121, 42))); + eltList.add(new BarGraph(MessageFormat.format(JMeterUtils.getResString("aggregate_report_xx_pct2_line"),new Object[]{pct2Label}), false, new Color(242, 226, 8))); + eltList.add(new BarGraph(MessageFormat.format(JMeterUtils.getResString("aggregate_report_xx_pct3_line"),new Object[]{pct3Label}), false, new Color(202, 10 , 232))); + eltList.add(new BarGraph(JMeterUtils.getResString("aggregate_report_min"), false, Color.LIGHT_GRAY)); + eltList.add(new BarGraph(JMeterUtils.getResString("aggregate_report_max"), false, Color.DARK_GRAY)); + clearData(); + init(); + } + + /** + * Creates that Table model + * @return ObjectTableModel + */ + static ObjectTableModel createObjectTableModel() { + return new ObjectTableModel(COLUMNS, + SamplingStatCalculator.class, + new Functor[] { + new Functor("getLabel"), //$NON-NLS-1$ + new Functor("getCount"), //$NON-NLS-1$ + new Functor("getMeanAsNumber"), //$NON-NLS-1$ + new Functor("getMedian"), //$NON-NLS-1$ + new Functor("getPercentPoint", //$NON-NLS-1$ + new Object[] { pct1Value }), + new Functor("getPercentPoint", //$NON-NLS-1$ + new Object[] { pct2Value }), + new Functor("getPercentPoint", //$NON-NLS-1$ + new Object[] { pct3Value }), + new Functor("getMin"), //$NON-NLS-1$ + new Functor("getMax"), //$NON-NLS-1$ + new Functor("getErrorPercentage"), //$NON-NLS-1$ + new Functor("getRate"), //$NON-NLS-1$ + new Functor("getKBPerSecond") }, //$NON-NLS-1$ + new Functor[] { null, null, null, null, null, null, null, null, null, null, null, null }, + new Class[] { String.class, Long.class, Long.class, Long.class, Long.class, + Long.class, Long.class, Long.class, Long.class, String.class, + String.class, String.class }); + } + + // Column formats + static final Format[] FORMATS = + new Format[]{ + null, // Label + null, // count + null, // Mean + null, // median + null, // 90% + null, // 95% + null, // 99% + null, // Min + null, // Max + new DecimalFormat("#0.00%"), // Error %age //$NON-NLS-1$ + new DecimalFormat("#.0"), // Throughput //$NON-NLS-1$ + new DecimalFormat("#.0") // pageSize //$NON-NLS-1$ + }; + + // Column renderers + static final TableCellRenderer[] RENDERERS = + new TableCellRenderer[]{ + null, // Label + null, // count + null, // Mean + null, // median + null, // 90% + null, // 95% + null, // 99% + null, // Min + null, // Max + new NumberRenderer("#0.00%"), // Error %age //$NON-NLS-1$ + new RateRenderer("#.0"), // Throughput //$NON-NLS-1$ + new NumberRenderer("#.0"), // pageSize //$NON-NLS-1$ + }; + + public static boolean testFunctors(){ + StatGraphVisualizer instance = new StatGraphVisualizer(); + return instance.model.checkFunctors(null,instance.getClass()); + } + + @Override + public String getLabelResource() { + return "aggregate_graph_title"; //$NON-NLS-1$ + } + + @Override + public void add(final SampleResult res) { + final String sampleLabel = res.getSampleLabel(); + // Sampler selection + if (columnSelection.isSelected() && pattern != null) { + matcher = pattern.matcher(sampleLabel); + } + if ((matcher == null) || (matcher.find())) { + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + SamplingStatCalculator row = null; + synchronized (lock) { + row = tableRows.get(sampleLabel); + if (row == null) { + row = new SamplingStatCalculator(sampleLabel); + tableRows.put(row.getLabel(), row); + model.insertRow(row, model.getRowCount() - 1); + } + } + row.addSample(res); + tableRows.get(TOTAL_ROW_LABEL).addSample(res); + model.fireTableDataChanged(); + } + }); + } + } + + /** + * Clears this visualizer and its model, and forces a repaint of the table. + */ + @Override + public void clearData() { + synchronized (lock) { + model.clearData(); + tableRows.clear(); + tableRows.put(TOTAL_ROW_LABEL, new SamplingStatCalculator(TOTAL_ROW_LABEL)); + model.addRow(tableRows.get(TOTAL_ROW_LABEL)); + } + } + + /** + * Main visualizer setup. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + Border margin2 = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.add(makeTitlePanel()); + + myJTable = new JTable(model); + // Fix centering of titles + myJTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer(COLUMNS_MSG_PARAMETERS)); + myJTable.setPreferredScrollableViewportSize(new Dimension(500, 70)); + RendererUtils.applyRenderers(myJTable, RENDERERS); + myScrollPane = new JScrollPane(myJTable); + + settingsPane = new VerticalPanel(); + settingsPane.setBorder(margin2); + + graphPanel = new AxisGraph(); + graphPanel.setPreferredSize(new Dimension(defaultWidth, defaultHeight)); + + settingsPane.add(createGraphActionsPane()); + settingsPane.add(createGraphColumnPane()); + settingsPane.add(createGraphTitlePane()); + settingsPane.add(createGraphDimensionPane()); + JPanel axisPane = new JPanel(new BorderLayout()); + axisPane.add(createGraphXAxisPane(), BorderLayout.WEST); + axisPane.add(createGraphYAxisPane(), BorderLayout.CENTER); + settingsPane.add(axisPane); + settingsPane.add(createLegendPane()); + + tabbedGraph.addTab(JMeterUtils.getResString("aggregate_graph_tab_settings"), settingsPane); //$NON-NLS-1$ + tabbedGraph.addTab(JMeterUtils.getResString("aggregate_graph_tab_graph"), graphPanel); //$NON-NLS-1$ + + // If clic on the Graph tab, make the graph (without apply interval or filter) + ChangeListener changeListener = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent changeEvent) { + JTabbedPane srcTab = (JTabbedPane) changeEvent.getSource(); + int index = srcTab.getSelectedIndex(); + if (srcTab.getTitleAt(index).equals(JMeterUtils.getResString("aggregate_graph_tab_graph"))) { //$NON-NLS-1$ + actionMakeGraph(); + } + } + }; + tabbedGraph.addChangeListener(changeListener); + + spane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + spane.setLeftComponent(myScrollPane); + spane.setRightComponent(tabbedGraph); + spane.setResizeWeight(.2); + spane.setBorder(null); // see bug jdk 4131528 + spane.setContinuousLayout(true); + + this.add(mainPanel, BorderLayout.NORTH); + this.add(spane, BorderLayout.CENTER); + } + + public void makeGraph() { + nbColToGraph = getNbColumns(); + Dimension size = graphPanel.getSize(); + String lstr = maxLengthXAxisLabel.getText(); + // canvas size + int width = (int) size.getWidth(); + int height = (int) size.getHeight(); + if (!dynamicGraphSize.isSelected()) { + String wstr = graphWidth.getText(); + String hstr = graphHeight.getText(); + if (wstr.length() != 0) { + width = Integer.parseInt(wstr); + } + if (hstr.length() != 0) { + height = Integer.parseInt(hstr); + } + } + + if (lstr.length() == 0) { + lstr = "20";//$NON-NLS-1$ + } + int maxLength = Integer.parseInt(lstr); + String yAxisStr = maxValueYAxisLabel.getText(); + int maxYAxisScale = yAxisStr.length() == 0 ? 0 : Integer.parseInt(yAxisStr); + + graphPanel.setData(this.getData()); + graphPanel.setTitle(graphTitle.getText()); + graphPanel.setMaxLength(maxLength); + graphPanel.setMaxYAxisScale(maxYAxisScale); + graphPanel.setXAxisLabels(getAxisLabels()); + graphPanel.setXAxisTitle(JMeterUtils.getResString((String) columnsList.getSelectedItem())); + graphPanel.setYAxisLabels(this.yAxisLabel); + graphPanel.setYAxisTitle(this.yAxisTitle); + graphPanel.setLegendLabels(getLegendLabels()); + graphPanel.setColor(getBackColors()); + graphPanel.setForeColor(colorForeGraph); + graphPanel.setOutlinesBarFlag(drawOutlinesBar.isSelected()); + graphPanel.setShowGrouping(numberShowGrouping.isSelected()); + graphPanel.setValueOrientation(valueLabelsVertical.isSelected()); + graphPanel.setLegendPlacement(StatGraphProperties.getPlacementNameMap() + .get(legendPlacementList.getSelectedItem()).intValue()); + + graphPanel.setTitleFont(new Font(StatGraphProperties.getFontNameMap().get(titleFontNameList.getSelectedItem()), + StatGraphProperties.getFontStyleMap().get(titleFontStyleList.getSelectedItem()).intValue(), + Integer.parseInt((String) titleFontSizeList.getSelectedItem()))); + graphPanel.setLegendFont(new Font(StatGraphProperties.getFontNameMap().get(fontNameList.getSelectedItem()), + StatGraphProperties.getFontStyleMap().get(fontStyleList.getSelectedItem()).intValue(), + Integer.parseInt((String) fontSizeList.getSelectedItem()))); + graphPanel.setValueFont(new Font(StatGraphProperties.getFontNameMap().get(valueFontNameList.getSelectedItem()), + StatGraphProperties.getFontStyleMap().get(valueFontStyleList.getSelectedItem()).intValue(), + Integer.parseInt((String) valueFontSizeList.getSelectedItem()))); + + graphPanel.setHeight(height); + graphPanel.setWidth(width); + spane.repaint(); + } + + public double[][] getData() { + if (model.getRowCount() > 1) { + int count = model.getRowCount() -1; + + int size = nbColToGraph; + double[][] data = new double[size][count]; + int s = 0; + int cpt = 0; + for (BarGraph bar : eltList) { + if (bar.getChkBox().isSelected()) { + int col = model.findColumn((String) columnsList.getItemAt(cpt)); + for (int idx=0; idx < count; idx++) { + data[s][idx] = ((Number)model.getValueAt(idx,col)).doubleValue(); + } + s++; + } + cpt++; + } + return data; + } + // API expects null, not empty array + return null; + } + + public String[] getAxisLabels() { + if (model.getRowCount() > 1) { + int count = model.getRowCount() -1; + String[] labels = new String[count]; + for (int idx=0; idx < count; idx++) { + labels[idx] = (String)model.getValueAt(idx,0); + } + return labels; + } + // API expects null, not empty array + return null; + } + + private String[] getLegendLabels() { + String[] legends = new String[nbColToGraph]; + int i = 0; + for (BarGraph bar : eltList) { + if (bar.getChkBox().isSelected()) { + legends[i] = bar.getLabel(); + i++; + } + } + return legends; + } + + private Color[] getBackColors() { + Color[] backColors = new Color[nbColToGraph]; + int i = 0; + for (BarGraph bar : eltList) { + if (bar.getChkBox().isSelected()) { + backColors[i] = bar.getBackColor(); + i++; + } + } + return backColors; + } + + private int getNbColumns() { + int i = 0; + for (BarGraph bar : eltList) { + if (bar.getChkBox().isSelected()) { + i++; + } + } + return i; + } + + /** + * We use this method to get the data, since we are using + * ObjectTableModel, so the calling getDataVector doesn't + * work as expected. + * @param model {@link ObjectTableModel} + * @param formats Array of {@link Format} array can contain null formatters in this case value is added as is + * @return the data from the model + */ + public static List> getAllTableData(ObjectTableModel model, Format[] formats) { + List> data = new ArrayList>(); + if (model.getRowCount() > 0) { + for (int rw=0; rw < model.getRowCount(); rw++) { + int cols = model.getColumnCount(); + List column = new ArrayList(); + data.add(column); + for (int idx=0; idx < cols; idx++) { + Object val = model.getValueAt(rw,idx); + if(formats[idx] != null) { + column.add(formats[idx].format(val)); + } else { + column.add(val); + } + } + } + } + return data; + } + + @Override + public void actionPerformed(ActionEvent event) { + boolean forceReloadData = false; + final Object eventSource = event.getSource(); + if (eventSource == displayButton) { + actionMakeGraph(); + } else if (eventSource == saveGraph) { + saveGraphToFile = true; + try { + ActionRouter.getInstance().getAction( + ActionNames.SAVE_GRAPHICS,SaveGraphics.class.getName()).doAction( + new ActionEvent(this,event.getID(),ActionNames.SAVE_GRAPHICS)); + } catch (Exception e) { + log.error(e.getMessage()); + } + } else if (eventSource == saveTable) { + JFileChooser chooser = FileDialoger.promptToSaveFile("statistics.csv"); //$NON-NLS-1$ + if (chooser == null) { + return; + } + FileWriter writer = null; + try { + writer = new FileWriter(chooser.getSelectedFile()); // TODO Charset ? + CSVSaveService.saveCSVStats(getAllTableData(model, FORMATS),writer,saveHeaders.isSelected() ? getLabels(COLUMNS) : null); + } catch (FileNotFoundException e) { + JMeterUtils.reportErrorToUser(e.getMessage(), "Error saving data"); + } catch (IOException e) { + JMeterUtils.reportErrorToUser(e.getMessage(), "Error saving data"); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } else if (eventSource == chooseForeColor) { + Color color = JColorChooser.showDialog( + null, + JMeterUtils.getResString("aggregate_graph_choose_color"), //$NON-NLS-1$ + colorBarGraph); + if (color != null) { + colorForeGraph = color; + } + } else if (eventSource == syncWithName) { + graphTitle.setText(namePanel.getName()); + } else if (eventSource == dynamicGraphSize) { + // if use dynamic graph size is checked, we disable the dimension fields + if (dynamicGraphSize.isSelected()) { + graphWidth.setEnabled(false); + graphHeight.setEnabled(false); + } else { + graphWidth.setEnabled(true); + graphHeight.setEnabled(true); + } + } else if (eventSource == columnSelection) { + if (columnSelection.isSelected()) { + columnMatchLabel.setEnabled(true); + applyFilterBtn.setEnabled(true); + caseChkBox.setEnabled(true); + regexpChkBox.setEnabled(true); + } else { + columnMatchLabel.setEnabled(false); + applyFilterBtn.setEnabled(false); + caseChkBox.setEnabled(false); + regexpChkBox.setEnabled(false); + // Force reload data + forceReloadData = true; + } + } + // Not 'else if' because forceReloadData + if (eventSource == applyFilterBtn || forceReloadData) { + if (columnSelection.isSelected() && columnMatchLabel.getText() != null + && columnMatchLabel.getText().length() > 0) { + pattern = createPattern(columnMatchLabel.getText()); + } else if (forceReloadData) { + pattern = null; + matcher = null; + } + if (getFile() != null && getFile().length() > 0) { + clearData(); + FilePanel filePanel = (FilePanel) getFilePanel(); + filePanel.actionPerformed(event); + } + } else if (eventSource instanceof JButton) { + // Changing color for column + JButton btn = ((JButton) eventSource); + if (btn.getName() != null) { + try { + BarGraph bar = eltList.get(Integer.parseInt(btn.getName())); + Color color = JColorChooser.showDialog(null, bar.getLabel(), bar.getBackColor()); + if (color != null) { + bar.setBackColor(color); + btn.setBackground(bar.getBackColor()); + } + } catch (NumberFormatException nfe) { } // nothing to do + } + } + } + + /** + * + * @param keys I18N keys + * @return labels + */ + static String[] getLabels(String[] keys) { + String[] labels = new String[keys.length]; + for (int i = 0; i < labels.length; i++) { + labels[i]=MessageFormat.format(JMeterUtils.getResString(keys[i]), COLUMNS_MSG_PARAMETERS[i]); + } + return labels; + } + + private void actionMakeGraph() { + if (model.getRowCount() > 1) { + makeGraph(); + tabbedGraph.setSelectedIndex(1); + } else { + JOptionPane.showMessageDialog(null, JMeterUtils + .getResString("aggregate_graph_no_values_to_graph"), // $NON-NLS-1$ + JMeterUtils.getResString("aggregate_graph_no_values_to_graph"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + } + } + @Override + public JComponent getPrintableComponent() { + if (saveGraphToFile == true) { + saveGraphToFile = false; + graphPanel.setBounds(graphPanel.getLocation().x,graphPanel.getLocation().y, + graphPanel.width,graphPanel.height); + return graphPanel; + } + return this; + } + + private JPanel createGraphActionsPane() { + JPanel buttonPanel = new JPanel(new BorderLayout()); + JPanel displayPane = new JPanel(); + displayPane.add(displayButton); + displayButton.addActionListener(this); + buttonPanel.add(displayPane, BorderLayout.WEST); + + JPanel savePane = new JPanel(); + savePane.add(saveGraph); + savePane.add(saveTable); + savePane.add(saveHeaders); + saveGraph.addActionListener(this); + saveTable.addActionListener(this); + syncWithName.addActionListener(this); + buttonPanel.add(savePane, BorderLayout.EAST); + + return buttonPanel; + } + + private JPanel createGraphColumnPane() { + JPanel colPanel = new JPanel(); + colPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0)); + + JLabel label = new JLabel(JMeterUtils.getResString("aggregate_graph_columns_to_display")); //$NON-NLS-1$ + colPanel.add(label); + for (BarGraph bar : eltList) { + colPanel.add(bar.getChkBox()); + colPanel.add(createColorBarButton(bar, eltList.indexOf(bar))); + } + colPanel.add(Box.createRigidArea(new Dimension(5,0))); + chooseForeColor.setFont(FONT_SMALL); + colPanel.add(chooseForeColor); + chooseForeColor.addActionListener(this); + + JPanel optionsPanel = new JPanel(); + optionsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + optionsPanel.add(createGraphFontValuePane()); + optionsPanel.add(drawOutlinesBar); + optionsPanel.add(numberShowGrouping); + optionsPanel.add(valueLabelsVertical); + + JPanel barPane = new JPanel(new BorderLayout()); + barPane.add(colPanel, BorderLayout.NORTH); + barPane.add(Box.createRigidArea(new Dimension(0,3)), BorderLayout.CENTER); + barPane.add(optionsPanel, BorderLayout.SOUTH); + + JPanel columnPane = new JPanel(new BorderLayout()); + columnPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_column_settings"))); // $NON-NLS-1$ + columnPane.add(barPane, BorderLayout.NORTH); + columnPane.add(Box.createRigidArea(new Dimension(0,3)), BorderLayout.CENTER); + columnPane.add(createGraphSelectionSubPane(), BorderLayout.SOUTH); + + return columnPane; + } + + private JButton createColorBarButton(BarGraph barGraph, int index) { + // Button + JButton colorBtn = new JButton(); + colorBtn.setName(String.valueOf(index)); + colorBtn.setFont(FONT_SMALL); + colorBtn.addActionListener(this); + colorBtn.setBackground(barGraph.getBackColor()); + return colorBtn; + } + + private JPanel createGraphSelectionSubPane() { + Font font = new Font("SansSerif", Font.PLAIN, 10); + // Search field + JPanel searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); + searchPanel.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); + + searchPanel.add(columnSelection); + columnMatchLabel.setEnabled(false); + applyFilterBtn.setEnabled(false); + caseChkBox.setEnabled(false); + regexpChkBox.setEnabled(false); + columnSelection.addActionListener(this); + + searchPanel.add(columnMatchLabel); + searchPanel.add(Box.createRigidArea(new Dimension(5,0))); + + // Button + applyFilterBtn.setFont(font); + applyFilterBtn.addActionListener(this); + searchPanel.add(applyFilterBtn); + + // checkboxes + caseChkBox.setFont(font); + searchPanel.add(caseChkBox); + regexpChkBox.setFont(font); + searchPanel.add(regexpChkBox); + + return searchPanel; + } + + private JPanel createGraphTitlePane() { + JPanel titleNamePane = new JPanel(new BorderLayout()); + syncWithName.setFont(new Font("SansSerif", Font.PLAIN, 10)); + titleNamePane.add(graphTitle, BorderLayout.CENTER); + titleNamePane.add(syncWithName, BorderLayout.EAST); + + JPanel titleStylePane = new JPanel(); + titleStylePane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 5)); + titleStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_font"), //$NON-NLS-1$ + titleFontNameList)); + titleFontNameList.setSelectedIndex(0); // default: sans serif + titleStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_size"), //$NON-NLS-1$ + titleFontSizeList)); + titleFontSizeList.setSelectedItem(StatGraphProperties.fontSize[6]); // default: 16 + titleStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_style"), //$NON-NLS-1$ + titleFontStyleList)); + titleFontStyleList.setSelectedItem(JMeterUtils.getResString("fontstyle.bold")); // $NON-NLS-1$ // default: bold + + JPanel titlePane = new JPanel(new BorderLayout()); + titlePane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_title_group"))); // $NON-NLS-1$ + titlePane.add(titleNamePane, BorderLayout.NORTH); + titlePane.add(titleStylePane, BorderLayout.SOUTH); + return titlePane; + } + + private JPanel createGraphFontValuePane() { + JPanel fontValueStylePane = new JPanel(); + fontValueStylePane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + fontValueStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_value_font"), //$NON-NLS-1$ + valueFontNameList)); + valueFontNameList.setSelectedIndex(0); // default: sans serif + fontValueStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_size"), //$NON-NLS-1$ + valueFontSizeList)); + valueFontSizeList.setSelectedItem(StatGraphProperties.fontSize[2]); // default: 10 + fontValueStylePane.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_style"), //$NON-NLS-1$ + valueFontStyleList)); + valueFontStyleList.setSelectedItem(JMeterUtils.getResString("fontstyle.normal")); // default: normal //$NON-NLS-1$ + + return fontValueStylePane; + } + + private JPanel createGraphDimensionPane() { + JPanel dimensionPane = new JPanel(); + dimensionPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + dimensionPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_dimension"))); // $NON-NLS-1$ + + dimensionPane.add(dynamicGraphSize); + dynamicGraphSize.setSelected(true); // default option + graphWidth.setEnabled(false); + graphHeight.setEnabled(false); + dynamicGraphSize.addActionListener(this); + dimensionPane.add(Box.createRigidArea(new Dimension(10,0))); + dimensionPane.add(graphWidth); + dimensionPane.add(Box.createRigidArea(new Dimension(5,0))); + dimensionPane.add(graphHeight); + return dimensionPane; + } + + /** + * Create pane for X Axis options + * @return X Axis pane + */ + private JPanel createGraphXAxisPane() { + JPanel xAxisPane = new JPanel(); + xAxisPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + xAxisPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_xaxis_group"))); // $NON-NLS-1$ + xAxisPane.add(maxLengthXAxisLabel); + return xAxisPane; + } + + /** + * Create pane for Y Axis options + * @return Y Axis pane + */ + private JPanel createGraphYAxisPane() { + JPanel yAxisPane = new JPanel(); + yAxisPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + yAxisPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_yaxis_group"))); // $NON-NLS-1$ + yAxisPane.add(maxValueYAxisLabel); + return yAxisPane; + } + + /** + * Create pane for legend settings + * @return Legend pane + */ + private JPanel createLegendPane() { + JPanel legendPanel = new JPanel(); + legendPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + legendPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_legend"))); // $NON-NLS-1$ + + legendPanel.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_legend_placement"), //$NON-NLS-1$ + legendPlacementList)); + legendPlacementList.setSelectedItem(JMeterUtils.getResString("aggregate_graph_legend.placement.bottom")); // $NON-NLS-1$ // default: bottom + legendPanel.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_font"), //$NON-NLS-1$ + fontNameList)); + fontNameList.setSelectedIndex(0); // default: sans serif + legendPanel.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_size"), //$NON-NLS-1$ + fontSizeList)); + fontSizeList.setSelectedItem(StatGraphProperties.fontSize[2]); // default: 10 + legendPanel.add(GuiUtils.createLabelCombo(JMeterUtils.getResString("aggregate_graph_style"), //$NON-NLS-1$ + fontStyleList)); + fontStyleList.setSelectedItem(JMeterUtils.getResString("fontstyle.normal")); // $NON-NLS-1$ // default: normal + + return legendPanel; + } + + /** + * @param textToFind + * @return pattern ready to search + */ + private Pattern createPattern(String textToFind) { + String textToFindQ = Pattern.quote(textToFind); + if (regexpChkBox.isSelected()) { + textToFindQ = textToFind; + } + Pattern pattern = null; + try { + if (caseChkBox.isSelected()) { + pattern = Pattern.compile(textToFindQ); + } else { + pattern = Pattern.compile(textToFindQ, Pattern.CASE_INSENSITIVE); + } + } catch (PatternSyntaxException pse) { + return null; + } + return pattern; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/StatVisualizer.java b/src/components/org/apache/jmeter/visualizers/StatVisualizer.java new file mode 100644 index 00000000000..01d85def397 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/StatVisualizer.java @@ -0,0 +1,318 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Aggregrate Table-Based Reporting Visualizer for JMeter. Props to the people + * who've done the other visualizers ahead of me (Stefano Mazzocchi), who I + * borrowed code from to start me off (and much code may still exist). Thank + * you! + * + */ +public class StatVisualizer extends AbstractVisualizer implements Clearable, ActionListener { + + private static final long serialVersionUID = 240L; + + private static final String USE_GROUP_NAME = "useGroupName"; //$NON-NLS-1$ + + private static final String SAVE_HEADERS = "saveHeaders"; //$NON-NLS-1$ + + private final String TOTAL_ROW_LABEL = JMeterUtils + .getResString("aggregate_report_total_label"); //$NON-NLS-1$ + + private JTable myJTable; + + private JScrollPane myScrollPane; + + private final JButton saveTable = new JButton( + JMeterUtils.getResString("aggregate_graph_save_table")); //$NON-NLS-1$ + + // should header be saved with the data? + private final JCheckBox saveHeaders = new JCheckBox( + JMeterUtils.getResString("aggregate_graph_save_table_header"), true); //$NON-NLS-1$ + + private final JCheckBox useGroupName = new JCheckBox( + JMeterUtils.getResString("aggregate_graph_use_group_name")); //$NON-NLS-1$ + + private transient ObjectTableModel model; + + /** + * Lock used to protect tableRows update + model update + */ + private final transient Object lock = new Object(); + + private final Map tableRows = + new ConcurrentHashMap(); + + public StatVisualizer() { + super(); + model = StatGraphVisualizer.createObjectTableModel(); + clearData(); + init(); + } + + /** + * @return true iff all functors can be found + * @deprecated - only for use in testing + * */ + @Deprecated + public static boolean testFunctors(){ + StatVisualizer instance = new StatVisualizer(); + return instance.model.checkFunctors(null,instance.getClass()); + } + + @Override + public String getLabelResource() { + return "aggregate_report"; //$NON-NLS-1$ + } + + @Override + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + SamplingStatCalculator row = null; + final String sampleLabel = res.getSampleLabel(useGroupName.isSelected()); + synchronized (lock) { + row = tableRows.get(sampleLabel); + if (row == null) { + row = new SamplingStatCalculator(sampleLabel); + tableRows.put(row.getLabel(), row); + model.insertRow(row, model.getRowCount() - 1); + } + } + /* + * Synch is needed because multiple threads can update the counts. + */ + synchronized(row) { + row.addSample(res); + } + SamplingStatCalculator tot = tableRows.get(TOTAL_ROW_LABEL); + synchronized(tot) { + tot.addSample(res); + } + model.fireTableDataChanged(); + } + }); + } + + /** + * Clears this visualizer and its model, and forces a repaint of the table. + */ + @Override + public void clearData() { + synchronized (lock) { + model.clearData(); + tableRows.clear(); + tableRows.put(TOTAL_ROW_LABEL, new SamplingStatCalculator(TOTAL_ROW_LABEL)); + model.addRow(tableRows.get(TOTAL_ROW_LABEL)); + } + } + + /** + * Main visualizer setup. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + + mainPanel.add(makeTitlePanel()); + + // SortFilterModel mySortedModel = + // new SortFilterModel(myStatTableModel); + myJTable = new JTable(model); + myJTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer(StatGraphVisualizer.COLUMNS_MSG_PARAMETERS)); + myJTable.setPreferredScrollableViewportSize(new Dimension(500, 70)); + RendererUtils.applyRenderers(myJTable, StatGraphVisualizer.RENDERERS); + myScrollPane = new JScrollPane(myJTable); + this.add(mainPanel, BorderLayout.NORTH); + this.add(myScrollPane, BorderLayout.CENTER); + saveTable.addActionListener(this); + JPanel opts = new JPanel(); + opts.add(useGroupName, BorderLayout.WEST); + opts.add(saveTable, BorderLayout.CENTER); + opts.add(saveHeaders, BorderLayout.EAST); + this.add(opts,BorderLayout.SOUTH); + } + + @Override + public void modifyTestElement(TestElement c) { + super.modifyTestElement(c); + c.setProperty(USE_GROUP_NAME, useGroupName.isSelected(), false); + c.setProperty(SAVE_HEADERS, saveHeaders.isSelected(), true); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + useGroupName.setSelected(el.getPropertyAsBoolean(USE_GROUP_NAME, false)); + saveHeaders.setSelected(el.getPropertyAsBoolean(SAVE_HEADERS, true)); + } + + @Override + public void actionPerformed(ActionEvent ev) { + if (ev.getSource() == saveTable) { + JFileChooser chooser = FileDialoger.promptToSaveFile("aggregate.csv");//$NON-NLS-1$ + if (chooser == null) { + return; + } + FileWriter writer = null; + try { + writer = new FileWriter(chooser.getSelectedFile()); // TODO Charset ? + CSVSaveService.saveCSVStats(StatGraphVisualizer.getAllTableData(model, StatGraphVisualizer.FORMATS),writer, + saveHeaders.isSelected() ? StatGraphVisualizer.getLabels(StatGraphVisualizer.COLUMNS) : null); + } catch (FileNotFoundException e) { + JMeterUtils.reportErrorToUser(e.getMessage(), "Error saving data"); + } catch (IOException e) { + JMeterUtils.reportErrorToUser(e.getMessage(), "Error saving data"); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } + } +} + +/** + * Pulled this mainly out of a Core Java book to implement a sorted table - + * haven't implemented this yet, it needs some non-trivial work done to it to + * support our dynamically-sizing TableModel for this visualizer. + * + */ + +//class SortFilterModel extends AbstractTableModel { +// private TableModel model; +// +// private int sortColumn; +// +// private Row[] rows; +// +// public SortFilterModel(TableModel m) { +// model = m; +// rows = new Row[model.getRowCount()]; +// for (int i = 0; i < rows.length; i++) { +// rows[i] = new Row(); +// rows[i].index = i; +// } +// } +// +// public SortFilterModel() { +// } +// +// public void setValueAt(Object aValue, int r, int c) { +// model.setValueAt(aValue, rows[r].index, c); +// } +// +// public Object getValueAt(int r, int c) { +// return model.getValueAt(rows[r].index, c); +// } +// +// public boolean isCellEditable(int r, int c) { +// return model.isCellEditable(rows[r].index, c); +// } +// +// public int getRowCount() { +// return model.getRowCount(); +// } +// +// public int getColumnCount() { +// return model.getColumnCount(); +// } +// +// public String getColumnName(int c) { +// return model.getColumnName(c); +// } +// +// public Class getColumnClass(int c) { +// return model.getColumnClass(c); +// } +// +// public void sort(int c) { +// sortColumn = c; +// Arrays.sort(rows); +// fireTableDataChanged(); +// } +// +// public void addMouseListener(final JTable table) { +// table.getTableHeader().addMouseListener(new MouseAdapter() { +// public void mouseClicked(MouseEvent event) { +// if (event.getClickCount() < 2) { +// return; +// } +// int tableColumn = table.columnAtPoint(event.getPoint()); +// int modelColumn = table.convertColumnIndexToModel(tableColumn); +// +// sort(modelColumn); +// } +// }); +// } +// +// private class Row implements Comparable { +// public int index; +// +// public int compareTo(Object other) { +// Row otherRow = (Row) other; +// Object a = model.getValueAt(index, sortColumn); +// Object b = model.getValueAt(otherRow.index, sortColumn); +// +// if (a instanceof Comparable) { +// return ((Comparable) a).compareTo(b); +// } else { +// return index - otherRow.index; +// } +// } +// } +//} // class SortFilterModel diff --git a/src/components/org/apache/jmeter/visualizers/SummaryReport.java b/src/components/org/apache/jmeter/visualizers/SummaryReport.java new file mode 100644 index 00000000000..d31f3789601 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/SummaryReport.java @@ -0,0 +1,288 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.Format; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableCellRenderer; + +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.Calculator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.NumberRenderer; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RateRenderer; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.reflect.Functor; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Simpler (lower memory) version of Aggregate Report (StatVisualizer). + * Excludes the Median and 90% columns, which are expensive in memory terms + */ +public class SummaryReport extends AbstractVisualizer implements Clearable, ActionListener { + + private static final long serialVersionUID = 240L; + + private static final String USE_GROUP_NAME = "useGroupName"; //$NON-NLS-1$ + + private static final String SAVE_HEADERS = "saveHeaders"; //$NON-NLS-1$ + + private static final String[] COLUMNS = { + "sampler_label", //$NON-NLS-1$ + "aggregate_report_count", //$NON-NLS-1$ + "average", //$NON-NLS-1$ + "aggregate_report_min", //$NON-NLS-1$ + "aggregate_report_max", //$NON-NLS-1$ + "aggregate_report_stddev", //$NON-NLS-1$ + "aggregate_report_error%", //$NON-NLS-1$ + "aggregate_report_rate", //$NON-NLS-1$ + "aggregate_report_bandwidth", //$NON-NLS-1$ + "average_bytes", //$NON-NLS-1$ + }; + + private final String TOTAL_ROW_LABEL + = JMeterUtils.getResString("aggregate_report_total_label"); //$NON-NLS-1$ + + private JTable myJTable; + + private JScrollPane myScrollPane; + + private final JButton saveTable = + new JButton(JMeterUtils.getResString("aggregate_graph_save_table")); //$NON-NLS-1$ + + private final JCheckBox saveHeaders = // should header be saved with the data? + new JCheckBox(JMeterUtils.getResString("aggregate_graph_save_table_header"),true); //$NON-NLS-1$ + + private final JCheckBox useGroupName = + new JCheckBox(JMeterUtils.getResString("aggregate_graph_use_group_name")); //$NON-NLS-1$ + + private transient ObjectTableModel model; + + /** + * Lock used to protect tableRows update + model update + */ + private final transient Object lock = new Object(); + + private final Map tableRows = + new ConcurrentHashMap(); + + // Column renderers + private static final TableCellRenderer[] RENDERERS = + new TableCellRenderer[]{ + null, // Label + null, // count + null, // Mean + null, // Min + null, // Max + new NumberRenderer("#0.00"), // Std Dev. //$NON-NLS-1$ + new NumberRenderer("#0.00%"), // Error %age //$NON-NLS-1$ + new RateRenderer("#.0"), // Throughput //$NON-NLS-1$ + new NumberRenderer("#0.00"), // kB/sec //$NON-NLS-1$ + new NumberRenderer("#.0"), // avg. pageSize //$NON-NLS-1$ + }; + + // Column formats + static final Format[] FORMATS = + new Format[]{ + null, // Label + null, // count + null, // Mean + null, // Min + null, // Max + new DecimalFormat("#0.00"), // Std Dev. //$NON-NLS-1$ + new DecimalFormat("#0.00%"), // Error %age //$NON-NLS-1$ + new DecimalFormat("#.0"), // Throughput //$NON-NLS-1$ + new DecimalFormat("#0.00"), // kB/sec //$NON-NLS-1$ + new DecimalFormat("#.0"), // avg. pageSize //$NON-NLS-1$ + }; + + public SummaryReport() { + super(); + model = new ObjectTableModel(COLUMNS, + Calculator.class,// All rows have this class + new Functor[] { + new Functor("getLabel"), //$NON-NLS-1$ + new Functor("getCount"), //$NON-NLS-1$ + new Functor("getMeanAsNumber"), //$NON-NLS-1$ + new Functor("getMin"), //$NON-NLS-1$ + new Functor("getMax"), //$NON-NLS-1$ + new Functor("getStandardDeviation"), //$NON-NLS-1$ + new Functor("getErrorPercentage"), //$NON-NLS-1$ + new Functor("getRate"), //$NON-NLS-1$ + new Functor("getKBPerSecond"), //$NON-NLS-1$ + new Functor("getAvgPageBytes"), //$NON-NLS-1$ + }, + new Functor[] { null, null, null, null, null, null, null, null , null, null }, + new Class[] { String.class, Long.class, Long.class, Long.class, Long.class, + String.class, String.class, String.class, String.class, String.class }); + clearData(); + init(); + } + + /** + * @return true iff all functors can be found + * @deprecated - only for use in testing + * */ + @Deprecated + public static boolean testFunctors(){ + SummaryReport instance = new SummaryReport(); + return instance.model.checkFunctors(null,instance.getClass()); + } + + @Override + public String getLabelResource() { + return "summary_report"; //$NON-NLS-1$ + } + + @Override + public void add(final SampleResult res) { + final String sampleLabel = res.getSampleLabel(useGroupName.isSelected()); + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + Calculator row = null; + synchronized (lock) { + row = tableRows.get(sampleLabel); + if (row == null) { + row = new Calculator(sampleLabel); + tableRows.put(row.getLabel(), row); + model.insertRow(row, model.getRowCount() - 1); + } + } + /* + * Synch is needed because multiple threads can update the counts. + */ + synchronized(row) { + row.addSample(res); + } + Calculator tot = tableRows.get(TOTAL_ROW_LABEL); + synchronized(tot) { + tot.addSample(res); + } + model.fireTableDataChanged(); + } + }); + } + + /** + * Clears this visualizer and its model, and forces a repaint of the table. + */ + @Override + public void clearData() { + //Synch is needed because a clear can occur while add occurs + synchronized (lock) { + model.clearData(); + tableRows.clear(); + tableRows.put(TOTAL_ROW_LABEL, new Calculator(TOTAL_ROW_LABEL)); + model.addRow(tableRows.get(TOTAL_ROW_LABEL)); + } + } + + /** + * Main visualizer setup. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + + mainPanel.add(makeTitlePanel()); + + myJTable = new JTable(model); + myJTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + myJTable.setPreferredScrollableViewportSize(new Dimension(500, 70)); + RendererUtils.applyRenderers(myJTable, RENDERERS); + myScrollPane = new JScrollPane(myJTable); + this.add(mainPanel, BorderLayout.NORTH); + this.add(myScrollPane, BorderLayout.CENTER); + saveTable.addActionListener(this); + JPanel opts = new JPanel(); + opts.add(useGroupName, BorderLayout.WEST); + opts.add(saveTable, BorderLayout.CENTER); + opts.add(saveHeaders, BorderLayout.EAST); + this.add(opts,BorderLayout.SOUTH); + } + + @Override + public void modifyTestElement(TestElement c) { + super.modifyTestElement(c); + c.setProperty(USE_GROUP_NAME, useGroupName.isSelected(), false); + c.setProperty(SAVE_HEADERS, saveHeaders.isSelected(), true); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + useGroupName.setSelected(el.getPropertyAsBoolean(USE_GROUP_NAME, false)); + saveHeaders.setSelected(el.getPropertyAsBoolean(SAVE_HEADERS, true)); + } + + @Override + public void actionPerformed(ActionEvent ev) { + if (ev.getSource() == saveTable) { + JFileChooser chooser = FileDialoger.promptToSaveFile("summary.csv");//$NON-NLS-1$ + if (chooser == null) { + return; + } + FileWriter writer = null; + try { + writer = new FileWriter(chooser.getSelectedFile()); + CSVSaveService.saveCSVStats(StatGraphVisualizer.getAllTableData(model, FORMATS),writer, + saveHeaders.isSelected() ? StatGraphVisualizer.getLabels(COLUMNS) : null); + } catch (FileNotFoundException e) { + JMeterUtils.reportErrorToUser(e.getMessage(), "Error saving data"); + } catch (IOException e) { + JMeterUtils.reportErrorToUser(e.getMessage(), "Error saving data"); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } + } +} diff --git a/src/components/org/apache/jmeter/visualizers/TableVisualizer.java b/src/components/org/apache/jmeter/visualizers/TableVisualizer.java new file mode 100644 index 00000000000..639d758e2f7 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/TableVisualizer.java @@ -0,0 +1,344 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.FlowLayout; +import java.text.Format; +import java.text.SimpleDateFormat; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableCellRenderer; + +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.Calculator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.gui.RightAlignRenderer; +import org.apache.jorphan.gui.layout.VerticalLayout; +import org.apache.jorphan.reflect.Functor; + +/** + * This class implements a statistical analyser that calculates both the average + * and the standard deviation of the sampling process. The samples are displayed + * in a JTable, and the statistics are displayed at the bottom of the table. + * + * created March 10, 2002 + * + */ +public class TableVisualizer extends AbstractVisualizer implements Clearable { + + private static final long serialVersionUID = 240L; + + // Note: the resource string won't respond to locale-changes, + // however this does not matter as it is only used when pasting to the clipboard + private static final ImageIcon imageSuccess = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.success", //$NON-NLS-1$ + "icon_success_sml.gif"), //$NON-NLS-1$ + JMeterUtils.getResString("table_visualizer_success")); //$NON-NLS-1$ + + private static final ImageIcon imageFailure = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.failure", //$NON-NLS-1$ + "icon_warning_sml.gif"), //$NON-NLS-1$ + JMeterUtils.getResString("table_visualizer_warning")); //$NON-NLS-1$ + + private static final String[] COLUMNS = new String[] { + "table_visualizer_sample_num", // $NON-NLS-1$ + "table_visualizer_start_time", // $NON-NLS-1$ + "table_visualizer_thread_name", // $NON-NLS-1$ + "sampler_label", // $NON-NLS-1$ + "table_visualizer_sample_time", // $NON-NLS-1$ + "table_visualizer_status", // $NON-NLS-1$ + "table_visualizer_bytes", // $NON-NLS-1$ + "table_visualizer_latency", // $NON-NLS-1$ + "table_visualizer_connect"}; // $NON-NLS-1$ + + private ObjectTableModel model = null; + + private JTable table = null; + + private JTextField dataField = null; + + private JTextField averageField = null; + + private JTextField deviationField = null; + + private JTextField noSamplesField = null; + + private JScrollPane tableScrollPanel = null; + + private JCheckBox autoscroll = null; + + private JCheckBox childSamples = null; + + private transient Calculator calc = new Calculator(); + + private Format format = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$ + + // Column renderers + private static final TableCellRenderer[] RENDERERS = + new TableCellRenderer[]{ + new RightAlignRenderer(), // Sample number (string) + new RightAlignRenderer(), // Start Time + null, // Thread Name + null, // Label + null, // Sample Time + null, // Status + null, // Bytes + }; + + /** + * Constructor for the TableVisualizer object. + */ + public TableVisualizer() { + super(); + model = new ObjectTableModel(COLUMNS, + TableSample.class, // The object used for each row + new Functor[] { + new Functor("getSampleNumberString"), // $NON-NLS-1$ + new Functor("getStartTimeFormatted", // $NON-NLS-1$ + new Object[]{format}), + new Functor("getThreadName"), // $NON-NLS-1$ + new Functor("getLabel"), // $NON-NLS-1$ + new Functor("getElapsed"), // $NON-NLS-1$ + new SampleSuccessFunctor("isSuccess"), // $NON-NLS-1$ + new Functor("getBytes"), // $NON-NLS-1$ + new Functor("getLatency"), // $NON-NLS-1$ + new Functor("getConnectTime") }, // $NON-NLS-1$ + new Functor[] { null, null, null, null, null, null, null, null, null }, + new Class[] { + String.class, String.class, String.class, String.class, Long.class, ImageIcon.class, Long.class, Long.class, Long.class }); + init(); + } + + public static boolean testFunctors(){ + TableVisualizer instance = new TableVisualizer(); + return instance.model.checkFunctors(null,instance.getClass()); + } + + + @Override + public String getLabelResource() { + return "view_results_in_table"; // $NON-NLS-1$ + } + + protected synchronized void updateTextFields(SampleResult res) { + noSamplesField.setText(Long.toString(calc.getCount())); + if(res.getSampleCount() > 0) { + dataField.setText(Long.toString(res.getTime()/res.getSampleCount())); + } else { + dataField.setText("0"); + } + averageField.setText(Long.toString((long) calc.getMean())); + deviationField.setText(Long.toString((long) calc.getStandardDeviation())); + } + + @Override + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + if (childSamples.isSelected()) { + SampleResult[] subResults = res.getSubResults(); + if (subResults.length > 0) { + for (SampleResult sr : subResults) { + add(sr); + } + return; + } + } + synchronized (calc) { + calc.addSample(res); + int count = calc.getCount(); + TableSample newS = new TableSample( + count, + res.getSampleCount(), + res.getStartTime(), + res.getThreadName(), + res.getSampleLabel(), + res.getTime(), + res.isSuccessful(), + res.getBytes(), + res.getLatency(), + res.getConnectTime() + ); + model.addRow(newS); + } + updateTextFields(res); + if (autoscroll.isSelected()) { + table.scrollRectToVisible(table.getCellRect(table.getRowCount() - 1, 0, true)); + } + } + }); + } + + @Override + public synchronized void clearData() { + model.clearData(); + calc.clear(); + noSamplesField.setText("0"); // $NON-NLS-1$ + dataField.setText("0"); // $NON-NLS-1$ + averageField.setText("0"); // $NON-NLS-1$ + deviationField.setText("0"); // $NON-NLS-1$ + repaint(); + } + + @Override + public String toString() { + return "Show the samples in a table"; + } + + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + + // NAME + mainPanel.add(makeTitlePanel()); + + // Set up the table itself + table = new JTable(model); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + // table.getTableHeader().setReorderingAllowed(false); + RendererUtils.applyRenderers(table, RENDERERS); + + tableScrollPanel = new JScrollPane(table); + tableScrollPanel.setViewportBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + + autoscroll = new JCheckBox(JMeterUtils.getResString("view_results_autoscroll")); //$NON-NLS-1$ + + childSamples = new JCheckBox(JMeterUtils.getResString("view_results_childsamples")); //$NON-NLS-1$ + + // Set up footer of table which displays numerics of the graphs + JPanel dataPanel = new JPanel(); + JLabel dataLabel = new JLabel(JMeterUtils.getResString("graph_results_latest_sample")); // $NON-NLS-1$ + dataLabel.setForeground(Color.black); + dataField = new JTextField(5); + dataField.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + dataField.setEditable(false); + dataField.setForeground(Color.black); + dataField.setBackground(getBackground()); + dataPanel.add(dataLabel); + dataPanel.add(dataField); + + JPanel averagePanel = new JPanel(); + JLabel averageLabel = new JLabel(JMeterUtils.getResString("graph_results_average")); // $NON-NLS-1$ + averageLabel.setForeground(Color.blue); + averageField = new JTextField(5); + averageField.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + averageField.setEditable(false); + averageField.setForeground(Color.blue); + averageField.setBackground(getBackground()); + averagePanel.add(averageLabel); + averagePanel.add(averageField); + + JPanel deviationPanel = new JPanel(); + JLabel deviationLabel = new JLabel(JMeterUtils.getResString("graph_results_deviation")); // $NON-NLS-1$ + deviationLabel.setForeground(Color.red); + deviationField = new JTextField(5); + deviationField.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + deviationField.setEditable(false); + deviationField.setForeground(Color.red); + deviationField.setBackground(getBackground()); + deviationPanel.add(deviationLabel); + deviationPanel.add(deviationField); + + JPanel noSamplesPanel = new JPanel(); + JLabel noSamplesLabel = new JLabel(JMeterUtils.getResString("graph_results_no_samples")); // $NON-NLS-1$ + + noSamplesField = new JTextField(8); + noSamplesField.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + noSamplesField.setEditable(false); + noSamplesField.setForeground(Color.black); + noSamplesField.setBackground(getBackground()); + noSamplesPanel.add(noSamplesLabel); + noSamplesPanel.add(noSamplesField); + + JPanel tableInfoPanel = new JPanel(); + tableInfoPanel.setLayout(new FlowLayout()); + tableInfoPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + tableInfoPanel.add(noSamplesPanel); + tableInfoPanel.add(dataPanel); + tableInfoPanel.add(averagePanel); + tableInfoPanel.add(deviationPanel); + + JPanel tableControlsPanel = new JPanel(new BorderLayout()); + tableControlsPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + JPanel jp = new HorizontalPanel(); + jp.add(autoscroll); + jp.add(childSamples); + tableControlsPanel.add(jp, BorderLayout.WEST); + tableControlsPanel.add(tableInfoPanel, BorderLayout.CENTER); + + // Set up the table with footer + JPanel tablePanel = new JPanel(); + + tablePanel.setLayout(new BorderLayout()); + tablePanel.add(tableScrollPanel, BorderLayout.CENTER); + tablePanel.add(tableControlsPanel, BorderLayout.SOUTH); + + // Add the main panel and the graph + this.add(mainPanel, BorderLayout.NORTH); + this.add(tablePanel, BorderLayout.CENTER); + } + + public static class SampleSuccessFunctor extends Functor { + public SampleSuccessFunctor(String methodName) { + super(methodName); + } + + @Override + public Object invoke(Object p_invokee) { + Boolean success = (Boolean)super.invoke(p_invokee); + + if(success != null) { + if(success.booleanValue()) { + return imageSuccess; + } + else { + return imageFailure; + } + } + else { + return null; + } + } + } +} diff --git a/src/components/org/apache/jmeter/visualizers/TreeNodeRenderer.java b/src/components/org/apache/jmeter/visualizers/TreeNodeRenderer.java new file mode 100644 index 00000000000..cb7113bc8ac --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/TreeNodeRenderer.java @@ -0,0 +1,69 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Component; + +import javax.swing.ImageIcon; +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Tree cell renderer used by ComparisonVisualizer. + */ +public class TreeNodeRenderer extends DefaultTreeCellRenderer { + + private static final long serialVersionUID = 240L; + + // Same ViewResultsTree + private static final ImageIcon imageSuccess = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.success", //$NON-NLS-1$ + "icon_success_sml.gif")); //$NON-NLS-1$ + + private static final ImageIcon imageFailure = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.failure", //$NON-NLS-1$ + "icon_warning_sml.gif")); //$NON-NLS-1$ + + public TreeNodeRenderer() { + super(); + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, + boolean leaf, int row, boolean focus) { + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, focus); + Object obj = ((DefaultMutableTreeNode) value).getUserObject(); + if(obj instanceof SampleResult) + { + if (!((SampleResult) obj).isSuccessful()) { + this.setForeground(Color.red); + this.setIcon(imageFailure); + } else { + this.setIcon(imageSuccess); + } + } + return this; + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java b/src/components/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java new file mode 100644 index 00000000000..744b551b64a --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java @@ -0,0 +1,451 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.ImageIcon; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Base for ViewResults + * + */ +public class ViewResultsFullVisualizer extends AbstractVisualizer +implements ActionListener, TreeSelectionListener, Clearable, ItemListener { + + private static final long serialVersionUID = 7338676747296593842L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final Color SERVER_ERROR_COLOR = Color.red; + + public static final Color CLIENT_ERROR_COLOR = Color.blue; + + public static final Color REDIRECT_COLOR = Color.green; + + private JSplitPane mainSplit; + + private DefaultMutableTreeNode root; + + private DefaultTreeModel treeModel; + + private JTree jTree; + + private Component leftSide; + + private JTabbedPane rightSide; + + private JComboBox selectRenderPanel; + + private int selectedTab; + + protected static final String COMBO_CHANGE_COMMAND = "change_combo"; // $NON-NLS-1$ + + private static final ImageIcon imageSuccess = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.success", //$NON-NLS-1$ + "icon_success_sml.gif")); //$NON-NLS-1$ + + private static final ImageIcon imageFailure = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.failure", //$NON-NLS-1$ + "icon_warning_sml.gif")); //$NON-NLS-1$ + + // Maximum size that we will display + private static final int MAX_DISPLAY_SIZE = + JMeterUtils.getPropDefault("view.results.tree.max_size", 200 * 1024); // $NON-NLS-1$ + + // default display order + private static final String VIEWERS_ORDER = + JMeterUtils.getPropDefault("view.results.tree.renderers_order", ""); // $NON-NLS-1$ //$NON-NLS-2$ + + private ResultRenderer resultsRender = null; + + private TreeSelectionEvent lastSelectionEvent; + + private JCheckBox autoScrollCB; + + /** + * Constructor + */ + public ViewResultsFullVisualizer() { + super(); + init(); + } + + /** {@inheritDoc} */ + @Override + public void add(final SampleResult sample) { + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + updateGui(sample); + } + }); + } + + /** + * Update the visualizer with new data. + */ + private synchronized void updateGui(SampleResult res) { + // Add sample + DefaultMutableTreeNode currNode = new DefaultMutableTreeNode(res); + treeModel.insertNodeInto(currNode, root, root.getChildCount()); + addSubResults(currNode, res); + // Add any assertion that failed as children of the sample node + AssertionResult assertionResults[] = res.getAssertionResults(); + int assertionIndex = currNode.getChildCount(); + for (int j = 0; j < assertionResults.length; j++) { + AssertionResult item = assertionResults[j]; + + if (item.isFailure() || item.isError()) { + DefaultMutableTreeNode assertionNode = new DefaultMutableTreeNode(item); + treeModel.insertNodeInto(assertionNode, currNode, assertionIndex++); + } + } + + if (root.getChildCount() == 1) { + jTree.expandPath(new TreePath(root)); + } + if (autoScrollCB.isSelected() && root.getChildCount() > 1) { + jTree.scrollPathToVisible(new TreePath(new Object[] { root, + treeModel.getChild(root, root.getChildCount() - 1) })); + } + } + + private void addSubResults(DefaultMutableTreeNode currNode, SampleResult res) { + SampleResult[] subResults = res.getSubResults(); + + int leafIndex = 0; + + for (int i = 0; i < subResults.length; i++) { + SampleResult child = subResults[i]; + + if (log.isDebugEnabled()) { + log.debug("updateGui1 : child sample result - " + child); + } + DefaultMutableTreeNode leafNode = new DefaultMutableTreeNode(child); + + treeModel.insertNodeInto(leafNode, currNode, leafIndex++); + addSubResults(leafNode, child); + // Add any assertion that failed as children of the sample node + AssertionResult assertionResults[] = child.getAssertionResults(); + int assertionIndex = leafNode.getChildCount(); + for (int j = 0; j < assertionResults.length; j++) { + AssertionResult item = assertionResults[j]; + + if (item.isFailure() || item.isError()) { + DefaultMutableTreeNode assertionNode = new DefaultMutableTreeNode(item); + treeModel.insertNodeInto(assertionNode, leafNode, assertionIndex++); + } + } + } + } + + /** {@inheritDoc} */ + @Override + public synchronized void clearData() { + while (root.getChildCount() > 0) { + // the child to be removed will always be 0 'cos as the nodes are + // removed the nth node will become (n-1)th + treeModel.removeNodeFromParent((DefaultMutableTreeNode) root.getChildAt(0)); + } + resultsRender.clearData(); + } + + /** {@inheritDoc} */ + @Override + public String getLabelResource() { + return "view_results_tree_title"; // $NON-NLS-1$ + } + + /** + * Initialize this visualizer + */ + protected void init() { + log.debug("init() - pass"); + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + leftSide = createLeftPanel(); + // Prepare the common tab + rightSide = new JTabbedPane(); + + // Create the split pane + mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftSide, rightSide); + add(mainSplit, BorderLayout.CENTER); + // init right side with first render + resultsRender.setRightSide(rightSide); + resultsRender.init(); + } + + /** {@inheritDoc} */ + @Override + public void valueChanged(TreeSelectionEvent e) { + lastSelectionEvent = e; + DefaultMutableTreeNode node = null; + synchronized (this) { + node = (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent(); + } + + if (node != null) { + // to restore last tab used + if (rightSide.getTabCount() > selectedTab) { + resultsRender.setLastSelectedTab(rightSide.getSelectedIndex()); + } + Object userObject = node.getUserObject(); + resultsRender.setSamplerResult(userObject); + resultsRender.setupTabPane(); // Processes Assertions + // display a SampleResult + if (userObject instanceof SampleResult) { + SampleResult sampleResult = (SampleResult) userObject; + if (isTextDataType(sampleResult)){ + resultsRender.renderResult(sampleResult); + } else { + byte[] responseBytes = sampleResult.getResponseData(); + if (responseBytes != null) { + resultsRender.renderImage(sampleResult); + } + } + } + } + } + + /** + * @param sampleResult SampleResult + * @return true if sampleResult is text or has empty content type + */ + protected static boolean isTextDataType(SampleResult sampleResult) { + return (SampleResult.TEXT).equals(sampleResult.getDataType()) + || StringUtils.isEmpty(sampleResult.getDataType()); + } + + private synchronized Component createLeftPanel() { + SampleResult rootSampleResult = new SampleResult(); + rootSampleResult.setSampleLabel("Root"); + rootSampleResult.setSuccessful(true); + root = new DefaultMutableTreeNode(rootSampleResult); + + treeModel = new DefaultTreeModel(root); + jTree = new JTree(treeModel); + jTree.setCellRenderer(new ResultsNodeRenderer()); + jTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + jTree.addTreeSelectionListener(this); + jTree.setRootVisible(false); + jTree.setShowsRootHandles(true); + JScrollPane treePane = new JScrollPane(jTree); + treePane.setPreferredSize(new Dimension(200, 300)); + + VerticalPanel leftPane = new VerticalPanel(); + leftPane.add(treePane, BorderLayout.CENTER); + leftPane.add(createComboRender(), BorderLayout.NORTH); + autoScrollCB = new JCheckBox(JMeterUtils.getResString("view_results_autoscroll")); // $NON-NLS-1$ + autoScrollCB.setSelected(false); + autoScrollCB.addItemListener(this); + leftPane.add(autoScrollCB, BorderLayout.SOUTH); + return leftPane; + } + + /** + * Create the drop-down list to changer render + * @return List of all render (implement ResultsRender) + */ + private Component createComboRender() { + ComboBoxModel nodesModel = new DefaultComboBoxModel(); + // drop-down list for renderer + selectRenderPanel = new JComboBox(nodesModel); + selectRenderPanel.setActionCommand(COMBO_CHANGE_COMMAND); + selectRenderPanel.addActionListener(this); + + // if no results render in jmeter.properties, load Standard (default) + List classesToAdd = Collections.emptyList(); + try { + classesToAdd = JMeterUtils.findClassesThatExtend(ResultRenderer.class); + } catch (IOException e1) { + // ignored + } + String textRenderer = JMeterUtils.getResString("view_results_render_text"); // $NON-NLS-1$ + Object textObject = null; + Map map = new HashMap(classesToAdd.size()); + for (String clazz : classesToAdd) { + try { + // Instantiate render classes + final ResultRenderer renderer = (ResultRenderer) Class.forName(clazz).newInstance(); + if (textRenderer.equals(renderer.toString())){ + textObject=renderer; + } + renderer.setBackgroundColor(getBackground()); + map.put(renderer.getClass().getName(), renderer); + } catch (Exception e) { + log.warn("Error loading result renderer:" + clazz, e); + } + } + if(VIEWERS_ORDER.length()>0) { + String[] keys = VIEWERS_ORDER.split(","); + for (String key : keys) { + if(key.startsWith(".")) { + key = "org.apache.jmeter.visualizers"+key; //$NON-NLS-1$ + } + ResultRenderer renderer = map.remove(key); + if(renderer != null) { + selectRenderPanel.addItem(renderer); + } else { + log.warn("Missing (check spelling error in renderer name) or already added(check doublon) " + + "result renderer, check property 'view.results.tree.renderers_order', renderer name:'"+key+"'"); + } + } + } + // Add remaining (plugins or missed in property) + for (ResultRenderer renderer : map.values()) { + selectRenderPanel.addItem(renderer); + } + nodesModel.setSelectedItem(textObject); // preset to "Text" option + return selectRenderPanel; + } + + /** {@inheritDoc} */ + @Override + public void actionPerformed(ActionEvent event) { + String command = event.getActionCommand(); + if (COMBO_CHANGE_COMMAND.equals(command)) { + JComboBox jcb = (JComboBox) event.getSource(); + + if (jcb != null) { + resultsRender = (ResultRenderer) jcb.getSelectedItem(); + if (rightSide != null) { + // to restore last selected tab (better user-friendly) + selectedTab = rightSide.getSelectedIndex(); + // Remove old right side + mainSplit.remove(rightSide); + + // create and add a new right side + rightSide = new JTabbedPane(); + mainSplit.add(rightSide); + resultsRender.setRightSide(rightSide); + resultsRender.setLastSelectedTab(selectedTab); + log.debug("selectedTab=" + selectedTab); + resultsRender.init(); + // To display current sampler result before change + this.valueChanged(lastSelectionEvent); + } + } + } + } + + public static String getResponseAsString(SampleResult res) { + String response = null; + if (isTextDataType(res)) { + // Showing large strings can be VERY costly, so we will avoid + // doing so if the response + // data is larger than 200K. TODO: instead, we could delay doing + // the result.setText + // call until the user chooses the "Response data" tab. Plus we + // could warn the user + // if this happens and revert the choice if he doesn't confirm + // he's ready to wait. + int len = res.getResponseDataAsString().length(); + if (MAX_DISPLAY_SIZE > 0 && len > MAX_DISPLAY_SIZE) { + StringBuilder builder = new StringBuilder(MAX_DISPLAY_SIZE+100); + builder.append(JMeterUtils.getResString("view_results_response_too_large_message")) //$NON-NLS-1$ + .append(len).append(" > Max: ").append(MAX_DISPLAY_SIZE) + .append(", ").append(JMeterUtils.getResString("view_results_response_partial_message")) // $NON-NLS-1$ + .append("\n").append(res.getResponseDataAsString().substring(0, MAX_DISPLAY_SIZE)).append("\n..."); + response = builder.toString(); + } else { + response = res.getResponseDataAsString(); + } + } + return response; + } + + private static class ResultsNodeRenderer extends DefaultTreeCellRenderer { + private static final long serialVersionUID = 4159626601097711565L; + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, + boolean sel, boolean expanded, boolean leaf, int row, boolean focus) { + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, focus); + boolean failure = true; + Object userObject = ((DefaultMutableTreeNode) value).getUserObject(); + if (userObject instanceof SampleResult) { + failure = !(((SampleResult) userObject).isSuccessful()); + } else if (userObject instanceof AssertionResult) { + AssertionResult assertion = (AssertionResult) userObject; + failure = assertion.isError() || assertion.isFailure(); + } + + // Set the status for the node + if (failure) { + this.setForeground(Color.red); + this.setIcon(imageFailure); + } else { + this.setIcon(imageSuccess); + } + return this; + } + } + + /** + * Handler for Checkbox + */ + @Override + public void itemStateChanged(ItemEvent e) { + // NOOP state is held by component + } +} diff --git a/src/components/org/apache/jmeter/visualizers/XMLDefaultMutableTreeNode.java b/src/components/org/apache/jmeter/visualizers/XMLDefaultMutableTreeNode.java new file mode 100644 index 00000000000..405069621f4 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/XMLDefaultMutableTreeNode.java @@ -0,0 +1,212 @@ +/* + * 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.visualizers; + +import javax.swing.tree.DefaultMutableTreeNode; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.xml.sax.SAXException; + +/** + * A extended class of DefaultMutableTreeNode except that it also attached XML + * node and convert XML document into DefaultMutableTreeNode. + * + */ +public class XMLDefaultMutableTreeNode extends DefaultMutableTreeNode { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + // private static final int LIMIT_STR_SIZE = 100; + // private boolean isRoot; + private transient Node xmlNode; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public XMLDefaultMutableTreeNode(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + + public XMLDefaultMutableTreeNode(Node root) throws SAXException { + super(root.getNodeName()); + initAttributeNode(root, this); + initRoot(root); + + } + + public XMLDefaultMutableTreeNode(String name, Node xmlNode) { + super(name); + this.xmlNode = xmlNode; + + } + + /** + * init root + * + * @param root + * @throws SAXException + */ + private void initRoot(Node xmlRoot) throws SAXException { + + NodeList childNodes = xmlRoot.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + initNode(childNode, this); + } + + } + + /** + * init node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initNode(Node node, XMLDefaultMutableTreeNode mTreeNode) throws SAXException { + + switch (node.getNodeType()) { + case Node.ELEMENT_NODE: + initElementNode(node, mTreeNode); + break; + + case Node.TEXT_NODE: + initTextNode((Text) node, mTreeNode); + break; + + case Node.CDATA_SECTION_NODE: + initCDATASectionNode((CDATASection) node, mTreeNode); + break; + case Node.COMMENT_NODE: + initCommentNode((Comment) node, mTreeNode); + break; + + default: + // if other node type, we will just skip it + break; + + } + + } + + /** + * init element node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initElementNode(Node node, DefaultMutableTreeNode mTreeNode) throws SAXException { + String nodeName = node.getNodeName(); + + NodeList childNodes = node.getChildNodes(); + XMLDefaultMutableTreeNode childTreeNode = new XMLDefaultMutableTreeNode(nodeName, node); + + mTreeNode.add(childTreeNode); + initAttributeNode(node, childTreeNode); + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + initNode(childNode, childTreeNode); + } + + } + + /** + * init attribute node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initAttributeNode(Node node, DefaultMutableTreeNode mTreeNode) throws SAXException { + NamedNodeMap nm = node.getAttributes(); + for (int i = 0; i < nm.getLength(); i++) { + Attr nmNode = (Attr) nm.item(i); + String value = nmNode.getName() + " = \"" + nmNode.getValue() + "\""; // $NON-NLS-1$ $NON-NLS-2$ + XMLDefaultMutableTreeNode attributeNode = new XMLDefaultMutableTreeNode(value, nmNode); + mTreeNode.add(attributeNode); + + } + } + + /** + * init comment Node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initCommentNode(Comment node, DefaultMutableTreeNode mTreeNode) throws SAXException { + String data = node.getData(); + if (data != null && data.length() > 0) { + String value = ""; // $NON-NLS-1$ $NON-NLS-2$ + XMLDefaultMutableTreeNode commentNode = new XMLDefaultMutableTreeNode(value, node); + mTreeNode.add(commentNode); + } + } + + /** + * init CDATASection Node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initCDATASectionNode(CDATASection node, DefaultMutableTreeNode mTreeNode) throws SAXException { + String data = node.getData(); + if (data != null && data.length() > 0) { + String value = ""; // $NON-NLS-1$ $NON-NLS-2$ + XMLDefaultMutableTreeNode commentNode = new XMLDefaultMutableTreeNode(value, node); + mTreeNode.add(commentNode); + } + } + + /** + * init the TextNode + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initTextNode(Text node, DefaultMutableTreeNode mTreeNode) throws SAXException { + String text = node.getNodeValue().trim(); + if (text != null && text.length() > 0) { + XMLDefaultMutableTreeNode textNode = new XMLDefaultMutableTreeNode(text, node); + mTreeNode.add(textNode); + } + } + + /** + * get the xml node + * + * @return the XML node + */ + public Node getXMLNode() { + return xmlNode; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java b/src/components/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java new file mode 100644 index 00000000000..5cd28cdfcc5 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java @@ -0,0 +1,127 @@ +/* + * 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.visualizers.backend; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * An abstract implementation of the BackendListenerClient interface. This + * implementation provides default implementations of most of the methods in the + * interface, as well as some convenience methods, in order to simplify + * development of BackendListenerClient implementations. + * + * While it may be necessary to make changes to the BackendListenerClient interface + * from time to time (therefore requiring changes to any implementations of this + * interface), we intend to make this abstract class provide reasonable + * implementations of any new methods so that subclasses do not necessarily need + * to be updated for new versions. Therefore, when creating a new + * BackendListenerClient implementation, developers are encouraged to subclass this + * abstract class rather than implementing the BackendListenerClient interface + * directly. Implementing BackendListenerClient directly will continue to be + * supported for cases where extending this class is not possible (for example, + * when the client class is already a subclass of some other class). + *

+ * The {@link BackendListenerClient#handleSampleResults(java.util.List, BackendListenerContext)} + * method of BackendListenerClient does not have a default + * implementation here, so subclasses must define at least this method. It may + * be useful to override other methods as well. + * + * @see BackendListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) + * @since 2.13 + */ +public abstract class AbstractBackendListenerClient implements BackendListenerClient { + + private static final Logger LOGGER = LoggingManager.getLoggerForClass(); + private UserMetric userMetrics = new UserMetric(); + + private ConcurrentHashMap metricsPerSampler = new ConcurrentHashMap(); + + /* Implements BackendListenerClient.setupTest(BackendListenerContext) */ + @Override + public void setupTest(BackendListenerContext context) throws Exception { + LOGGER.debug(getClass().getName() + ": setupTest"); + } + + /* Implements BackendListenerClient.teardownTest(BackendListenerContext) */ + @Override + public void teardownTest(BackendListenerContext context) throws Exception { + LOGGER.debug(getClass().getName() + ": teardownTest"); + metricsPerSampler.clear(); + } + + /* Implements BackendListenerClient.getDefaultParameters() */ + @Override + public Arguments getDefaultParameters() { + return null; + } + + /** + * Get a Logger instance which can be used by subclasses to log information. + * As this class is designed to be subclassed this is useful. + * + * @return a Logger instance which can be used for logging + */ + protected Logger getLogger() { + return LOGGER; + } + + /** + * {@inheritDoc} + */ + @Override + public SampleResult createSampleResult(BackendListenerContext context, SampleResult result) { + return result; + } + + /** + * @param sampleLabel Name of sample used as key + * @return {@link SamplerMetric} + */ + protected final SamplerMetric getSamplerMetric(String sampleLabel) { + SamplerMetric samplerMetric = metricsPerSampler.get(sampleLabel); + if(samplerMetric == null) { + samplerMetric = new SamplerMetric(); + SamplerMetric oldValue = metricsPerSampler.putIfAbsent(sampleLabel, samplerMetric); + if(oldValue != null ){ + samplerMetric = oldValue; + } + } + return samplerMetric; + } + + /** + * @return Map where key is SampleLabel and {@link SamplerMetric} is the metrics of this Sample + */ + protected Map getMetricsPerSampler() { + return metricsPerSampler; + } + + /** + * @return {@link UserMetric} + */ + protected UserMetric getUserMetrics() { + return userMetrics; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/BackendListener.java b/src/components/org/apache/jmeter/visualizers/backend/BackendListener.java new file mode 100644 index 00000000000..eab20c3835c --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/BackendListener.java @@ -0,0 +1,486 @@ +/* + * 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.visualizers.backend; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.LockSupport; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Async Listener that delegates SampleResult handling to implementations of {@link BackendListenerClient} + * @since 2.13 + */ +public class BackendListener extends AbstractTestElement + implements Serializable, SampleListener, TestStateListener, NoThreadClone, Remoteable { + + /** + * + */ + private static final class ListenerClientData { + private BackendListenerClient client; + private BlockingQueue queue; + private AtomicLong queueWaits; // how many times we had to wait to queue a SampleResult + private AtomicLong queueWaitTime; // how long we had to wait (nanoSeconds) + // @GuardedBy("LOCK") + private int instanceCount; // number of active tests + private CountDownLatch latch; + } + + /** + * + */ + private static final long serialVersionUID = 8184103677832024335L; + + private static final Logger LOGGER = LoggingManager.getLoggerForClass(); + + /** + * Property key representing the classname of the BackendListenerClient to user. + */ + public static final String CLASSNAME = "classname"; + + /** + * Queue size + */ + public static final String QUEUE_SIZE = "QUEUE_SIZE"; + + /** + * Lock used to protect accumulators update + instanceCount update + */ + private static final Object LOCK = new Object(); + + /** + * Property key representing the arguments for the BackendListenerClient. + */ + public static final String ARGUMENTS = "arguments"; + + /** + * The BackendListenerClient class used by this sampler. + * Created by testStarted; copied to cloned instances. + */ + private Class clientClass; + + public static final String DEFAULT_QUEUE_SIZE = "5000"; + + // Create unique object as marker for end of queue + private transient static final SampleResult FINAL_SAMPLE_RESULT = new SampleResult(); + + // Name of the test element. Set up by testStarted(). + private transient String myName; + + // Holds listenerClientData for this test element + private transient ListenerClientData listenerClientData; + + /* + * This is needed for distributed testing where there is 1 instance + * per server. But we need the total to be shared. + */ + //@GuardedBy("LOCK") - needed to ensure consistency between this and instanceCount + private static final Map queuesByTestElementName = + new ConcurrentHashMap(); + + /** + * Create a BackendListener. + */ + public BackendListener() { + synchronized (LOCK) { + queuesByTestElementName.clear(); + } + + setArguments(new Arguments()); + } + + /* + * Ensure that the required class variables are cloned, + * as this is not currently done by the super-implementation. + */ + @Override + public Object clone() { + BackendListener clone = (BackendListener) super.clone(); + clone.clientClass = this.clientClass; + return clone; + } + + private void initClass() { + String name = getClassname().trim(); + try { + clientClass = Class.forName(name, false, Thread.currentThread().getContextClassLoader()); + } catch (Exception e) { + LOGGER.error(whoAmI() + "\tException initialising: " + name, e); + } + } + + /** + * Generate a String identifier of this instance for debugging purposes. + * + * @return a String identifier for this sampler instance + */ + private String whoAmI() { + StringBuilder sb = new StringBuilder(); + sb.append(Thread.currentThread().getName()); + sb.append("@"); + sb.append(Integer.toHexString(hashCode())); + sb.append("-"); + sb.append(getName()); + return sb.toString(); + } + + + /* (non-Javadoc) + * @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) + */ + @Override + public void sampleOccurred(SampleEvent event) { + Arguments args = getArguments(); + BackendListenerContext context = new BackendListenerContext(args); + + SampleResult sr = listenerClientData.client.createSampleResult(context, event.getResult()); + if(sr == null) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug(getName()+"=>Dropping SampleResult:"+event.getResult()); + } + return; + } + try { + if (!listenerClientData.queue.offer(sr)){ // we failed to add the element first time + listenerClientData.queueWaits.incrementAndGet(); + long t1 = System.nanoTime(); + listenerClientData.queue.put(sr); + long t2 = System.nanoTime(); + listenerClientData.queueWaitTime.addAndGet(t2-t1); + } + } catch (Exception err) { + LOGGER.error("sampleOccurred, failed to queue the sample", err); + } + } + + /** + * Thread that dequeus data from queue to send it to {@link BackendListenerClient} + */ + private static final class Worker extends Thread { + + private final ListenerClientData listenerClientData; + private final BackendListenerContext context; + private final BackendListenerClient backendListenerClient; + private Worker(BackendListenerClient backendListenerClient, Arguments arguments, ListenerClientData listenerClientData){ + this.listenerClientData = listenerClientData; + // Allow BackendListenerClient implementations to get access to test element name + arguments.addArgument(TestElement.NAME, getName()); + context = new BackendListenerContext(arguments); + this.backendListenerClient = backendListenerClient; + } + + @Override + public void run() { + boolean isDebugEnabled = LOGGER.isDebugEnabled(); + List sampleResults = new ArrayList(listenerClientData.queue.size()); + try { + try { + + boolean endOfLoop = false; + while (!endOfLoop) { + if(isDebugEnabled) { + LOGGER.debug("Thread:"+Thread.currentThread().getName()+" taking SampleResult from queue:"+listenerClientData.queue.size()); + } + SampleResult sampleResult = listenerClientData.queue.take(); + if(isDebugEnabled) { + LOGGER.debug("Thread:"+Thread.currentThread().getName()+" took SampleResult:"+sampleResult+", isFinal:" + (sampleResult==FINAL_SAMPLE_RESULT)); + } + while (!(endOfLoop = (sampleResult == FINAL_SAMPLE_RESULT)) && sampleResult != null ) { // try to process as many as possible + sampleResults.add(sampleResult); + if(isDebugEnabled) { + LOGGER.debug("Thread:"+Thread.currentThread().getName()+" polling from queue:"+listenerClientData.queue.size()); + } + sampleResult = listenerClientData.queue.poll(); // returns null if nothing on queue currently + if(isDebugEnabled) { + LOGGER.debug("Thread:"+Thread.currentThread().getName()+" took from queue:"+sampleResult+", isFinal:" + (sampleResult==FINAL_SAMPLE_RESULT)); + } + } + if(isDebugEnabled) { + LOGGER.debug("Thread:"+Thread.currentThread().getName()+ + " exiting with FINAL EVENT:"+(sampleResult == FINAL_SAMPLE_RESULT) + +", null:" + (sampleResult==null)); + } + sendToListener(backendListenerClient, context, sampleResults); + if(!endOfLoop) { + LockSupport.parkNanos(100); + } + } + } catch (InterruptedException e) { + // NOOP + } + // We may have been interrupted + sendToListener(backendListenerClient, context, sampleResults); + LOGGER.info("Worker ended"); + } finally { + listenerClientData.latch.countDown(); + } + } + } + + /** + * Send sampleResults to {@link BackendListenerClient} + * @param backendListenerClient {@link BackendListenerClient} + * @param context {@link BackendListenerContext} + * @param sampleResults List of {@link SampleResult} + */ + static final void sendToListener(final BackendListenerClient backendListenerClient, + final BackendListenerContext context, + final List sampleResults) { + if (sampleResults.size() > 0) { + backendListenerClient.handleSampleResults(sampleResults, context); + sampleResults.clear(); + } + } + + /** + * Returns reference to {@link BackendListener} + * @param clientClass {@link BackendListenerClient} client class + * @return BackendListenerClient reference. + */ + static BackendListenerClient createBackendListenerClientImpl(Class clientClass) { + if (clientClass == null) { // failed to initialise the class + return new ErrorBackendListenerClient(); + } + try { + return (BackendListenerClient) clientClass.newInstance(); + } catch (Exception e) { + LOGGER.error("Exception creating: " + clientClass, e); + return new ErrorBackendListenerClient(); + } + } + + // TestStateListener implementation + /** + * Implements TestStateListener.testStarted() + **/ + @Override + public void testStarted() { + testStarted("local"); //$NON-NLS-1$ + } + + /** Implements TestStateListener.testStarted(String) + **/ + @Override + public void testStarted(String host) { + if(LOGGER.isDebugEnabled()){ + LOGGER.debug(whoAmI() + "\ttestStarted(" + host + ")"); + } + + int queueSize; + final String size = getQueueSize(); + try { + queueSize = Integer.parseInt(size); + } catch (NumberFormatException nfe) { + LOGGER.warn("Invalid queue size '" + size + "' defaulting to " + DEFAULT_QUEUE_SIZE); + queueSize = Integer.parseInt(DEFAULT_QUEUE_SIZE); + } + + synchronized (LOCK) { + myName = getName(); + listenerClientData = queuesByTestElementName.get(myName); + if (listenerClientData == null){ + // We need to do this to ensure in Distributed testing + // that only 1 instance of BackendListenerClient is used + initClass(); + BackendListenerClient backendListenerClient = createBackendListenerClientImpl(clientClass); + BackendListenerContext context = new BackendListenerContext((Arguments)getArguments().clone()); + + listenerClientData = new ListenerClientData(); + listenerClientData.queue = new ArrayBlockingQueue(queueSize); + listenerClientData.queueWaits = new AtomicLong(0L); + listenerClientData.queueWaitTime = new AtomicLong(0L); + listenerClientData.latch = new CountDownLatch(1); + listenerClientData.client = backendListenerClient; + LOGGER.info(getName()+":Starting worker with class:"+clientClass +" and queue capacity:"+getQueueSize()); + Worker worker = new Worker(backendListenerClient, (Arguments) getArguments().clone(), listenerClientData); + worker.setDaemon(true); + worker.start(); + LOGGER.info(getName()+": Started worker with class:"+clientClass); + try { + backendListenerClient.setupTest(context); + } catch (Exception e) { + throw new java.lang.IllegalStateException("Failed calling setupTest", e); + } + queuesByTestElementName.put(myName, listenerClientData); + } + listenerClientData.instanceCount++; + } + } + + /** + * Method called at the end of the test. This is called only on one instance + * of BackendListener. This method will loop through all of the other + * BackendListenerClients which have been registered (automatically in the + * constructor) and notify them that the test has ended, allowing the + * BackendListenerClients to cleanup. + * Implements TestStateListener.testEnded(String) + */ + @Override + public void testEnded(String host) { + synchronized (LOCK) { + ListenerClientData listenerClientData = queuesByTestElementName.get(myName); + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("testEnded called on instance "+myName+"#"+listenerClientData.instanceCount); + } + listenerClientData.instanceCount--; + if (listenerClientData.instanceCount > 0){ + // Not the last instance of myName + return; + } + } + try { + listenerClientData.queue.put(FINAL_SAMPLE_RESULT); + } catch (Exception ex) { + LOGGER.warn("testEnded() with exception:"+ex.getMessage(), ex); + } + if (listenerClientData.queueWaits.get() > 0) { + LOGGER.warn("QueueWaits: "+listenerClientData.queueWaits+"; QueueWaitTime: "+listenerClientData.queueWaitTime+ + " (nanoseconds), you may need to increase queue capacity, see property 'backend_queue_capacity'"); + } + try { + listenerClientData.latch.await(); + BackendListenerContext context = new BackendListenerContext(getArguments()); + listenerClientData.client.teardownTest(context); + } catch (Exception e) { + throw new java.lang.IllegalStateException("Failed calling teardownTest", e); + } + } + + /** Implements TestStateListener.testEnded(String) + **/ + @Override + public void testEnded() { + testEnded("local"); //$NON-NLS-1$ + } + + /** + * A {@link BackendListenerClient} implementation used for error handling. If an + * error occurs while creating the real BackendListenerClient object, it is + * replaced with an instance of this class. Each time a sample occurs with + * this class, the result is marked as a failure so the user can see that + * the test failed. + */ + static class ErrorBackendListenerClient extends AbstractBackendListenerClient { + /** + * Return SampleResult with data on error. + * + * @see BackendListenerClient#handleSampleResults(List, BackendListenerContext) + */ + @Override + public void handleSampleResults(List sampleResults, BackendListenerContext context) { + LOGGER.warn("ErrorBackendListenerClient#handleSampleResult called, noop"); + Thread.yield(); + } + } + + /* (non-Javadoc) + * @see org.apache.jmeter.samplers.SampleListener#sampleStarted(org.apache.jmeter.samplers.SampleEvent) + */ + @Override + public void sampleStarted(SampleEvent e) { + // NOOP + } + + /* (non-Javadoc) + * @see org.apache.jmeter.samplers.SampleListener#sampleStopped(org.apache.jmeter.samplers.SampleEvent) + */ + @Override + public void sampleStopped(SampleEvent e) { + // NOOP + } + + /** + * Set the arguments (parameters) for the BackendListenerClient to be executed + * with. + * + * @param args + * the new arguments. These replace any existing arguments. + */ + public void setArguments(Arguments args) { + setProperty(new TestElementProperty(ARGUMENTS, args)); + } + + /** + * Get the arguments (parameters) for the BackendListenerClient to be executed + * with. + * + * @return the arguments + */ + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /** + * Sets the Classname of the BackendListenerClient object + * + * @param classname + * the new Classname value + */ + public void setClassname(String classname) { + setProperty(CLASSNAME, classname); + } + + /** + * Gets the Classname of the BackendListenerClient object + * + * @return the Classname value + */ + public String getClassname() { + return getPropertyAsString(CLASSNAME); + } + + /** + * Sets the queue size + * + * @param queueSize the size of the queue + * + */ + public void setQueueSize(String queueSize) { + setProperty(QUEUE_SIZE, queueSize, DEFAULT_QUEUE_SIZE); + } + + /** + * Gets the queue size + * + * @return int queueSize + */ + public String getQueueSize() { + return getPropertyAsString(QUEUE_SIZE, DEFAULT_QUEUE_SIZE); + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/BackendListenerClient.java b/src/components/org/apache/jmeter/visualizers/backend/BackendListenerClient.java new file mode 100644 index 00000000000..27b6700f7ff --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/BackendListenerClient.java @@ -0,0 +1,128 @@ +/* + * 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.visualizers.backend; + +import java.util.List; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.samplers.SampleResult; + +/** + * This interface defines the interactions between the BackendListener and external + * Java programs which can be executed by JMeter. Any Java class which wants to + * be executed as a JMeter test must implement this interface (either directly + * or indirectly through AbstractBackendListenerClient). + *

+ * JMeter will create one instance of a BackendListenerClient implementation for + * each user/thread in the test. Additional instances may be created for + * internal use by JMeter (for example, to find out what parameters are + * supported by the client). + *

+ * When the test is started, setupTest() will be called on each thread's + * BackendListenerClient instance to initialize the client. Then handleSampleResult() will be + * called for each SampleResult notification. Finally, teardownTest() will be called + * to allow the client to do any necessary clean-up. + *

+ * The JMeter BackendListener GUI allows a list of parameters to be defined for the + * test. These are passed to the various test methods through the + * {@link BackendListenerContext}. A list of default parameters can be defined + * through the getDefaultParameters() method. These parameters and any default + * values associated with them will be shown in the GUI. Users can add other + * parameters as well. + *

+ * When possible, Listeners should extend {@link AbstractBackendListenerClient + * AbstractBackendListenerClient} rather than implementing BackendListenerClient + * directly. This should protect your tests from future changes to the + * interface. While it may be necessary to make changes to the BackendListenerClient + * interface from time to time (therefore requiring changes to any + * implementations of this interface), we intend to make this abstract class + * provide reasonable default implementations of any new methods so that + * subclasses do not necessarily need to be updated for new versions. + * Implementing BackendListenerClient directly will continue to be supported for + * cases where extending this class is not possible (for example, when the + * client class is already a subclass of some other class). + * + * @since 2.13 + */ +public interface BackendListenerClient { + /** + * Do any initialization required by this client. It is generally + * recommended to do any initialization such as getting parameter values in + * the setupTest method rather than the runTest method in order to add as + * little overhead as possible to the test. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + * Context is readonly + * @throws Exception when setup fails + */ + void setupTest(BackendListenerContext context) throws Exception; + + /** + * Handle sampleResults, this can be done in many ways: + *

    + *
  • Write to a file
  • + *
  • Write to a distant server
  • + *
  • ...
  • + *
+ * @param sampleResults List of {@link SampleResult} + * @param context + * the context to run with. This provides access to + * initialization parameters. + * + */ + void handleSampleResults(List sampleResults, BackendListenerContext context); + + /** + * Do any clean-up required at the end of a test run. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + * @throws Exception when tear down fails + */ + void teardownTest(BackendListenerContext context) throws Exception; + + /** + * Provide a list of parameters which this test supports. Any parameter + * names and associated values returned by this method will appear in the + * GUI by default so the user doesn't have to remember the exact names. The + * user can add other parameters which are not listed here. If this method + * returns null then no parameters will be listed. If the value for some + * parameter is null then that parameter will be listed in the GUI with an + * empty value. + * + * @return a specification of the parameters used by this test which should + * be listed in the GUI, or null if no parameters should be listed. + */ + Arguments getDefaultParameters(); + + /** + * Create a copy of SampleResult, this method is here to allow customizing + * what is kept in the copy, for example copy could remove some useless fields. + * Note that if it returns null, the sample result is not put in the queue. + * Defaults to returning result. + * @param context {@link BackendListenerContext} + * @param result {@link SampleResult} + * @return {@link SampleResult} + */ + SampleResult createSampleResult( + BackendListenerContext context, SampleResult result); +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/BackendListenerContext.java b/src/components/org/apache/jmeter/visualizers/backend/BackendListenerContext.java new file mode 100644 index 00000000000..ab7f1a023c5 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/BackendListenerContext.java @@ -0,0 +1,236 @@ +/* + + * 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.visualizers.backend; + +import java.util.Iterator; +import java.util.Map; + +import org.apache.jmeter.config.Arguments; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * BackendListenerContext is used to provide context information to a + * BackendListenerClient implementation. This currently consists of the + * initialization parameters which were specified in the GUI. + * @since 2.13 + */ +public class BackendListenerContext { + /* + * Implementation notes: + * + * All of the methods in this class are currently read-only. If update + * methods are included in the future, they should be defined so that a + * single instance of BackendListenerContext can be associated with each thread. + * Therefore, no synchronization should be needed. The same instance should + * be used for the call to setupTest, all calls to runTest, and the call to + * teardownTest. + */ + + /** Logging */ + private static final Logger LOGGER = LoggingManager.getLoggerForClass(); + + /** + * Map containing the initialization parameters for the BackendListenerClient. + */ + private final Map params; + + /** + * + * @param args + * the initialization parameters. + */ + public BackendListenerContext(Arguments args) { + this.params = args.getArgumentsAsMap(); + } + + /** + * Determine whether or not a value has been specified for the parameter + * with this name. + * + * @param name + * the name of the parameter to test + * @return true if the parameter value has been specified, false otherwise. + */ + public boolean containsParameter(String name) { + return params.containsKey(name); + } + + /** + * Get an iterator of the parameter names. Each entry in the Iterator is a + * String. + * + * @return an Iterator of Strings listing the names of the parameters which + * have been specified for this test. + */ + public Iterator getParameterNamesIterator() { + return params.keySet().iterator(); + } + + /** + * Get the value of a specific parameter as a String, or null if the value + * was not specified. + * + * @param name + * the name of the parameter whose value should be retrieved + * @return the value of the parameter, or null if the value was not + * specified + */ + public String getParameter(String name) { + return getParameter(name, null); + } + + /** + * Get the value of a specified parameter as a String, or return the + * specified default value if the value was not specified. + * + * @param name + * the name of the parameter whose value should be retrieved + * @param defaultValue + * the default value to return if the value of this parameter was + * not specified + * @return the value of the parameter, or the default value if the parameter + * was not specified + */ + public String getParameter(String name, String defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + return params.get(name); + } + + /** + * Get the value of a specified parameter as an integer. An exception will + * be thrown if the parameter is not specified or if it is not an integer. + * The value may be specified in decimal, hexadecimal, or octal, as defined + * by Integer.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @return the value of the parameter + * + * @throws NumberFormatException + * if the parameter is not specified or is not an integer + * + * @see java.lang.Integer#decode(java.lang.String) + */ + public int getIntParameter(String name) throws NumberFormatException { + if (params == null || !params.containsKey(name)) { + throw new IllegalArgumentException("No value for parameter named '" + name + "'."); + } + + return Integer.parseInt(params.get(name)); + } + + /** + * Get the value of a specified parameter as an integer, or return the + * specified default value if the value was not specified or is not an + * integer. A warning will be logged if the value is not an integer. The + * value may be specified in decimal, hexadecimal, or octal, as defined by + * Integer.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @param defaultValue + * the default value to return if the value of this parameter was + * not specified + * @return the value of the parameter, or the default value if the parameter + * was not specified + * + * @see java.lang.Integer#decode(java.lang.String) + */ + public int getIntParameter(String name, int defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + + try { + return Integer.parseInt(params.get(name)); + } catch (NumberFormatException e) { + LOGGER.warn("Value for parameter '" + name + "' not an integer: '" + params.get(name) + "'. Using default: '" + + defaultValue + "'.", e); + return defaultValue; + } + } + + /** + * Get the value of a specified parameter as a long. An exception will be + * thrown if the parameter is not specified or if it is not a long. The + * value may be specified in decimal, hexadecimal, or octal, as defined by + * Long.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @return the value of the parameter + * + * @throws NumberFormatException + * if the parameter is not specified or is not a long + * + * @see Long#decode(String) + */ + public long getLongParameter(String name) throws NumberFormatException { + if (params == null || !params.containsKey(name)) { + throw new NumberFormatException("No value for parameter named '" + name + "'."); + } + + return Long.parseLong(params.get(name)); + } + + /** + * Get the value of a specified parameter as along, or return the specified + * default value if the value was not specified or is not a long. A warning + * will be logged if the value is not a long. The value may be specified in + * decimal, hexadecimal, or octal, as defined by Long.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @param defaultValue + * the default value to return if the value of this parameter was + * not specified + * @return the value of the parameter, or the default value if the parameter + * was not specified + * + * @see Long#decode(String) + */ + public long getLongParameter(String name, long defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + try { + return Long.decode(params.get(name)).longValue(); + } catch (NumberFormatException e) { + LOGGER.warn("Value for parameter '" + name + "' not a long: '" + params.get(name) + "'. Using default: '" + + defaultValue + "'.", e); + return defaultValue; + } + } + + /** + * @param name Parameter name + * @param defaultValue Default value used if name is not in params + * @return boolean + */ + public boolean getBooleanParameter(String name, boolean defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + return Boolean.parseBoolean(params.get(name)); + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/BackendListenerGui.java b/src/components/org/apache/jmeter/visualizers/backend/BackendListenerGui.java new file mode 100644 index 00000000000..a3a7cb9195c --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/BackendListenerGui.java @@ -0,0 +1,282 @@ +/* + * 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.visualizers.backend; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.ComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractListenerGui; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +/** + * The {@link BackendListenerGui} class provides the user interface for the + * {@link BackendListener} object. + * @since 2.13 + */ +public class BackendListenerGui extends AbstractListenerGui implements ActionListener { + + /** + * + */ + private static final long serialVersionUID = 4331668988576438604L; + + /** Logging */ + private static final Logger LOGGER = LoggingManager.getLoggerForClass(); + + /** A combo box allowing the user to choose a backend class. */ + private JComboBox classnameCombo; + + /** + * A field allowing the user to specify the size of Queue + */ + private JTextField queueSize; + + /** A panel allowing the user to set arguments for this test. */ + private ArgumentsPanel argsPanel; + + /** + * Create a new BackendListenerGui as a standalone component. + */ + public BackendListenerGui() { + super(); + init(); + } + + + /** {@inheritDoc} */ + @Override + public String getLabelResource() { + return "backend_listener"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout. + */ + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(0, 5)); + + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel classnameRequestPanel = new JPanel(new BorderLayout(0, 5)); + classnameRequestPanel.add(createClassnamePanel(), BorderLayout.NORTH); + classnameRequestPanel.add(createParameterPanel(), BorderLayout.CENTER); + + add(classnameRequestPanel, BorderLayout.CENTER); + } + + /** + * Create a panel with GUI components allowing the user to select a test + * class. + * + * @return a panel containing the relevant components + */ + private JPanel createClassnamePanel() { + List possibleClasses = new ArrayList(); + + try { + // Find all the classes which implement the BackendListenerClient + // interface. + possibleClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), + new Class[] { BackendListenerClient.class }); + + // Remove the BackendListener class from the list since it only + // implements the interface for error conditions. + + possibleClasses.remove(BackendListener.class.getName() + "$ErrorBackendListenerClient"); + } catch (Exception e) { + LOGGER.debug("Exception getting interfaces.", e); + } + + JLabel label = new JLabel(JMeterUtils.getResString("backend_listener_classname")); // $NON-NLS-1$ + + classnameCombo = new JComboBox(possibleClasses.toArray()); + classnameCombo.addActionListener(this); + classnameCombo.setEditable(false); + label.setLabelFor(classnameCombo); + + HorizontalPanel classNamePanel = new HorizontalPanel(); + classNamePanel.add(label); + classNamePanel.add(classnameCombo); + + queueSize = new JTextField(BackendListener.DEFAULT_QUEUE_SIZE, 5); + queueSize.setName("Queue Size"); //$NON-NLS-1$ + JLabel queueSizeLabel = new JLabel(JMeterUtils.getResString("backend_listener_queue_size")); // $NON-NLS-1$ + queueSizeLabel.setLabelFor(queueSize); + HorizontalPanel queueSizePanel = new HorizontalPanel(); + queueSizePanel.add(queueSizeLabel, BorderLayout.WEST); + queueSizePanel.add(queueSize); + + JPanel panel = new JPanel(new BorderLayout(0, 5)); + panel.add(classNamePanel, BorderLayout.NORTH); + panel.add(queueSizePanel, BorderLayout.CENTER); + return panel; + } + + /** + * Handle action events for this component. This method currently handles + * events for the classname combo box. + * + * @param event + * the ActionEvent to be handled + */ + @Override + public void actionPerformed(ActionEvent event) { + if (event.getSource() == classnameCombo) { + String className = ((String) classnameCombo.getSelectedItem()).trim(); + try { + BackendListenerClient client = (BackendListenerClient) Class.forName(className, true, + Thread.currentThread().getContextClassLoader()).newInstance(); + + Arguments currArgs = new Arguments(); + argsPanel.modifyTestElement(currArgs); + Map currArgsMap = currArgs.getArgumentsAsMap(); + + Arguments newArgs = new Arguments(); + Arguments testParams = null; + try { + testParams = client.getDefaultParameters(); + } catch (AbstractMethodError e) { + LOGGER.warn("BackendListenerClient doesn't implement " + + "getDefaultParameters. Default parameters won't " + + "be shown. Please update your client class: " + className); + } + + if (testParams != null) { + PropertyIterator i = testParams.getArguments().iterator(); + while (i.hasNext()) { + Argument arg = (Argument) i.next().getObjectValue(); + String name = arg.getName(); + String value = arg.getValue(); + + // If a user has set parameters in one test, and then + // selects a different test which supports the same + // parameters, those parameters should have the same + // values that they did in the original test. + if (currArgsMap.containsKey(name)) { + String newVal = currArgsMap.get(name); + if (newVal != null && newVal.length() > 0) { + value = newVal; + } + } + newArgs.addArgument(name, value); + } + } + + argsPanel.configure(newArgs); + } catch (Exception e) { + LOGGER.error("Error getting argument list for " + className, e); + } + } + } + + /** + * Create a panel containing components allowing the user to provide + * arguments to be passed to the test class instance. + * + * @return a panel containing the relevant components + */ + private JPanel createParameterPanel() { + argsPanel = new ArgumentsPanel(JMeterUtils.getResString("backend_listener_paramtable")); // $NON-NLS-1$ + return argsPanel; + } + + /** {@inheritDoc} */ + @Override + public void configure(TestElement config) { + super.configure(config); + + argsPanel.configure((Arguments) config.getProperty(BackendListener.ARGUMENTS).getObjectValue()); + + String className = config.getPropertyAsString(BackendListener.CLASSNAME); + if(checkContainsClassName(classnameCombo.getModel(), className)) { + classnameCombo.setSelectedItem(className); + } else { + LOGGER.error("Error setting class:'"+className+"' in BackendListener: "+getName()+ + ", check for a missing jar in your jmeter 'search_paths' and 'plugin_dependency_paths' properties"); + } + queueSize.setText(((BackendListener)config).getQueueSize()); + } + + /** + * Check combo contains className + * @param model ComboBoxModel + * @param className String class name + * @return boolean true if model contains className + */ + private static final boolean checkContainsClassName(ComboBoxModel model, String className) { + int size = model.getSize(); + Set set = new HashSet(size); + for (int i = 0; i < size; i++) { + set.add((String)model.getElementAt(i)); + } + return set.contains(className); + } + + /** {@inheritDoc} */ + @Override + public TestElement createTestElement() { + BackendListener config = new BackendListener(); + modifyTestElement(config); + return config; + } + + /** {@inheritDoc} */ + @Override + public void modifyTestElement(TestElement config) { + configureTestElement(config); + BackendListener backendListener = (BackendListener) config; + backendListener.setArguments((Arguments) argsPanel.createTestElement()); + backendListener.setClassname(String.valueOf(classnameCombo.getSelectedItem())); + backendListener.setQueueSize(queueSize.getText()); + + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#clearGui() + */ + @Override + public void clearGui() { + super.clearGui(); + argsPanel.clearGui(); + classnameCombo.setSelectedIndex(0); + queueSize.setText(BackendListener.DEFAULT_QUEUE_SIZE); + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/SamplerMetric.java b/src/components/org/apache/jmeter/visualizers/backend/SamplerMetric.java new file mode 100644 index 00000000000..dfb16a102c7 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/SamplerMetric.java @@ -0,0 +1,229 @@ +/* + * 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.visualizers.backend; + +import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Sampler metric + * @since 2.13 + */ +public class SamplerMetric { + private static final int SLIDING_WINDOW_SIZE = JMeterUtils.getPropDefault("backend_metrics_window", 100); //$NON-NLS-1$ + + // Response times for OK samples + // Limit to sliding window of SLIDING_WINDOW_SIZE values + private DescriptiveStatistics okResponsesStats = new DescriptiveStatistics(SLIDING_WINDOW_SIZE); + // Response times for KO samples + // Limit to sliding window of SLIDING_WINDOW_SIZE values + private DescriptiveStatistics koResponsesStats = new DescriptiveStatistics(SLIDING_WINDOW_SIZE); + // Response times for All samples + // Limit to sliding window of SLIDING_WINDOW_SIZE values + private DescriptiveStatistics allResponsesStats = new DescriptiveStatistics(SLIDING_WINDOW_SIZE); + private int successes; + private int failures; + /** + * + */ + public SamplerMetric() { + } + + /** + * Add a {@link SampleResult} to be used in the statistics + * @param result {@link SampleResult} to be used + */ + public synchronized void add(SampleResult result) { + if(result.isSuccessful()) { + successes+=result.getSampleCount()-result.getErrorCount(); + } else { + failures+=result.getErrorCount(); + } + long time = result.getTime(); + allResponsesStats.addValue(time); + if(result.isSuccessful()) { + // Should we also compute KO , all response time ? + // only take successful requests for time computing + okResponsesStats.addValue(time); + }else { + koResponsesStats.addValue(time); + } + } + + /** + * Reset metric except for percentile related data + */ + public synchronized void resetForTimeInterval() { + // We don't clear responsesStats nor usersStats as it will slide as per my understanding of + // http://commons.apache.org/proper/commons-math/userguide/stat.html + successes = 0; + failures = 0; + } + + /** + * Get the number of total requests for the current time slot + * + * @return number of total requests + */ + public int getTotal() { + return successes+failures; + } + + /** + * Get the number of successful requests for the current time slot + * + * @return number of successful requests + */ + public int getSuccesses() { + return successes; + } + + /** + * Get the number of failed requests for the current time slot + * + * @return number of failed requests + */ + public int getFailures() { + return failures; + } + + /** + * Get the maximal elapsed time for requests within sliding window + * + * @return the maximal elapsed time, or 0 if no requests have + * been added yet + */ + public double getOkMaxTime() { + return okResponsesStats.getMax(); + } + + /** + * Get the minimal elapsed time for requests within sliding window + * + * @return the minTime, or {@link Long#MAX_VALUE} if no requests have been + * added yet + */ + public double getOkMinTime() { + return okResponsesStats.getMin(); + } + + /** + * Get the arithmetic mean of the stored values + * + * @return The arithmetic mean of the stored values + */ + public double getOkMean() { + return okResponsesStats.getMean(); + } + + /** + * Returns an estimate for the requested percentile of the stored values. + * + * @param percentile + * the requested percentile (scaled from 0 - 100) + * @return Returns an estimate for the requested percentile of the stored + * values. + */ + public double getOkPercentile(double percentile) { + return okResponsesStats.getPercentile(percentile); + } + + /** + * Get the maximal elapsed time for requests within sliding window + * + * @return the maximal elapsed time, or 0 if no requests have + * been added yet + */ + public double getKoMaxTime() { + return koResponsesStats.getMax(); + } + + /** + * Get the minimal elapsed time for requests within sliding window + * + * @return the minTime, or {@link Long#MAX_VALUE} if no requests have been + * added yet + */ + public double getKoMinTime() { + return koResponsesStats.getMin(); + } + + /** + * Get the arithmetic mean of the stored values + * + * @return The arithmetic mean of the stored values + */ + public double getKoMean() { + return koResponsesStats.getMean(); + } + + /** + * Returns an estimate for the requested percentile of the stored values. + * + * @param percentile + * the requested percentile (scaled from 0 - 100) + * @return Returns an estimate for the requested percentile of the stored + * values. + */ + public double getKoPercentile(double percentile) { + return koResponsesStats.getPercentile(percentile); + } + + /** + * Get the maximal elapsed time for requests within sliding window + * + * @return the maximal elapsed time, or 0 if no requests have + * been added yet + */ + public double getAllMaxTime() { + return allResponsesStats.getMax(); + } + + /** + * Get the minimal elapsed time for requests within sliding window + * + * @return the minTime, or {@link Long#MAX_VALUE} if no requests have been + * added yet + */ + public double getAllMinTime() { + return allResponsesStats.getMin(); + } + + /** + * Get the arithmetic mean of the stored values + * + * @return The arithmetic mean of the stored values + */ + public double getAllMean() { + return allResponsesStats.getMean(); + } + + /** + * Returns an estimate for the requested percentile of the stored values. + * + * @param percentile + * the requested percentile (scaled from 0 - 100) + * @return Returns an estimate for the requested percentile of the stored + * values. + */ + public double getAllPercentile(double percentile) { + return allResponsesStats.getPercentile(percentile); + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/UserMetric.java b/src/components/org/apache/jmeter/visualizers/backend/UserMetric.java new file mode 100644 index 00000000000..c9df528ef50 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/UserMetric.java @@ -0,0 +1,93 @@ +/* + * 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.visualizers.backend; + +import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; + +/** + * User metric + * @since 2.13 + */ +public class UserMetric { + private static final int SLIDING_WINDOW_SIZE = JMeterUtils.getPropDefault("backend_metrics_window", 100); //$NON-NLS-1$ + + // Limit to sliding window of SLIDING_WINDOW_SIZE values + private DescriptiveStatistics usersStats = new DescriptiveStatistics(SLIDING_WINDOW_SIZE); + /** + * + */ + public UserMetric() { + } + + /** + * Add a {@link SampleResult} to be used in the statistics + * @param result {@link SampleResult} to be used + */ + public synchronized void add(SampleResult result) { + usersStats.addValue(JMeterContextService.getThreadCounts().activeThreads); + } + + /** + * Reset metric except for percentile related data + */ + public synchronized void resetForTimeInterval() { + // NOOP + } + + /** + * @return the max number of active threads for this test run + * using a sliding window of SLIDING_WINDOW_SIZE + */ + public int getMaxActiveThreads() { + return (int) usersStats.getMin(); + } + + /** + * @return the mean number of active threads for this test run + * using a sliding window of SLIDING_WINDOW_SIZE + */ + public int getMeanActiveThreads() { + return (int) usersStats.getMean(); + } + + /** + * @return the min number of active threads for this test run + * using a sliding window of SLIDING_WINDOW_SIZE + */ + public int getMinActiveThreads() { + return (int) usersStats.getMax(); + } + + /** + * @return finished threads + */ + public int getFinishedThreads() { + return JMeterContextService.getThreadCounts().finishedThreads; + } + + /** + * @return started threads + */ + public int getStartedThreads() { + return JMeterContextService.getThreadCounts().startedThreads; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/graphite/AbstractGraphiteMetricsSender.java b/src/components/org/apache/jmeter/visualizers/backend/graphite/AbstractGraphiteMetricsSender.java new file mode 100644 index 00000000000..0d2842f5a20 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/graphite/AbstractGraphiteMetricsSender.java @@ -0,0 +1,70 @@ +/* + * 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.visualizers.backend.graphite; + +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; + +/** + * Base class for {@link GraphiteMetricsSender} + * @since 2.13 + */ +abstract class AbstractGraphiteMetricsSender implements GraphiteMetricsSender { + + /** + * Create a new keyed pool of {@link SocketOutputStream}s using a + * {@link SocketOutputStreamPoolFactory}. The keys for the pool are + * {@link SocketConnectionInfos} instances. + * + * @return GenericKeyedObjectPool the newly generated pool + */ + protected GenericKeyedObjectPool createSocketOutputStreamPool() { + GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig(); + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + config.setMaxTotalPerKey(-1); + config.setMaxTotal(-1); + config.setMaxIdlePerKey(-1); + config.setMinEvictableIdleTimeMillis(TimeUnit.MINUTES.toMillis(3)); + config.setTimeBetweenEvictionRunsMillis(TimeUnit.MINUTES.toMillis(3)); + + return new GenericKeyedObjectPool( + new SocketOutputStreamPoolFactory(SOCKET_CONNECT_TIMEOUT_MS, SOCKET_TIMEOUT), config); + } + + /** + * Replaces Graphite reserved chars: + *
    + *
  • ' ' by '-'
  • + *
  • '\\' by '-'
  • + *
  • '.' by '_'
  • + *
+ * + * @param s + * text to be sanitized + * @return the sanitized text + */ + static final String sanitizeString(String s) { + // String#replace uses regexp + return StringUtils.replaceChars(s, "\\ .", "--_"); + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java b/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java new file mode 100644 index 00000000000..b1123d3eba9 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java @@ -0,0 +1,300 @@ +/* + * 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.visualizers.backend.graphite; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; +import org.apache.jmeter.visualizers.backend.BackendListenerContext; +import org.apache.jmeter.visualizers.backend.SamplerMetric; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Graphite based Listener using Pickle Protocol + * @see Graphite Overview + * @since 2.13 + */ +public class GraphiteBackendListenerClient extends AbstractBackendListenerClient implements Runnable { + private static final int DEFAULT_PLAINTEXT_PROTOCOL_PORT = 2003; + private static final String TEST_CONTEXT_NAME = "test"; + private static final String ALL_CONTEXT_NAME = "all"; + + private static final Logger LOGGER = LoggingManager.getLoggerForClass(); + private static final String DEFAULT_METRICS_PREFIX = "jmeter."; //$NON-NLS-1$ + private static final String CUMULATED_METRICS = "__cumulated__"; //$NON-NLS-1$ + // User Metrics + private static final String METRIC_MAX_ACTIVE_THREADS = "maxAT"; //$NON-NLS-1$ + private static final String METRIC_MIN_ACTIVE_THREADS = "minAT"; //$NON-NLS-1$ + private static final String METRIC_MEAN_ACTIVE_THREADS = "meanAT"; //$NON-NLS-1$ + private static final String METRIC_STARTED_THREADS = "startedT"; //$NON-NLS-1$ + private static final String METRIC_FINISHED_THREADS = "endedT"; //$NON-NLS-1$ + + // Response time Metrics + private static final String METRIC_SEPARATOR = "."; //$NON-NLS-1$ + private static final String METRIC_OK_PREFIX = "ok"; //$NON-NLS-1$ + private static final String METRIC_KO_PREFIX = "ko"; //$NON-NLS-1$ + private static final String METRIC_ALL_PREFIX = "a"; + + + private static final String METRIC_COUNT = "count"; //$NON-NLS-1$ + private static final String METRIC_MIN_RESPONSE_TIME = "min"; //$NON-NLS-1$ + private static final String METRIC_MAX_RESPONSE_TIME = "max"; //$NON-NLS-1$ + private static final String METRIC_PERCENTILE = "pct"; //$NON-NLS-1$ + + private static final String METRIC_OK_COUNT = METRIC_OK_PREFIX+METRIC_SEPARATOR+METRIC_COUNT; + private static final String METRIC_OK_MIN_RESPONSE_TIME = METRIC_OK_PREFIX+METRIC_SEPARATOR+METRIC_MIN_RESPONSE_TIME; + private static final String METRIC_OK_MAX_RESPONSE_TIME = METRIC_OK_PREFIX+METRIC_SEPARATOR+METRIC_MAX_RESPONSE_TIME; + private static final String METRIC_OK_PERCENTILE_PREFIX = METRIC_OK_PREFIX+METRIC_SEPARATOR+METRIC_PERCENTILE; + + private static final String METRIC_KO_COUNT = METRIC_KO_PREFIX+METRIC_SEPARATOR+METRIC_COUNT; + private static final String METRIC_KO_MIN_RESPONSE_TIME = METRIC_KO_PREFIX+METRIC_SEPARATOR+METRIC_MIN_RESPONSE_TIME; + private static final String METRIC_KO_MAX_RESPONSE_TIME = METRIC_KO_PREFIX+METRIC_SEPARATOR+METRIC_MAX_RESPONSE_TIME; + private static final String METRIC_KO_PERCENTILE_PREFIX = METRIC_KO_PREFIX+METRIC_SEPARATOR+METRIC_PERCENTILE; + + private static final String METRIC_ALL_COUNT = METRIC_ALL_PREFIX+METRIC_SEPARATOR+METRIC_COUNT; + private static final String METRIC_ALL_MIN_RESPONSE_TIME = METRIC_ALL_PREFIX+METRIC_SEPARATOR+METRIC_MIN_RESPONSE_TIME; + private static final String METRIC_ALL_MAX_RESPONSE_TIME = METRIC_ALL_PREFIX+METRIC_SEPARATOR+METRIC_MAX_RESPONSE_TIME; + private static final String METRIC_ALL_PERCENTILE_PREFIX = METRIC_ALL_PREFIX+METRIC_SEPARATOR+METRIC_PERCENTILE; + + private static final long ONE_SECOND = 1L; + private static final int MAX_POOL_SIZE = 1; + private static final String DEFAULT_PERCENTILES = "90;95;99"; + private static final String SEPARATOR = ";"; //$NON-NLS-1$ + private static final Object LOCK = new Object(); + + private String graphiteHost; + private int graphitePort; + private boolean summaryOnly; + private String rootMetricsPrefix; + private String samplersList = ""; //$NON-NLS-1$ + private Set samplersToFilter; + private Map okPercentiles; + private Map koPercentiles; + private Map allPercentiles; + + + private GraphiteMetricsSender graphiteMetricsManager; + + private ScheduledExecutorService scheduler; + private ScheduledFuture timerHandle; + + public GraphiteBackendListenerClient() { + super(); + } + + @Override + public void run() { + sendMetrics(); + } + + /** + * Send metrics to Graphite + */ + protected void sendMetrics() { + // Need to convert millis to seconds for Graphite + long timestampInSeconds = TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS); + synchronized (LOCK) { + for (Map.Entry entry : getMetricsPerSampler().entrySet()) { + SamplerMetric metric = entry.getValue(); + if(entry.getKey().equals(CUMULATED_METRICS)) { + addMetrics(timestampInSeconds, ALL_CONTEXT_NAME, metric); + } else { + addMetrics(timestampInSeconds, AbstractGraphiteMetricsSender.sanitizeString(entry.getKey()), metric); + } + // We are computing on interval basis so cleanup + metric.resetForTimeInterval(); + } + } + graphiteMetricsManager.addMetric(timestampInSeconds, TEST_CONTEXT_NAME, METRIC_MIN_ACTIVE_THREADS, Integer.toString(getUserMetrics().getMinActiveThreads())); + graphiteMetricsManager.addMetric(timestampInSeconds, TEST_CONTEXT_NAME, METRIC_MAX_ACTIVE_THREADS, Integer.toString(getUserMetrics().getMaxActiveThreads())); + graphiteMetricsManager.addMetric(timestampInSeconds, TEST_CONTEXT_NAME, METRIC_MEAN_ACTIVE_THREADS, Integer.toString(getUserMetrics().getMeanActiveThreads())); + graphiteMetricsManager.addMetric(timestampInSeconds, TEST_CONTEXT_NAME, METRIC_STARTED_THREADS, Integer.toString(getUserMetrics().getStartedThreads())); + graphiteMetricsManager.addMetric(timestampInSeconds, TEST_CONTEXT_NAME, METRIC_FINISHED_THREADS, Integer.toString(getUserMetrics().getFinishedThreads())); + + graphiteMetricsManager.writeAndSendMetrics(); + } + + + /** + * Add request metrics to metrics manager. + * Note if total number of requests is 0, no response time metrics are sent. + * @param timestampInSeconds long + * @param contextName String + * @param metric {@link SamplerMetric} + */ + private void addMetrics(long timestampInSeconds, String contextName, SamplerMetric metric) { + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, METRIC_OK_COUNT, Integer.toString(metric.getSuccesses())); + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, METRIC_KO_COUNT, Integer.toString(metric.getFailures())); + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, METRIC_ALL_COUNT, Integer.toString(metric.getTotal())); + // See https://bz.apache.org/bugzilla/show_bug.cgi?id=57350 + if(metric.getTotal() > 0) { + if(metric.getSuccesses()>0) { + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, METRIC_OK_MIN_RESPONSE_TIME, Double.toString(metric.getOkMinTime())); + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, METRIC_OK_MAX_RESPONSE_TIME, Double.toString(metric.getOkMaxTime())); + for (Map.Entry entry : okPercentiles.entrySet()) { + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, + entry.getKey(), + Double.toString(metric.getOkPercentile(entry.getValue().floatValue()))); + } + } + if(metric.getFailures()>0) { + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, METRIC_KO_MIN_RESPONSE_TIME, Double.toString(metric.getKoMinTime())); + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, METRIC_KO_MAX_RESPONSE_TIME, Double.toString(metric.getKoMaxTime())); + for (Map.Entry entry : koPercentiles.entrySet()) { + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, + entry.getKey(), + Double.toString(metric.getKoPercentile(entry.getValue().floatValue()))); + } + } + if(metric.getTotal()>0) { + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, METRIC_ALL_MIN_RESPONSE_TIME, Double.toString(metric.getAllMinTime())); + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, METRIC_ALL_MAX_RESPONSE_TIME, Double.toString(metric.getAllMaxTime())); + for (Map.Entry entry : allPercentiles.entrySet()) { + graphiteMetricsManager.addMetric(timestampInSeconds, contextName, + entry.getKey(), + Double.toString(metric.getAllPercentile(entry.getValue().floatValue()))); + } + + } + } + } + + /** + * @return the samplersList + */ + public String getSamplersList() { + return samplersList; + } + + /** + * @param samplersList the samplersList to set + */ + public void setSamplersList(String samplersList) { + this.samplersList = samplersList; + } + + @Override + public void handleSampleResults(List sampleResults, + BackendListenerContext context) { + synchronized (LOCK) { + for (SampleResult sampleResult : sampleResults) { + getUserMetrics().add(sampleResult); + if(!summaryOnly && samplersToFilter.contains(sampleResult.getSampleLabel())) { + SamplerMetric samplerMetric = getSamplerMetric(sampleResult.getSampleLabel()); + samplerMetric.add(sampleResult); + } + SamplerMetric cumulatedMetrics = getSamplerMetric(CUMULATED_METRICS); + cumulatedMetrics.add(sampleResult); + } + } + } + + @Override + public void setupTest(BackendListenerContext context) throws Exception { + String graphiteMetricsSenderClass = context.getParameter("graphiteMetricsSender"); + + graphiteHost = context.getParameter("graphiteHost"); + graphitePort = context.getIntParameter("graphitePort", DEFAULT_PLAINTEXT_PROTOCOL_PORT); + summaryOnly = context.getBooleanParameter("summaryOnly", true); + samplersList = context.getParameter("samplersList", ""); + rootMetricsPrefix = context.getParameter("rootMetricsPrefix", DEFAULT_METRICS_PREFIX); + String percentilesAsString = context.getParameter("percentiles", DEFAULT_METRICS_PREFIX); + String[] percentilesStringArray = percentilesAsString.split(SEPARATOR); + okPercentiles = new HashMap(percentilesStringArray.length); + koPercentiles = new HashMap(percentilesStringArray.length); + allPercentiles = new HashMap(percentilesStringArray.length); + DecimalFormat format = new DecimalFormat("0.##"); + for (int i = 0; i < percentilesStringArray.length; i++) { + if(!StringUtils.isEmpty(percentilesStringArray[i].trim())) { + try { + Float percentileValue = Float.parseFloat(percentilesStringArray[i].trim()); + okPercentiles.put( + METRIC_OK_PERCENTILE_PREFIX+AbstractGraphiteMetricsSender.sanitizeString(format.format(percentileValue)), + percentileValue); + koPercentiles.put( + METRIC_KO_PERCENTILE_PREFIX+AbstractGraphiteMetricsSender.sanitizeString(format.format(percentileValue)), + percentileValue); + allPercentiles.put( + METRIC_ALL_PERCENTILE_PREFIX+AbstractGraphiteMetricsSender.sanitizeString(format.format(percentileValue)), + percentileValue); + + } catch(Exception e) { + LOGGER.error("Error parsing percentile:'"+percentilesStringArray[i]+"'", e); + } + } + } + Class clazz = Class.forName(graphiteMetricsSenderClass); + this.graphiteMetricsManager = (GraphiteMetricsSender) clazz.newInstance(); + graphiteMetricsManager.setup(graphiteHost, graphitePort, rootMetricsPrefix); + String[] samplers = samplersList.split(SEPARATOR); + samplersToFilter = new HashSet(); + for (String samplerName : samplers) { + samplersToFilter.add(samplerName); + } + scheduler = Executors.newScheduledThreadPool(MAX_POOL_SIZE); + // Don't change this as metrics are per second + this.timerHandle = scheduler.scheduleAtFixedRate(this, ONE_SECOND, ONE_SECOND, TimeUnit.SECONDS); + } + + @Override + public void teardownTest(BackendListenerContext context) throws Exception { + boolean cancelState = timerHandle.cancel(false); + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("Canceled state:"+cancelState); + } + scheduler.shutdown(); + try { + scheduler.awaitTermination(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.error("Error waiting for end of scheduler"); + } + // Send last set of data before ending + sendMetrics(); + + samplersToFilter.clear(); + graphiteMetricsManager.destroy(); + super.teardownTest(context); + } + + @Override + public Arguments getDefaultParameters() { + Arguments arguments = new Arguments(); + arguments.addArgument("graphiteMetricsSender", TextGraphiteMetricsSender.class.getName()); + arguments.addArgument("graphiteHost", ""); + arguments.addArgument("graphitePort", Integer.toString(DEFAULT_PLAINTEXT_PROTOCOL_PORT)); + arguments.addArgument("rootMetricsPrefix", DEFAULT_METRICS_PREFIX); + arguments.addArgument("summaryOnly", "true"); + arguments.addArgument("samplersList", ""); + arguments.addArgument("percentiles", DEFAULT_PERCENTILES); + return arguments; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteMetricsSender.java b/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteMetricsSender.java new file mode 100644 index 00000000000..062484d8825 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteMetricsSender.java @@ -0,0 +1,71 @@ +/* + * 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.visualizers.backend.graphite; + +/** + * @since 2.13 + */ +interface GraphiteMetricsSender { + final int SOCKET_CONNECT_TIMEOUT_MS = 1000; + final int SOCKET_TIMEOUT = 1000; + + + String CHARSET_NAME = "UTF-8"; //$NON-NLS-1$ + + final class MetricTuple { + String name; + long timestamp; + String value; + MetricTuple(String name, long timestamp, String value) { + this.name = name; + this.timestamp = timestamp; + this.value = value; + } + } + /** + * Convert the metric to a python tuple of the form: + * (timestamp, (prefix.contextName.metricName, metricValue)) + * And add it to the list of metrics. + * @param timestamp in Seconds from 1970 + * @param contextName name of the context of this metric + * @param metricName name of this metric + * @param metricValue value of this metric + */ + public abstract void addMetric(long timestamp, String contextName, + String metricName, String metricValue); + + /** + * + * @param graphiteHost Host + * @param graphitePort Port + * @param prefix Root Data prefix + */ + public void setup(String graphiteHost, int graphitePort, String prefix); + + /** + * Write metrics to Graphite using custom format + */ + public abstract void writeAndSendMetrics(); + + /** + * Destroy sender + */ + public abstract void destroy(); + +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/graphite/PickleGraphiteMetricsSender.java b/src/components/org/apache/jmeter/visualizers/backend/graphite/PickleGraphiteMetricsSender.java new file mode 100644 index 00000000000..8cb428ec4df --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/graphite/PickleGraphiteMetricsSender.java @@ -0,0 +1,182 @@ +/* + * 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.visualizers.backend.graphite; + +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Pickle Graphite format + * Partly based on https://github.com/BrightcoveOS/metrics-graphite-pickle/blob/master/src/main/java/com/brightcove/metrics/reporting/GraphitePickleReporter.java + * as per license https://github.com/BrightcoveOS/metrics-graphite-pickle/blob/master/LICENSE.txt + * @since 2.13 + */ +class PickleGraphiteMetricsSender extends AbstractGraphiteMetricsSender { + private static final Logger LOG = LoggingManager.getLoggerForClass(); + + /** + * Pickle opcodes needed for implementation + */ + private static final char APPEND = 'a'; + private static final char LIST = 'l'; + private static final char LONG = 'L'; + private static final char MARK = '('; + private static final char STOP = '.'; + private static final char STRING = 'S'; + private static final char TUPLE = 't'; + private static final char QUOTE = '\''; + private static final char LF = '\n'; + + private String prefix; + + // graphite expects a python-pickled list of nested tuples. + private List metrics = new LinkedList(); + + private GenericKeyedObjectPool socketOutputStreamPool; + + private SocketConnectionInfos socketConnectionInfos; + + + PickleGraphiteMetricsSender() { + super(); + } + + /** + * @param graphiteHost Graphite Host + * @param graphitePort Graphite Port + * @param prefix Common Metrics prefix + */ + @Override + public void setup(String graphiteHost, int graphitePort, String prefix) { + this.prefix = prefix; + this.socketConnectionInfos = new SocketConnectionInfos(graphiteHost, graphitePort); + this.socketOutputStreamPool = createSocketOutputStreamPool(); + + if(LOG.isInfoEnabled()) { + LOG.info("Created PickleGraphiteMetricsSender with host:"+graphiteHost+", port:"+graphitePort+", prefix:"+prefix); + } + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#addMetric(long, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void addMetric(long timestamp, String contextName, String metricName, String metricValue) { + StringBuilder sb = new StringBuilder(50); + sb + .append(prefix) + .append(contextName) + .append(".") + .append(metricName); + metrics.add(new MetricTuple(sb.toString(), timestamp, metricValue)); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#writeAndSendMetrics() + */ + @Override + public void writeAndSendMetrics() { + if (metrics.size()>0) { + SocketOutputStream out = null; + try { + String payload = convertMetricsToPickleFormat(metrics); + + int length = payload.length(); + byte[] header = ByteBuffer.allocate(4).putInt(length).array(); + + out = socketOutputStreamPool.borrowObject(socketConnectionInfos); + out.write(header); + // pickleWriter is not closed as it would close the underlying pooled out + Writer pickleWriter = new OutputStreamWriter(out, CHARSET_NAME); + pickleWriter.write(payload); + pickleWriter.flush(); + socketOutputStreamPool.returnObject(socketConnectionInfos, out); + } catch (Exception e) { + if(out != null) { + try { + socketOutputStreamPool.invalidateObject(socketConnectionInfos, out); + } catch (Exception e1) { + LOG.warn("Exception invalidating socketOutputStream connected to graphite server '"+socketConnectionInfos.getHost()+"':"+socketConnectionInfos.getPort(), e1); + } + } + LOG.error("Error writing to Graphite:"+e.getMessage()); + } + + // if there was an error, we might miss some data. for now, drop those on the floor and + // try to keep going. + if(LOG.isDebugEnabled()) { + LOG.debug("Wrote "+ metrics.size() +" metrics"); + } + metrics.clear(); + } + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#destroy() + */ + @Override + public void destroy() { + socketOutputStreamPool.close(); + } + + /** + * See: http://readthedocs.org/docs/graphite/en/1.0/feeding-carbon.html + */ + private static final String convertMetricsToPickleFormat(List metrics) { + StringBuilder pickled = new StringBuilder(metrics.size()*75); + pickled.append(MARK).append(LIST); + + for (MetricTuple tuple : metrics) { + // begin outer tuple + pickled.append(MARK); + + // the metric name is a string. + pickled.append(STRING) + // the single quotes are to match python's repr("abcd") + .append(QUOTE).append(tuple.name).append(QUOTE).append(LF); + + // begin the inner tuple + pickled.append(MARK); + + // timestamp is a long + pickled.append(LONG).append(tuple.timestamp) + // the trailing L is to match python's repr(long(1234)) + .append(LONG).append(LF); + + // and the value is a string. + pickled.append(STRING).append(QUOTE).append(tuple.value).append(QUOTE).append(LF); + + pickled.append(TUPLE) // end inner tuple + .append(TUPLE); // end outer tuple + + pickled.append(APPEND); + } + + // every pickle ends with STOP + pickled.append(STOP); + return pickled.toString(); + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketConnectionInfos.java b/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketConnectionInfos.java new file mode 100644 index 00000000000..c709dfdb6c8 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketConnectionInfos.java @@ -0,0 +1,63 @@ +/* + * 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.visualizers.backend.graphite; + +/** + * Bean to embed host/port to Graphite + * @since 2.13 + */ +public class SocketConnectionInfos { + private String host; + private int port; + + /** + * @param host the name of the host to connect to + * @param port the port to connect to + */ + public SocketConnectionInfos(String host, int port) { + super(); + this.host = host; + this.port = port; + } + + /** + * @return the host + */ + public String getHost() { + return host; + } + /** + * @param host the host to set + */ + public void setHost(String host) { + this.host = host; + } + /** + * @return the port + */ + public int getPort() { + return port; + } + /** + * @param port the port to set + */ + public void setPort(int port) { + this.port = port; + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStream.java b/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStream.java new file mode 100644 index 00000000000..54276d3693b --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStream.java @@ -0,0 +1,59 @@ +/* + * 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.visualizers.backend.graphite; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + +/** + * Convenience class for writing bytes to a {@linkplain java.net.Socket}. + * @since 2.13 + */ +public class SocketOutputStream extends FilterOutputStream { + + private final Socket socket; + + public SocketOutputStream(InetSocketAddress inetSocketAddress) throws IOException { + this(new Socket(inetSocketAddress.getAddress(), inetSocketAddress.getPort())); + } + + public SocketOutputStream(Socket socket) throws IOException { + super(socket.getOutputStream()); + this.socket = socket; + } + + /** + * Return the underlying Socket + * + * @return the underlying {@link Socket} + */ + public Socket getSocket() { + return socket; + } + + @Override + public String toString() { + return "SocketOutputStream{" + + "socket=" + socket + + '}'; + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStreamPoolFactory.java b/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStreamPoolFactory.java new file mode 100644 index 00000000000..767884ab932 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStreamPoolFactory.java @@ -0,0 +1,86 @@ +/* + * 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.visualizers.backend.graphite; + +import java.net.InetSocketAddress; +import java.net.Socket; + +import org.apache.commons.pool2.BaseKeyedPooledObjectFactory; +import org.apache.commons.pool2.KeyedPooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Pool Factory of {@link SocketOutputStream} + * @since 2.13 + */ +public class SocketOutputStreamPoolFactory + extends BaseKeyedPooledObjectFactory + implements KeyedPooledObjectFactory { + + private final int socketTimeoutInMillis; + private final int socketConnectTimeoutInMillis; + + public SocketOutputStreamPoolFactory(int socketConnectTimeoutInMillis, int socketTimeoutInMillis) { + this.socketConnectTimeoutInMillis = socketConnectTimeoutInMillis; + this.socketTimeoutInMillis = socketTimeoutInMillis; + } + + @Override + public PooledObject makeObject(SocketConnectionInfos connectionInfos) throws Exception { + return wrap(create(connectionInfos)); + } + + @Override + public void destroyObject(SocketConnectionInfos socketConnectionInfos, PooledObject socketOutputStream) throws Exception { + super.destroyObject(socketConnectionInfos, socketOutputStream); + SocketOutputStream outputStream = socketOutputStream.getObject(); + JOrphanUtils.closeQuietly(outputStream); + JOrphanUtils.closeQuietly(outputStream.getSocket()); + } + + /** + */ + @Override + public boolean validateObject(SocketConnectionInfos HostAndPort, PooledObject socketOutputStream) { + Socket socket = socketOutputStream.getObject().getSocket(); + return socket.isConnected() + && socket.isBound() + && !socket.isClosed() + && !socket.isInputShutdown() + && !socket.isOutputShutdown(); + } + + @Override + public SocketOutputStream create(SocketConnectionInfos connectionInfos) + throws Exception { + Socket socket = new Socket(); + socket.setKeepAlive(true); + socket.setSoTimeout(socketTimeoutInMillis); + socket.connect(new InetSocketAddress(connectionInfos.getHost(), connectionInfos.getPort()), socketConnectTimeoutInMillis); + + return new SocketOutputStream(socket); + } + + @Override + public PooledObject wrap(SocketOutputStream outputStream) { + return new DefaultPooledObject(outputStream); + } +} diff --git a/src/components/org/apache/jmeter/visualizers/backend/graphite/TextGraphiteMetricsSender.java b/src/components/org/apache/jmeter/visualizers/backend/graphite/TextGraphiteMetricsSender.java new file mode 100644 index 00000000000..a980d446566 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/backend/graphite/TextGraphiteMetricsSender.java @@ -0,0 +1,124 @@ +/* + * 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.visualizers.backend.graphite; + +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * PlainText Graphite sender + * @since 2.13 + */ +class TextGraphiteMetricsSender extends AbstractGraphiteMetricsSender { + private static final Logger LOG = LoggingManager.getLoggerForClass(); + + private String prefix; + + private List metrics = new ArrayList(); + + private GenericKeyedObjectPool socketOutputStreamPool; + + private SocketConnectionInfos socketConnectionInfos; + + /** + * @param graphiteHost Graphite Host + * @param graphitePort Graphite Port + * @param prefix Common Metrics prefix + */ + TextGraphiteMetricsSender() { + super(); + } + + @Override + public void setup(String graphiteHost, int graphitePort, String prefix) { + this.prefix = prefix; + this.socketConnectionInfos = new SocketConnectionInfos(graphiteHost, graphitePort); + this.socketOutputStreamPool = createSocketOutputStreamPool(); + + if(LOG.isInfoEnabled()) { + LOG.info("Created TextGraphiteMetricsSender with host:"+graphiteHost+", port:"+graphitePort+", prefix:"+prefix); + } + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#addMetric(long, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void addMetric(long timestamp, String contextName, String metricName, String metricValue) { + StringBuilder sb = new StringBuilder(50); + sb + .append(prefix) + .append(contextName) + .append(".") + .append(metricName); + metrics.add(new MetricTuple(sb.toString(), timestamp, metricValue)); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#writeAndSendMetrics() + */ + @Override + public void writeAndSendMetrics() { + if (metrics.size()>0) { + SocketOutputStream out = null; + try { + out = socketOutputStreamPool.borrowObject(socketConnectionInfos); + // pw is not closed as it would close the underlying pooled out + PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, CHARSET_NAME), false); + for (MetricTuple metric: metrics) { + pw.printf("%s %s %d%n", metric.name, metric.value, Long.valueOf(metric.timestamp)); + } + pw.flush(); + // if there was an error, we might miss some data. for now, drop those on the floor and + // try to keep going. + if(LOG.isDebugEnabled()) { + LOG.debug("Wrote "+ metrics.size() +" metrics"); + } + socketOutputStreamPool.returnObject(socketConnectionInfos, out); + } catch (Exception e) { + if(out != null) { + try { + socketOutputStreamPool.invalidateObject(socketConnectionInfos, out); + } catch (Exception e1) { + LOG.warn("Exception invalidating socketOutputStream connected to graphite server '"+ + socketConnectionInfos.getHost()+"':"+socketConnectionInfos.getPort(), e1); + } + } + LOG.error("Error writing to Graphite:"+e.getMessage()); + } + // We drop metrics in all cases + metrics.clear(); + } + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#destroy() + */ + @Override + public void destroy() { + socketOutputStreamPool.close(); + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/utils/Colors.java b/src/components/org/apache/jmeter/visualizers/utils/Colors.java new file mode 100644 index 00000000000..3b3ffa21aec --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/utils/Colors.java @@ -0,0 +1,96 @@ +/* + * 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.visualizers.utils; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class Colors { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ENTRY_SEP = ","; //$NON-NLS-1$ + + private static final String ORDER_PROP_NAME = "order"; //$NON-NLS-1$ + + protected static final String DEFAULT_COLORS_PROPERTY_FILE = "org/apache/jmeter/visualizers/utils/colors.properties"; //$NON-NLS-1$ + + protected static final String USER_DEFINED_COLORS_PROPERTY_FILE = "jmeter.colors"; //$NON-NLS-1$ + + private static final String COLORS_ORDER = "jmeter.order"; + + /** + * Parse icon set file. + * @return List of icons/action definition + */ + public static List getColors() { + Properties defaultProps = JMeterUtils.loadProperties(DEFAULT_COLORS_PROPERTY_FILE); + if (defaultProps == null) { + JOptionPane.showMessageDialog(null, + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + return null; + } + Properties p; + String userProp = JMeterUtils.getProperty(USER_DEFINED_COLORS_PROPERTY_FILE); + if (userProp != null){ + p = JMeterUtils.loadProperties(userProp, defaultProps); + } else { + p=defaultProps; + } + + String order = JMeterUtils.getPropDefault(COLORS_ORDER, p.getProperty(ORDER_PROP_NAME)); + + if (order == null) { + log.warn("Could not find order list"); + JOptionPane.showMessageDialog(null, + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + return null; + } + + String[] oList = order.split(ENTRY_SEP); + + List listColors = new ArrayList(); + for (String key : oList) { + String trimmed = key.trim(); + String property = p.getProperty(trimmed); + try { + String[] lcol = property.split(ENTRY_SEP); + Color itb = new Color(Integer.parseInt(lcol[0]), Integer.parseInt(lcol[1]), Integer.parseInt(lcol[2])); + listColors.add(itb); + } catch (java.lang.Exception e) { + log.warn("Error in colors.properties, current property=" + property); // $NON-NLS-1$ + } + } + return listColors; + } + +} diff --git a/src/components/org/apache/jmeter/visualizers/utils/colors.properties b/src/components/org/apache/jmeter/visualizers/utils/colors.properties new file mode 100644 index 00000000000..7f09e6f9ea4 --- /dev/null +++ b/src/components/org/apache/jmeter/visualizers/utils/colors.properties @@ -0,0 +1,49 @@ +# 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. + +# Color order. Keys separate by comma. +order=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30 + +# R,G,B code +1=7,4,56 +2=220,20,60 +3=0,139,69 +4=255,193,37 +5=139,0,139 +6=91,91,91 +7=255,106,106 +8=205,91,69 +9=0,205,205 +10=176,23,31 +11=0,178,238 +12=139,139,0 +13=238,0,238 +14=3,168,158 +15=139,54,38 +16=143,188,143 +17=184,184,184 +18=113,198,113 +19=255,105,180 +20=135,206,255 +21=124,252,0 +22=79,148,205 +23=10,10,10 +24=139,131,120 +25=64,224,208 +26=198,113,113 +27=255,0,255 +28=205,104,137 +29=192,255,62 +30=197,193,170 \ No newline at end of file diff --git a/src/core/org/apache/jmeter/DynamicClassLoader.java b/src/core/org/apache/jmeter/DynamicClassLoader.java new file mode 100644 index 00000000000..2c93c3f27f9 --- /dev/null +++ b/src/core/org/apache/jmeter/DynamicClassLoader.java @@ -0,0 +1,65 @@ +/* + * 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; + +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLStreamHandlerFactory; + +/** + * This is a basic URL classloader for loading new resources + * dynamically. + * + * It allows public access to the addURL() method. + * + * It also adds a convenience method to update the current thread classloader + * + */ +public class DynamicClassLoader extends URLClassLoader { + + public DynamicClassLoader(URL[] urls) { + super(urls); + } + + public DynamicClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + public DynamicClassLoader(URL[] urls, ClassLoader parent, + URLStreamHandlerFactory factory) { + super(urls, parent, factory); + } + + // Make the addURL method visible + @Override + public void addURL(URL url) { + super.addURL(url); + } + + /** + * + * @param urls - list of URLs to add to the thread's classloader + */ + public static void updateLoader(URL [] urls) { + DynamicClassLoader loader + = (DynamicClassLoader) Thread.currentThread().getContextClassLoader(); + for(URL url : urls) { + loader.addURL(url); + } + } +} diff --git a/src/core/org/apache/jmeter/JMeter.java b/src/core/org/apache/jmeter/JMeter.java new file mode 100644 index 00000000000..b63aa85fec0 --- /dev/null +++ b/src/core/org/apache/jmeter/JMeter.java @@ -0,0 +1,1160 @@ +/* + * 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; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; +import java.net.Authenticator; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.SocketException; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.JTree; +import javax.swing.UIManager; +import javax.swing.tree.TreePath; + +import org.apache.commons.cli.avalon.CLArgsParser; +import org.apache.commons.cli.avalon.CLOption; +import org.apache.commons.cli.avalon.CLOptionDescriptor; +import org.apache.commons.cli.avalon.CLUtil; +import org.apache.jmeter.control.ReplaceableController; +import org.apache.jmeter.engine.ClientJMeterEngine; +import org.apache.jmeter.engine.DistributedRunner; +import org.apache.jmeter.engine.JMeterEngine; +import org.apache.jmeter.engine.RemoteJMeterEngineImpl; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.MainFrame; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.Load; +import org.apache.jmeter.gui.action.LoadRecentProject; +import org.apache.jmeter.gui.action.LookAndFeelCommand; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.FocusRequester; +import org.apache.jmeter.plugin.JMeterPlugin; +import org.apache.jmeter.plugin.PluginManager; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.reporters.Summariser; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.RemoteThreadsListenerTestElement; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellServer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.SearchByClass; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassTools; +import org.apache.jorphan.util.HeapDumper; +import org.apache.jorphan.util.JMeterException; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.converters.ConversionException; + +/** + * Main JMeter class; processes options and starts the GUI, non-GUI or server as appropriate. + */ +public class JMeter implements JMeterPlugin { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final int UDP_PORT_DEFAULT = 4445; // needed for ShutdownClient + + public static final String HTTP_PROXY_PASS = "http.proxyPass"; // $NON-NLS-1$ + + public static final String HTTP_PROXY_USER = "http.proxyUser"; // $NON-NLS-1$ + + public static final String JMETER_NON_GUI = "JMeter.NonGui"; // $NON-NLS-1$ + + // If the -t flag is to "LAST", then the last loaded file (if any) is used + private static final String USE_LAST_JMX = "LAST"; + // If the -j or -l flag is set to LAST or LAST.log|LAST.jtl, then the last loaded file name is used to + // generate the log file name by removing .JMX and replacing it with .log|.jtl + + private static final int PROXY_PASSWORD = 'a';// $NON-NLS-1$ + private static final int JMETER_HOME_OPT = 'd';// $NON-NLS-1$ + private static final int HELP_OPT = 'h';// $NON-NLS-1$ + // jmeter.log + private static final int JMLOGFILE_OPT = 'j';// $NON-NLS-1$ + // sample result log file + private static final int LOGFILE_OPT = 'l';// $NON-NLS-1$ + private static final int NONGUI_OPT = 'n';// $NON-NLS-1$ + private static final int PROPFILE_OPT = 'p';// $NON-NLS-1$ + private static final int PROPFILE2_OPT = 'q';// $NON-NLS-1$ + private static final int REMOTE_OPT = 'r';// $NON-NLS-1$ + private static final int SERVER_OPT = 's';// $NON-NLS-1$ + private static final int TESTFILE_OPT = 't';// $NON-NLS-1$ + private static final int PROXY_USERNAME = 'u';// $NON-NLS-1$ + private static final int VERSION_OPT = 'v';// $NON-NLS-1$ + + private static final int SYSTEM_PROPERTY = 'D';// $NON-NLS-1$ + private static final int JMETER_GLOBAL_PROP = 'G';// $NON-NLS-1$ + private static final int PROXY_HOST = 'H';// $NON-NLS-1$ + private static final int JMETER_PROPERTY = 'J';// $NON-NLS-1$ + private static final int LOGLEVEL = 'L';// $NON-NLS-1$ + private static final int NONPROXY_HOSTS = 'N';// $NON-NLS-1$ + private static final int PROXY_PORT = 'P';// $NON-NLS-1$ + private static final int REMOTE_OPT_PARAM = 'R';// $NON-NLS-1$ + private static final int SYSTEM_PROPFILE = 'S';// $NON-NLS-1$ + private static final int REMOTE_STOP = 'X';// $NON-NLS-1$ + + + + /** + * Define the understood options. Each CLOptionDescriptor contains: + *
    + *
  • The "long" version of the option. Eg, "help" means that "--help" + * will be recognised.
  • + *
  • The option flags, governing the option's argument(s).
  • + *
  • The "short" version of the option. Eg, 'h' means that "-h" will be + * recognised.
  • + *
  • A description of the option.
  • + *
+ */ + private static final CLOptionDescriptor[] options = new CLOptionDescriptor[] { + new CLOptionDescriptor("help", CLOptionDescriptor.ARGUMENT_DISALLOWED, HELP_OPT, + "print usage information and exit"), + new CLOptionDescriptor("version", CLOptionDescriptor.ARGUMENT_DISALLOWED, VERSION_OPT, + "print the version information and exit"), + new CLOptionDescriptor("propfile", CLOptionDescriptor.ARGUMENT_REQUIRED, PROPFILE_OPT, + "the jmeter property file to use"), + new CLOptionDescriptor("addprop", CLOptionDescriptor.ARGUMENT_REQUIRED + | CLOptionDescriptor.DUPLICATES_ALLOWED, PROPFILE2_OPT, + "additional JMeter property file(s)"), + new CLOptionDescriptor("testfile", CLOptionDescriptor.ARGUMENT_REQUIRED, TESTFILE_OPT, + "the jmeter test(.jmx) file to run"), + new CLOptionDescriptor("logfile", CLOptionDescriptor.ARGUMENT_REQUIRED, LOGFILE_OPT, + "the file to log samples to"), + new CLOptionDescriptor("jmeterlogfile", CLOptionDescriptor.ARGUMENT_REQUIRED, JMLOGFILE_OPT, + "jmeter run log file (jmeter.log)"), + new CLOptionDescriptor("nongui", CLOptionDescriptor.ARGUMENT_DISALLOWED, NONGUI_OPT, + "run JMeter in nongui mode"), + new CLOptionDescriptor("server", CLOptionDescriptor.ARGUMENT_DISALLOWED, SERVER_OPT, + "run the JMeter server"), + new CLOptionDescriptor("proxyHost", CLOptionDescriptor.ARGUMENT_REQUIRED, PROXY_HOST, + "Set a proxy server for JMeter to use"), + new CLOptionDescriptor("proxyPort", CLOptionDescriptor.ARGUMENT_REQUIRED, PROXY_PORT, + "Set proxy server port for JMeter to use"), + new CLOptionDescriptor("nonProxyHosts", CLOptionDescriptor.ARGUMENT_REQUIRED, NONPROXY_HOSTS, + "Set nonproxy host list (e.g. *.apache.org|localhost)"), + new CLOptionDescriptor("username", CLOptionDescriptor.ARGUMENT_REQUIRED, PROXY_USERNAME, + "Set username for proxy server that JMeter is to use"), + new CLOptionDescriptor("password", CLOptionDescriptor.ARGUMENT_REQUIRED, PROXY_PASSWORD, + "Set password for proxy server that JMeter is to use"), + new CLOptionDescriptor("jmeterproperty", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, JMETER_PROPERTY, + "Define additional JMeter properties"), + new CLOptionDescriptor("globalproperty", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, JMETER_GLOBAL_PROP, + "Define Global properties (sent to servers)\n\t\te.g. -Gport=123 or -Gglobal.properties"), + new CLOptionDescriptor("systemproperty", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, SYSTEM_PROPERTY, + "Define additional system properties"), + new CLOptionDescriptor("systemPropertyFile", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENT_REQUIRED, SYSTEM_PROPFILE, + "additional system property file(s)"), + new CLOptionDescriptor("loglevel", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, LOGLEVEL, + "[category=]level e.g. jorphan=INFO or jmeter.util=DEBUG"), + new CLOptionDescriptor("runremote", CLOptionDescriptor.ARGUMENT_DISALLOWED, REMOTE_OPT, + "Start remote servers (as defined in remote_hosts)"), + new CLOptionDescriptor("remotestart", CLOptionDescriptor.ARGUMENT_REQUIRED, REMOTE_OPT_PARAM, + "Start these remote servers (overrides remote_hosts)"), + new CLOptionDescriptor("homedir", CLOptionDescriptor.ARGUMENT_REQUIRED, JMETER_HOME_OPT, + "the jmeter home directory to use"), + new CLOptionDescriptor("remoteexit", CLOptionDescriptor.ARGUMENT_DISALLOWED, REMOTE_STOP, + "Exit the remote servers at end of test (non-GUI)"), + }; + + public JMeter() { + } + + // Hack to allow automated tests to find when test has ended + //transient boolean testEnded = false; + + private JMeter parent; + + private Properties remoteProps; // Properties to be sent to remote servers + + private boolean remoteStop; // should remote engines be stopped at end of non-GUI test? + + /** + * Starts up JMeter in GUI mode + */ + private void startGui(String testFile) { + String jMeterLaf = LookAndFeelCommand.getJMeterLaf(); + try { + UIManager.setLookAndFeel(jMeterLaf); + } catch (Exception ex) { + log.warn("Could not set LAF to:"+jMeterLaf, ex); + } + + PluginManager.install(this, true); + + JMeterTreeModel treeModel = new JMeterTreeModel(); + JMeterTreeListener treeLis = new JMeterTreeListener(treeModel); + treeLis.setActionHandler(ActionRouter.getInstance()); + // NOTUSED: GuiPackage guiPack = + GuiPackage.getInstance(treeLis, treeModel); + MainFrame main = new MainFrame(treeModel, treeLis); + ComponentUtil.centerComponentInWindow(main, 80); + main.setVisible(true); + ActionRouter.getInstance().actionPerformed(new ActionEvent(main, 1, ActionNames.ADD_ALL)); + if (testFile != null) { + try { + File f = new File(testFile); + log.info("Loading file: " + f); + FileServer.getFileServer().setBaseForScript(f); + + HashTree tree = SaveService.loadTree(f); + + GuiPackage.getInstance().setTestPlanFile(f.getAbsolutePath()); + + Load.insertLoadedTree(1, tree); + } catch (ConversionException e) { + log.error("Failure loading test file", e); + JMeterUtils.reportErrorToUser(SaveService.CEtoString(e)); + } catch (Exception e) { + log.error("Failure loading test file", e); + JMeterUtils.reportErrorToUser(e.toString()); + } + } else { + JTree jTree = GuiPackage.getInstance().getMainFrame().getTree(); + TreePath path = jTree.getPathForRow(0); + jTree.setSelectionPath(path); + FocusRequester.requestFocus(jTree); + } + } + + /** + * Takes the command line arguments and uses them to determine how to + * startup JMeter. + * + * Called reflectively by {@link NewDriver#main(String[])} + * @param args The arguments for JMeter + */ + public void start(String[] args) { + + CLArgsParser parser = new CLArgsParser(args, options); + String error = parser.getErrorString(); + if (error == null){// Check option combinations + boolean gui = parser.getArgumentById(NONGUI_OPT)==null; + boolean nonGuiOnly = parser.getArgumentById(REMOTE_OPT)!=null + || parser.getArgumentById(REMOTE_OPT_PARAM)!=null + || parser.getArgumentById(REMOTE_STOP)!=null; + if (gui && nonGuiOnly) { + error = "-r and -R and -X are only valid in non-GUI mode"; + } + } + if (null != error) { + System.err.println("Error: " + error); + System.out.println("Usage"); + System.out.println(CLUtil.describeOptions(options).toString()); + return; + } + try { + initializeProperties(parser); // Also initialises JMeter logging + + /* + * The following is needed for HTTPClient. + * (originally tried doing this in HTTPSampler2, + * but it appears that it was done too late when running in GUI mode) + * Set the commons logging default to Avalon Logkit, if not already defined + */ + if (System.getProperty("org.apache.commons.logging.Log") == null) { // $NON-NLS-1$ + System.setProperty("org.apache.commons.logging.Log" // $NON-NLS-1$ + , "org.apache.commons.logging.impl.LogKitLogger"); // $NON-NLS-1$ + } + + Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + if (!(e instanceof ThreadDeath)) { + log.error("Uncaught exception: ", e); + System.err.println("Uncaught Exception " + e + ". See log file for details."); + } + } + }); + + log.info(JMeterUtils.getJMeterCopyright()); + log.info("Version " + JMeterUtils.getJMeterVersion()); + logProperty("java.version"); //$NON-NLS-1$ + logProperty("java.vm.name"); //$NON-NLS-1$ + logProperty("os.name"); //$NON-NLS-1$ + logProperty("os.arch"); //$NON-NLS-1$ + logProperty("os.version"); //$NON-NLS-1$ + logProperty("file.encoding"); // $NON-NLS-1$ + log.info("Max memory ="+ Runtime.getRuntime().maxMemory()); + log.info("Available Processors ="+ Runtime.getRuntime().availableProcessors()); + log.info("Default Locale=" + Locale.getDefault().getDisplayName()); + log.info("JMeter Locale=" + JMeterUtils.getLocale().getDisplayName()); + log.info("JMeterHome=" + JMeterUtils.getJMeterHome()); + logProperty("user.dir"," ="); //$NON-NLS-1$ + log.info("PWD ="+new File(".").getCanonicalPath());//$NON-NLS-1$ + log.info("IP: "+JMeterUtils.getLocalHostIP() + +" Name: "+JMeterUtils.getLocalHostName() + +" FullName: "+JMeterUtils.getLocalHostFullName()); + setProxy(parser); + + updateClassLoader(); + if (log.isDebugEnabled()) + { + String jcp=System.getProperty("java.class.path");// $NON-NLS-1$ + String bits[] =jcp.split(File.pathSeparator); + log.debug("ClassPath"); + for(String bit : bits){ + log.debug(bit); + } + log.debug(jcp); + } + + // Set some (hopefully!) useful properties + long now=System.currentTimeMillis(); + JMeterUtils.setProperty("START.MS",Long.toString(now));// $NON-NLS-1$ + Date today=new Date(now); // so it agrees with above + // TODO perhaps should share code with __time() function for this... + JMeterUtils.setProperty("START.YMD",new SimpleDateFormat("yyyyMMdd").format(today));// $NON-NLS-1$ $NON-NLS-2$ + JMeterUtils.setProperty("START.HMS",new SimpleDateFormat("HHmmss").format(today));// $NON-NLS-1$ $NON-NLS-2$ + + if (parser.getArgumentById(VERSION_OPT) != null) { + System.out.println(JMeterUtils.getJMeterCopyright()); + System.out.println("Version " + JMeterUtils.getJMeterVersion()); + } else if (parser.getArgumentById(HELP_OPT) != null) { + System.out.println(JMeterUtils.getResourceFileAsText("org/apache/jmeter/help.txt"));// $NON-NLS-1$ + } else if (parser.getArgumentById(SERVER_OPT) != null) { + // Start the server + try { + RemoteJMeterEngineImpl.startServer(JMeterUtils.getPropDefault("server_port", 0)); // $NON-NLS-1$ + } catch (Exception ex) { + System.err.println("Server failed to start: "+ex); + log.error("Giving up, as server failed with:", ex); + throw ex; + } + startOptionalServers(); + } else { + String testFile=null; + CLOption testFileOpt = parser.getArgumentById(TESTFILE_OPT); + if (testFileOpt != null){ + testFile = testFileOpt.getArgument(); + if (USE_LAST_JMX.equals(testFile)) { + testFile = LoadRecentProject.getRecentFile(0);// most recent + } + } + if (parser.getArgumentById(NONGUI_OPT) == null) { + startGui(testFile); + startOptionalServers(); + } else { + CLOption rem=parser.getArgumentById(REMOTE_OPT_PARAM); + if (rem==null) { rem=parser.getArgumentById(REMOTE_OPT); } + CLOption jtl = parser.getArgumentById(LOGFILE_OPT); + String jtlFile = null; + if (jtl != null){ + jtlFile=processLAST(jtl.getArgument(), ".jtl"); // $NON-NLS-1$ + } + startNonGui(testFile, jtlFile, rem); + startOptionalServers(); + } + } + } catch (IllegalUserActionException e) { + System.out.println(e.getMessage()); + System.out.println("Incorrect Usage"); + System.out.println(CLUtil.describeOptions(options).toString()); + } catch (Throwable e) { + log.fatalError("An error occurred: ",e); + System.out.println("An error occurred: " + e.getMessage()); + System.exit(1); // TODO - could this be return? + } + } + + // Update classloader if necessary + private void updateClassLoader() { + updatePath("search_paths",";", true); //$NON-NLS-1$//$NON-NLS-2$ + updatePath("user.classpath",File.pathSeparator, true);//$NON-NLS-1$ + updatePath("plugin_dependency_paths",";", false);//$NON-NLS-1$ + } + + private void updatePath(String property, String sep, boolean cp) { + String userpath= JMeterUtils.getPropDefault(property,"");// $NON-NLS-1$ + if (userpath.length() <= 0) { return; } + log.info(property+"="+userpath); //$NON-NLS-1$ + StringTokenizer tok = new StringTokenizer(userpath, sep); + while(tok.hasMoreTokens()) { + String path=tok.nextToken(); + File f=new File(path); + if (!f.canRead() && !f.isDirectory()) { + log.warn("Can't read "+path); + } else { + if (cp) { + log.info("Adding to classpath and loader: "+path); + try { + NewDriver.addPath(path); + } catch (MalformedURLException e) { + log.warn("Error adding: "+path+" "+e.getLocalizedMessage()); + } + } else { + log.info("Adding to loader: "+path); + NewDriver.addURL(path); + } + } + } + } + + /** + * + */ + private void startOptionalServers() { + int bshport = JMeterUtils.getPropDefault("beanshell.server.port", 0);// $NON-NLS-1$ + String bshfile = JMeterUtils.getPropDefault("beanshell.server.file", "");// $NON-NLS-1$ $NON-NLS-2$ + if (bshport > 0) { + log.info("Starting Beanshell server (" + bshport + "," + bshfile + ")"); + Runnable t = new BeanShellServer(bshport, bshfile); + t.run(); + } + + // Should we run a beanshell script on startup? + String bshinit = JMeterUtils.getProperty("beanshell.init.file");// $NON-NLS-1$ + if (bshinit != null){ + log.info("Run Beanshell on file: "+bshinit); + try { + BeanShellInterpreter bsi = new BeanShellInterpreter();//bshinit,log); + bsi.source(bshinit); + } catch (ClassNotFoundException e) { + log.warn("Could not start Beanshell: "+e.getLocalizedMessage()); + } catch (JMeterException e) { + log.warn("Could not process Beanshell file: "+e.getLocalizedMessage()); + } + } + + int mirrorPort=JMeterUtils.getPropDefault("mirror.server.port", 0);// $NON-NLS-1$ + if (mirrorPort > 0){ + log.info("Starting Mirror server (" + mirrorPort + ")"); + try { + Object instance = ClassTools.construct( + "org.apache.jmeter.protocol.http.control.HttpMirrorControl",// $NON-NLS-1$ + mirrorPort); + ClassTools.invoke(instance,"startHttpMirror"); + } catch (JMeterException e) { + log.warn("Could not start Mirror server",e); + } + } + } + + /** + * Sets a proxy server for the JVM if the command line arguments are + * specified. + */ + private void setProxy(CLArgsParser parser) throws IllegalUserActionException { + if (parser.getArgumentById(PROXY_USERNAME) != null) { + Properties jmeterProps = JMeterUtils.getJMeterProperties(); + if (parser.getArgumentById(PROXY_PASSWORD) != null) { + String u, p; + Authenticator.setDefault(new ProxyAuthenticator(u = parser.getArgumentById(PROXY_USERNAME) + .getArgument(), p = parser.getArgumentById(PROXY_PASSWORD).getArgument())); + log.info("Set Proxy login: " + u + "/" + p); + jmeterProps.setProperty(HTTP_PROXY_USER, u);//for Httpclient + jmeterProps.setProperty(HTTP_PROXY_PASS, p);//for Httpclient + } else { + String u; + Authenticator.setDefault(new ProxyAuthenticator(u = parser.getArgumentById(PROXY_USERNAME) + .getArgument(), "")); + log.info("Set Proxy login: " + u); + jmeterProps.setProperty(HTTP_PROXY_USER, u); + } + } + if (parser.getArgumentById(PROXY_HOST) != null && parser.getArgumentById(PROXY_PORT) != null) { + String h = parser.getArgumentById(PROXY_HOST).getArgument(); + String p = parser.getArgumentById(PROXY_PORT).getArgument(); + System.setProperty("http.proxyHost", h );// $NON-NLS-1$ + System.setProperty("https.proxyHost", h);// $NON-NLS-1$ + System.setProperty("http.proxyPort", p);// $NON-NLS-1$ + System.setProperty("https.proxyPort", p);// $NON-NLS-1$ + log.info("Set http[s].proxyHost: " + h + " Port: " + p); + } else if (parser.getArgumentById(PROXY_HOST) != null || parser.getArgumentById(PROXY_PORT) != null) { + throw new IllegalUserActionException(JMeterUtils.getResString("proxy_cl_error"));// $NON-NLS-1$ + } + + if (parser.getArgumentById(NONPROXY_HOSTS) != null) { + String n = parser.getArgumentById(NONPROXY_HOSTS).getArgument(); + System.setProperty("http.nonProxyHosts", n );// $NON-NLS-1$ + System.setProperty("https.nonProxyHosts", n );// $NON-NLS-1$ + log.info("Set http[s].nonProxyHosts: "+n); + } + } + + private void initializeProperties(CLArgsParser parser) { + if (parser.getArgumentById(PROPFILE_OPT) != null) { + JMeterUtils.loadJMeterProperties(parser.getArgumentById(PROPFILE_OPT).getArgument()); + } else { + JMeterUtils.loadJMeterProperties(NewDriver.getJMeterDir() + File.separator + + "bin" + File.separator // $NON-NLS-1$ + + "jmeter.properties");// $NON-NLS-1$ + } + + if (parser.getArgumentById(JMLOGFILE_OPT) != null){ + String jmlogfile=parser.getArgumentById(JMLOGFILE_OPT).getArgument(); + jmlogfile = processLAST(jmlogfile, ".log");// $NON-NLS-1$ + JMeterUtils.setProperty(LoggingManager.LOG_FILE,jmlogfile); + } + + JMeterUtils.initLogging(); + JMeterUtils.initLocale(); + // Bug 33845 - allow direct override of Home dir + if (parser.getArgumentById(JMETER_HOME_OPT) == null) { + JMeterUtils.setJMeterHome(NewDriver.getJMeterDir()); + } else { + JMeterUtils.setJMeterHome(parser.getArgumentById(JMETER_HOME_OPT).getArgument()); + } + + Properties jmeterProps = JMeterUtils.getJMeterProperties(); + remoteProps = new Properties(); + + // Add local JMeter properties, if the file is found + String userProp = JMeterUtils.getPropDefault("user.properties",""); //$NON-NLS-1$ + if (userProp.length() > 0){ //$NON-NLS-1$ + FileInputStream fis=null; + try { + File file = JMeterUtils.findFile(userProp); + if (file.canRead()){ + log.info("Loading user properties from: "+file.getCanonicalPath()); + fis = new FileInputStream(file); + Properties tmp = new Properties(); + tmp.load(fis); + jmeterProps.putAll(tmp); + LoggingManager.setLoggingLevels(tmp);//Do what would be done earlier + } + } catch (IOException e) { + log.warn("Error loading user property file: " + userProp, e); + } finally { + JOrphanUtils.closeQuietly(fis); + } + } + + // Add local system properties, if the file is found + String sysProp = JMeterUtils.getPropDefault("system.properties",""); //$NON-NLS-1$ + if (sysProp.length() > 0){ + FileInputStream fis=null; + try { + File file = JMeterUtils.findFile(sysProp); + if (file.canRead()){ + log.info("Loading system properties from: "+file.getCanonicalPath()); + fis = new FileInputStream(file); + System.getProperties().load(fis); + } + } catch (IOException e) { + log.warn("Error loading system property file: " + sysProp, e); + } finally { + JOrphanUtils.closeQuietly(fis); + } + } + + // Process command line property definitions + // These can potentially occur multiple times + + List clOptions = parser.getArguments(); + int size = clOptions.size(); + + for (int i = 0; i < size; i++) { + CLOption option = clOptions.get(i); + String name = option.getArgument(0); + String value = option.getArgument(1); + FileInputStream fis = null; + + switch (option.getDescriptor().getId()) { + + // Should not have any text arguments + case CLOption.TEXT_ARGUMENT: + throw new IllegalArgumentException("Unknown arg: "+option.getArgument()); + + case PROPFILE2_OPT: // Bug 33920 - allow multiple props + try { + fis = new FileInputStream(new File(name)); + Properties tmp = new Properties(); + tmp.load(fis); + jmeterProps.putAll(tmp); + LoggingManager.setLoggingLevels(tmp);//Do what would be done earlier + } catch (FileNotFoundException e) { + log.warn("Can't find additional property file: " + name, e); + } catch (IOException e) { + log.warn("Error loading additional property file: " + name, e); + } finally { + JOrphanUtils.closeQuietly(fis); + } + break; + case SYSTEM_PROPFILE: + log.info("Setting System properties from file: " + name); + try { + fis = new FileInputStream(new File(name)); + System.getProperties().load(fis); + } catch (IOException e) { + log.warn("Cannot find system property file "+e.getLocalizedMessage()); + } finally { + JOrphanUtils.closeQuietly(fis); + } + break; + case SYSTEM_PROPERTY: + if (value.length() > 0) { // Set it + log.info("Setting System property: " + name + "=" + value); + System.getProperties().setProperty(name, value); + } else { // Reset it + log.warn("Removing System property: " + name); + System.getProperties().remove(name); + } + break; + case JMETER_PROPERTY: + if (value.length() > 0) { // Set it + log.info("Setting JMeter property: " + name + "=" + value); + jmeterProps.setProperty(name, value); + } else { // Reset it + log.warn("Removing JMeter property: " + name); + jmeterProps.remove(name); + } + break; + case JMETER_GLOBAL_PROP: + if (value.length() > 0) { // Set it + log.info("Setting Global property: " + name + "=" + value); + remoteProps.setProperty(name, value); + } else { + File propFile = new File(name); + if (propFile.canRead()) { + log.info("Setting Global properties from the file "+name); + try { + fis = new FileInputStream(propFile); + remoteProps.load(fis); + } catch (FileNotFoundException e) { + log.warn("Could not find properties file: "+e.getLocalizedMessage()); + } catch (IOException e) { + log.warn("Could not load properties file: "+e.getLocalizedMessage()); + } finally { + JOrphanUtils.closeQuietly(fis); + } + } + } + break; + case LOGLEVEL: + if (value.length() > 0) { // Set category + log.info("LogLevel: " + name + "=" + value); + LoggingManager.setPriority(value, name); + } else { // Set root level + log.warn("LogLevel: " + name); + LoggingManager.setPriority(name); + } + break; + case REMOTE_STOP: + remoteStop = true; + break; + default: + // ignored + break; + } + } + + String sample_variables = (String) jmeterProps.get(SampleEvent.SAMPLE_VARIABLES); + if (sample_variables != null){ + remoteProps.put(SampleEvent.SAMPLE_VARIABLES, sample_variables); + } + jmeterProps.put("jmeter.version", JMeterUtils.getJMeterVersion()); + } + + /* + * Checks for LAST or LASTsuffix. + * Returns the LAST name with .JMX replaced by suffix. + */ + private String processLAST(String jmlogfile, String suffix) { + if (USE_LAST_JMX.equals(jmlogfile) || USE_LAST_JMX.concat(suffix).equals(jmlogfile)){ + String last = LoadRecentProject.getRecentFile(0);// most recent + final String JMX_SUFFIX = ".JMX"; // $NON-NLS-1$ + if (last.toUpperCase(Locale.ENGLISH).endsWith(JMX_SUFFIX)){ + jmlogfile=last.substring(0, last.length() - JMX_SUFFIX.length()).concat(suffix); + } + } + return jmlogfile; + } + + private void startNonGui(String testFile, String logFile, CLOption remoteStart) + throws IllegalUserActionException { + // add a system property so samplers can check to see if JMeter + // is running in NonGui mode + System.setProperty(JMETER_NON_GUI, "true");// $NON-NLS-1$ + JMeter driver = new JMeter();// TODO - why does it create a new instance? + driver.remoteProps = this.remoteProps; + driver.remoteStop = this.remoteStop; + driver.parent = this; + PluginManager.install(this, false); + + String remote_hosts_string = null; + if (remoteStart != null) { + remote_hosts_string = remoteStart.getArgument(); + if (remote_hosts_string == null) { + remote_hosts_string = JMeterUtils.getPropDefault( + "remote_hosts", //$NON-NLS-1$ + "127.0.0.1");//$NON-NLS-1$ + } + } + if (testFile == null) { + throw new IllegalUserActionException("Non-GUI runs require a test plan"); + } + driver.runNonGui(testFile, logFile, remoteStart != null, remote_hosts_string); + } + + // run test in batch mode + private void runNonGui(String testFile, String logFile, boolean remoteStart, String remote_hosts_string) { + try { + File f = new File(testFile); + if (!f.exists() || !f.isFile()) { + println("Could not open " + testFile); + return; + } + FileServer.getFileServer().setBaseForScript(f); + + HashTree tree = SaveService.loadTree(f); + + @SuppressWarnings("deprecation") // Deliberate use of deprecated ctor + JMeterTreeModel treeModel = new JMeterTreeModel(new Object());// Create non-GUI version to avoid headless problems + JMeterTreeNode root = (JMeterTreeNode) treeModel.getRoot(); + treeModel.addSubTree(tree, root); + + // Hack to resolve ModuleControllers in non GUI mode + SearchByClass replaceableControllers = new SearchByClass(ReplaceableController.class); + tree.traverse(replaceableControllers); + Collection replaceableControllersRes = replaceableControllers.getSearchResults(); + for (Iterator iter = replaceableControllersRes.iterator(); iter.hasNext();) { + ReplaceableController replaceableController = iter.next(); + replaceableController.resolveReplacementSubTree(root); + } + + // Remove the disabled items + // For GUI runs this is done in Start.java + convertSubTree(tree); + + Summariser summer = null; + String summariserName = JMeterUtils.getPropDefault("summariser.name", "summary");//$NON-NLS-1$ + if (summariserName.length() > 0) { + log.info("Creating summariser <" + summariserName + ">"); + println("Creating summariser <" + summariserName + ">"); + summer = new Summariser(summariserName); + } + + if (logFile != null) { + ResultCollector logger = new ResultCollector(summer); + logger.setFilename(logFile); + tree.add(tree.getArray()[0], logger); + } + else { + // only add Summariser if it can not be shared with the ResultCollector + if (summer != null) { + tree.add(tree.getArray()[0], summer); + } + } + // Used for remote notification of threads start/stop,see BUG 54152 + // Summariser uses this feature to compute correctly number of threads + // when NON GUI mode is used + tree.add(tree.getArray()[0], new RemoteThreadsListenerTestElement()); + + List engines = new LinkedList(); + tree.add(tree.getArray()[0], new ListenToTest(parent, (remoteStart && remoteStop) ? engines : null)); + println("Created the tree successfully using "+testFile); + if (!remoteStart) { + JMeterEngine engine = new StandardJMeterEngine(); + engine.configure(tree); + long now=System.currentTimeMillis(); + println("Starting the test @ "+new Date(now)+" ("+now+")"); + engine.runTest(); + engines.add(engine); + } else { + java.util.StringTokenizer st = new java.util.StringTokenizer(remote_hosts_string, ",");//$NON-NLS-1$ + List hosts = new LinkedList(); + while (st.hasMoreElements()) { + hosts.add((String) st.nextElement()); + } + + DistributedRunner distributedRunner=new DistributedRunner(this.remoteProps); + distributedRunner.setStdout(System.out); + distributedRunner.setStdErr(System.err); + distributedRunner.init(hosts, tree); + engines.addAll(distributedRunner.getEngines()); + distributedRunner.start(); + } + startUdpDdaemon(engines); + } catch (Exception e) { + System.out.println("Error in NonGUIDriver " + e.toString()); + log.error("Error in NonGUIDriver", e); + } + } + + /** + * Refactored from AbstractAction.java + * + * @param tree The {@link HashTree} to convert + */ + public static void convertSubTree(HashTree tree) { + LinkedList copyList = new LinkedList(tree.list()); + for (Object o : copyList) { + if (o instanceof TestElement) { + TestElement item = (TestElement) o; + if (item.isEnabled()) { + if (item instanceof ReplaceableController) { + ReplaceableController rc = ensureReplaceableControllerIsLoaded(item); + + HashTree subTree = tree.getTree(item); + if (subTree != null) { + HashTree replacementTree = rc.getReplacementSubTree(); + if (replacementTree != null) { + convertSubTree(replacementTree); + tree.replaceKey(item, rc); + tree.set(rc, replacementTree); + } + } + } else { // not Replaceable Controller + convertSubTree(tree.getTree(item)); + } + } else { // Not enabled + tree.remove(item); + } + } else { // Not a TestElement + JMeterTreeNode item = (JMeterTreeNode) o; + if (item.isEnabled()) { + // Replacement only needs to occur when starting the engine + // @see StandardJMeterEngine.run() + if (item.getUserObject() instanceof ReplaceableController) { + TestElement controllerAsItem = item.getTestElement(); + ReplaceableController rc = ensureReplaceableControllerIsLoaded(controllerAsItem); + + HashTree subTree = tree.getTree(item); + + if (subTree != null) { + HashTree replacementTree = rc.getReplacementSubTree(); + if (replacementTree != null) { + convertSubTree(replacementTree); + tree.replaceKey(item, rc); + tree.set(rc, replacementTree); + } + } + } else { // Not a ReplaceableController + convertSubTree(tree.getTree(item)); + TestElement testElement = item.getTestElement(); + tree.replaceKey(item, testElement); + } + } else { // Not enabled + tree.remove(item); + } + } + } + } + + /** + * Ensures the {@link ReplaceableController} is loaded + * @param item {@link TestElement} + * @return {@link ReplaceableController} loaded + */ + private static ReplaceableController ensureReplaceableControllerIsLoaded( + TestElement item) { + ReplaceableController rc; + // TODO this bit of code needs to be tidied up + // Unfortunately ModuleController is in components, not core + if (item.getClass().getName().equals("org.apache.jmeter.control.ModuleController")){ // Bug 47165 + rc = (ReplaceableController) item; + } else { + // HACK: force the controller to load its tree + rc = (ReplaceableController) item.clone(); + } + return rc; + } + + /* + * Listen to test and handle tidyup after non-GUI test completes. + * If running a remote test, then after waiting a few seconds for listeners to finish files, + * it calls ClientJMeterEngine.tidyRMI() to deal with the Naming Timer Thread. + */ + private static class ListenToTest implements TestStateListener, Runnable, Remoteable { + private final AtomicInteger started = new AtomicInteger(0); // keep track of remote tests + + //NOT YET USED private JMeter _parent; + + private final List engines; + + /** + * @param unused JMeter unused for now + * @param engines List + */ + public ListenToTest(JMeter unused, List engines) { + //_parent = unused; + this.engines=engines; + } + + @Override + public void testEnded(String host) { + long now=System.currentTimeMillis(); + log.info("Finished remote host: " + host + " ("+now+")"); + if (started.decrementAndGet() <= 0) { + Thread stopSoon = new Thread(this); + stopSoon.start(); + } + } + + @Override + public void testEnded() { + long now = System.currentTimeMillis(); + println("Tidying up ... @ "+new Date(now)+" ("+now+")"); + println("... end of run"); + checkForRemainingThreads(); + } + + @Override + public void testStarted(String host) { + started.incrementAndGet(); + long now=System.currentTimeMillis(); + log.info("Started remote host: " + host + " ("+now+")"); + } + + @Override + public void testStarted() { + long now=System.currentTimeMillis(); + log.info(JMeterUtils.getResString("running_test")+" ("+now+")");//$NON-NLS-1$ + } + + /** + * This is a hack to allow listeners a chance to close their files. Must + * implement a queue for sample responses tied to the engine, and the + * engine won't deliver testEnded signal till all sample responses have + * been delivered. Should also improve performance of remote JMeter + * testing. + */ + @Override + public void run() { + long now = System.currentTimeMillis(); + println("Tidying up remote @ "+new Date(now)+" ("+now+")"); + if (engines!=null){ // it will be null unless remoteStop = true + println("Exitting remote servers"); + for (JMeterEngine e : engines){ + e.exit(); + } + } + try { + TimeUnit.SECONDS.sleep(5); // Allow listeners to close files + } catch (InterruptedException ignored) { + } + ClientJMeterEngine.tidyRMI(log); + println("... end of run"); + checkForRemainingThreads(); + } + + /** + * Runs daemon thread which waits a short while; + * if JVM does not exit, lists remaining non-daemon threads on stdout. + */ + private void checkForRemainingThreads() { + // This cannot be a JMeter class variable, because properties + // are not initialised until later. + final int REMAIN_THREAD_PAUSE = + JMeterUtils.getPropDefault("jmeter.exit.check.pause", 2000); // $NON-NLS-1$ + + if (REMAIN_THREAD_PAUSE > 0) { + Thread daemon = new Thread(){ + @Override + public void run(){ + try { + TimeUnit.MILLISECONDS.sleep(REMAIN_THREAD_PAUSE); // Allow enough time for JVM to exit + } catch (InterruptedException ignored) { + } + // This is a daemon thread, which should only reach here if there are other + // non-daemon threads still active + System.out.println("The JVM should have exitted but did not."); + System.out.println("The following non-daemon threads are still running (DestroyJavaVM is OK):"); + JOrphanUtils.displayThreads(false); + } + + }; + daemon.setDaemon(true); + daemon.start(); + } else if(REMAIN_THREAD_PAUSE<=0) { + if(log.isDebugEnabled()) { + log.debug("jmeter.exit.check.pause is <= 0, JMeter won't check for unterminated non-daemon threads"); + } + } + } + + } + + private static void println(String str) { + System.out.println(str); + } + + private static final String[][] DEFAULT_ICONS = { + { "org.apache.jmeter.control.gui.TestPlanGui", "org/apache/jmeter/images/beaker.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.timers.gui.AbstractTimerGui", "org/apache/jmeter/images/timer.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.threads.gui.ThreadGroupGui", "org/apache/jmeter/images/thread.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.visualizers.gui.AbstractListenerGui", "org/apache/jmeter/images/meter.png" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.config.gui.AbstractConfigGui", "org/apache/jmeter/images/testtubes.png" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.processor.gui.AbstractPreProcessorGui", "org/apache/jmeter/images/leafnode.gif"}, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.processor.gui.AbstractPostProcessorGui","org/apache/jmeter/images/leafnodeflip.gif"},//$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.control.gui.AbstractControllerGui", "org/apache/jmeter/images/knob.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.control.gui.WorkBenchGui", "org/apache/jmeter/images/clipboard.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.samplers.gui.AbstractSamplerGui", "org/apache/jmeter/images/pipet.png" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.assertions.gui.AbstractAssertionGui", "org/apache/jmeter/images/question.gif"} //$NON-NLS-1$ $NON-NLS-2$ + }; + + @Override + public String[][] getIconMappings() { + final String defaultIconProp = "org/apache/jmeter/images/icon.properties"; //$NON-NLS-1$ + String iconProp = JMeterUtils.getPropDefault("jmeter.icons", defaultIconProp);//$NON-NLS-1$ + Properties p = JMeterUtils.loadProperties(iconProp); + if (p == null && !iconProp.equals(defaultIconProp)) { + log.info(iconProp + " not found - using " + defaultIconProp); + iconProp = defaultIconProp; + p = JMeterUtils.loadProperties(iconProp); + } + if (p == null) { + log.info(iconProp + " not found - using inbuilt icon set"); + return DEFAULT_ICONS; + } + log.info("Loaded icon properties from " + iconProp); + String[][] iconlist = new String[p.size()][3]; + Enumeration pe = p.keys(); + int i = 0; + while (pe.hasMoreElements()) { + String key = (String) pe.nextElement(); + String icons[] = JOrphanUtils.split(p.getProperty(key), " ");//$NON-NLS-1$ + iconlist[i][0] = key; + iconlist[i][1] = icons[0]; + if (icons.length > 1) { + iconlist[i][2] = icons[1]; + } + i++; + } + return iconlist; + } + + @Override + public String[][] getResourceBundles() { + return new String[0][]; + } + + /** + * Check if JMeter is running in non-GUI mode. + * + * @return true if JMeter is running in non-GUI mode. + */ + public static boolean isNonGUI(){ + return "true".equals(System.getProperty(JMeter.JMETER_NON_GUI)); //$NON-NLS-1$ + } + + private void logProperty(String prop){ + log.info(prop+"="+System.getProperty(prop));//$NON-NLS-1$ + } + private void logProperty(String prop,String separator){ + log.info(prop+separator+System.getProperty(prop));//$NON-NLS-1$ + } + + private static void startUdpDdaemon(final List engines) { + int port = JMeterUtils.getPropDefault("jmeterengine.nongui.port", UDP_PORT_DEFAULT); // $NON-NLS-1$ + int maxPort = JMeterUtils.getPropDefault("jmeterengine.nongui.maxport", 4455); // $NON-NLS-1$ + if (port > 1000){ + final DatagramSocket socket = getSocket(port, maxPort); + if (socket != null) { + Thread waiter = new Thread("UDP Listener"){ + @Override + public void run() { + waitForSignals(engines, socket); + } + }; + waiter.setDaemon(true); + waiter.start(); + } else { + System.out.println("Failed to create UDP port"); + } + } + } + + private static void waitForSignals(final List engines, DatagramSocket socket) { + byte[] buf = new byte[80]; + System.out.println("Waiting for possible shutdown message on port "+socket.getLocalPort()); + DatagramPacket request = new DatagramPacket(buf, buf.length); + try { + while(true) { + socket.receive(request); + InetAddress address = request.getAddress(); + // Only accept commands from the local host + if (address.isLoopbackAddress()){ + String command = new String(request.getData(), request.getOffset(), request.getLength(),"ASCII"); + System.out.println("Command: "+command+" received from "+address); + log.info("Command: "+command+" received from "+address); + if (command.equals("StopTestNow")){ + for(JMeterEngine engine : engines) { + engine.stopTest(true); + } + } else if (command.equals("Shutdown")) { + for(JMeterEngine engine : engines) { + engine.stopTest(false); + } + } else if (command.equals("HeapDump")) { + HeapDumper.dumpHeap(); + } else { + System.out.println("Command: "+command+" not recognised "); + } + } + } + } catch (Exception e) { + System.out.println(e); + } finally { + socket.close(); + } + } + + private static DatagramSocket getSocket(int udpPort, int udpPortMax) { + DatagramSocket socket = null; + int i = udpPort; + while (i<= udpPortMax) { + try { + socket = new DatagramSocket(i); + break; + } catch (SocketException e) { + i++; + } + } + + return socket; + } +} diff --git a/src/core/org/apache/jmeter/NewDriver.java b/src/core/org/apache/jmeter/NewDriver.java new file mode 100644 index 00000000000..8fed462d33f --- /dev/null +++ b/src/core/org/apache/jmeter/NewDriver.java @@ -0,0 +1,270 @@ +/* + * 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; + +// N.B. this must only use standard Java packages +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.AccessController; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * Main class for JMeter - sets up initial classpath and the loader. + * + */ +public final class NewDriver { + + private static final String CLASSPATH_SEPARATOR = File.pathSeparator; + + private static final String OS_NAME = System.getProperty("os.name");// $NON-NLS-1$ + + private static final String OS_NAME_LC = OS_NAME.toLowerCase(java.util.Locale.ENGLISH); + + private static final String JAVA_CLASS_PATH = "java.class.path";// $NON-NLS-1$ + + /** The class loader to use for loading JMeter classes. */ + private static final DynamicClassLoader loader; + + /** The directory JMeter is installed in. */ + private static final String jmDir; + + static { + final List jars = new LinkedList(); + final String initial_classpath = System.getProperty(JAVA_CLASS_PATH); + + // Find JMeter home dir from the initial classpath + String tmpDir=null; + StringTokenizer tok = new StringTokenizer(initial_classpath, File.pathSeparator); + if (tok.countTokens() == 1 + || (tok.countTokens() == 2 // Java on Mac OS can add a second entry to the initial classpath + && OS_NAME_LC.startsWith("mac os x")// $NON-NLS-1$ + ) + ) { + File jar = new File(tok.nextToken()); + try { + tmpDir = jar.getCanonicalFile().getParentFile().getParent(); + } catch (IOException e) { + } + } else {// e.g. started from IDE with full classpath + tmpDir = System.getProperty("jmeter.home","");// Allow override $NON-NLS-1$ $NON-NLS-2$ + if (tmpDir.length() == 0) { + File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$ + tmpDir = userDir.getAbsoluteFile().getParent(); + } + } + jmDir=tmpDir; + + /* + * Does the system support UNC paths? If so, may need to fix them up + * later + */ + boolean usesUNC = OS_NAME_LC.startsWith("windows");// $NON-NLS-1$ + + // Add standard jar locations to initial classpath + StringBuilder classpath = new StringBuilder(); + File[] libDirs = new File[] { new File(jmDir + File.separator + "lib"),// $NON-NLS-1$ $NON-NLS-2$ + new File(jmDir + File.separator + "lib" + File.separator + "ext"),// $NON-NLS-1$ $NON-NLS-2$ + new File(jmDir + File.separator + "lib" + File.separator + "junit")};// $NON-NLS-1$ $NON-NLS-2$ + for (File libDir : libDirs) { + File[] libJars = libDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) {// only accept jar files + return name.endsWith(".jar");// $NON-NLS-1$ + } + }); + if (libJars == null) { + new Throwable("Could not access " + libDir).printStackTrace(); + continue; + } + Arrays.sort(libJars); // Bug 50708 Ensure predictable order of jars + for (File libJar : libJars) { + try { + String s = libJar.getPath(); + + // Fix path to allow the use of UNC URLs + if (usesUNC) { + if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {// $NON-NLS-1$ $NON-NLS-2$ + s = "\\\\" + s;// $NON-NLS-1$ + } else if (s.startsWith("//") && !s.startsWith("///")) {// $NON-NLS-1$ $NON-NLS-2$ + s = "//" + s;// $NON-NLS-1$ + } + } // usesUNC + + jars.add(new File(s).toURI().toURL());// See Java bug 4496398 + classpath.append(CLASSPATH_SEPARATOR); + classpath.append(s); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + } + + // ClassFinder needs the classpath + System.setProperty(JAVA_CLASS_PATH, initial_classpath + classpath.toString()); + loader = AccessController.doPrivileged( + new java.security.PrivilegedAction() { + @Override + public DynamicClassLoader run() { + return new DynamicClassLoader(jars.toArray(new URL[jars.size()])); + } + } + ); + } + + /** + * Prevent instantiation. + */ + private NewDriver() { + } + + /** + * Generate an array of jar files located in a directory. + * Jar files located in sub directories will not be added. + * + * @param dir to search for the jar files. + */ + private static File[] listJars(File dir) { + if (dir.isDirectory()) { + return dir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File f, String name) { + if (name.endsWith(".jar")) {// $NON-NLS-1$ + File jar = new File(f, name); + return jar.isFile() && jar.canRead(); + } + return false; + } + }); + } + return new File[0]; + } + + /** + * Add a URL to the loader classpath only; does not update the system classpath. + * + * @param path to be added. + */ + public static void addURL(String path) { + File furl = new File(path); + try { + loader.addURL(furl.toURI().toURL()); // See Java bug 4496398 + } catch (MalformedURLException e) { + e.printStackTrace(); + } + File[] jars = listJars(furl); + for (File jar : jars) { + try { + loader.addURL(jar.toURI().toURL()); // See Java bug 4496398 + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + } + + /** + * Add a URL to the loader classpath only; does not update the system + * classpath. + * + * @param url + * The {@link URL} to add to the classpath + */ + public static void addURL(URL url) { + loader.addURL(url); + } + + /** + * Add a directory or jar to the loader and system classpaths. + * + * @param path + * to add to the loader and system classpath + * @throws MalformedURLException + * if path can not be transformed to a valid + * {@link URL} + */ + public static void addPath(String path) throws MalformedURLException { + File file = new File(path); + // Ensure that directory URLs end in "/" + if (file.isDirectory() && !path.endsWith("/")) {// $NON-NLS-1$ + file = new File(path + "/");// $NON-NLS-1$ + } + loader.addURL(file.toURI().toURL()); // See Java bug 4496398 + StringBuilder sb = new StringBuilder(System.getProperty(JAVA_CLASS_PATH)); + sb.append(CLASSPATH_SEPARATOR); + sb.append(path); + File[] jars = listJars(file); + for (File jar : jars) { + try { + loader.addURL(jar.toURI().toURL()); // See Java bug 4496398 + sb.append(CLASSPATH_SEPARATOR); + sb.append(jar.getPath()); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + + // ClassFinder needs this + System.setProperty(JAVA_CLASS_PATH,sb.toString()); + } + + /** + * Get the directory where JMeter is installed. This is the absolute path + * name. + * + * @return the directory where JMeter is installed. + */ + public static String getJMeterDir() { + return jmDir; + } + + /** + * The main program which actually runs JMeter. + * + * @param args + * the command line arguments + */ + public static void main(String[] args) { + Thread.currentThread().setContextClassLoader(loader); + if (System.getProperty("log4j.configuration") == null) {// $NON-NLS-1$ $NON-NLS-2$ + File conf = new File(jmDir, "bin" + File.separator + "log4j.conf");// $NON-NLS-1$ $NON-NLS-2$ + System.setProperty("log4j.configuration", "file:" + conf); + } + + try { + Class initialClass; + if (args != null && args.length > 0 && args[0].equals("report")) {// $NON-NLS-1$ + initialClass = loader.loadClass("org.apache.jmeter.JMeterReport");// $NON-NLS-1$ + } else { + initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$ + } + Object instance = initialClass.newInstance(); + Method startup = initialClass.getMethod("start", new Class[] { new String[0].getClass() });// $NON-NLS-1$ + startup.invoke(instance, new Object[] { args }); + } catch(Throwable e){ + e.printStackTrace(); + System.err.println("JMeter home directory was detected as: "+jmDir); + } + } +} diff --git a/src/core/org/apache/jmeter/ProxyAuthenticator.java b/src/core/org/apache/jmeter/ProxyAuthenticator.java new file mode 100644 index 00000000000..c6b22269781 --- /dev/null +++ b/src/core/org/apache/jmeter/ProxyAuthenticator.java @@ -0,0 +1,70 @@ +/* + * 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; + +import java.net.Authenticator; +import java.net.PasswordAuthentication; + +/** + * Provides JMeter the ability to use proxy servers that require username and + * password. + * + * @version $Revision$ + */ +public class ProxyAuthenticator extends Authenticator { + /** The username to authenticate with. */ + private final String userName; + + /** The password to authenticate with. */ + private final char password[]; + + /** + * Create a ProxyAuthenticator with the specified username and password. + * + * @param userName + * the username to authenticate with + * @param password + * the password to authenticate with + */ + public ProxyAuthenticator(String userName, String password) { + this.userName = userName; + this.password = password.toCharArray(); + } + + /** + * Return a PasswordAuthentication instance using the userName and password + * specified in the constructor. + * Only applies to PROXY request types. + * + * @return a PasswordAuthentication instance to use for authenticating with + * the proxy + */ + @Override + protected PasswordAuthentication getPasswordAuthentication() { + switch (getRequestorType()){ + case PROXY: + return new PasswordAuthentication(userName, password); + case SERVER: + break; + default: + break; + } + return null; + } +} diff --git a/src/core/org/apache/jmeter/assertions/Assertion.java b/src/core/org/apache/jmeter/assertions/Assertion.java new file mode 100644 index 00000000000..f775578939c --- /dev/null +++ b/src/core/org/apache/jmeter/assertions/Assertion.java @@ -0,0 +1,44 @@ +/* + * 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.assertions; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * An Assertion checks a SampleResult to determine whether or not it is + * successful. The resulting success status can be obtained from a corresponding + * Assertion Result. For example, if a web response doesn't contain an expected + * expression, it would be considered a failure. + * + * @version $Revision$ + */ +public interface Assertion { + /** + * Returns the AssertionResult object encapsulating information about the + * success or failure of the assertion. + * + * @param response + * the SampleResult containing information about the Sample + * (duration, success, etc) + * + * @return the AssertionResult containing the information about whether the + * assertion passed or failed. + */ + AssertionResult getResult(SampleResult response); +} diff --git a/src/core/org/apache/jmeter/assertions/AssertionResult.java b/src/core/org/apache/jmeter/assertions/AssertionResult.java new file mode 100644 index 00000000000..81aaa408976 --- /dev/null +++ b/src/core/org/apache/jmeter/assertions/AssertionResult.java @@ -0,0 +1,165 @@ +/* + * 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.assertions; + +import java.io.Serializable; + +/** + * Implements Response Assertion checking. + */ +public class AssertionResult implements Serializable { + public static final String RESPONSE_WAS_NULL = "Response was null"; // $NON-NLS-1$ + + private static final long serialVersionUID = 240L; + + /** Name of the assertion. */ + private final String name; + + /** True if the assertion failed. */ + private boolean failure; + + /** True if there was an error checking the assertion. */ + private boolean error; + + /** A message describing the failure. */ + private String failureMessage; + + /** + * Create a new Assertion Result. The result will indicate no failure or + * error. + * @deprecated - use the named constructor + */ + @Deprecated + public AssertionResult() { // Needs to be public for tests + this.name = null; + } + + /** + * Create a new Assertion Result. The result will indicate no failure or + * error. + * + * @param name the name of the assertion + */ + public AssertionResult(String name) { + this.name = name; + } + + /** + * Get the name of the assertion + * + * @return the name of the assertion + */ + public String getName() { + return name; + } + + /** + * Check if the assertion failed. If it failed, the failure message may give + * more details about the failure. + * + * @return true if the assertion failed, false if the sample met the + * assertion criteria + */ + public boolean isFailure() { + return failure; + } + + /** + * Check if an error occurred while checking the assertion. If an error + * occurred, the failure message may give more details about the error. + * + * @return true if an error occurred while checking the assertion, false + * otherwise. + */ + public boolean isError() { + return error; + } + + /** + * Get the message associated with any failure or error. This method may + * return null if no message was set. + * + * @return a failure or error message, or null if no message has been set + */ + public String getFailureMessage() { + return failureMessage; + } + + /** + * Set the flag indicating whether or not an error occurred. + * + * @param e + * true if an error occurred, false otherwise + */ + public void setError(boolean e) { + error = e; + } + + /** + * Set the flag indicating whether or not a failure occurred. + * + * @param f + * true if a failure occurred, false otherwise + */ + public void setFailure(boolean f) { + failure = f; + } + + /** + * Set the failure message giving more details about a failure or error. + * + * @param message + * the message to set + */ + public void setFailureMessage(String message) { + failureMessage = message; + } + + /** + * Convenience method for setting up failed results + * + * @param message + * the message to set + * @return this + * + */ + public AssertionResult setResultForFailure(String message) { + error = false; + failure = true; + failureMessage = message; + return this; + } + + /** + * Convenience method for setting up results where the response was null + * + * @return assertion result with appropriate fields set up + */ + public AssertionResult setResultForNull() { + error = false; + failure = true; + failureMessage = RESPONSE_WAS_NULL; + return this; + } + + @Override + public String toString() { + return getName() != null ? getName() : super.toString(); + } +} diff --git a/src/core/org/apache/jmeter/assertions/CompareAssertionResult.java b/src/core/org/apache/jmeter/assertions/CompareAssertionResult.java new file mode 100644 index 00000000000..580c551da32 --- /dev/null +++ b/src/core/org/apache/jmeter/assertions/CompareAssertionResult.java @@ -0,0 +1,93 @@ +/* + * 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.assertions; + + +public class CompareAssertionResult extends AssertionResult { + private static final long serialVersionUID = 1; + + private transient final ResultHolder comparedResults = new ResultHolder(); + + /** + * For testing only + * @deprecated Use the other ctor + */ + @Deprecated + public CompareAssertionResult() { // needs to be public for testing + super(); + } + + public CompareAssertionResult(String name) { + super(name); + } + + public void addToBaseResult(String resultData) + { + comparedResults.addToBaseResult(resultData); + } + + public void addToSecondaryResult(String resultData) + { + comparedResults.addToSecondaryResult(resultData); + } + + public String getBaseResult() + { + return comparedResults.baseResult; + } + + public String getSecondaryResult() + { + return comparedResults.secondaryResult; + } + + private static class ResultHolder + { + private String baseResult; + private String secondaryResult; + + public ResultHolder() + { + } + + public void addToBaseResult(String r) + { + if(baseResult == null) + { + baseResult = r; + } + else + { + baseResult = baseResult + "\n\n" + r; //$NON-NLS-1$ + } + } + + public void addToSecondaryResult(String r) + { + if(secondaryResult == null) + { + secondaryResult = r; + } + else + { + secondaryResult = secondaryResult + "\n\n" + r; //$NON-NLS-1$ + } + } + } +} diff --git a/src/core/org/apache/jmeter/assertions/gui/AbstractAssertionGui.java b/src/core/org/apache/jmeter/assertions/gui/AbstractAssertionGui.java new file mode 100644 index 00000000000..547733e4c67 --- /dev/null +++ b/src/core/org/apache/jmeter/assertions/gui/AbstractAssertionGui.java @@ -0,0 +1,54 @@ +/* + * 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.assertions.gui; + +import java.util.Arrays; +import java.util.Collection; + + +import org.apache.jmeter.gui.AbstractScopedJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage assertions. + * + * Assertions which can be applied to different scopes (parent, children or both) + * need to use the createScopePanel() to add the panel to the GUI, and they also + * need to use saveScopeSettings() and showScopeSettings() to keep the test element + * and GUI in synch. + * + */ +public abstract class AbstractAssertionGui extends AbstractScopedJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#ASSERTIONS}, which is + * appropriate for most assertion components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.ASSERTIONS }); + } +} diff --git a/src/core/org/apache/jmeter/config/Argument.java b/src/core/org/apache/jmeter/config/Argument.java new file mode 100644 index 00000000000..a08fc474cd2 --- /dev/null +++ b/src/core/org/apache/jmeter/config/Argument.java @@ -0,0 +1,210 @@ +/* + * 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.config; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Class representing an argument. Each argument consists of a name/value pair, + * as well as (optional) metadata. + * + */ +public class Argument extends AbstractTestElement implements Serializable { + private static final long serialVersionUID = 240L; + + /** Name used to store the argument's name. */ + public static final String ARG_NAME = "Argument.name"; // $NON-NLS-1$ + + /** Name used to store the argument's value. */ + public static final String VALUE = "Argument.value"; // $NON-NLS-1$ + + /** Name used to store the argument's description. */ + public static final String DESCRIPTION = "Argument.desc"; // $NON-NLS-1$ + + private static final String DFLT_DESCRIPTION = ""; // $NON-NLS-1$ + + /** Name used to store the argument's metadata. */ + public static final String METADATA = "Argument.metadata"; // $NON-NLS-1$ + + /** + * Create a new Argument without a name, value, or metadata. + */ + public Argument() { + this(null, null, null, null); + } + + /** + * Create a new Argument with the specified name and value, and no metadata. + * + * @param name + * the argument name + * @param value + * the argument value + */ + public Argument(String name, String value) { + this(name, value, null, null); + } + + /** + * Create a new Argument with the specified name, value, and metadata. + * + * @param name + * the argument name + * @param value + * the argument value + * @param metadata + * the argument metadata + */ + public Argument(String name, String value, String metadata) { + this(name, value, metadata, null); + } + + /** + * Create a new Argument with the specified name, value, and metadata. + * + * @param name + * the argument name + * @param value + * the argument value + * @param metadata + * the argument metadata + * @param description + * the argument description + */ + public Argument(String name, String value, String metadata, String description) { + if(name != null) { + setProperty(new StringProperty(ARG_NAME, name)); + } + if(value != null) { + setProperty(new StringProperty(VALUE, value)); + } + if(metadata != null) { + setProperty(new StringProperty(METADATA, metadata)); + } + if(description != null) { + setProperty(DESCRIPTION, description, DFLT_DESCRIPTION); + } + } + + /** + * Set the name of the Argument. + * + * @param newName + * the new name + */ + @Override + public void setName(String newName) { + setProperty(new StringProperty(ARG_NAME, newName)); + } + + /** + * Get the name of the Argument. + * + * @return the attribute's name + */ + @Override + public String getName() { + return getPropertyAsString(ARG_NAME); + } + + /** + * Sets the value of the Argument. + * + * @param newValue + * the new value + */ + public void setValue(String newValue) { + setProperty(new StringProperty(VALUE, newValue)); + } + + /** + * Gets the value of the Argument object. + * + * @return the attribute's value + */ + public String getValue() { + return getPropertyAsString(VALUE); + } + + /** + * Sets the Description attribute of the Argument. + * + * @param description + * the new description + */ + public void setDescription(String description) { + setProperty(DESCRIPTION, description, DFLT_DESCRIPTION); + } + + /** + * Gets the Meta Data attribute of the Argument. + * + * @return the MetaData value + */ + public String getDescription() { + return getPropertyAsString(DESCRIPTION, DFLT_DESCRIPTION); + } + + /** + * Sets the Meta Data attribute of the Argument. + * + * @param newMetaData + * the new metadata + */ + public void setMetaData(String newMetaData) { + setProperty(new StringProperty(METADATA, newMetaData)); + } + + /** + * Gets the Meta Data attribute of the Argument. + * + * @return the MetaData value + */ + public String getMetaData() { + return getPropertyAsString(METADATA); + } + + @Override + public String toString() { + return getName() + getMetaData() + getValue(); + } + + /** + * Is this parameter skippable, i.e. empty/blank string + * or it looks like an unrecognised variable. + * + * @param parameterName - parameter name + * @return true if parameter should be skipped + */ + public boolean isSkippable(String parameterName) { + if (JOrphanUtils.isBlank(parameterName)){ + return true; // Skip parameters with a blank name (allows use of optional variables in parameter lists) + } + // TODO: improve this test + if (parameterName.trim().startsWith("${") && parameterName.endsWith("}")){// $NON-NLS-1$ $NON-NLS-2$ + return true; // Missing variable name + } + return false; + } + +} diff --git a/src/core/org/apache/jmeter/config/Arguments.java b/src/core/org/apache/jmeter/config/Arguments.java new file mode 100644 index 00000000000..2b5f8119998 --- /dev/null +++ b/src/core/org/apache/jmeter/config/Arguments.java @@ -0,0 +1,259 @@ +/* + * 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.config; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * A set of Argument objects. + * + */ +public class Arguments extends ConfigTestElement implements Serializable { + private static final long serialVersionUID = 240L; + + /** The name of the property used to store the arguments. */ + public static final String ARGUMENTS = "Arguments.arguments"; //$NON-NLS-1$ + + /** + * Create a new Arguments object with no arguments. + */ + public Arguments() { + setProperty(new CollectionProperty(ARGUMENTS, new ArrayList())); + } + + /** + * Get the arguments. + * + * @return the arguments + */ + public CollectionProperty getArguments() { + return (CollectionProperty) getProperty(ARGUMENTS); + } + + /** + * Clear the arguments. + */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(ARGUMENTS, new ArrayList())); + } + + /** + * Set the list of arguments. Any existing arguments will be lost. + * + * @param arguments + * the new arguments + */ + public void setArguments(List arguments) { + setProperty(new CollectionProperty(ARGUMENTS, arguments)); + } + + /** + * Get the arguments as a Map. Each argument name is used as the key, and + * its value as the value. + * + * @return a new Map with String keys and values containing the arguments + */ + public Map getArgumentsAsMap() { + PropertyIterator iter = getArguments().iterator(); + Map argMap = new LinkedHashMap(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + // Because CollectionProperty.mergeIn will not prevent adding two + // properties of the same name, we need to select the first value so + // that this element's values prevail over defaults provided by + // configuration + // elements: + if (!argMap.containsKey(arg.getName())) { + argMap.put(arg.getName(), arg.getValue()); + } + } + return argMap; + } + + /** + * Add a new argument with the given name and value. + * + * @param name + * the name of the argument + * @param value + * the value of the argument + */ + public void addArgument(String name, String value) { + addArgument(new Argument(name, value, null)); + } + + /** + * Add a new argument. + * + * @param arg + * the new argument + */ + public void addArgument(Argument arg) { + TestElementProperty newArg = new TestElementProperty(arg.getName(), arg); + if (isRunningVersion()) { + this.setTemporary(newArg); + } + getArguments().addItem(newArg); + } + + /** + * Add a new argument with the given name, value, and metadata. + * + * @param name + * the name of the argument + * @param value + * the value of the argument + * @param metadata + * the metadata for the argument + */ + public void addArgument(String name, String value, String metadata) { + addArgument(new Argument(name, value, metadata)); + } + + /** + * Get a PropertyIterator of the arguments. + * + * @return an iteration of the arguments + */ + public PropertyIterator iterator() { + return getArguments().iterator(); + } + + /** + * Create a string representation of the arguments. + * + * @return the string representation of the arguments + */ + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + final String metaData = arg.getMetaData(); + str.append(arg.getName()); + if (metaData == null) { + str.append("="); //$NON-NLS-1$ + } else { + str.append(metaData); + } + str.append(arg.getValue()); + if (iter.hasNext()) { + str.append("&"); //$NON-NLS-1$ + } + } + return str.toString(); + } + + /** + * Remove the specified argument from the list. + * + * @param row + * the index of the argument to remove + */ + public void removeArgument(int row) { + if (row < getArguments().size()) { + getArguments().remove(row); + } + } + + /** + * Remove the specified argument from the list. + * + * @param arg + * the argument to remove + */ + public void removeArgument(Argument arg) { + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + if (arg.equals(item)) { + iter.remove(); + } + } + } + + /** + * Remove the argument with the specified name. + * + * @param argName + * the name of the argument to remove + */ + public void removeArgument(String argName) { + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + if (arg.getName().equals(argName)) { + iter.remove(); + } + } + } + + /** + * Remove all arguments from the list. + */ + public void removeAllArguments() { + getArguments().clear(); + } + + /** + * Add a new empty argument to the list. The new argument will have the + * empty string as its name and value, and null metadata. + */ + public void addEmptyArgument() { + addArgument(new Argument("", "", null)); + } + + /** + * Get the number of arguments in the list. + * + * @return the number of arguments + */ + public int getArgumentCount() { + return getArguments().size(); + } + + /** + * Get a single argument. + * + * @param row + * the index of the argument to return. + * @return the argument at the specified index, or null if no argument + * exists at that index. + */ + public Argument getArgument(int row) { + Argument argument = null; + + if (row < getArguments().size()) { + argument = (Argument) getArguments().get(row).getObjectValue(); + } + + return argument; + } +} diff --git a/src/core/org/apache/jmeter/config/ConfigElement.java b/src/core/org/apache/jmeter/config/ConfigElement.java new file mode 100644 index 00000000000..da494ffba74 --- /dev/null +++ b/src/core/org/apache/jmeter/config/ConfigElement.java @@ -0,0 +1,51 @@ +/* + * 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.config; + +public interface ConfigElement extends Cloneable { + + /** + * Add a configuration element to this one. This allows config elements to + * combine and give a "layered" effect. For example, + * HTTPConfigElements have properties for domain, path, method, and + * parameters. If element A has everything filled in, but null for domain, + * and element B is added, which has only domain filled in, then after + * adding B to A, A will have the domain from B. If A already had a domain, + * then the correct behavior is for A to ignore the addition of element B. + * + * @param config + * the element to be added to this ConfigElement + */ + void addConfigElement(ConfigElement config); + + /** + * If your config element expects to be modified in the process of a test + * run, and you want those modifications to carry over from sample to sample + * (as in a cookie manager - you want to save all cookies that get set + * throughout the test), then return true for this method. Your config + * element will not be cloned for each sample. If your config elements are + * more static in nature, return false. If in doubt, return false. + * + * @return true if the element expects to be modified over the course of a + * test run + */ + boolean expectsModification(); + + Object clone(); +} diff --git a/src/core/org/apache/jmeter/config/ConfigTestElement.java b/src/core/org/apache/jmeter/config/ConfigTestElement.java new file mode 100644 index 00000000000..a8d36a04127 --- /dev/null +++ b/src/core/org/apache/jmeter/config/ConfigTestElement.java @@ -0,0 +1,58 @@ +/* + * 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.config; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; + +public class ConfigTestElement extends AbstractTestElement implements Serializable, ConfigElement { + private static final long serialVersionUID = 240L; + + public static final String USERNAME = "ConfigTestElement.username"; + + public static final String PASSWORD = "ConfigTestElement.password"; + + public ConfigTestElement() { + } + + @Override + public void addTestElement(TestElement parm1) { + if (parm1 instanceof ConfigTestElement) { + mergeIn(parm1); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addConfigElement(ConfigElement config) { + mergeIn((TestElement) config); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean expectsModification() { + return false; + } +} diff --git a/src/core/org/apache/jmeter/config/LoginConfig.java b/src/core/org/apache/jmeter/config/LoginConfig.java new file mode 100644 index 00000000000..b16902868dd --- /dev/null +++ b/src/core/org/apache/jmeter/config/LoginConfig.java @@ -0,0 +1,79 @@ +/* + * 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.config; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.property.StringProperty; + +public class LoginConfig extends ConfigTestElement implements Serializable +// TODO: move this to components -- the only reason why it's in core is because +// it's used as a guinea pig by a couple of tests. +{ + private static final long serialVersionUID = 240L; + + /** + * Constructor for the LoginConfig object. + */ + public LoginConfig() { + } + + /** + * Sets the Username attribute of the LoginConfig object. + * + * @param username + * the new Username value + */ + public void setUsername(String username) { + setProperty(new StringProperty(ConfigTestElement.USERNAME, username)); + } + + /** + * Sets the Password attribute of the LoginConfig object. + * + * @param password + * the new Password value + */ + public void setPassword(String password) { + setProperty(new StringProperty(ConfigTestElement.PASSWORD, password)); + } + + /** + * Gets the Username attribute of the LoginConfig object. + * + * @return the Username value + */ + public String getUsername() { + return getPropertyAsString(ConfigTestElement.USERNAME); + } + + /** + * Gets the Password attribute of the LoginConfig object. + * + * @return the Password value + */ + public String getPassword() { + return getPropertyAsString(ConfigTestElement.PASSWORD); + } + + @Override + public String toString() { + return getUsername() + "=" + getPassword(); //$NON-NLS-1$ + } +} diff --git a/src/core/org/apache/jmeter/config/gui/AbstractConfigGui.java b/src/core/org/apache/jmeter/config/gui/AbstractConfigGui.java new file mode 100644 index 00000000000..0e34203a3f4 --- /dev/null +++ b/src/core/org/apache/jmeter/config/gui/AbstractConfigGui.java @@ -0,0 +1,66 @@ +/* + * 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.config.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which provide configuration + * for some other component. + * + */ +public abstract class AbstractConfigGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most configuration + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + @Override + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultConfigElementMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#CONFIG_ELEMENTS}, which is + * appropriate for most configuration components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.CONFIG_ELEMENTS }); + } +} diff --git a/src/core/org/apache/jmeter/config/gui/ArgumentsPanel.java b/src/core/org/apache/jmeter/config/gui/ArgumentsPanel.java new file mode 100644 index 00000000000..8fe3d4b65fc --- /dev/null +++ b/src/core/org/apache/jmeter/config/gui/ArgumentsPanel.java @@ -0,0 +1,692 @@ +/* + * 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.config.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +/** + * A GUI panel allowing the user to enter name-value argument pairs. These + * arguments (or parameters) are usually used to provide configuration values + * for some other component. + * + */ +public class ArgumentsPanel extends AbstractConfigGui implements ActionListener { + + private static final long serialVersionUID = 240L; + + /** The title label for this component. */ + private JLabel tableLabel; + + /** The table containing the list of arguments. */ + private transient JTable table; + + /** The model for the arguments table. */ + protected transient ObjectTableModel tableModel; // will only contain Argument or HTTPArgument + + /** A button for adding new arguments to the table. */ + private JButton add; + + /** A button for removing arguments from the table. */ + private JButton delete; + + /** + * Added background support for reporting tool + */ + private Color background; + + /** + * Boolean indicating whether this component is a standalone component or it + * is intended to be used as a subpanel for another component. + */ + private final boolean standalone; + + /** Button to move a argument up*/ + private JButton up; + + /** Button to move a argument down*/ + private JButton down; + + private final boolean enableUpDown; + + /** Command for adding a row to the table. */ + private static final String ADD = "add"; // $NON-NLS-1$ + + /** Command for adding rows from the clipboard */ + private static final String ADD_FROM_CLIPBOARD = "addFromClipboard"; // $NON-NLS-1$ + + /** Command for removing a row from the table. */ + private static final String DELETE = "delete"; // $NON-NLS-1$ + + /** Command for moving a row up in the table. */ + private static final String UP = "up"; // $NON-NLS-1$ + + /** Command for moving a row down in the table. */ + private static final String DOWN = "down"; // $NON-NLS-1$ + + /** Command for showing detail. */ + private static final String DETAIL = "detail"; // $NON-NLS-1$ + + public static final String COLUMN_RESOURCE_NAMES_0 = "name"; // $NON-NLS-1$ + + public static final String COLUMN_RESOURCE_NAMES_1 = "value"; // $NON-NLS-1$ + + public static final String COLUMN_RESOURCE_NAMES_2 = "description"; // $NON-NLS-1$ + + /** + * Create a new ArgumentsPanel as a standalone component. + */ + public ArgumentsPanel() { + this(JMeterUtils.getResString("user_defined_variables"),null, true, true);// $NON-NLS-1$ + } + + /** + * Create a new ArgumentsPanel as an embedded component, using the specified + * title. + * + * @param label + * the title for the component. + */ + public ArgumentsPanel(String label) { + this(label, null, true, false); + } + + /** + * Create a new ArgumentsPanel as an embedded component, using the specified + * title. + * + * @param label + * the title for the component. + * @param enableUpDown Add up/down buttons + */ + public ArgumentsPanel(String label, boolean enableUpDown) { + this(label, null, enableUpDown, false); + } + + /** + * Create a new ArgumentsPanel with a border and color background + * @param label text for label + * @param bkg background colour + */ + public ArgumentsPanel(String label, Color bkg) { + this(label, bkg, true, false); + } + + /** + * Create a new ArgumentsPanel with a border and color background + * @param label text for label + * @param bkg background colour + * @param enableUpDown Add up/down buttons + * @param standalone is standalone + */ + public ArgumentsPanel(String label, Color bkg, boolean enableUpDown, boolean standalone) { + this(label, bkg, enableUpDown, standalone, null); + } + + /** + * Create a new ArgumentsPanel with a border and color background + * @param label text for label + * @param bkg background colour + * @param enableUpDown Add up/down buttons + * @param standalone is standalone + * @param model the table model to use + */ + public ArgumentsPanel(String label, Color bkg, boolean enableUpDown, boolean standalone, ObjectTableModel model) { + tableLabel = new JLabel(label); + this.enableUpDown = enableUpDown; + this.background = bkg; + this.standalone = standalone; + this.tableModel = model; + init(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + if (standalone) { + return super.getMenuCategories(); + } + return null; + } + + @Override + public String getLabelResource() { + return "user_defined_variables"; // $NON-NLS-1$ + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + Arguments args = new Arguments(); + modifyTestElement(args); + return args; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement args) { + GuiUtils.stopTableEditing(table); + Arguments arguments = null; + if (args instanceof Arguments) { + arguments = (Arguments) args; + arguments.clear(); + @SuppressWarnings("unchecked") // only contains Argument (or HTTPArgument) + Iterator modelData = (Iterator) tableModel.iterator(); + while (modelData.hasNext()) { + Argument arg = modelData.next(); + if(StringUtils.isEmpty(arg.getName()) && StringUtils.isEmpty(arg.getValue())) { + continue; + } + arg.setMetaData("="); // $NON-NLS-1$ + arguments.addArgument(arg); + } + } + this.configureTestElement(args); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof Arguments) { + tableModel.clearData(); + PropertyIterator iter = ((Arguments) el).iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + tableModel.addRow(arg); + } + } + checkDeleteStatus(); + } + + /** + * Get the table used to enter arguments. + * + * @return the table used to enter arguments + */ + protected JTable getTable() { + return table; + } + + /** + * Get the title label for this component. + * + * @return the title label displayed with the table + */ + protected JLabel getTableLabel() { + return tableLabel; + } + + /** + * Get the button used to delete rows from the table. + * + * @return the button used to delete rows from the table + */ + protected JButton getDeleteButton() { + return delete; + } + + /** + * Get the button used to add rows to the table. + * + * @return the button used to add rows to the table + */ + protected JButton getAddButton() { + return add; + } + + /** + * Enable or disable the delete button depending on whether or not there is + * a row to be deleted. + */ + protected void checkDeleteStatus() { + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } else { + delete.setEnabled(true); + } + + if(enableUpDown && tableModel.getRowCount()>1) { + up.setEnabled(true); + down.setEnabled(true); + } + } + + @Override + public void clearGui(){ + super.clearGui(); + clear(); + } + + /** + * Clear all rows from the table. T.Elanjchezhiyan(chezhiyan@siptech.co.in) + */ + public void clear() { + GuiUtils.stopTableEditing(table); + tableModel.clearData(); + } + + /** + * Invoked when an action occurs. This implementation supports the add and + * delete buttons. + * + * @param e + * the event that has occurred + */ + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(DELETE)) { + deleteArgument(); + } else if (action.equals(ADD)) { + addArgument(); + } else if (action.equals(ADD_FROM_CLIPBOARD)) { + addFromClipboard(); + } else if (action.equals(UP)) { + moveUp(); + } else if (action.equals(DOWN)) { + moveDown(); + } else if (action.equals(DETAIL)) { + showDetail(); + } + } + + /** + * Cancel cell editing if it is being edited + */ + private void cancelEditing() { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + } + + /** + * Move a row down + */ + private void moveDown() { + cancelEditing(); + + int[] rowsSelected = table.getSelectedRows(); + if (rowsSelected.length > 0 && rowsSelected[rowsSelected.length - 1] < table.getRowCount() - 1) { + table.clearSelection(); + for (int i = rowsSelected.length - 1; i >= 0; i--) { + int rowSelected = rowsSelected[i]; + tableModel.moveRow(rowSelected, rowSelected + 1, rowSelected + 1); + } + for (int rowSelected : rowsSelected) { + table.addRowSelectionInterval(rowSelected + 1, rowSelected + 1); + } + } + } + + /** + * Move a row down + */ + private void moveUp() { + cancelEditing(); + + int[] rowsSelected = table.getSelectedRows(); + if (rowsSelected.length > 0 && rowsSelected[0] > 0) { + table.clearSelection(); + for (int rowSelected : rowsSelected) { + tableModel.moveRow(rowSelected, rowSelected + 1, rowSelected - 1); + } + for (int rowSelected : rowsSelected) { + table.addRowSelectionInterval(rowSelected - 1, rowSelected - 1); + } + } + } + + /** + * Show Row Detail + */ + private void showDetail() { + cancelEditing(); + + int[] rowsSelected = table.getSelectedRows(); + if (rowsSelected.length == 1) { + table.clearSelection(); + RowDetailDialog detailDialog = new RowDetailDialog(tableModel, rowsSelected[0]); + detailDialog.setVisible(true); + } + } + + /** + * Remove the currently selected argument from the table. + */ + protected void deleteArgument() { + cancelEditing(); + + int[] rowsSelected = table.getSelectedRows(); + int anchorSelection = table.getSelectionModel().getAnchorSelectionIndex(); + table.clearSelection(); + if (rowsSelected.length > 0) { + for (int i = rowsSelected.length - 1; i >= 0; i--) { + tableModel.removeRow(rowsSelected[i]); + } + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + else if (tableModel.getRowCount() > 0) { + if (anchorSelection >= tableModel.getRowCount()) { + anchorSelection = tableModel.getRowCount() - 1; + } + table.setRowSelectionInterval(anchorSelection, anchorSelection); + } + + if(enableUpDown && tableModel.getRowCount()>1) { + up.setEnabled(true); + down.setEnabled(true); + } + } + } + + /** + * Add a new argument row to the table. + */ + protected void addArgument() { + // If a table cell is being edited, we should accept the current value + // and stop the editing before adding a new row. + GuiUtils.stopTableEditing(table); + + tableModel.addRow(makeNewArgument()); + + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + if(enableUpDown && tableModel.getRowCount()>1) { + up.setEnabled(true); + down.setEnabled(true); + } + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + + /** + * Add values from the clipboard + */ + protected void addFromClipboard() { + GuiUtils.stopTableEditing(table); + int rowCount = table.getRowCount(); + try { + String clipboardContent = GuiUtils.getPastedText(); + if(clipboardContent == null) { + return; + } + String[] clipboardLines = clipboardContent.split("\n"); + for (String clipboardLine : clipboardLines) { + String[] clipboardCols = clipboardLine.split("\t"); + if (clipboardCols.length > 0) { + Argument argument = makeNewArgument(); + argument.setName(clipboardCols[0]); + if (clipboardCols.length > 1) { + argument.setValue(clipboardCols[1]); + if (clipboardCols.length > 2) { + argument.setDescription(clipboardCols[2]); + } + } + tableModel.addRow(argument); + } + } + if (table.getRowCount() > rowCount) { + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + + // Highlight (select) the appropriate rows. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowCount, rowToSelect); + } + } catch (IOException ioe) { + JOptionPane.showMessageDialog(this, + "Could not add read arguments from clipboard:\n" + ioe.getLocalizedMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } catch (UnsupportedFlavorException ufe) { + JOptionPane.showMessageDialog(this, + "Could not add retrieve " + DataFlavor.stringFlavor.getHumanPresentableName() + + " from clipboard" + ufe.getLocalizedMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + + /** + * Create a new Argument object. + * + * @return a new Argument object + */ + protected Argument makeNewArgument() { + return new Argument("", ""); // $NON-NLS-1$ // $NON-NLS-2$ + } + + /** + * Stop any editing that is currently being done on the table. This will + * save any changes that have already been made. + * Needed for subclasses + */ + protected void stopTableEditing() { + GuiUtils.stopTableEditing(table); + } + + /** + * Initialize the table model used for the arguments table. + */ + protected void initializeTableModel() { + if (tableModel == null) { + if(standalone) { + tableModel = new ObjectTableModel(new String[] { COLUMN_RESOURCE_NAMES_0, COLUMN_RESOURCE_NAMES_1, COLUMN_RESOURCE_NAMES_2 }, + Argument.class, + new Functor[] { + new Functor("getName"), // $NON-NLS-1$ + new Functor("getValue"), // $NON-NLS-1$ + new Functor("getDescription") }, // $NON-NLS-1$ + new Functor[] { + new Functor("setName"), // $NON-NLS-1$ + new Functor("setValue"), // $NON-NLS-1$ + new Functor("setDescription") }, // $NON-NLS-1$ + new Class[] { String.class, String.class, String.class }); + } else { + tableModel = new ObjectTableModel(new String[] { COLUMN_RESOURCE_NAMES_0, COLUMN_RESOURCE_NAMES_1 }, + Argument.class, + new Functor[] { + new Functor("getName"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + new Functor("setName"), // $NON-NLS-1$ + new Functor("setValue") }, // $NON-NLS-1$ + new Class[] { String.class, String.class }); + } + } + } + + public static boolean testFunctors(){ + ArgumentsPanel instance = new ArgumentsPanel(); + instance.initializeTableModel(); + return instance.tableModel.checkFunctors(null,instance.getClass()); + } + + /** + * Resize the table columns to appropriate widths. + * + * @param _table + * the table to resize columns for + */ + protected void sizeColumns(JTable _table) { + } + + /** + * Create the main GUI panel which contains the argument table. + * + * @return the main GUI panel + */ + private Component makeMainPanel() { + initializeTableModel(); + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + if (this.background != null) { + table.setBackground(this.background); + } + return makeScrollPane(table); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + protected Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + labelPanel.add(tableLabel); + if (this.background != null) { + labelPanel.setBackground(this.background); + } + return labelPanel; + } + + /** + * Create a panel containing the add and delete buttons. + * + * @return a GUI panel containing the buttons + */ + private JPanel makeButtonPanel() { + JButton showDetail = new JButton(JMeterUtils.getResString("detail")); // $NON-NLS-1$ + showDetail.setActionCommand(DETAIL); + showDetail.setEnabled(true); + + add = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + add.setActionCommand(ADD); + add.setEnabled(true); + /** A button for adding new arguments to the table from the clipboard. */ + JButton addFromClipboard = new JButton(JMeterUtils.getResString("add_from_clipboard")); // $NON-NLS-1$ + addFromClipboard.setActionCommand(ADD_FROM_CLIPBOARD); + addFromClipboard.setEnabled(true); + + delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + delete.setActionCommand(DELETE); + + if(enableUpDown) { + up = new JButton(JMeterUtils.getResString("up")); // $NON-NLS-1$ + up.setActionCommand(UP); + + down = new JButton(JMeterUtils.getResString("down")); // $NON-NLS-1$ + down.setActionCommand(DOWN); + } + checkDeleteStatus(); + + JPanel buttonPanel = new JPanel(); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + if (this.background != null) { + buttonPanel.setBackground(this.background); + } + showDetail.addActionListener(this); + add.addActionListener(this); + addFromClipboard.addActionListener(this); + delete.addActionListener(this); + buttonPanel.add(showDetail); + buttonPanel.add(add); + buttonPanel.add(addFromClipboard); + buttonPanel.add(delete); + if(enableUpDown) { + up.addActionListener(this); + down.addActionListener(this); + buttonPanel.add(up); + buttonPanel.add(down); + } + return buttonPanel; + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + JPanel p = this; + + if (standalone) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + p = new JPanel(); + } + + p.setLayout(new BorderLayout()); + + p.add(makeLabelPanel(), BorderLayout.NORTH); + p.add(makeMainPanel(), BorderLayout.CENTER); + // Force a minimum table height of 70 pixels + p.add(Box.createVerticalStrut(70), BorderLayout.WEST); + p.add(makeButtonPanel(), BorderLayout.SOUTH); + + if (standalone) { + add(p, BorderLayout.CENTER); + } + + table.revalidate(); + sizeColumns(table); + } +} diff --git a/src/core/org/apache/jmeter/config/gui/LoginConfigGui.java b/src/core/org/apache/jmeter/config/gui/LoginConfigGui.java new file mode 100644 index 00000000000..6ee30ef844c --- /dev/null +++ b/src/core/org/apache/jmeter/config/gui/LoginConfigGui.java @@ -0,0 +1,170 @@ +/* + * 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.config.gui; + +import java.awt.BorderLayout; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; + +/** + * A GUI component allowing the user to enter a username and password for a + * login. + * + */ +public class LoginConfigGui extends AbstractConfigGui { + private static final long serialVersionUID = 240L; + + /** Field allowing the user to enter a username. */ + private final JTextField username = new JTextField(15); + + /** Field allowing the user to enter a password. */ + private final JPasswordField password = new JPasswordField(15); + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** + * Create a new LoginConfigGui as a standalone component. + */ + public LoginConfigGui() { + this(true); + } + + /** + * Create a new LoginConfigGui as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public LoginConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + @Override + public String getLabelResource() { + return "login_config_element"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + username.setText(element.getPropertyAsString(ConfigTestElement.USERNAME)); + password.setText(element.getPropertyAsString(ConfigTestElement.PASSWORD)); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + modifyTestElement(element); + return element; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement element) { + configureTestElement(element); + element.setProperty(new StringProperty(ConfigTestElement.USERNAME, username.getText())); + + String passwordString = new String(password.getPassword()); + element.setProperty(new StringProperty(ConfigTestElement.PASSWORD, passwordString)); + } + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + username.setText(""); //$NON-NLS-1$ + password.setText(""); //$NON-NLS-1$ + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + VerticalPanel mainPanel = new VerticalPanel(); + mainPanel.add(createUsernamePanel()); + mainPanel.add(createPasswordPanel()); + add(mainPanel, BorderLayout.CENTER); + } + + /** + * Create a panel containing the username field and corresponding label. + * + * @return a GUI panel containing the username field + */ + private JPanel createUsernamePanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("username")); // $NON-NLS-1$ + label.setLabelFor(username); + panel.add(label, BorderLayout.WEST); + panel.add(username, BorderLayout.CENTER); + return panel; + } + + /** + * Create a panel containing the password field and corresponding label. + * + * @return a GUI panel containing the password field + */ + private JPanel createPasswordPanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("password")); // $NON-NLS-1$ + label.setLabelFor(password); + panel.add(label, BorderLayout.WEST); + panel.add(password, BorderLayout.CENTER); + return panel; + } +} diff --git a/src/core/org/apache/jmeter/config/gui/ObsoleteGui.java b/src/core/org/apache/jmeter/config/gui/ObsoleteGui.java new file mode 100644 index 00000000000..69b46cd4387 --- /dev/null +++ b/src/core/org/apache/jmeter/config/gui/ObsoleteGui.java @@ -0,0 +1,77 @@ +/* + * 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.config.gui; + +import java.awt.BorderLayout; +import java.util.Collection; + +import javax.swing.JLabel; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Default config gui for Configuration Element. + */ +public class ObsoleteGui extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + private final JLabel obsoleteMessage = + new JLabel(JMeterUtils.getResString("obsolete_test_element")); // $NON-NLS-1$ + + public ObsoleteGui(){ + init(); + } + + private void init() { + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + //add(makeTitlePanel(), BorderLayout.NORTH); + add(obsoleteMessage,BorderLayout.WEST); + } + + @Override + public String getLabelResource() { + return "obsolete_test_element"; // $NON-NLS-1$ + } + + @Override + public TestElement createTestElement() { + return new ConfigTestElement(); + } + + @Override + public void modifyTestElement(TestElement element) { + } + + @Override + public JPopupMenu createPopupMenu() { + return null; + } + + @Override + public Collection getMenuCategories() { + return null; + } + +} diff --git a/src/core/org/apache/jmeter/config/gui/RowDetailDialog.java b/src/core/org/apache/jmeter/config/gui/RowDetailDialog.java new file mode 100644 index 00000000000..eedd9a5abe6 --- /dev/null +++ b/src/core/org/apache/jmeter/config/gui/RowDetailDialog.java @@ -0,0 +1,265 @@ +/* + * 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.config.gui; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.gui.ObjectTableModel; + +/** + * Show detail of a Row + */ +public class RowDetailDialog extends JDialog implements ActionListener, DocumentListener { + + private static final long serialVersionUID = 6578889215615435475L; + + /** Command for moving a row up in the table. */ + private static final String NEXT = "next"; // $NON-NLS-1$ + + /** Command for moving a row down in the table. */ + private static final String PREVIOUS = "previous"; // $NON-NLS-1$ + + /** Command for CANCEL. */ + private static final String CLOSE = "close"; // $NON-NLS-1$ + + private static final String UPDATE = "update"; // $NON-NLS-1$ + + private JLabel nameLabel; + + private JTextField nameTF; + + private JLabel valueLabel; + + private JSyntaxTextArea valueTA; + + private JButton nextButton; + + private JButton previousButton; + + private JButton closeButton; + + private ObjectTableModel tableModel; + + private int selectedRow; + + private boolean textChanged = true; // change to false after the first insert + + + public RowDetailDialog() { + super(); + } + + public RowDetailDialog(ObjectTableModel tableModel, int selectedRow) { + super((JFrame) null, JMeterUtils.getResString("detail"), true); //$NON-NLS-1$ + this.tableModel = tableModel; + this.selectedRow = selectedRow; + init(); + } + + @Override + protected JRootPane createRootPane() { + JRootPane rootPane = new JRootPane(); + // Hide Window on ESC + Action escapeAction = new AbstractAction("ESCAPE") { + + private static final long serialVersionUID = -8699034338969407625L; + + @Override + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }; + // Do update on Enter + Action enterAction = new AbstractAction("ENTER") { + + private static final long serialVersionUID = -1529005452976176873L; + + @Override + public void actionPerformed(ActionEvent actionEvent) { + doUpdate(actionEvent); + setVisible(false); + } + }; + ActionMap actionMap = rootPane.getActionMap(); + actionMap.put(escapeAction.getValue(Action.NAME), escapeAction); + actionMap.put(enterAction.getValue(Action.NAME), enterAction); + InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + inputMap.put(KeyStrokes.ESC, escapeAction.getValue(Action.NAME)); + inputMap.put(KeyStrokes.ENTER, enterAction.getValue(Action.NAME)); + return rootPane; + } + + private void init() { + this.getContentPane().setLayout(new BorderLayout(10,10)); + + nameLabel = new JLabel(JMeterUtils.getResString("name")); //$NON-NLS-1$ + nameTF = new JTextField(JMeterUtils.getResString("name"), 20); //$NON-NLS-1$ + nameTF.getDocument().addDocumentListener(this); + JPanel namePane = new JPanel(new BorderLayout()); + namePane.add(nameLabel, BorderLayout.WEST); + namePane.add(nameTF, BorderLayout.CENTER); + + valueLabel = new JLabel(JMeterUtils.getResString("value")); //$NON-NLS-1$ + valueTA = new JSyntaxTextArea(30, 80); + valueTA.getDocument().addDocumentListener(this); + setValues(selectedRow); + JPanel valuePane = new JPanel(new BorderLayout()); + valuePane.add(valueLabel, BorderLayout.NORTH); + JTextScrollPane jTextScrollPane = new JTextScrollPane(valueTA); + valuePane.add(jTextScrollPane, BorderLayout.CENTER); + + JPanel detailPanel = new JPanel(new BorderLayout()); + detailPanel.add(namePane, BorderLayout.NORTH); + detailPanel.add(valuePane, BorderLayout.CENTER); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(7, 3, 3, 3)); + mainPanel.add(detailPanel, BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + + JButton updateButton = new JButton(JMeterUtils.getResString("update")); //$NON-NLS-1$ + updateButton.setActionCommand(UPDATE); + updateButton.addActionListener(this); + closeButton = new JButton(JMeterUtils.getResString("close")); //$NON-NLS-1$ + closeButton.setActionCommand(CLOSE); + closeButton.addActionListener(this); + nextButton = new JButton(JMeterUtils.getResString("next")); //$NON-NLS-1$ + nextButton.setActionCommand(NEXT); + nextButton.addActionListener(this); + nextButton.setEnabled(selectedRow < tableModel.getRowCount()-1); + previousButton = new JButton(JMeterUtils.getResString("previous")); //$NON-NLS-1$ + previousButton.setActionCommand(PREVIOUS); + previousButton.addActionListener(this); + previousButton.setEnabled(selectedRow > 0); + + buttonsPanel.add(updateButton); + buttonsPanel.add(previousButton); + buttonsPanel.add(nextButton); + buttonsPanel.add(closeButton); + mainPanel.add(buttonsPanel, BorderLayout.SOUTH); + this.getContentPane().add(mainPanel); + nameTF.requestFocusInWindow(); + + this.pack(); + ComponentUtil.centerComponentInWindow(this); + } + + /** + * Do search + * @param e {@link ActionEvent} + */ + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(CLOSE)) { + this.setVisible(false); + } + else if (action.equals(NEXT)) { + selectedRow++; + previousButton.setEnabled(true); + nextButton.setEnabled(selectedRow < tableModel.getRowCount()-1); + setValues(selectedRow); + } + else if (action.equals(PREVIOUS)) { + selectedRow--; + nextButton.setEnabled(true); + previousButton.setEnabled(selectedRow > 0); + setValues(selectedRow); + } + else if (action.equals(UPDATE)) { + doUpdate(e); + } + } + + /** + * Set TextField and TA values from model + * @param selectedRow Selected row + */ + private void setValues(int selectedRow) { + nameTF.setText((String)tableModel.getValueAt(selectedRow, 0)); + valueTA.setInitialText((String)tableModel.getValueAt(selectedRow, 1)); + valueTA.setCaretPosition(0); + textChanged = false; + } + + /** + * Update model values + * @param actionEvent the event that led to this call + */ + protected void doUpdate(ActionEvent actionEvent) { + tableModel.setValueAt(nameTF.getText(), selectedRow, 0); + tableModel.setValueAt(valueTA.getText(), selectedRow, 1); + // Change Cancel label to Close + closeButton.setText(JMeterUtils.getResString("close")); //$NON-NLS-1$ + textChanged = false; + } + + /** + * Change the label of Close button to Cancel (after the first text changes) + */ + private void changeLabelButton() { + if (!textChanged) { + closeButton.setText(JMeterUtils.getResString("cancel")); //$NON-NLS-1$ + textChanged = true; + } + } + + @Override + public void insertUpdate(DocumentEvent e) { + changeLabelButton(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + changeLabelButton(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + changeLabelButton(); + } + +} diff --git a/src/core/org/apache/jmeter/config/gui/SimpleConfigGui.java b/src/core/org/apache/jmeter/config/gui/SimpleConfigGui.java new file mode 100644 index 00000000000..af56c7e0aa6 --- /dev/null +++ b/src/core/org/apache/jmeter/config/gui/SimpleConfigGui.java @@ -0,0 +1,311 @@ +/* + * 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.config.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.Data; +import org.apache.jorphan.gui.GuiUtils; + +/** + * Default config gui for Configuration Element. + */ +public class SimpleConfigGui extends AbstractConfigGui implements ActionListener { + /* This class created for enhancement Bug ID 9101. */ + + private static final long serialVersionUID = 240L; + + // TODO: This class looks a lot like ArgumentsPanel. What exactly is the + // difference? Could they be combined? + // Note: it seems that this class is not actually used ... + + /** The table of configuration parameters. */ + private JTable table; + + /** The model for the parameter table. */ + private PowerTableModel tableModel; + + /** A button for removing parameters from the table. */ + private JButton delete; + + /** Command for adding a row to the table. */ + private static final String ADD = "add"; + + /** Command for removing a row from the table. */ + private static final String DELETE = "delete"; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private final boolean displayName; + + /** The resource names of the columns in the table. */ + private static final String COLUMN_NAMES_0 = "name"; // $NON-NLS-1$ + + private static final String COLUMN_NAMES_1 = "value"; // $NON-NLS-1$ + + /** + * Create a new standalone SimpleConfigGui. + */ + public SimpleConfigGui() { + this(true); + } + + /** + * Create a new SimpleConfigGui as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public SimpleConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + @Override + public String getLabelResource() { + return "simple_config_element"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + *

+ * This implementation retrieves all key/value pairs from the TestElement + * object and sets these values in the GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + tableModel.clearData(); + PropertyIterator iter = el.propertyIterator(); + while (iter.hasNext()) { + JMeterProperty prop = iter.next(); + tableModel.addRow(new Object[] { prop.getName(), prop.getStringValue() }); + } + checkDeleteStatus(); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + TestElement el = new ConfigTestElement(); + modifyTestElement(el); + return el; + } + + /** + * Get all of the values from the GUI component and set them in the + * TestElement. + * + * @param el + * the TestElement to modify + */ + @Override + public void modifyTestElement(TestElement el) { + GuiUtils.stopTableEditing(table); + Data model = tableModel.getData(); + model.reset(); + while (model.next()) { + el.setProperty(new StringProperty((String) model.getColumnValue(COLUMN_NAMES_0), (String) model + .getColumnValue(COLUMN_NAMES_1))); + } + super.configureTestElement(el); + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(0, 10)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + add(createTablePanel(), BorderLayout.CENTER); + // Force the table to be at least 70 pixels high + add(Box.createVerticalStrut(70), BorderLayout.WEST); + add(createButtonPanel(), BorderLayout.SOUTH); + } + + /** + * Invoked when an action occurs. This implementation supports the add and + * delete buttons. + * + * @param e + * the event that has occurred + */ + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(DELETE)) { + deleteArgument(); + } else if (action.equals(ADD)) { + addArgument(); + } + } + + /** + * Create a GUI panel containing the table of configuration parameters. + * + * @return a GUI panel containing the parameter table + */ + private Component createTablePanel() { + tableModel = new PowerTableModel( + new String[] { COLUMN_NAMES_0, COLUMN_NAMES_1 }, + new Class[] { String.class, String.class }); + + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + return makeScrollPane(table); + } + + /** + * Create a panel containing the add and delete buttons. + * + * @return a GUI panel containing the buttons + */ + private JPanel createButtonPanel() { + /** A button for adding new parameters to the table. */ + JButton add = new JButton(JMeterUtils.getResString("add")); //$NON-NLS-1$ + add.setActionCommand(ADD); + add.addActionListener(this); + add.setEnabled(true); + + delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + delete.setActionCommand(DELETE); + delete.addActionListener(this); + + checkDeleteStatus(); + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(add); + buttonPanel.add(delete); + return buttonPanel; + } + + /** + * Enable or disable the delete button depending on whether or not there is + * a row to be deleted. + */ + protected void checkDeleteStatus() { + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } else { + delete.setEnabled(true); + } + } + + /** + * Add a new argument row to the table. + */ + protected void addArgument() { + // If a table cell is being edited, we should accept the current value + // and stop the editing before adding a new row. + GuiUtils.stopTableEditing(table); + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + + /** + * Stop any editing that is currently being done on the table. This will + * save any changes that have already been made. + */ + protected void stopTableEditing() { + GuiUtils.stopTableEditing(table); + } + + /** + * Remove the currently selected argument from the table. + */ + protected void deleteArgument() { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = table.getSelectedRow(); + + if (rowSelected >= 0) { + + // removeProperty(tableModel.getValueAt ( + // table.getSelectedRow(),0).toString()); + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } else { + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } +} diff --git a/src/core/org/apache/jmeter/control/Controller.java b/src/core/org/apache/jmeter/control/Controller.java new file mode 100644 index 00000000000..e46104aadad --- /dev/null +++ b/src/core/org/apache/jmeter/control/Controller.java @@ -0,0 +1,71 @@ +/* + * 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.control; + +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; + +/** + * This interface is used by JMeterThread in the following manner: + *

+ * while (running && (sampler = controller.next()) != null) + */ +public interface Controller extends TestElement { + /** + * Delivers the next Sampler or null + * + * @return org.apache.jmeter.samplers.Sampler or null + */ + Sampler next(); + + /** + * Indicates whether the Controller is done delivering Samplers for the rest + * of the test. + * + * When the top-level controller returns true to JMeterThread, + * the thread is complete. + * + * @return boolean + */ + boolean isDone(); + + /** + * Controllers have to notify listeners of when they begin an iteration + * through their sub-elements. + * @param listener The {@link LoopIterationListener} to add + */ + void addIterationListener(LoopIterationListener listener); + + /** + * Called to initialize a controller at the beginning of a test iteration. + */ + void initialize(); + + /** + * Unregister IterationListener + * @param iterationListener {@link LoopIterationListener} + */ + void removeIterationListener(LoopIterationListener iterationListener); + + /** + * Trigger end of loop condition on controller (used by Start Next Loop feature) + */ + void triggerEndOfLoop(); +} diff --git a/src/core/org/apache/jmeter/control/GenericController.java b/src/core/org/apache/jmeter/control/GenericController.java new file mode 100644 index 00000000000..5d1e73f23d5 --- /dev/null +++ b/src/core/org/apache/jmeter/control/GenericController.java @@ -0,0 +1,435 @@ +/* + * 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.control; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.TestCompiler; +import org.apache.jmeter.threads.TestCompilerHelper; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + *

+ * This class is the basis for all the controllers. + * It also implements SimpleController. + *

+ *

+ * The main entry point is next(), which is called by by JMeterThread as follows: + *

+ *

+ * while (running && (sampler = controller.next()) != null) + *

+ */ +public class GenericController extends AbstractTestElement implements Controller, Serializable, TestCompilerHelper { + + private static final long serialVersionUID = 234L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private transient LinkedList iterationListeners = + new LinkedList(); + + // Only create the map if it is required + private transient ConcurrentMap children = + TestCompiler.IS_USE_STATIC_SET ? null : new ConcurrentHashMap(); + + private static final Object DUMMY = new Object(); + + // May be replaced by RandomOrderController + protected transient List subControllersAndSamplers = + new ArrayList(); + + /** + * Index of current sub controller or sampler + */ + protected transient int current; + + /** + * TODO document this + */ + private transient int iterCount; + + /** + * Controller has ended + */ + private transient boolean done; + + /** + * First sampler or sub-controller + */ + private transient boolean first; + + /** + * Creates a Generic Controller + */ + public GenericController() { + } + + @Override + public void initialize() { + resetCurrent(); + resetIterCount(); + done = false; // TODO should this use setDone()? + first = true; // TODO should this use setFirst()? + initializeSubControllers(); + } + + /** + * (re)Initializes sub controllers + * See Bug 50032 + */ + protected void initializeSubControllers() { + for (TestElement te : subControllersAndSamplers) { + if(te instanceof GenericController) { + ((Controller) te).initialize(); + } + } + } + + /** + * Resets the controller (called after execution of last child of controller): + *
    + *
  • resetCurrent() (i.e. current=0)
  • + *
  • increment iteration count
  • + *
  • sets first=true
  • + *
  • recoverRunningVersion() to set the controller back to the initial state
  • + *
+ * + */ + protected void reInitialize() { + resetCurrent(); + incrementIterCount(); + setFirst(true); + recoverRunningVersion(); + } + + /** + *

+ * Determines the next sampler to be processed. + *

+ * + *

+ * If {@link #isDone()} is true, returns null. + *

+ * + *

+ * Gets the list element using current pointer. + * If this is null, calls {@link #nextIsNull()}. + *

+ * + *

+ * If the list element is a {@link Sampler}, calls {@link #nextIsASampler(Sampler)}, + * otherwise calls {@link #nextIsAController(Controller)} + *

+ * + *

+ * If any of the called methods throws {@link NextIsNullException}, returns null, + * otherwise the value obtained above is returned. + *

+ * + * @return the next sampler or null + */ + @Override + public Sampler next() { + fireIterEvents(); + if (log.isDebugEnabled()) { + log.debug("Calling next on: " + this.getClass().getName()); + } + if (isDone()) { + return null; + } + Sampler returnValue = null; + try { + TestElement currentElement = getCurrentElement(); + setCurrentElement(currentElement); + if (currentElement == null) { + // incrementCurrent(); + returnValue = nextIsNull(); + } else { + if (currentElement instanceof Sampler) { + returnValue = nextIsASampler((Sampler) currentElement); + } else { // must be a controller + returnValue = nextIsAController((Controller) currentElement); + } + } + } catch (NextIsNullException e) { + // NOOP + } + return returnValue; + } + + /** + * @see org.apache.jmeter.control.Controller#isDone() + */ + @Override + public boolean isDone() { + return done; + } + + protected void setDone(boolean done) { + this.done = done; + } + + /** + * @return true if it's the controller is returning the first of its children + */ + protected boolean isFirst() { + return first; + } + + /** + * If b is true, it means first is reset which means Controller has executed all its children + * @param b The flag, whether first is reseted + */ + public void setFirst(boolean b) { + first = b; + } + + /** + * Called by {@link #next()} if the element is a Controller, and returns the + * next sampler from the controller. If this is null, then + * updates the current pointer and makes recursive call to {@link #next()}. + * + * @param controller the current next element + * @return the next sampler + * @throws NextIsNullException when the end of the list has already been reached + */ + protected Sampler nextIsAController(Controller controller) throws NextIsNullException { + Sampler sampler = controller.next(); + if (sampler == null) { + currentReturnedNull(controller); + sampler = next(); + } + return sampler; + } + + /** + * Increment the current pointer and return the element. Called by + * {@link #next()} if the element is a sampler. (May be overriden by + * sub-classes). + * + * @param element + * the current next element + * @return input element + * @throws NextIsNullException when the end of the list has already been reached + */ + protected Sampler nextIsASampler(Sampler element) throws NextIsNullException { + incrementCurrent(); + return element; + } + + /** + * Called by {@link #next()} when {@link #getCurrentElement()} returns null. + * Reinitialises the controller. + * + * @return null (always, for this class) + * @throws NextIsNullException when the end of the list has already been reached + */ + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + reInitialize(); + } + + /** + * Called to re-initialize a index of controller's elements (Bug 50032) + * @deprecated replaced by GeneriController#initializeSubControllers + */ + protected void reInitializeSubController() { + initializeSubControllers(); + } + + /** + * If the controller is done, remove it from the list, + * otherwise increment to next entry in list. + * + * @param c controller + */ + protected void currentReturnedNull(Controller c) { + if (c.isDone()) { + removeCurrentElement(); + } else { + incrementCurrent(); + } + } + + /** + * Gets the SubControllers attribute of the GenericController object + * + * @return the SubControllers value + */ + protected List getSubControllers() { + return subControllersAndSamplers; + } + + private void addElement(TestElement child) { + subControllersAndSamplers.add(child); + } + + /** + * Empty implementation - does nothing. + * + * @param currentElement + * the current element + * @throws NextIsNullException + * when the list has been completed already + */ + protected void setCurrentElement(TestElement currentElement) throws NextIsNullException { + } + + /** + *

+ * Gets the element indicated by the current index, if one exists, + * from the subControllersAndSamplers list. + *

+ *

+ * If the subControllersAndSamplers list is empty, + * then set done = true, and throw NextIsNullException. + *

+ * @return the current element - or null if current index too large + * @throws NextIsNullException if list is empty + */ + protected TestElement getCurrentElement() throws NextIsNullException { + if (current < subControllersAndSamplers.size()) { + return subControllersAndSamplers.get(current); + } + if (subControllersAndSamplers.size() == 0) { + setDone(true); + throw new NextIsNullException(); + } + return null; + } + + protected void removeCurrentElement() { + subControllersAndSamplers.remove(current); + } + + /** + * Increments the current pointer; called by currentReturnedNull to move the + * controller on to its next child. + */ + protected void incrementCurrent() { + current++; + } + + protected void resetCurrent() { + current = 0; + } + + @Override + public void addTestElement(TestElement child) { + if (child instanceof Controller || child instanceof Sampler) { + addElement(child); + } + } + + /** + * {@inheritDoc} + */ + @Override + public final boolean addTestElementOnce(TestElement child){ + if (children.putIfAbsent(child, DUMMY) == null) { + addTestElement(child); + return true; + } + return false; + } + + @Override + public void addIterationListener(LoopIterationListener lis) { + /* + * A little hack - add each listener to the start of the list - this + * ensures that the thread running the show is the first listener and + * can modify certain values before other listeners are called. + */ + iterationListeners.addFirst(lis); + } + + /** + * Remove listener + */ + @Override + public void removeIterationListener(LoopIterationListener iterationListener) { + for (Iterator iterator = iterationListeners.iterator(); iterator.hasNext();) { + LoopIterationListener listener = iterator.next(); + if(listener == iterationListener) + { + iterator.remove(); + break; // can only match once + } + } + } + + protected void fireIterEvents() { + if (isFirst()) { + fireIterationStart(); + first = false; // TODO - should this use setFirst() ? + } + } + + protected void fireIterationStart() { + LoopIterationEvent event = new LoopIterationEvent(this, getIterCount()); + for (LoopIterationListener item : iterationListeners) { + item.iterationStart(event); + } + } + + protected int getIterCount() { + return iterCount; + } + + protected void incrementIterCount() { + iterCount++; + } + + protected void resetIterCount() { + iterCount = 0; + } + + protected Object readResolve(){ + iterationListeners = + new LinkedList(); + children = + TestCompiler.IS_USE_STATIC_SET ? null : new ConcurrentHashMap(); + + subControllersAndSamplers = + new ArrayList(); + + return this; + } +} diff --git a/src/core/org/apache/jmeter/control/IfController.java b/src/core/org/apache/jmeter/control/IfController.java new file mode 100644 index 00000000000..2a755656b4d --- /dev/null +++ b/src/core/org/apache/jmeter/control/IfController.java @@ -0,0 +1,212 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** + * + * + * This is a Conditional Controller; it will execute the set of statements + * (samplers/controllers, etc) while the 'condition' is true. + *

+ * In a programming world - this is equivalent of : + *

+ * if (condition) {
+ *          statements ....
+ *          }
+ * 
+ * In JMeter you may have : + *
 
+ * Thread-Group (set to loop a number of times or indefinitely,
+ *    ... Samplers ... (e.g. Counter )
+ *    ... Other Controllers ....
+ *    ... IfController ( condition set to something like - ${counter} < 10)
+ *       ... statements to perform if condition is true
+ *       ...
+ *    ... Other Controllers /Samplers }
+ * 
+ */ + +// for unit test code @see TestIfController + +public class IfController extends GenericController implements Serializable { + + private static final Logger logger = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final String CONDITION = "IfController.condition"; //$NON-NLS-1$ + + private static final String EVALUATE_ALL = "IfController.evaluateAll"; //$NON-NLS-1$ + + private static final String USE_EXPRESSION = "IfController.useExpression"; //$NON-NLS-1$ + + /** + * constructor + */ + public IfController() { + super(); + } + + /** + * constructor + * @param condition The condition for this controller + */ + public IfController(String condition) { + super(); + this.setCondition(condition); + } + + /** + * Condition Accessor - this is gonna be like ${count} < 10 + * @param condition The condition for this controller + */ + public void setCondition(String condition) { + setProperty(new StringProperty(CONDITION, condition)); + } + + /** + * Condition Accessor - this is gonna be like ${count} < 10 + * @return the condition associated with this controller + */ + public String getCondition() { + return getPropertyAsString(CONDITION); + } + + /** + * evaluate the condition clause log error if bad condition + */ + private boolean evaluateCondition(String cond) { + logger.debug(" getCondition() : [" + cond + "]"); + + String resultStr = ""; + boolean result = false; + + // now evaluate the condition using JavaScript + Context cx = Context.enter(); + try { + Scriptable scope = cx.initStandardObjects(null); + Object cxResultObject = cx.evaluateString(scope, cond + /** * conditionString ** */ + , "", 1, null); + resultStr = Context.toString(cxResultObject); + + if (resultStr.equals("false")) { //$NON-NLS-1$ + result = false; + } else if (resultStr.equals("true")) { //$NON-NLS-1$ + result = true; + } else { + throw new Exception(" BAD CONDITION :: " + cond + " :: expected true or false"); + } + + logger.debug(" >> evaluate Condition - [ " + cond + "] results is [" + result + "]"); + } catch (Exception e) { + logger.error(getName()+": error while processing "+ "[" + cond + "]\n", e); + } finally { + Context.exit(); + } + + return result; + } + + private static boolean evaluateExpression(String cond) { + return cond.equalsIgnoreCase("true"); // $NON-NLS-1$ + } + + /** + * This is overriding the parent method. IsDone indicates whether the + * termination condition is reached. I.e. if the condition evaluates to + * False - then isDone() returns TRUE + */ + @Override + public boolean isDone() { + // boolean result = true; + // try { + // result = !evaluateCondition(); + // } catch (Exception e) { + // logger.error(e.getMessage(), e); + // } + // setDone(true); + // return result; + // setDone(false); + return false; + } + + /** + * @see org.apache.jmeter.control.Controller#next() + */ + @Override + public Sampler next() { + // We should only evalute the condition if it is the first + // time ( first "iteration" ) we are called. + // For subsequent calls, we are inside the IfControllerGroup, + // so then we just pass the control to the next item inside the if control + boolean result = true; + if(isEvaluateAll() || isFirst()) { + result = isUseExpression() ? + evaluateExpression(getCondition()) + : + evaluateCondition(getCondition()); + } + + if (result) { + return super.next(); + } + // If-test is false, need to re-initialize indexes + try { + initializeSubControllers(); + return nextIsNull(); + } catch (NextIsNullException e1) { + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + super.initializeSubControllers(); + super.triggerEndOfLoop(); + } + + public boolean isEvaluateAll() { + return getPropertyAsBoolean(EVALUATE_ALL,false); + } + + public void setEvaluateAll(boolean b) { + setProperty(EVALUATE_ALL,b); + } + + public boolean isUseExpression() { + return getPropertyAsBoolean(USE_EXPRESSION, false); + } + + public void setUseExpression(boolean selected) { + setProperty(USE_EXPRESSION, selected, false); + } +} diff --git a/src/core/org/apache/jmeter/control/LoopController.java b/src/core/org/apache/jmeter/control/LoopController.java new file mode 100644 index 00000000000..87ba38170b4 --- /dev/null +++ b/src/core/org/apache/jmeter/control/LoopController.java @@ -0,0 +1,197 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; + +/** + * Class that implements the Loop Controller, ie iterate infinitely or a configured number of times + */ +public class LoopController extends GenericController implements Serializable { + + public static final int INFINITE_LOOP_COUNT = -1; // $NON-NLS-1$ + + public static final String LOOPS = "LoopController.loops"; // $NON-NLS-1$ + + private static final long serialVersionUID = 7833960784370272300L; + + /* + * In spite of the name, this is actually used to determine if the loop controller is repeatable. + * + * The value is only used in nextIsNull() when the loop end condition has been detected: + * If forever==true, then it calls resetLoopCount(), otherwise it calls setDone(true). + * + * Loop Controllers always set forever=true, so that they will be executed next time + * the parent invokes them. + * + * Thread Group sets the value false, so nextIsNull() sets done, and the Thread Group will not be repeated. + * However, it's not clear that a Thread Group could ever be repeated. + */ + private static final String CONTINUE_FOREVER = "LoopController.continue_forever"; // $NON-NLS-1$ + + private transient int loopCount = 0; + + // Cache loop value, see Bug 54467 + private transient Integer nbLoops; + + public LoopController() { + setContinueForever_private(true); + } + + public void setLoops(int loops) { + setProperty(new IntegerProperty(LOOPS, loops)); + } + + public void setLoops(String loopValue) { + setProperty(new StringProperty(LOOPS, loopValue)); + } + + public int getLoops() { + // Evaluation occurs when nbLoops is not yet evaluated + // or when nbLoops is equal to special value INFINITE_LOOP_COUNT + if(nbLoops==null || // No evaluated yet + nbLoops.intValue()==0 || // Last iteration led to nbLoops == 0, + // in this case as resetLoopCount will not be called, + // it leads to no further evaluations if we don't evaluate, see BUG 56276 + nbLoops.intValue()==INFINITE_LOOP_COUNT // Number of iteration is set to infinite + ) { + try { + JMeterProperty prop = getProperty(LOOPS); + nbLoops = Integer.valueOf(prop.getStringValue()); + } catch (NumberFormatException e) { + nbLoops = Integer.valueOf(0); + } + } + return nbLoops.intValue(); + } + + public String getLoopString() { + return getPropertyAsString(LOOPS); + } + + /** + * Determines whether the loop will return any samples if it is rerun. + * + * @param forever + * true if the loop must be reset after ending a run + */ + public void setContinueForever(boolean forever) { + setContinueForever_private(forever); + } + + private void setContinueForever_private(boolean forever) { + setProperty(new BooleanProperty(CONTINUE_FOREVER, forever)); + } + + private boolean getContinueForever() { + return getPropertyAsBoolean(CONTINUE_FOREVER); + } + + /** + * {@inheritDoc} + */ + @Override + public Sampler next() { + if(endOfLoop()) { + if (!getContinueForever()) { + setDone(true); + } + return null; + } + return super.next(); + } + + private boolean endOfLoop() { + final int loops = getLoops(); + return (loops > INFINITE_LOOP_COUNT) && (loopCount >= loops); + } + + @Override + protected void setDone(boolean done) { + nbLoops = null; + super.setDone(done); + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + if (endOfLoop()) { + if (!getContinueForever()) { + setDone(true); + } else { + resetLoopCount(); + } + return null; + } + return next(); + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + super.triggerEndOfLoop(); + resetLoopCount(); + } + + protected void incrementLoopCount() { + loopCount++; + } + + protected void resetLoopCount() { + loopCount = 0; + nbLoops = null; + } + + /** + * {@inheritDoc} + */ + @Override + protected int getIterCount() { + return loopCount + 1; + } + + /** + * {@inheritDoc} + */ + @Override + protected void reInitialize() { + setFirst(true); + resetCurrent(); + incrementLoopCount(); + recoverRunningVersion(); + } + + /** + * Start next iteration + */ + public void startNextLoop() { + reInitialize(); + } +} diff --git a/src/core/org/apache/jmeter/control/NextIsNullException.java b/src/core/org/apache/jmeter/control/NextIsNullException.java new file mode 100644 index 00000000000..b7bbb2444af --- /dev/null +++ b/src/core/org/apache/jmeter/control/NextIsNullException.java @@ -0,0 +1,46 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 30, 2003 + */ +package org.apache.jmeter.control; + +/** + * Used by the Generic and Interleave controllers to signal the end of their samples + */ +public class NextIsNullException extends Exception { + private static final long serialVersionUID = 240L; + + public NextIsNullException() { + super(); + } + + public NextIsNullException(String message, Throwable cause) { + super(message, cause); + } + + public NextIsNullException(String message) { + super(message); + } + + public NextIsNullException(Throwable cause) { + super(cause); + } + +} diff --git a/src/core/org/apache/jmeter/control/ReplaceableController.java b/src/core/org/apache/jmeter/control/ReplaceableController.java new file mode 100644 index 00000000000..e3a4cbba4c8 --- /dev/null +++ b/src/core/org/apache/jmeter/control/ReplaceableController.java @@ -0,0 +1,48 @@ +/* + * 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.control; + +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jorphan.collections.HashTree; + +/** + * This interface represents a controller that gets replaced during the + * compilation phase of test execution in an arbitrary way. + * + */ +public interface ReplaceableController { + + /** + * Used to replace the test execution tree (usually by adding the + * subelements of the TestElement that is replacing the + * ReplaceableController. + * + * @return The replaced sub tree + * + * @see org.apache.jorphan.collections.HashTree + */ + HashTree getReplacementSubTree(); + + /** + * Compute the replacement tree. + * + * @param context the starting point of the replacement + */ + void resolveReplacementSubTree(JMeterTreeNode context); +} diff --git a/src/core/org/apache/jmeter/control/RunTime.java b/src/core/org/apache/jmeter/control/RunTime.java new file mode 100644 index 00000000000..567348246ce --- /dev/null +++ b/src/core/org/apache/jmeter/control/RunTime.java @@ -0,0 +1,137 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.testelement.property.StringProperty; + +/** + * Runtime Controller that runs its children until configured Runtime(s) is exceeded + */ +public class RunTime extends GenericController implements Serializable { + + private static final long serialVersionUID = 240L; + + private static final String SECONDS = "RunTime.seconds"; //$NON-NLS-1$ + + private long startTime = 0; + + private int loopCount = 0; // for getIterCount + + public RunTime() { + } + + public void setRuntime(long seconds) { + setProperty(new LongProperty(SECONDS, seconds)); + } + + public void setRuntime(String seconds) { + setProperty(new StringProperty(SECONDS, seconds)); + } + + public long getRuntime() { + try { + return Long.parseLong(getPropertyAsString(SECONDS)); + } catch (NumberFormatException e) { + return 0L; + } + } + + public String getRuntimeString() { + return getPropertyAsString(SECONDS); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDone() { + if (getRuntime() > 0 && getSubControllers().size() > 0) { + return super.isDone(); + } + return true; // Runtime is zero - no point staying around + } + + private boolean endOfLoop() { + return ((System.nanoTime() - startTime)/1000000000L) >= getRuntime(); + } + + @Override + public Sampler next() { + if (startTime == 0) { + startTime = System.nanoTime(); + } + if (endOfLoop()) { + reInitialize();// ?? + resetLoopCount(); + return null; + } + return super.next(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + if (endOfLoop()) { + resetLoopCount(); + return null; + } + return next(); + } + + protected void incrementLoopCount() { + loopCount++; + } + + protected void resetLoopCount() { + loopCount = 0; + startTime = 0; + } + + /* + * This is needed for OnceOnly to work like other Loop Controllers + */ + @Override + protected int getIterCount() { + return loopCount + 1; + } + + @Override + protected void reInitialize() { + setFirst(true); + resetCurrent(); + incrementLoopCount(); + recoverRunningVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + super.triggerEndOfLoop(); + resetLoopCount(); + } +} diff --git a/src/core/org/apache/jmeter/control/TestFragmentController.java b/src/core/org/apache/jmeter/control/TestFragmentController.java new file mode 100644 index 00000000000..c8dff7fd93e --- /dev/null +++ b/src/core/org/apache/jmeter/control/TestFragmentController.java @@ -0,0 +1,27 @@ +/* + * 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.control; + +import java.io.Serializable; + +public class TestFragmentController extends GenericController implements Serializable { + + private static final long serialVersionUID = 1L; + +} diff --git a/src/core/org/apache/jmeter/control/TransactionController.java b/src/core/org/apache/jmeter/control/TransactionController.java new file mode 100644 index 00000000000..c1a16d21ff9 --- /dev/null +++ b/src/core/org/apache/jmeter/control/TransactionController.java @@ -0,0 +1,323 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterThread; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.threads.ListenerNotifier; +import org.apache.jmeter.threads.SamplePackage; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Transaction Controller to measure transaction times + * + * There are two different modes for the controller: + * - generate additional total sample after nested samples (as in JMeter 2.2) + * - generate parent sampler containing the nested samples + * + */ +public class TransactionController extends GenericController implements SampleListener, Controller, Serializable { + private static final long serialVersionUID = 233L; + + private static final String TRUE = Boolean.toString(true); // i.e. "true" + + private static final String PARENT = "TransactionController.parent";// $NON-NLS-1$ + + private static final String INCLUDE_TIMERS = "TransactionController.includeTimers";// $NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final boolean DEFAULT_VALUE_FOR_INCLUDE_TIMERS = true; // default true for compatibility + + /** + * Only used in parent Mode + */ + private transient TransactionSampler transactionSampler; + + /** + * Only used in NON parent Mode + */ + private transient ListenerNotifier lnf; + + /** + * Only used in NON parent Mode + */ + private transient SampleResult res; + + /** + * Only used in NON parent Mode + */ + private transient int calls; + + /** + * Only used in NON parent Mode + */ + private transient int noFailingSamples; + + /** + * Cumulated pause time to excluse timer and post/pre processor times + * Only used in NON parent Mode + */ + private transient long pauseTime; + + /** + * Previous end time + * Only used in NON parent Mode + */ + private transient long prevEndTime; + + /** + * Creates a Transaction Controller + */ + public TransactionController() { + lnf = new ListenerNotifier(); + } + + @Override + protected Object readResolve(){ + super.readResolve(); + lnf = new ListenerNotifier(); + return this; + } + + public void setParent(boolean _parent){ + setProperty(new BooleanProperty(PARENT, _parent)); + } + + public boolean isParent(){ + return getPropertyAsBoolean(PARENT); + } + + /** + * @see org.apache.jmeter.control.Controller#next() + */ + @Override + public Sampler next(){ + if (isParent()){ + return next1(); + } + return next2(); + } + +///////////////// Transaction Controller - parent //////////////// + + private Sampler next1() { + // Check if transaction is done + if(transactionSampler != null && transactionSampler.isTransactionDone()) { + if (log.isDebugEnabled()) { + log.debug("End of transaction " + getName()); + } + // This transaction is done + transactionSampler = null; + return null; + } + + // Check if it is the start of a new transaction + if (isFirst()) // must be the start of the subtree + { + if (log.isDebugEnabled()) { + log.debug("Start of transaction " + getName()); + } + transactionSampler = new TransactionSampler(this, getName()); + } + + // Sample the children of the transaction + Sampler subSampler = super.next(); + transactionSampler.setSubSampler(subSampler); + // If we do not get any sub samplers, the transaction is done + if (subSampler == null) { + transactionSampler.setTransactionDone(); + } + return transactionSampler; + } + + @Override + protected Sampler nextIsAController(Controller controller) throws NextIsNullException { + if (!isParent()) { + return super.nextIsAController(controller); + } + Sampler returnValue; + Sampler sampler = controller.next(); + if (sampler == null) { + currentReturnedNull(controller); + // We need to call the super.next, instead of this.next, which is done in GenericController, + // because if we call this.next(), it will return the TransactionSampler, and we do not want that. + // We need to get the next real sampler or controller + returnValue = super.next(); + } else { + returnValue = sampler; + } + return returnValue; + } + +////////////////////// Transaction Controller - additional sample ////////////////////////////// + + private Sampler next2() { + if (isFirst()) // must be the start of the subtree + { + calls = 0; + noFailingSamples = 0; + res = new SampleResult(); + res.setSampleLabel(getName()); + // Assume success + res.setSuccessful(true); + res.sampleStart(); + prevEndTime = res.getStartTime();//??? + pauseTime = 0; + } + boolean isLast = current==super.subControllersAndSamplers.size(); + Sampler returnValue = super.next(); + if (returnValue == null && isLast) // Must be the end of the controller + { + if (res != null) { + // See BUG 55816 + if (!isIncludeTimers()) { + long processingTimeOfLastChild = res.currentTimeInMillis() - prevEndTime; + pauseTime += processingTimeOfLastChild; + } + res.setIdleTime(pauseTime+res.getIdleTime()); + res.sampleEnd(); + res.setResponseMessage("Number of samples in transaction : " + calls + ", number of failing samples : " + noFailingSamples); + if(res.isSuccessful()) { + res.setResponseCodeOK(); + } + notifyListeners(); + } + } + else { + // We have sampled one of our children + calls++; + } + + return returnValue; + } + + /** + * @see org.apache.jmeter.control.GenericController#triggerEndOfLoop() + */ + @Override + public void triggerEndOfLoop() { + if(!isParent()) { + if (res != null) { + res.setIdleTime(pauseTime+res.getIdleTime()); + res.sampleEnd(); + res.setSuccessful(TRUE.equals(JMeterContextService.getContext().getVariables().get(JMeterThread.LAST_SAMPLE_OK))); + res.setResponseMessage("Number of samples in transaction : " + calls + ", number of failing samples : " + noFailingSamples); + notifyListeners(); + } + } else { + Sampler subSampler = transactionSampler.getSubSampler(); + // See Bug 56811 + // triggerEndOfLoop is called when error occurs to end Main Loop + // in this case normal workflow doesn't happen, so we need + // to notify the childs of TransactionController and + // update them with SubSamplerResult + if(subSampler instanceof TransactionSampler) { + TransactionSampler tc = (TransactionSampler) subSampler; + tc.getTransactionController().triggerEndOfLoop(); + transactionSampler.addSubSamplerResult(tc.getTransactionResult()); + } + transactionSampler.setTransactionDone(); + // This transaction is done + transactionSampler = null; + } + super.triggerEndOfLoop(); + } + + /** + * Create additional SampleEvent in NON Parent Mode + */ + protected void notifyListeners() { + // TODO could these be done earlier (or just once?) + JMeterContext threadContext = getThreadContext(); + JMeterVariables threadVars = threadContext.getVariables(); + SamplePackage pack = (SamplePackage) threadVars.getObject(JMeterThread.PACKAGE_OBJECT); + if (pack == null) { + // If child of TransactionController is a ThroughputController and TPC does + // not sample its children, then we will have this + // TODO Should this be at warn level ? + log.warn("Could not fetch SamplePackage"); + } else { + SampleEvent event = new SampleEvent(res, threadContext.getThreadGroup().getName(),threadVars, true); + // We must set res to null now, before sending the event for the transaction, + // so that we can ignore that event in our sampleOccured method + res = null; + lnf.notifyListeners(event, pack.getSampleListeners()); + } + } + + @Override + public void sampleOccurred(SampleEvent se) { + if (!isParent()) { + // Check if we are still sampling our children + if(res != null && !se.isTransactionSampleEvent()) { + SampleResult sampleResult = se.getResult(); + res.setThreadName(sampleResult.getThreadName()); + res.setBytes(res.getBytes() + sampleResult.getBytes()); + if (!isIncludeTimers()) {// Accumulate waiting time for later + pauseTime += sampleResult.getEndTime() - sampleResult.getTime() - prevEndTime; + prevEndTime = sampleResult.getEndTime(); + } + if(!sampleResult.isSuccessful()) { + res.setSuccessful(false); + noFailingSamples++; + } + res.setAllThreads(sampleResult.getAllThreads()); + res.setGroupThreads(sampleResult.getGroupThreads()); + res.setLatency(res.getLatency() + sampleResult.getLatency()); + res.setConnectTime(res.getConnectTime() + sampleResult.getConnectTime()); + } + } + } + + @Override + public void sampleStarted(SampleEvent e) { + } + + @Override + public void sampleStopped(SampleEvent e) { + } + + /** + * Whether to include timers and pre/post processor time in overall sample. + * @param includeTimers Flag whether timers and pre/post processor should be included in overall sample + */ + public void setIncludeTimers(boolean includeTimers) { + setProperty(INCLUDE_TIMERS, includeTimers, DEFAULT_VALUE_FOR_INCLUDE_TIMERS); + } + + /** + * Whether to include timer and pre/post processor time in overall sample. + * + * @return boolean (defaults to true for backwards compatibility) + */ + public boolean isIncludeTimers() { + return getPropertyAsBoolean(INCLUDE_TIMERS, DEFAULT_VALUE_FOR_INCLUDE_TIMERS); + } +} diff --git a/src/core/org/apache/jmeter/control/TransactionSampler.java b/src/core/org/apache/jmeter/control/TransactionSampler.java new file mode 100644 index 00000000000..f3f79a51c4c --- /dev/null +++ b/src/core/org/apache/jmeter/control/TransactionSampler.java @@ -0,0 +1,156 @@ +/* + * 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. + * + */ + +/* + * N.B. Although this is a type of sampler, it is only used by the transaction controller, + * and so is in the control package +*/ +package org.apache.jmeter.control; + + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; + +/** + * Transaction Sampler class to measure transaction times + * (not exposed a a GUI class, as it is only used internally by TransactionController in Generate Parent sample mode) + */ +public class TransactionSampler extends AbstractSampler { + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private boolean transactionDone = false; + + private TransactionController transactionController; + + private Sampler subSampler; + + private SampleResult transactionSampleResult; + + private int calls = 0; + + private int noFailingSamples = 0; + + private int totalTime = 0; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public TransactionSampler(){ + //log.warn("Constructor only intended for use in testing"); + } + + public TransactionSampler(TransactionController controller, String name) { + transactionController = controller; + setName(name); // ensure name is available for debugging + transactionSampleResult = new SampleResult(); + transactionSampleResult.setSampleLabel(name); + // Assume success + transactionSampleResult.setSuccessful(true); + transactionSampleResult.sampleStart(); + } + + /** + * One cannot sample the TransactionSampler directly. + */ + @Override + public SampleResult sample(Entry e) { + throw new RuntimeException("Cannot sample TransactionSampler directly"); + // It is the JMeterThread which knows how to sample a real sampler + } + + public Sampler getSubSampler() { + return subSampler; + } + + public SampleResult getTransactionResult() { + return transactionSampleResult; + } + + public TransactionController getTransactionController() { + return transactionController; + } + + public boolean isTransactionDone() { + return transactionDone; + } + + public void addSubSamplerResult(SampleResult res) { + // Another subsample for the transaction + calls++; + + // Set Response code of transaction + if (noFailingSamples == 0) { + transactionSampleResult.setResponseCode(res.getResponseCode()); + } + + // The transaction fails if any sub sample fails + if (!res.isSuccessful()) { + transactionSampleResult.setSuccessful(false); + noFailingSamples++; + } + // Add the sub result to the transaction result + transactionSampleResult.addSubResult(res); + // Add current time to total for later use (exclude pause time) + totalTime += res.getTime(); + } + + protected void setTransactionDone() { + this.transactionDone = true; + // Set the overall status for the transaction sample + // TODO: improve, e.g. by adding counts to the SampleResult class + transactionSampleResult.setResponseMessage("Number of samples in transaction : " + + calls + ", number of failing samples : " + + noFailingSamples); + if (transactionSampleResult.isSuccessful()) { + transactionSampleResult.setResponseCodeOK(); + } + // Bug 50080 (not include pause time when generate parent) + if (!transactionController.isIncludeTimers()) { + long end = transactionSampleResult.currentTimeInMillis(); + transactionSampleResult.setIdleTime(end + - transactionSampleResult.getStartTime() - totalTime); + transactionSampleResult.setEndTime(end); + } + } + + protected void setSubSampler(Sampler subSampler) { + this.subSampler = subSampler; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/core/org/apache/jmeter/control/WhileController.java b/src/core/org/apache/jmeter/control/WhileController.java new file mode 100644 index 00000000000..f980c946d60 --- /dev/null +++ b/src/core/org/apache/jmeter/control/WhileController.java @@ -0,0 +1,132 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterThread; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// @see TestWhileController for unit tests + +public class WhileController extends GenericController implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + private static final String CONDITION = "WhileController.condition"; // $NON-NLS-1$ + + public WhileController() { + } + + /* + * Evaluate the condition, which can be: + * blank or LAST = was the last sampler OK? + * otherwise, evaluate the condition to see if it is not "false" + * If blank, only evaluate at the end of the loop + * + * Must only be called at start and end of loop + * + * @param loopEnd - are we at loop end? + * @return true means OK to continue + */ + private boolean endOfLoop(boolean loopEnd) { + String cnd = getCondition().trim(); + if(log.isDebugEnabled()) { + log.debug("Condition string:" + cnd+"."); + } + boolean res; + // If blank, only check previous sample when at end of loop + if ((loopEnd && cnd.length() == 0) || "LAST".equalsIgnoreCase(cnd)) {// $NON-NLS-1$ + JMeterVariables threadVars = JMeterContextService.getContext().getVariables(); + res = "false".equalsIgnoreCase(threadVars.get(JMeterThread.LAST_SAMPLE_OK));// $NON-NLS-1$ + } else { + // cnd may be null if next() called us + res = "false".equalsIgnoreCase(cnd);// $NON-NLS-1$ + } + if(log.isDebugEnabled()) { + log.debug("Condition value: " + res); + } + return res; + } + + /** + * Only called at End of Loop + *

+ * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + if (endOfLoop(true)){ + return null; + } + return next(); + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + super.triggerEndOfLoop(); + endOfLoop(true); + } + + /** + * This skips controller entirely if the condition is false on first entry. + *

+ * {@inheritDoc} + */ + @Override + public Sampler next(){ + if (isFirst()){ + if (endOfLoop(false)){ + return null; + } + } + return super.next(); + } + + /** + * @param string + * the condition to save + */ + public void setCondition(String string) { + if(log.isDebugEnabled()) { + log.debug("setCondition(" + string + ")"); + } + setProperty(new StringProperty(CONDITION, string)); + } + + /** + * @return the condition + */ + public String getCondition() { + JMeterProperty prop=getProperty(CONDITION); + prop.recoverRunningVersion(this); + return prop.getStringValue(); + } +} diff --git a/src/core/org/apache/jmeter/control/gui/AbstractControllerGui.java b/src/core/org/apache/jmeter/control/gui/AbstractControllerGui.java new file mode 100644 index 00000000000..71edd91a6b6 --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/AbstractControllerGui.java @@ -0,0 +1,65 @@ +/* + * 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.control.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage controllers. + * + */ +public abstract class AbstractControllerGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most controller + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + @Override + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultControllerMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#CONTROLLERS}, which is + * appropriate for most controller components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.CONTROLLERS }); + } +} diff --git a/src/core/org/apache/jmeter/control/gui/IfControllerPanel.java b/src/core/org/apache/jmeter/control/gui/IfControllerPanel.java new file mode 100644 index 00000000000..b2ac7c417b4 --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/IfControllerPanel.java @@ -0,0 +1,196 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.IfController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The user interface for a controller which specifies that its subcomponents + * should be executed while a condition holds. This component can be used + * standalone or embedded into some other component. + * + */ + +public class IfControllerPanel extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + /** + * A field allowing the user to specify the number of times the controller + * should loop. + */ + private JTextField theCondition; + + private JCheckBox useExpression; + + private JCheckBox evaluateAll; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** + * Create a new LoopControlPanel as a standalone component. + */ + public IfControllerPanel() { + this(true); + } + + /** + * Create a new IfControllerPanel as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public IfControllerPanel(boolean displayName) { + this.displayName = displayName; + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + if (element instanceof IfController) { + IfController ifController = (IfController) element; + theCondition.setText(ifController.getCondition()); + evaluateAll.setSelected(ifController.isEvaluateAll()); + useExpression.setSelected(ifController.isUseExpression()); + } + + } + + /** + * Implements JMeterGUIComponent.createTestElement() + */ + @Override + public TestElement createTestElement() { + IfController controller = new IfController(); + modifyTestElement(controller); + return controller; + } + + /** + * Implements JMeterGUIComponent.modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement controller) { + configureTestElement(controller); + if (controller instanceof IfController) { + IfController ifController = (IfController) controller; + ifController.setCondition(theCondition.getText()); + ifController.setEvaluateAll(evaluateAll.isSelected()); + ifController.setUseExpression(useExpression.isSelected()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + theCondition.setText(""); // $NON-NLS-1$ + evaluateAll.setSelected(false); + } + + @Override + public String getLabelResource() { + return "if_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + // Standalone + if (displayName) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createConditionPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + + } else { + // Embedded + setLayout(new BorderLayout()); + add(createConditionPanel(), BorderLayout.NORTH); + } + } + + /** + * Create a GUI panel containing the condition. + * + * @return a GUI panel containing the condition components + */ + private JPanel createConditionPanel() { + JPanel conditionPanel = new JPanel(new BorderLayout(5, 0)); + + // Condition LABEL + JLabel conditionLabel = new JLabel(JMeterUtils.getResString("if_controller_label")); // $NON-NLS-1$ + conditionPanel.add(conditionLabel, BorderLayout.WEST); + + // TEXT FIELD + theCondition = new JTextField(""); // $NON-NLS-1$ + conditionLabel.setLabelFor(theCondition); + conditionPanel.add(theCondition, BorderLayout.CENTER); + + conditionPanel.add(Box.createHorizontalStrut(conditionLabel.getPreferredSize().width + + theCondition.getPreferredSize().width), BorderLayout.NORTH); + + JPanel optionPanel = new JPanel(); + + // Use expression instead of Javascript + useExpression = new JCheckBox(JMeterUtils.getResString("if_controller_expression")); // $NON-NLS-1$ + optionPanel.add(useExpression); + + // Evaluate All checkbox + evaluateAll = new JCheckBox(JMeterUtils.getResString("if_controller_evaluate_all")); // $NON-NLS-1$ + optionPanel.add(evaluateAll); + + conditionPanel.add(optionPanel,BorderLayout.SOUTH); + return conditionPanel; + } +} diff --git a/src/core/org/apache/jmeter/control/gui/LogicControllerGui.java b/src/core/org/apache/jmeter/control/gui/LogicControllerGui.java new file mode 100644 index 00000000000..7eae570f298 --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/LogicControllerGui.java @@ -0,0 +1,67 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; + +import org.apache.jmeter.control.GenericController; +import org.apache.jmeter.testelement.TestElement; + +/** + * A generic controller component. + * + */ +public class LogicControllerGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + /** + * Create a new LogicControllerGui instance. + */ + public LogicControllerGui() { + init(); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + GenericController lc = new GenericController(); + configureTestElement(lc); + return lc; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement el) { + configureTestElement(el); + } + + @Override + public String getLabelResource() { + return "logic_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } +} diff --git a/src/core/org/apache/jmeter/control/gui/LoopControlPanel.java b/src/core/org/apache/jmeter/control/gui/LoopControlPanel.java new file mode 100644 index 00000000000..734307fe110 --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/LoopControlPanel.java @@ -0,0 +1,268 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.LoopController; +import org.apache.jmeter.gui.util.FocusRequester; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The user interface for a controller which specifies that its subcomponents + * should be executed some number of times in a loop. This component can be used + * standalone or embedded into some other component. + * + */ + +public class LoopControlPanel extends AbstractControllerGui implements ActionListener { + private static final long serialVersionUID = 240L; + + /** + * A checkbox allowing the user to specify whether or not the controller + * should loop forever. + */ + private JCheckBox infinite; + + /** + * A field allowing the user to specify the number of times the controller + * should loop. + */ + private JTextField loops; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** The name of the infinite checkbox component. */ + private static final String INFINITE = "Infinite Field"; // $NON-NLS-1$ + + /** The name of the loops field component. */ + private static final String LOOPS = "Loops Field"; // $NON-NLS-1$ + + /** + * Create a new LoopControlPanel as a standalone component. + */ + public LoopControlPanel() { + this(true); + } + + /** + * Create a new LoopControlPanel as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public LoopControlPanel(boolean displayName) { + this.displayName = displayName; + init(); + setState(1); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + if (element instanceof LoopController) { + setState(((LoopController) element).getLoopString()); + } else { + setState(1); + } + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + LoopController lc = new LoopController(); + modifyTestElement(lc); + return lc; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement lc) { + configureTestElement(lc); + if (lc instanceof LoopController) { + if (loops.getText().length() > 0) { + ((LoopController) lc).setLoops(loops.getText()); + } else { + ((LoopController) lc).setLoops(LoopController.INFINITE_LOOP_COUNT); + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + loops.setText("1"); // $NON-NLS-1$ + infinite.setSelected(false); + } + + /** + * Invoked when an action occurs. This implementation assumes that the + * target component is the infinite loops checkbox. + * + * @param event + * the event that has occurred + */ + @Override + public void actionPerformed(ActionEvent event) { + if (infinite.isSelected()) { + loops.setText(""); // $NON-NLS-1$ + loops.setEnabled(false); + } else { + loops.setEnabled(true); + FocusRequester.requestFocus(loops); + } + } + + @Override + public String getLabelResource() { + return "loop_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + // The Loop Controller panel can be displayed standalone or inside + // another panel. For standalone, we want to display the TITLE, NAME, + // etc. (everything). However, if we want to display it within another + // panel, we just display the Loop Count fields (not the TITLE and + // NAME). + + // Standalone + if (displayName) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createLoopCountPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } else { + // Embedded + setLayout(new BorderLayout()); + add(createLoopCountPanel(), BorderLayout.NORTH); + } + } + + /** + * Create a GUI panel containing the components related to the number of + * loops which should be executed. + * + * @return a GUI panel containing the loop count components + */ + private JPanel createLoopCountPanel() { + JPanel loopPanel = new JPanel(new BorderLayout(5, 0)); + + // LOOP LABEL + JLabel loopsLabel = new JLabel(JMeterUtils.getResString("iterator_num")); // $NON-NLS-1$ + loopPanel.add(loopsLabel, BorderLayout.WEST); + + JPanel loopSubPanel = new JPanel(new BorderLayout(5, 0)); + + // TEXT FIELD + loops = new JTextField("1", 5); // $NON-NLS-1$ + loops.setName(LOOPS); + loopsLabel.setLabelFor(loops); + loopSubPanel.add(loops, BorderLayout.CENTER); + + // FOREVER CHECKBOX + infinite = new JCheckBox(JMeterUtils.getResString("infinite")); // $NON-NLS-1$ + infinite.setActionCommand(INFINITE); + infinite.addActionListener(this); + loopSubPanel.add(infinite, BorderLayout.WEST); + + loopPanel.add(loopSubPanel, BorderLayout.CENTER); + + loopPanel.add(Box.createHorizontalStrut(loopsLabel.getPreferredSize().width + loops.getPreferredSize().width + + infinite.getPreferredSize().width), BorderLayout.NORTH); + + return loopPanel; + } + + /** + * Set the number of loops which should be reflected in the GUI. The + * loopCount parameter should contain the String representation of an + * integer. This integer will be treated as the number of loops. If this + * integer is less than 0, the number of loops will be assumed to be + * infinity. + * + * @param loopCount + * the String representation of the number of loops + */ + private void setState(String loopCount) { + if (loopCount.startsWith("-")) { // $NON-NLS-1$ + setState(LoopController.INFINITE_LOOP_COUNT); + } else { + loops.setText(loopCount); + infinite.setSelected(false); + loops.setEnabled(true); + } + } + + /** + * Set the number of loops which should be reflected in the GUI. If the + * loopCount is less than 0, the number of loops will be assumed to be + * infinity. + * + * @param loopCount + * the number of loops + */ + private void setState(int loopCount) { + if (loopCount <= LoopController.INFINITE_LOOP_COUNT) { + infinite.setSelected(true); + loops.setEnabled(false); + loops.setText(""); // $NON-NLS-1$ + } else { + infinite.setSelected(false); + loops.setEnabled(true); + loops.setText(Integer.toString(loopCount)); + } + } +} diff --git a/src/core/org/apache/jmeter/control/gui/RunTimeGui.java b/src/core/org/apache/jmeter/control/gui/RunTimeGui.java new file mode 100644 index 00000000000..23d45eea249 --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/RunTimeGui.java @@ -0,0 +1,228 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.Box; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.RunTime; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The user interface for a controller which specifies that its subcomponents + * should be executed some number of seconds in a loop. This component can be + * used standalone or embedded into some other component. + * + */ + +public class RunTimeGui extends AbstractControllerGui implements ActionListener { + private static final long serialVersionUID = 240L; + + /** + * A field allowing the user to specify the number of seconds the controller + * should loop. + */ + private JTextField seconds; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** + * Create a new LoopControlPanel as a standalone component. + */ + public RunTimeGui() { + this(true); + } + + /** + * Create a new LoopControlPanel as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public RunTimeGui(boolean displayName) { + this.displayName = displayName; + init(); + setState(1); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + if (element instanceof RunTime) { + setState(((RunTime) element).getRuntimeString()); + } else { + setState(1); + } + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + RunTime lc = new RunTime(); + modifyTestElement(lc); + return lc; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement lc) { + configureTestElement(lc); + if (lc instanceof RunTime) { + if (seconds.getText().length() > 0) { + ((RunTime) lc).setRuntime(seconds.getText()); + } else { + ((RunTime) lc).setRuntime(0); + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + seconds.setText("1"); // $NON-NLS-1$ + } + + /** + * Invoked when an action occurs. This implementation assumes that the + * target component is the infinite seconds checkbox. + * + * @param event + * the event that has occurred + */ + @Override + public void actionPerformed(ActionEvent event) { + seconds.setEnabled(true); + } + + @Override + public String getLabelResource() { + return "runtime_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + // The Loop Controller panel can be displayed standalone or inside + // another panel. For standalone, we want to display the TITLE, NAME, + // etc. (everything). However, if we want to display it within another + // panel, we just display the Loop Count fields (not the TITLE and + // NAME). + + // Standalone + if (displayName) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createLoopCountPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } else { + // Embedded + setLayout(new BorderLayout()); + add(createLoopCountPanel(), BorderLayout.NORTH); + } + } + + /** + * Create a GUI panel containing the components related to the number of + * seconds which should be executed. + * + * @return a GUI panel containing the loop count components + */ + private JPanel createLoopCountPanel() { + JPanel loopPanel = new JPanel(new BorderLayout(5, 0)); + + // SECONDS LABEL + JLabel secondsLabel = new JLabel(JMeterUtils.getResString("runtime_seconds")); // $NON-NLS-1$ + loopPanel.add(secondsLabel, BorderLayout.WEST); + + JPanel loopSubPanel = new JPanel(new BorderLayout(5, 0)); + + // TEXT FIELD + seconds = new JTextField("1", 5); // $NON-NLS-1$ + secondsLabel.setLabelFor(seconds); + loopSubPanel.add(seconds, BorderLayout.CENTER); + + loopPanel.add(loopSubPanel, BorderLayout.CENTER); + + loopPanel.add(Box.createHorizontalStrut(secondsLabel.getPreferredSize().width + + seconds.getPreferredSize().width), BorderLayout.NORTH); + + return loopPanel; + } + + /** + * Set the number of seconds which should be reflected in the GUI. The + * secsCount parameter should contain the String representation of an + * integer. This integer will be treated as the number of seconds. If this + * integer is less than 0, the number of seconds will be assumed to be + * infinity. + * + * @param secsCount + * the String representation of the number of seconds + */ + private void setState(String secsCount) { + seconds.setText(secsCount); + seconds.setEnabled(true); + } + + /** + * Set the number of seconds which should be reflected in the GUI. If the + * secsCount is less than 0, the number of seconds will be assumed to be + * infinity. + * + * @param secsCount + * the number of seconds + */ + private void setState(long secsCount) { + seconds.setEnabled(true); + seconds.setText(Long.toString(secsCount)); + } +} diff --git a/src/core/org/apache/jmeter/control/gui/TestFragmentControllerGui.java b/src/core/org/apache/jmeter/control/gui/TestFragmentControllerGui.java new file mode 100644 index 00000000000..c0e47687de8 --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/TestFragmentControllerGui.java @@ -0,0 +1,85 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.jmeter.control.TestFragmentController; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; + +/** + * This defines a simple Test Fragment GUI that can be used instead of a Thread Group + * to allow for a non-execution part of the Test Plan that can be saved and referenced + * by a Module or Include Controller. + */ + +public class TestFragmentControllerGui extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + public TestFragmentControllerGui() { + init(); + } + + /** + * Implements JMeterGUIComponent.createTestElement() + */ + @Override + public TestElement createTestElement() { + TestFragmentController controller = new TestFragmentController(); + setEnabled(false); + modifyTestElement(controller); + return controller; + } + + /** + * Implements JMeterGUIComponent.modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement controller) { + configureTestElement(controller); + } + + @Override + public String getLabelResource() { + return "test_fragment_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + /** + * Over-ride this so that we add ourselves to the Test Fragment Category instead. + */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.FRAGMENTS }); + } + + +} diff --git a/src/core/org/apache/jmeter/control/gui/TestPlanGui.java b/src/core/org/apache/jmeter/control/gui/TestPlanGui.java new file mode 100644 index 00000000000..7b486f1d4cb --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/TestPlanGui.java @@ -0,0 +1,205 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import java.util.Collection; + +import javax.swing.JCheckBox; +import javax.swing.JMenu; +import javax.swing.JPopupMenu; +import javax.swing.JTextArea; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.util.FileListPanel; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.util.JMeterUtils; + +/** + * JMeter GUI component representing the test plan which will be executed when + * the test is run. + * + */ +public class TestPlanGui extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + /** + * A checkbox allowing the user to specify whether or not JMeter should do + * functional testing. + */ + private final JCheckBox functionalMode; + + private final JCheckBox serializedMode; + + private final JCheckBox tearDownOnShutdown; + + /** A panel allowing the user to define variables. */ + private final ArgumentsPanel argsPanel; + + private final FileListPanel browseJar; + + /** + * Create a new TestPlanGui. + */ + public TestPlanGui() { + browseJar = new FileListPanel(JMeterUtils.getResString("test_plan_classpath_browse"), ".jar"); // $NON-NLS-1$ $NON-NLS-2$ + argsPanel = new ArgumentsPanel(JMeterUtils.getResString("user_defined_variables")); // $NON-NLS-1$ + serializedMode = new JCheckBox(JMeterUtils.getResString("testplan.serialized")); // $NON-NLS-1$ + functionalMode = new JCheckBox(JMeterUtils.getResString("functional_mode")); // $NON-NLS-1$ + tearDownOnShutdown = new JCheckBox(JMeterUtils.getResString("teardown_on_shutdown")); // $NON-NLS-1$ + init(); + } + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * The TestPlan will return a popup menu allowing you to add ThreadGroups, + * Listeners, Configuration Elements, Assertions, PreProcessors, + * PostProcessors, and Timers. + * + * @return a JPopupMenu appropriate for the component. + */ + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + JMenu addMenu = new JMenu(JMeterUtils.getResString("add")); // $NON-NLS-1$ + addMenu.add(MenuFactory.makeMenu(MenuFactory.THREADS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.FRAGMENTS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.CONFIG_ELEMENTS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.TIMERS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.PRE_PROCESSORS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.POST_PROCESSORS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.ASSERTIONS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.LISTENERS, ActionNames.ADD)); + pop.add(addMenu); + MenuFactory.addPasteResetMenu(pop); + MenuFactory.addFileMenu(pop, false); + return pop; + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + TestPlan tp = new TestPlan(); + modifyTestElement(tp); + return tp; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement plan) { + super.configureTestElement(plan); + if (plan instanceof TestPlan) { + TestPlan tp = (TestPlan) plan; + tp.setFunctionalMode(functionalMode.isSelected()); + tp.setTearDownOnShutdown(tearDownOnShutdown.isSelected()); + tp.setSerialized(serializedMode.isSelected()); + tp.setUserDefinedVariables((Arguments) argsPanel.createTestElement()); + tp.setTestPlanClasspathArray(browseJar.getFiles()); + } + } + + @Override + public String getLabelResource() { + return "test_plan"; // $NON-NLS-1$ + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns null, since the TestPlan appears at + * the top level of the tree and cannot be added elsewhere. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return null; + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof TestPlan) { + TestPlan tp = (TestPlan) el; + functionalMode.setSelected(tp.isFunctionalMode()); + serializedMode.setSelected(tp.isSerialized()); + tearDownOnShutdown.setSelected(tp.isTearDownOnShutdown()); + final JMeterProperty udv = tp.getUserDefinedVariablesAsProperty(); + if (udv != null) { + argsPanel.configure((Arguments) udv.getObjectValue()); + } + browseJar.setFiles(tp.getTestPlanClasspathArray()); + } + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(10, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + add(argsPanel, BorderLayout.CENTER); + + VerticalPanel southPanel = new VerticalPanel(); + southPanel.add(serializedMode); + southPanel.add(tearDownOnShutdown); + southPanel.add(functionalMode); + JTextArea explain = new JTextArea(JMeterUtils.getResString("functional_mode_explanation")); // $NON-NLS-1$ + explain.setEditable(false); + explain.setBackground(this.getBackground()); + southPanel.add(explain); + southPanel.add(browseJar); + + add(southPanel, BorderLayout.SOUTH); + } + + @Override + public void clearGui() { + super.clearGui(); + functionalMode.setSelected(false); + serializedMode.setSelected(false); + tearDownOnShutdown.setSelected(false); + argsPanel.clear(); + browseJar.clearFiles(); + } +} diff --git a/src/core/org/apache/jmeter/control/gui/TransactionControllerGui.java b/src/core/org/apache/jmeter/control/gui/TransactionControllerGui.java new file mode 100644 index 00000000000..928573dd40f --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/TransactionControllerGui.java @@ -0,0 +1,90 @@ +/* + * 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.control.gui; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.control.TransactionController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * A Transaction controller component. + * + */ +public class TransactionControllerGui extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox parent; // If selected, then generate parent sample, otherwise as per original controller + + private JCheckBox includeTimers; // if selected, add duration of timers to total runtime + + /** + * Create a new TransactionControllerGui instance. + */ + public TransactionControllerGui() { + init(); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + TransactionController lc = new TransactionController(); + lc.setIncludeTimers(false); // change default for new test elements + configureTestElement(lc); + return lc; + } + + @Override + public void configure(TestElement el) { + super.configure(el); + parent.setSelected(((TransactionController) el).isParent()); + includeTimers.setSelected(((TransactionController) el).isIncludeTimers()); + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement el) { + configureTestElement(el); + ((TransactionController) el).setParent(parent.isSelected()); + TransactionController tc = ((TransactionController) el); + tc.setParent(parent.isSelected()); + tc.setIncludeTimers(includeTimers.isSelected()); + } + + @Override + public String getLabelResource() { + return "transaction_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + parent = new JCheckBox(JMeterUtils.getResString("transaction_controller_parent")); // $NON-NLS-1$ + add(parent); + includeTimers = new JCheckBox(JMeterUtils.getResString("transaction_controller_include_timers"), true); // $NON-NLS-1$ + add(includeTimers); + } +} diff --git a/src/core/org/apache/jmeter/control/gui/WhileControllerGui.java b/src/core/org/apache/jmeter/control/gui/WhileControllerGui.java new file mode 100644 index 00000000000..fde3402b0d8 --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/WhileControllerGui.java @@ -0,0 +1,148 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.WhileController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class WhileControllerGui extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + private static final String CONDITION_LABEL = "while_controller_label"; // $NON-NLS-1$ + + /** + * A field allowing the user to specify the condition (not yet used). + */ + private JTextField theCondition; + + /** The name of the condition field component. */ + private static final String CONDITION = "While_Condition"; // $NON-NLS-1$ + + /** + * Create a new LoopControlPanel as a standalone component. + */ + public WhileControllerGui() { + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + if (element instanceof WhileController) { + theCondition.setText(((WhileController) element).getCondition()); + } + + } + + /** + * Implements JMeterGUIComponent.createTestElement() + */ + @Override + public TestElement createTestElement() { + WhileController controller = new WhileController(); + modifyTestElement(controller); + return controller; + } + + /** + * Implements JMeterGUIComponent.modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement controller) { + configureTestElement(controller); + if (controller instanceof WhileController) { + if (theCondition.getText().length() > 0) { + ((WhileController) controller).setCondition(theCondition.getText()); + } else { + ((WhileController) controller).setCondition(""); // $NON-NLS-1$ + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + theCondition.setText(""); // $NON-NLS-1$ + } + + @Override + public String getLabelResource() { + return "while_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createConditionPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + + } + + /** + * Create a GUI panel containing the condition. TODO make use of the field + * + * @return a GUI panel containing the condition components + */ + private JPanel createConditionPanel() { + JPanel conditionPanel = new JPanel(new BorderLayout(5, 0)); + + // Condition LABEL + JLabel conditionLabel = new JLabel(JMeterUtils.getResString(CONDITION_LABEL)); + conditionPanel.add(conditionLabel, BorderLayout.WEST); + + // TEXT FIELD + // This means exit if last sample failed + theCondition = new JTextField(""); // $NON-NLS-1$ + theCondition.setName(CONDITION); + conditionLabel.setLabelFor(theCondition); + conditionPanel.add(theCondition, BorderLayout.CENTER); + + conditionPanel.add(Box.createHorizontalStrut(conditionLabel.getPreferredSize().width + + theCondition.getPreferredSize().width), BorderLayout.NORTH); + + return conditionPanel; + } +} diff --git a/src/core/org/apache/jmeter/control/gui/WorkBenchGui.java b/src/core/org/apache/jmeter/control/gui/WorkBenchGui.java new file mode 100644 index 00000000000..64f048ff74c --- /dev/null +++ b/src/core/org/apache/jmeter/control/gui/WorkBenchGui.java @@ -0,0 +1,158 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import java.util.Collection; + +import javax.swing.JCheckBox; +import javax.swing.JMenu; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.util.JMeterUtils; + +/** + * JMeter GUI component representing a work bench where users can make + * preparations for the test plan. + * + */ +public class WorkBenchGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + // This check-box defines whether to save WorkBench content or not + private JCheckBox saveWorkBench; + + /** + * Create a new WorkbenchGui. + */ + public WorkBenchGui() { + super(); + init(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns null, since the WorkBench appears at + * the top level of the tree and cannot be added elsewhere. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return null; + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + WorkBench wb = new WorkBench(); + modifyTestElement(wb); + return wb; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement wb) { + super.configureTestElement(wb); + ((WorkBench)wb).setSaveWorkBench(saveWorkBench.isSelected()); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof WorkBench) { + WorkBench tp = (WorkBench) el; + saveWorkBench.setSelected(tp.getSaveWorkBench()); + } + } + + @Override + public void clearGui() { + super.clearGui(); + saveWorkBench.setSelected(false); + } + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * The WorkBench returns a popup menu allowing you to add anything. + * + * @return a JPopupMenu appropriate for the component. + */ + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu menu = new JPopupMenu(); + JMenu addMenu = MenuFactory.makeMenus(new String[] { + MenuFactory.NON_TEST_ELEMENTS, + MenuFactory.CONTROLLERS, + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.TIMERS, + MenuFactory.PRE_PROCESSORS, + MenuFactory.SAMPLERS, + MenuFactory.POST_PROCESSORS, + MenuFactory.ASSERTIONS, + MenuFactory.LISTENERS, + }, + JMeterUtils.getResString("add"), // $NON-NLS-1$ + ActionNames.ADD); + menu.add(addMenu); + MenuFactory.addPasteResetMenu(menu); + MenuFactory.addFileMenu(menu); + return menu; + } + + @Override + public String getLabelResource() { + return "workbench_title"; // $NON-NLS-1$ + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + VerticalPanel workBenchPropsPanel = new VerticalPanel(5, 0); + + saveWorkBench = new JCheckBox(JMeterUtils.getResString("save_workbench")); + workBenchPropsPanel.add(saveWorkBench); + add(workBenchPropsPanel, BorderLayout.CENTER); + } +} diff --git a/src/core/org/apache/jmeter/engine/ClientJMeterEngine.java b/src/core/org/apache/jmeter/engine/ClientJMeterEngine.java new file mode 100644 index 00000000000..5eaab8f510e --- /dev/null +++ b/src/core/org/apache/jmeter/engine/ClientJMeterEngine.java @@ -0,0 +1,204 @@ +/* + * 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.engine; + +import java.io.File; +import java.net.MalformedURLException; +import java.rmi.Naming; +import java.rmi.NotBoundException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.server.RemoteObject; +import java.util.Properties; + +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Class to run remote tests from the client JMeter and collect remote samples + */ +public class ClientJMeterEngine implements JMeterEngine { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Object LOCK = new Object(); + + private RemoteJMeterEngine remote; + + private HashTree test; + + private final String host; + + private static RemoteJMeterEngine getEngine(String h) throws MalformedURLException, RemoteException, + NotBoundException { + final String name = "//" + h + "/" + RemoteJMeterEngineImpl.JMETER_ENGINE_RMI_NAME; // $NON-NLS-1$ $NON-NLS-2$ + Remote remobj = Naming.lookup(name); + if (remobj instanceof RemoteJMeterEngine){ + final RemoteJMeterEngine rje = (RemoteJMeterEngine) remobj; + if (remobj instanceof RemoteObject){ + RemoteObject robj = (RemoteObject) remobj; + System.out.println("Using remote object: "+robj.getRef().remoteToString()); + } + return rje; + } + throw new RemoteException("Could not find "+name); + } + + public ClientJMeterEngine(String host) throws MalformedURLException, NotBoundException, RemoteException { + this.remote = getEngine(host); + this.host = host; + } + + /** {@inheritDoc} */ + @Override + public void configure(HashTree testTree) { + TreeCloner cloner = new TreeCloner(false); + testTree.traverse(cloner); + test = cloner.getClonedTree(); + } + + /** {@inheritDoc} */ + @Override + public void stopTest(boolean now) { + log.info("about to "+(now ? "stop" : "shutdown")+" remote test on "+host); + try { + remote.rstopTest(now); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + } + + /** {@inheritDoc} */ + @Override + public void reset() { + try { + try { + remote.rreset(); + } catch (java.rmi.ConnectException e) { + log.info("Retry reset after: "+e); + remote = getEngine(host); + remote.rreset(); + } + } catch (Exception ex) { + log.error("Failed to reset remote engine", ex); // $NON-NLS-1$ + } + } + + @Override + public void runTest() throws JMeterEngineException { + log.info("running clientengine run method"); + + // See https://bz.apache.org/bugzilla/show_bug.cgi?id=55510 + JMeterContextService.clearTotalThreads(); + HashTree testTree = test; + + synchronized(testTree) { + testTree.traverse(new PreCompiler(true)); // limit the changes to client only test elements + testTree.traverse(new TurnElementsOn()); + testTree.traverse(new ConvertListeners()); + } + + String methodName="unknown"; + try { + JMeterContextService.startTest(); + /* + * Add fix for Deadlocks, see: + * + * See https://bz.apache.org/bugzilla/show_bug.cgi?id=48350 + */ + File baseDirRelative = FileServer.getFileServer().getBaseDirRelative(); + String scriptName = FileServer.getFileServer().getScriptName(); + synchronized(LOCK) + { + methodName="rconfigure()"; + remote.rconfigure(testTree, host, baseDirRelative, scriptName); + } + log.info("sent test to " + host + " basedir='"+baseDirRelative+"'"); // $NON-NLS-1$ + if(savep == null) { + savep = new Properties(); + } + log.info("Sending properties "+savep); + try { + methodName="rsetProperties()"; + remote.rsetProperties(savep); + } catch (RemoteException e) { + log.warn("Could not set properties: " + e.toString()); + } + methodName="rrunTest()"; + remote.rrunTest(); + log.info("sent run command to "+ host); + } catch (IllegalStateException ex) { + log.error("Error in "+methodName+" method "+ex); // $NON-NLS-1$ $NON-NLS-2$ + tidyRMI(log); + throw ex; // Don't wrap this error - display it as is + } catch (Exception ex) { + log.error("Error in "+methodName+" method "+ex); // $NON-NLS-1$ $NON-NLS-2$ + tidyRMI(log); + throw new JMeterEngineException("Error in "+methodName+" method "+ex, ex); // $NON-NLS-1$ $NON-NLS-2$ + } + } + + /** + * Tidy up RMI access to allow JMeter client to exit. + * Currently just interrups the "RMI Reaper" thread. + * @param logger where to log the information + */ + public static void tidyRMI(Logger logger) { + String reaperRE = JMeterUtils.getPropDefault("rmi.thread.name", "^RMI Reaper$"); + for(Thread t : Thread.getAllStackTraces().keySet()){ + String name = t.getName(); + if (name.matches(reaperRE)) { + logger.info("Interrupting "+name); + t.interrupt(); + } + } + } + + /** {@inheritDoc} */ + // Called by JMeter ListenToTest if remoteStop is true + @Override + public void exit() { + log.info("about to exit remote server on "+host); + try { + remote.rexit(); + } catch (RemoteException e) { + log.warn("Could not perform remote exit: " + e.toString()); + } + } + + private Properties savep; + /** {@inheritDoc} */ + @Override + public void setProperties(Properties p) { + savep = p; + // Sent later + } + + @Override + public boolean isActive() { + return true; + } + + public String getHost() { + return host; + } +} diff --git a/src/core/org/apache/jmeter/engine/ConvertListeners.java b/src/core/org/apache/jmeter/engine/ConvertListeners.java new file mode 100644 index 00000000000..4b4408028dd --- /dev/null +++ b/src/core/org/apache/jmeter/engine/ConvertListeners.java @@ -0,0 +1,112 @@ +/* + * 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.engine; + +import java.rmi.RemoteException; + +import org.apache.jmeter.samplers.RemoteListenerWrapper; +import org.apache.jmeter.samplers.RemoteSampleListener; +import org.apache.jmeter.samplers.RemoteSampleListenerImpl; +import org.apache.jmeter.samplers.RemoteSampleListenerWrapper; +import org.apache.jmeter.samplers.RemoteTestListenerWrapper; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.threads.RemoteThreadsListenerImpl; +import org.apache.jmeter.threads.RemoteThreadsListenerTestElement; +import org.apache.jmeter.threads.RemoteThreadsListenerWrapper; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Converts the Remoteable Test and Sample Listeners in the test tree by wrapping + * them with RemoteSampleListeners so that the samples are returned to the client. + * + * N.B. Does not handle ThreadListeners. + * + */ +public class ConvertListeners implements HashTreeTraverser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * {@inheritDoc} + */ + @Override + public void addNode(Object node, HashTree subTree) { + for (Object item : subTree.list()) { + if (item instanceof AbstractThreadGroup) { + log.debug("num threads = " + ((AbstractThreadGroup) item).getNumThreads()); + } + if (item instanceof Remoteable) { + if (item instanceof RemoteThreadsListenerTestElement){ + // Used for remote notification of threads start/stop,see BUG 54152 + try { + RemoteThreadsListenerWrapper wrapper = new RemoteThreadsListenerWrapper(new RemoteThreadsListenerImpl()); + subTree.replaceKey(item, wrapper); + } catch (RemoteException e) { + log.error("Error replacing "+RemoteThreadsListenerTestElement.class.getName() + +" by wrapper:"+RemoteThreadsListenerWrapper.class.getName(), e); + } + continue; + } + if (item instanceof ThreadListener){ + // TODO Document the reason for this + log.error("Cannot handle ThreadListener Remotable item "+item.getClass().getName()); + continue; + } + try { + RemoteSampleListener rtl = new RemoteSampleListenerImpl(item); + if (item instanceof TestStateListener && item instanceof SampleListener) { // TL - all + RemoteListenerWrapper wrap = new RemoteListenerWrapper(rtl); + subTree.replaceKey(item, wrap); + } else if (item instanceof TestStateListener) { + RemoteTestListenerWrapper wrap = new RemoteTestListenerWrapper(rtl); + subTree.replaceKey(item, wrap); + } else if (item instanceof SampleListener) { + RemoteSampleListenerWrapper wrap = new RemoteSampleListenerWrapper(rtl); + subTree.replaceKey(item, wrap); + } else { + log.warn("Could not replace Remotable item "+item.getClass().getName()); + } + } catch (RemoteException e) { + log.error("", e); // $NON-NLS-1$ + } + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void subtractNode() { + } + + /** + * {@inheritDoc} + */ + @Override + public void processPath() { + } + +} diff --git a/src/core/org/apache/jmeter/engine/DistributedRunner.java b/src/core/org/apache/jmeter/engine/DistributedRunner.java new file mode 100644 index 00000000000..4b62580daf9 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/DistributedRunner.java @@ -0,0 +1,274 @@ +/* + * 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.engine; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.MalformedURLException; +import java.rmi.NotBoundException; +import java.rmi.RemoteException; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class serves all responsibility of starting and stopping distributed tests. + * It was refactored from JMeter and RemoteStart classes to unify retry behavior. + * + * @see org.apache.jmeter.JMeter + * @see org.apache.jmeter.gui.action.RemoteStart + */ +public class DistributedRunner { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String RETRIES_NUMBER = "client.tries"; // $NON-NLS-1$ + public static final String RETRIES_DELAY = "client.retries_delay"; // $NON-NLS-1$ + public static final String CONTINUE_ON_FAIL = "client.continue_on_fail"; // $NON-NLS-1$ + + private final Properties remoteProps; + private final boolean continueOnFail; + private final int retriesDelay; + private final int retriesNumber; + private PrintStream stdout = new PrintStream(new SilentOutputStream()); + private PrintStream stderr = new PrintStream(new SilentOutputStream()); + private final Map engines = new HashMap(); + + + public DistributedRunner() { + this(new Properties()); + } + + public DistributedRunner(Properties props) { + remoteProps = props; + retriesNumber = JMeterUtils.getPropDefault(RETRIES_NUMBER, 1); + continueOnFail = JMeterUtils.getPropDefault(CONTINUE_ON_FAIL, false); + retriesDelay = JMeterUtils.getPropDefault(RETRIES_DELAY, 5000); + } + + public void init(List addresses, HashTree tree) { + // converting list into mutable version + List addrs = new LinkedList(addresses); + + for (int tryNo = 0; tryNo < retriesNumber; tryNo++) { + if (tryNo > 0) { + println("Following remote engines will retry configuring: " + addrs); + println("Pausing before retry for " + retriesDelay + "ms"); + try { + Thread.sleep(retriesDelay); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted while initializing remote", e); + } + } + + int idx = 0; + while (idx < addrs.size()) { + String address = addrs.get(idx); + println("Configuring remote engine: " + address); + JMeterEngine engine = getClientEngine(address.trim(), tree); + if (engine != null) { + engines.put(address, engine); + addrs.remove(address); + } else { + println("Failed to configure " + address); + idx++; + } + } + + if (addrs.size() == 0) { + break; + } + } + + if (addrs.size() > 0) { + String msg = "Following remote engines could not be configured:" + addrs; + if (!continueOnFail || engines.size() == 0) { + stop(); + throw new RuntimeException(msg); + } else { + println(msg); + println("Continuing without failed engines..."); + } + } + } + + /** + * Starts a remote testing engines + * + * @param addresses list of the DNS names or IP addresses of the remote testing engines + */ + public void start(List addresses) { + println("Starting remote engines"); + long now = System.currentTimeMillis(); + println("Starting the test @ " + new Date(now) + " (" + now + ")"); + for (String address : addresses) { + try { + if (engines.containsKey(address)) { + engines.get(address).runTest(); + } else { + log.warn("Host not found in list of active engines: " + address); + } + } catch (IllegalStateException e) { + JMeterUtils.reportErrorToUser(e.getMessage(), JMeterUtils.getResString("remote_error_starting")); // $NON-NLS-1$ + } catch (JMeterEngineException e) { + JMeterUtils.reportErrorToUser(e.getMessage(), JMeterUtils.getResString("remote_error_starting")); // $NON-NLS-1$ + } + } + println("Remote engines have been started"); + } + + /** + * Start all engines that were previously initiated + */ + public void start() { + List addresses = new LinkedList(); + addresses.addAll(engines.keySet()); + start(addresses); + } + + public void stop(List addresses) { + println("Stopping remote engines"); + for (String address : addresses) { + try { + if (engines.containsKey(address)) { + engines.get(address).stopTest(true); + } else { + log.warn("Host not found in list of active engines: " + address); + } + } catch (RuntimeException e) { + errln("Failed to stop test on " + address, e); + } + } + println("Remote engines have been stopped"); + } + + /** + * Stop all engines that were previously initiated + */ + public void stop() { + List addresses = new LinkedList(); + addresses.addAll(engines.keySet()); + stop(addresses); + } + + public void shutdown(List addresses) { + println("Shutting down remote engines"); + for (String address : addresses) { + try { + if (engines.containsKey(address)) { + engines.get(address).stopTest(false); + } else { + log.warn("Host not found in list of active engines: " + address); + } + + } catch (RuntimeException e) { + errln("Failed to shutdown test on " + address, e); + } + } + println("Remote engines have been shut down"); + } + + public void exit(List addresses) { + println("Exiting remote engines"); + for (String address : addresses) { + try { + if (engines.containsKey(address)) { + engines.get(address).exit(); + } else { + log.warn("Host not found in list of active engines: " + address); + } + } catch (RuntimeException e) { + errln("Failed to exit on " + address, e); + } + } + println("Remote engines have been exited"); + } + + private JMeterEngine getClientEngine(String address, HashTree testTree) { + JMeterEngine engine; + try { + engine = createEngine(address); + engine.configure(testTree); + if (!remoteProps.isEmpty()) { + engine.setProperties(remoteProps); + } + return engine; + } catch (Exception ex) { + log.error("Failed to create engine at " + address, ex); + JMeterUtils.reportErrorToUser(ex.getMessage(), + JMeterUtils.getResString("remote_error_init") + ": " + address); // $NON-NLS-1$ $NON-NLS-2$ + return null; + } + } + + /** + * A factory method that might be overridden for unit testing + * + * @param address address for engine + * @return engine instance + * @throws RemoteException + * @throws NotBoundException + * @throws MalformedURLException + */ + protected JMeterEngine createEngine(String address) throws RemoteException, NotBoundException, MalformedURLException { + return new ClientJMeterEngine(address); + } + + private void println(String s) { + log.info(s); + stdout.println(s); + } + + private void errln(String s, Exception e) { + log.error(s, e); + stderr.println(s + ": "); + e.printStackTrace(stderr); + } + + public void setStdout(PrintStream stdout) { + this.stdout = stdout; + } + + public void setStdErr(PrintStream stdErr) { + this.stderr = stdErr; + } + + private static class SilentOutputStream extends OutputStream { + @Override + public void write(int b) throws IOException { + // enjoy the silence + } + } + + /** + * @return {@link Collection} of {@link JMeterEngine} + */ + public Collection getEngines() { + return engines.values(); + } +} diff --git a/src/core/org/apache/jmeter/engine/JMeterEngine.java b/src/core/org/apache/jmeter/engine/JMeterEngine.java new file mode 100644 index 00000000000..f08f7a07415 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/JMeterEngine.java @@ -0,0 +1,42 @@ +/* + * 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.engine; + +import java.util.Properties; + +import org.apache.jorphan.collections.HashTree; + +/** + * This interface is implemented by classes that can run JMeter tests. + */ +public interface JMeterEngine { + void configure(HashTree testPlan); + + void runTest() throws JMeterEngineException; + + void stopTest(boolean now); + + void reset(); + + void setProperties(Properties p); + + void exit(); + + boolean isActive(); +} diff --git a/src/core/org/apache/jmeter/engine/JMeterEngineException.java b/src/core/org/apache/jmeter/engine/JMeterEngineException.java new file mode 100644 index 00000000000..eb4540ac2cf --- /dev/null +++ b/src/core/org/apache/jmeter/engine/JMeterEngineException.java @@ -0,0 +1,44 @@ +/* + * 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.engine; + +import java.io.Serializable; + +/** + * Exception class for use by {@link JMeterEngine#runTest()} and {@link RemoteJMeterEngine#rrunTest()} + */ +public class JMeterEngineException extends Exception implements Serializable { + private static final long serialVersionUID = 240L; + + public JMeterEngineException() { + super(); + } + + public JMeterEngineException(String msg) { + super(msg); + } + + public JMeterEngineException(Throwable t) { + super(t); + } + + public JMeterEngineException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/src/core/org/apache/jmeter/engine/PreCompiler.java b/src/core/org/apache/jmeter/engine/PreCompiler.java new file mode 100644 index 00000000000..e5651e5422f --- /dev/null +++ b/src/core/org/apache/jmeter/engine/PreCompiler.java @@ -0,0 +1,109 @@ +/* + * 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.engine; + +import java.util.Map; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Class to replace function and variable references in the test tree. + * + */ +public class PreCompiler implements HashTreeTraverser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final ValueReplacer replacer; + +// Used by both StandardJMeterEngine and ClientJMeterEngine. +// In the latter case, only ResultCollectors are updated, +// as only these are relevant to the client, and updating +// other elements causes all sorts of problems. + private final boolean isRemote; // skip certain processing for remote tests + + public PreCompiler() { + replacer = new ValueReplacer(); + isRemote = false; + } + + public PreCompiler(boolean remote) { + replacer = new ValueReplacer(); + isRemote = remote; + } + + /** {@inheritDoc} */ + @Override + public void addNode(Object node, HashTree subTree) { + if(isRemote && node instanceof ResultCollector) + { + try { + replacer.replaceValues((TestElement) node); + } catch (InvalidVariableException e) { + log.error("invalid variables", e); + } + } + if (isRemote) { + return; + } + if(node instanceof TestElement) + { + try { + replacer.replaceValues((TestElement) node); + } catch (InvalidVariableException e) { + log.error("invalid variables", e); + } + } + if (node instanceof TestPlan) { + ((TestPlan)node).prepareForPreCompile(); //A hack to make user-defined variables in the testplan element more dynamic + Map args = ((TestPlan) node).getUserDefinedVariables(); + replacer.setUserDefinedVariables(args); + JMeterVariables vars = new JMeterVariables(); + vars.putAll(args); + JMeterContextService.getContext().setVariables(vars); + } + + if (node instanceof Arguments) { + ((Arguments)node).setRunningVersion(true); + Map args = ((Arguments) node).getArgumentsAsMap(); + replacer.addVariables(args); + JMeterContextService.getContext().getVariables().putAll(args); + } + } + + /** {@inheritDoc} */ + @Override + public void subtractNode() { + } + + /** {@inheritDoc} */ + @Override + public void processPath() { + } +} diff --git a/src/core/org/apache/jmeter/engine/RemoteJMeterEngine.java b/src/core/org/apache/jmeter/engine/RemoteJMeterEngine.java new file mode 100644 index 00000000000..3103f33d261 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/RemoteJMeterEngine.java @@ -0,0 +1,43 @@ +/* + * 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.engine; + +import java.io.File; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.Properties; + +import org.apache.jorphan.collections.HashTree; + +/** + * This is the interface for the RMI server engine, i.e. {@link RemoteJMeterEngineImpl} + */ +public interface RemoteJMeterEngine extends Remote { + void rconfigure(HashTree testTree, String host, File jmxBase, String scriptName) throws RemoteException; + + void rrunTest() throws RemoteException, JMeterEngineException; + + void rstopTest(boolean now) throws RemoteException; + + void rreset() throws RemoteException; + + void rsetProperties(Properties p) throws RemoteException; + + void rexit() throws RemoteException; +} diff --git a/src/core/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java b/src/core/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java new file mode 100644 index 00000000000..eaab2e0f3da --- /dev/null +++ b/src/core/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java @@ -0,0 +1,238 @@ +/* + * 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.engine; + +import java.io.File; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.rmi.NotBoundException; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.ServerNotActiveException; +import java.util.Iterator; +import java.util.Properties; + +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This is the JMeter server main code. + */ +public final class RemoteJMeterEngineImpl extends java.rmi.server.UnicastRemoteObject implements RemoteJMeterEngine { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + static final String JMETER_ENGINE_RMI_NAME = "JMeterEngine"; // $NON-NLS-1$ + + private transient JMeterEngine backingEngine; + + private transient Thread ownerThread; + + private static final int DEFAULT_RMI_PORT = + JMeterUtils.getPropDefault("server.rmi.port", 1099); // $NON-NLS-1$ + + private static final int DEFAULT_LOCAL_PORT = + JMeterUtils.getPropDefault("server.rmi.localport", 0); // $NON-NLS-1$ + + static{ + if (DEFAULT_LOCAL_PORT != 0){ + System.out.println("Using local port: "+DEFAULT_LOCAL_PORT); + } + } + + // Should we create our own copy of the RMI registry? + private static final boolean createServer = + JMeterUtils.getPropDefault("server.rmi.create", true); // $NON-NLS-1$ + + private final Object LOCK = new Object(); + + private final int rmiPort; + + private Properties remotelySetProperties; + + private RemoteJMeterEngineImpl(int localPort, int rmiPort) throws RemoteException { + super(localPort); // Create this object using the specified port (0 means anonymous) + this.rmiPort = rmiPort; + System.out.println("Created remote object: "+this.getRef().remoteToString()); + } + + public static void startServer(int rmiPort) throws RemoteException { + RemoteJMeterEngineImpl engine = new RemoteJMeterEngineImpl(DEFAULT_LOCAL_PORT, rmiPort == 0 ? DEFAULT_RMI_PORT : rmiPort); + engine.init(); + } + + private void init() throws RemoteException { + log.info("Starting backing engine on " + this.rmiPort); + InetAddress localHost=null; + // Bug 47980 - allow override of local hostname + String host = System.getProperties().getProperty("java.rmi.server.hostname"); // $NON-NLS-1$ + try { + if( host==null ) { + localHost = InetAddress.getLocalHost(); + } else { + localHost = InetAddress.getByName(host); + } + } catch (UnknownHostException e1) { + throw new RemoteException("Cannot start. Unable to get local host IP address.", e1); + } + log.info("Local IP address="+localHost.getHostAddress()); + // BUG 52469 : Allow loopback address for SSH Tunneling of RMI traffic + if (host == null && localHost.isLoopbackAddress()){ + String hostName = localHost.getHostName(); + throw new RemoteException("Cannot start. "+hostName+" is a loopback address."); + } + if (localHost.isSiteLocalAddress()){ + // should perhaps be log.warn, but this causes the client-server test to fail + log.info("IP address is a site-local address; this may cause problems with remote access.\n" + + "\tCan be overridden by defining the system property 'java.rmi.server.hostname' - see jmeter-server script file"); + } + log.debug("This = " + this); + if (createServer){ + log.info("Creating RMI registry (server.rmi.create=true)"); + try { + LocateRegistry.createRegistry(this.rmiPort); + } catch (RemoteException e){ + String msg="Problem creating registry: "+e; + log.warn(msg); + System.err.println(msg); + System.err.println("Continuing..."); + } + } + try { + Registry reg = LocateRegistry.getRegistry(this.rmiPort); + reg.rebind(JMETER_ENGINE_RMI_NAME, this); + log.info("Bound to registry on port " + this.rmiPort); + } catch (Exception ex) { + log.error("rmiregistry needs to be running to start JMeter in server " + "mode\n\t" + ex.toString()); + // Throw an Exception to ensure caller knows ... + throw new RemoteException("Cannot start. See server log file.", ex); + } + } + + /** + * Adds a feature to the ThreadGroup attribute of the RemoteJMeterEngineImpl + * object. + * + * @param testTree + * the feature to be added to the ThreadGroup attribute + */ + @Override + public void rconfigure(HashTree testTree, String host, File jmxBase, String scriptName) throws RemoteException { + log.info("Creating JMeter engine on host "+host+" base '"+jmxBase+"'"); + try { + log.info("Remote client host: " + getClientHost()); + } catch (ServerNotActiveException e) { + // ignored + } + synchronized(LOCK) { // close window where another remote client might jump in + if (backingEngine != null && backingEngine.isActive()) { + log.warn("Engine is busy - cannot create JMeter engine"); + throw new IllegalStateException("Engine is busy - please try later"); + } + ownerThread = Thread.currentThread(); + backingEngine = new StandardJMeterEngine(host); + backingEngine.configure(testTree); // sets active = true + } + FileServer.getFileServer().setScriptName(scriptName); + FileServer.getFileServer().setBase(jmxBase); + } + + @Override + public void rrunTest() throws RemoteException, JMeterEngineException, IllegalStateException { + log.info("Running test"); + checkOwner("runTest"); + backingEngine.runTest(); + } + + @Override + public void rreset() throws RemoteException, IllegalStateException { + // Mail on userlist reported NPE here - looks like only happens if there are network errors, but check anyway + if (backingEngine != null) { + log.info("Reset"); + checkOwner("reset"); + backingEngine.reset(); + } else { + log.warn("Backing engine is null, ignoring reset"); + } + } + + @Override + public void rstopTest(boolean now) throws RemoteException { + if (now) { + log.info("Stopping test ..."); + } else { + log.info("Shutting test ..."); + } + backingEngine.stopTest(now); + log.info("... stopped"); + } + + /* + * Called by: + * - ClientJMeterEngine.exe() which is called on remoteStop + */ + @Override + public void rexit() throws RemoteException { + log.info("Exitting"); + backingEngine.exit(); + // Tidy up any objects we created + Registry reg = LocateRegistry.getRegistry(this.rmiPort); + try { + reg.unbind(JMETER_ENGINE_RMI_NAME); + } catch (NotBoundException e) { + log.warn(JMETER_ENGINE_RMI_NAME+" is not bound",e); + } + log.info("Unbound from registry"); + // Help with garbage control + JMeterUtils.helpGC(); + } + + @Override + public void rsetProperties(Properties p) throws RemoteException, IllegalStateException { + checkOwner("setProperties"); + if(remotelySetProperties != null) { + Properties jmeterProperties = JMeterUtils.getJMeterProperties(); + log.info("Cleaning previously set properties "+remotelySetProperties); + for (Iterator iterator = remotelySetProperties.keySet().iterator(); iterator.hasNext();) { + String key = (String) iterator.next(); + jmeterProperties.remove(key); + } + } + backingEngine.setProperties(p); + this.remotelySetProperties = p; + } + + /** + * Check if the caller owns the engine. + * @param methodName the name of the method for the log message + * @throws IllegalStateException if the caller is not the owner. + */ + private void checkOwner(String methodName) throws IllegalStateException { + if (ownerThread != null && ownerThread != Thread.currentThread()){ + String msg = "The engine is not owned by this thread - cannot call "+methodName; + log.warn(msg); + throw new IllegalStateException(msg); + } + } +} diff --git a/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java b/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java new file mode 100644 index 00000000000..f4999bd4e24 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java @@ -0,0 +1,588 @@ +/* + * 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.engine; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +import org.apache.jmeter.JMeter; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.ListenerNotifier; +import org.apache.jmeter.threads.PostThreadGroup; +import org.apache.jmeter.threads.SetupThreadGroup; +import org.apache.jmeter.threads.TestCompiler; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; +import org.apache.jorphan.collections.SearchByClass; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Runs JMeter tests, either directly for local GUI and non-GUI invocations, + * or started by {@link RemoteJMeterEngineImpl} when running in server mode. + */ +public class StandardJMeterEngine implements JMeterEngine, Runnable { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Should we exit at end of the test? (only applies to server, because host is non-null) + private static final boolean exitAfterTest = + JMeterUtils.getPropDefault("server.exitaftertest", false); // $NON-NLS-1$ + + private static final boolean startListenersLater = + JMeterUtils.getPropDefault("jmeterengine.startlistenerslater", true); // $NON-NLS-1$ + + static { + if (startListenersLater){ + log.info("Listeners will be started after enabling running version"); + log.info("To revert to the earlier behaviour, define jmeterengine.startlistenerslater=false"); + } + } + + // Allow engine and threads to be stopped from outside a thread + // e.g. from beanshell server + // Assumes that there is only one instance of the engine + // at any one time so it is not guaranteed to work ... + private volatile static StandardJMeterEngine engine; + + /* + * Allow functions etc to register for testStopped notification. + * Only used by the function parser so far. + * The list is merged with the testListeners and then cleared. + */ + private static final List testList = new ArrayList(); + + /** Whether to call System.exit(0) in exit after stopping RMI */ + private static final boolean REMOTE_SYSTEM_EXIT = JMeterUtils.getPropDefault("jmeterengine.remote.system.exit", false); + + /** Whether to call System.exit(1) if threads won't stop */ + private static final boolean SYSTEM_EXIT_ON_STOP_FAIL = JMeterUtils.getPropDefault("jmeterengine.stopfail.system.exit", true); + + /** Whether to call System.exit(0) unconditionally at end of non-GUI test */ + private static final boolean SYSTEM_EXIT_FORCED = JMeterUtils.getPropDefault("jmeterengine.force.system.exit", false); + + /** Flag to show whether test is running. Set to false to stop creating more threads. */ + private volatile boolean running = false; + + /** Flag to show whether test was shutdown gracefully. */ + private volatile boolean shutdown = false; + + /** Flag to show whether engine is active. Set to false at end of test. */ + private volatile boolean active = false; + + /** Thread Groups run sequentially */ + private volatile boolean serialized = false; + + /** tearDown Thread Groups run after shutdown of main threads */ + private volatile boolean tearDownOnShutdown = false; + + private HashTree test; + + private final String host; + + // The list of current thread groups; may be setUp, main, or tearDown. + private final List groups = new CopyOnWriteArrayList(); + + public static void stopEngineNow() { + if (engine != null) {// May be null if called from Unit test + engine.stopTest(true); + } + } + + public static void stopEngine() { + if (engine != null) { // May be null if called from Unit test + engine.stopTest(false); + } + } + + public static synchronized void register(TestStateListener tl) { + testList.add(tl); + } + + public static boolean stopThread(String threadName) { + return stopThread(threadName, false); + } + + public static boolean stopThreadNow(String threadName) { + return stopThread(threadName, true); + } + + private static boolean stopThread(String threadName, boolean now) { + if (engine == null) { + return false;// e.g. not yet started + } + boolean wasStopped = false; + // ConcurrentHashMap does not need synch. here + for (AbstractThreadGroup threadGroup : engine.groups) { + wasStopped = wasStopped || threadGroup.stopThread(threadName, now); + } + return wasStopped; + } + + // End of code to allow engine to be controlled remotely + + public StandardJMeterEngine() { + this(null); + } + + public StandardJMeterEngine(String host) { + this.host = host; + // Hack to allow external control + engine = this; + } + + @Override + public void configure(HashTree testTree) { + // Is testplan serialised? + SearchByClass testPlan = new SearchByClass(TestPlan.class); + testTree.traverse(testPlan); + Object[] plan = testPlan.getSearchResults().toArray(); + if (plan.length == 0) { + throw new RuntimeException("Could not find the TestPlan class!"); + } + TestPlan tp = (TestPlan) plan[0]; + serialized = tp.isSerialized(); + tearDownOnShutdown = tp.isTearDownOnShutdown(); + active = true; + test = testTree; + } + + @Override + public void runTest() throws JMeterEngineException { + if (host != null){ + long now=System.currentTimeMillis(); + System.out.println("Starting the test on host " + host + " @ "+new Date(now)+" ("+now+")"); + } + try { + Thread runningThread = new Thread(this, "StandardJMeterEngine"); + runningThread.start(); + } catch (Exception err) { + stopTest(); + throw new JMeterEngineException(err); + } + } + + private void removeThreadGroups(List elements) { + Iterator iter = elements.iterator(); + while (iter.hasNext()) { // Can't use for loop here because we remove elements + Object item = iter.next(); + if (item instanceof AbstractThreadGroup) { + iter.remove(); + } else if (!(item instanceof TestElement)) { + iter.remove(); + } + } + } + + @SuppressWarnings("deprecation") // Deliberate use of deprecated method + private void notifyTestListenersOfStart(SearchByClass testListeners) { + for (TestStateListener tl : testListeners.getSearchResults()) { + if (tl instanceof TestBean) { + TestBeanHelper.prepare((TestElement) tl); + } + if (host == null) { + tl.testStarted(); + } else { + tl.testStarted(host); + } + } + } + + private void notifyTestListenersOfEnd(SearchByClass testListeners) { + log.info("Notifying test listeners of end of test"); + for (TestStateListener tl : testListeners.getSearchResults()) { + try { + if (host == null) { + tl.testEnded(); + } else { + tl.testEnded(host); + } + } catch (Exception e) { + log.warn("Error encountered during shutdown of "+tl.toString(),e); + } + } + if (host != null) { + log.info("Test has ended on host "+host); + long now=System.currentTimeMillis(); + System.out.println("Finished the test on host " + host + " @ "+new Date(now)+" ("+now+")" + +(exitAfterTest ? " - exit requested." : "")); + if (exitAfterTest){ + exit(); + } + } + active=false; + } + + @Override + public void reset() { + if (running) { + stopTest(); + } + } + + public synchronized void stopTest() { + stopTest(true); + } + + @Override + public synchronized void stopTest(boolean now) { + shutdown = !now; + Thread stopThread = new Thread(new StopTest(now)); + stopThread.start(); + } + + private class StopTest implements Runnable { + private final boolean now; + + private StopTest(boolean b) { + now = b; + } + + @Override + public void run() { + running = false; + engine = null; + if (now) { + tellThreadGroupsToStop(); + pause(10 * countStillActiveThreads()); + boolean stopped = verifyThreadsStopped(); + if (!stopped) { // we totally failed to stop the test + if (JMeter.isNonGUI()) { + // TODO should we call test listeners? That might hang too ... + log.fatalError(JMeterUtils.getResString("stopping_test_failed")); //$NON-NLS-1$ + if (SYSTEM_EXIT_ON_STOP_FAIL) { // default is true + log.fatalError("Exitting"); + System.out.println("Fatal error, could not stop test, exitting"); + System.exit(1); + } else { + System.out.println("Fatal error, could not stop test"); + } + } else { + JMeterUtils.reportErrorToUser( + JMeterUtils.getResString("stopping_test_failed"), //$NON-NLS-1$ + JMeterUtils.getResString("stopping_test_title")); //$NON-NLS-1$ + } + } // else will be done by threadFinished() + } else { + stopAllThreadGroups(); + } + } + } + + @Override + public void run() { + log.info("Running the test!"); + running = true; + + /* + * Ensure that the sample variables are correctly initialised for each run. + * TODO is this the best way to do this? should it be done elsewhere ? + */ + SampleEvent.initSampleVariables(); + + JMeterContextService.startTest(); + try { + PreCompiler compiler = new PreCompiler(); + test.traverse(compiler); + } catch (RuntimeException e) { + log.error("Error occurred compiling the tree:",e); + JMeterUtils.reportErrorToUser("Error occurred compiling the tree: - see log file"); + return; // no point continuing + } + /** + * Notification of test listeners needs to happen after function + * replacement, but before setting RunningVersion to true. + */ + SearchByClass testListeners = new SearchByClass(TestStateListener.class); // TL - S&E + test.traverse(testListeners); + + // Merge in any additional test listeners + // currently only used by the function parser + testListeners.getSearchResults().addAll(testList); + testList.clear(); // no longer needed + + if (!startListenersLater ) { notifyTestListenersOfStart(testListeners); } + test.traverse(new TurnElementsOn()); + if (startListenersLater) { notifyTestListenersOfStart(testListeners); } + + List testLevelElements = new LinkedList(test.list(test.getArray()[0])); + removeThreadGroups(testLevelElements); + + SearchByClass setupSearcher = new SearchByClass(SetupThreadGroup.class); + SearchByClass searcher = new SearchByClass(AbstractThreadGroup.class); + SearchByClass postSearcher = new SearchByClass(PostThreadGroup.class); + + test.traverse(setupSearcher); + test.traverse(searcher); + test.traverse(postSearcher); + + TestCompiler.initialize(); + // for each thread group, generate threads + // hand each thread the sampler controller + // and the listeners, and the timer + Iterator setupIter = setupSearcher.getSearchResults().iterator(); + Iterator iter = searcher.getSearchResults().iterator(); + Iterator postIter = postSearcher.getSearchResults().iterator(); + + ListenerNotifier notifier = new ListenerNotifier(); + + int groupCount = 0; + JMeterContextService.clearTotalThreads(); + + if (setupIter.hasNext()) { + log.info("Starting setUp thread groups"); + while (running && setupIter.hasNext()) {//for each setup thread group + AbstractThreadGroup group = setupIter.next(); + groupCount++; + String groupName = group.getName(); + log.info("Starting setUp ThreadGroup: " + groupCount + " : " + groupName); + startThreadGroup(group, groupCount, setupSearcher, testLevelElements, notifier); + if (serialized && setupIter.hasNext()) { + log.info("Waiting for setup thread group: "+groupName+" to finish before starting next setup group"); + group.waitThreadsStopped(); + } + } + log.info("Waiting for all setup thread groups to exit"); + //wait for all Setup Threads To Exit + waitThreadsStopped(); + log.info("All Setup Threads have ended"); + groupCount=0; + JMeterContextService.clearTotalThreads(); + } + + groups.clear(); // The groups have all completed now + + /* + * Here's where the test really starts. Run a Full GC now: it's no harm + * at all (just delays test start by a tiny amount) and hitting one too + * early in the test can impair results for short tests. + */ + JMeterUtils.helpGC(); + + JMeterContextService.getContext().setSamplingStarted(true); + boolean mainGroups = running; // still running at this point, i.e. setUp was not cancelled + while (running && iter.hasNext()) {// for each thread group + AbstractThreadGroup group = iter.next(); + //ignore Setup and Post here. We could have filtered the searcher. but then + //future Thread Group objects wouldn't execute. + if (group instanceof SetupThreadGroup) { + continue; + } + if (group instanceof PostThreadGroup) { + continue; + } + groupCount++; + String groupName = group.getName(); + log.info("Starting ThreadGroup: " + groupCount + " : " + groupName); + startThreadGroup(group, groupCount, searcher, testLevelElements, notifier); + if (serialized && iter.hasNext()) { + log.info("Waiting for thread group: "+groupName+" to finish before starting next group"); + group.waitThreadsStopped(); + } + } // end of thread groups + if (groupCount == 0){ // No TGs found + log.info("No enabled thread groups found"); + } else { + if (running) { + log.info("All thread groups have been started"); + } else { + log.info("Test stopped - no more thread groups will be started"); + } + } + + //wait for all Test Threads To Exit + waitThreadsStopped(); + groups.clear(); // The groups have all completed now + + if (postIter.hasNext()){ + groupCount = 0; + JMeterContextService.clearTotalThreads(); + log.info("Starting tearDown thread groups"); + if (mainGroups && !running) { // i.e. shutdown/stopped during main thread groups + running = shutdown & tearDownOnShutdown; // re-enable for tearDown if necessary + } + while (running && postIter.hasNext()) {//for each setup thread group + AbstractThreadGroup group = postIter.next(); + groupCount++; + String groupName = group.getName(); + log.info("Starting tearDown ThreadGroup: " + groupCount + " : " + groupName); + startThreadGroup(group, groupCount, postSearcher, testLevelElements, notifier); + if (serialized && postIter.hasNext()) { + log.info("Waiting for post thread group: "+groupName+" to finish before starting next post group"); + group.waitThreadsStopped(); + } + } + waitThreadsStopped(); // wait for Post threads to stop + } + + notifyTestListenersOfEnd(testListeners); + JMeterContextService.endTest(); + if (JMeter.isNonGUI() && SYSTEM_EXIT_FORCED) { + log.info("Forced JVM shutdown requested at end of test"); + System.exit(0); + } + } + + /** + * @return total of active threads in all Thread Groups + */ + private int countStillActiveThreads() { + int reminingThreads= 0; + for (AbstractThreadGroup threadGroup : groups) { + reminingThreads += threadGroup.numberOfActiveThreads(); + } + return reminingThreads; + } + + private void startThreadGroup(AbstractThreadGroup group, int groupCount, SearchByClass searcher, List testLevelElements, ListenerNotifier notifier) + { + int numThreads = group.getNumThreads(); + JMeterContextService.addTotalThreads(numThreads); + boolean onErrorStopTest = group.getOnErrorStopTest(); + boolean onErrorStopTestNow = group.getOnErrorStopTestNow(); + boolean onErrorStopThread = group.getOnErrorStopThread(); + boolean onErrorStartNextLoop = group.getOnErrorStartNextLoop(); + String groupName = group.getName(); + log.info("Starting " + numThreads + " threads for group " + groupName + "."); + + if (onErrorStopTest) { + log.info("Test will stop on error"); + } else if (onErrorStopTestNow) { + log.info("Test will stop abruptly on error"); + } else if (onErrorStopThread) { + log.info("Thread will stop on error"); + } else if (onErrorStartNextLoop) { + log.info("Thread will start next loop on error"); + } else { + log.info("Thread will continue on error"); + } + ListedHashTree threadGroupTree = (ListedHashTree) searcher.getSubTree(group); + threadGroupTree.add(group, testLevelElements); + + groups.add(group); + group.start(groupCount, notifier, threadGroupTree, this); + } + + /** + * @return boolean true if all threads of all Threead Groups stopped + */ + private boolean verifyThreadsStopped() { + boolean stoppedAll = true; + // ConcurrentHashMap does not need synch. here + for (AbstractThreadGroup threadGroup : groups) { + stoppedAll = stoppedAll && threadGroup.verifyThreadsStopped(); + } + return stoppedAll; + } + + /** + * Wait for Group Threads to stop + */ + private void waitThreadsStopped() { + // ConcurrentHashMap does not need synch. here + for (AbstractThreadGroup threadGroup : groups) { + threadGroup.waitThreadsStopped(); + } + } + + /** + * For each thread group, invoke {@link AbstractThreadGroup#tellThreadsToStop()} + */ + private void tellThreadGroupsToStop() { + // ConcurrentHashMap does not need protecting + for (AbstractThreadGroup threadGroup : groups) { + threadGroup.tellThreadsToStop(); + } + } + + public void askThreadsToStop() { + if (engine != null) { // Will be null if StopTest thread has started + engine.stopTest(false); + } + } + + /** + * For each current thread group, invoke: + *
    + *
  • {@link AbstractThreadGroup#stop()} - set stop flag
  • + *
+ */ + private void stopAllThreadGroups() { + // ConcurrentHashMap does not need synch. here + for (AbstractThreadGroup threadGroup : groups) { + threadGroup.stop(); + } + } + + // Remote exit + // Called by RemoteJMeterEngineImpl.rexit() + // and by notifyTestListenersOfEnd() iff exitAfterTest is true; + // in turn that is called by the run() method and the StopTest class + // also called + @Override + public void exit() { + ClientJMeterEngine.tidyRMI(log); // This should be enough to allow server to exit. + if (REMOTE_SYSTEM_EXIT) { // default is false + log.warn("About to run System.exit(0) on "+host); + // Needs to be run in a separate thread to allow RMI call to return OK + Thread t = new Thread() { + @Override + public void run() { + pause(1000); // Allow RMI to complete + log.info("Bye from "+host); + System.out.println("Bye from "+host); + System.exit(0); + } + }; + t.start(); + } + } + + private void pause(long ms){ + try { + TimeUnit.MILLISECONDS.sleep(ms); + } catch (InterruptedException e) { + } + } + + @Override + public void setProperties(Properties p) { + log.info("Applying properties "+p); + JMeterUtils.getJMeterProperties().putAll(p); + } + + @Override + public boolean isActive() { + return active; + } +} diff --git a/src/core/org/apache/jmeter/engine/TreeCloner.java b/src/core/org/apache/jmeter/engine/TreeCloner.java new file mode 100644 index 00000000000..c114a03f819 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/TreeCloner.java @@ -0,0 +1,105 @@ +/* + * 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.engine; + +import java.util.LinkedList; + +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.collections.ListedHashTree; + +/** + * Clones the test tree, skipping test elements that implement {@link NoThreadClone} by default. + */ +public class TreeCloner implements HashTreeTraverser { + + private final ListedHashTree newTree; + + private final LinkedList objects = new LinkedList(); + + private final boolean honourNoThreadClone; + + /** + * Clone the test tree, honouring NoThreadClone markers. + * + */ + public TreeCloner() { + this(true); + } + + /** + * Clone the test tree. + * + * @param honourNoThreadClone set false to clone NoThreadClone nodes as well + */ + public TreeCloner(boolean honourNoThreadClone) { + newTree = new ListedHashTree(); + this.honourNoThreadClone = honourNoThreadClone; + } + + /** + * {@inheritDoc} + */ + @Override + public final void addNode(Object node, HashTree subTree) { + node = addNodeToTree(node); + addLast(node); + } + + /** + * @param node Node to add to tree or not + * @return Object node (clone or not) + */ + protected Object addNodeToTree(Object node) { + if ( (node instanceof TestElement) // Check can cast for clone + // Don't clone NoThreadClone unless honourNoThreadClone == false + && (!honourNoThreadClone || !(node instanceof NoThreadClone)) + ) { + node = ((TestElement) node).clone(); + newTree.add(objects, node); + } else { + newTree.add(objects, node); + } + return node; + } + + /** + * add node to objects LinkedList + * @param node Object + */ + private final void addLast(Object node) { + objects.addLast(node); + } + + @Override + public void subtractNode() { + objects.removeLast(); + } + + public ListedHashTree getClonedTree() { + return newTree; + } + + @Override + public void processPath() { + } + +} diff --git a/src/core/org/apache/jmeter/engine/TreeClonerNoTimer.java b/src/core/org/apache/jmeter/engine/TreeClonerNoTimer.java new file mode 100644 index 00000000000..463eead0f5a --- /dev/null +++ b/src/core/org/apache/jmeter/engine/TreeClonerNoTimer.java @@ -0,0 +1,54 @@ +/* + * 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.engine; + +import org.apache.jmeter.timers.Timer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Clones the test tree, skipping test elements that implement {@link Timer} by default. + */ +public class TreeClonerNoTimer extends TreeCloner{ + private static final Logger logger = LoggingManager.getLoggerForClass(); + + public TreeClonerNoTimer() { + super(); + } + + public TreeClonerNoTimer(boolean honourNoThreadClone) { + super(honourNoThreadClone); + } + + /** + * Doesn't add Timer to tree + * @see org.apache.jmeter.engine.TreeCloner#addNodeToTree(java.lang.Object) + */ + @Override + protected Object addNodeToTree(Object node) { + if(node instanceof Timer) { + if(logger.isDebugEnabled()) { + logger.debug("Ignoring timer node:"+ node); + } + return node; // don't add the timer + } else { + return super.addNodeToTree(node); + } + } +} diff --git a/src/core/org/apache/jmeter/engine/TurnElementsOn.java b/src/core/org/apache/jmeter/engine/TurnElementsOn.java new file mode 100644 index 00000000000..c52510e6a6a --- /dev/null +++ b/src/core/org/apache/jmeter/engine/TurnElementsOn.java @@ -0,0 +1,56 @@ +/* + * 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.engine; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; + +/** + * Invokes {@link TestElement#setRunningVersion(boolean) setRunningVersion(true)} for all matched nodes + */ +public class TurnElementsOn implements HashTreeTraverser { + + /** + * {@inheritDoc} + */ + @Override + public void addNode(Object node, HashTree subTree) { + if (node instanceof TestElement && !(node instanceof TestPlan)) { + ((TestElement) node).setRunningVersion(true); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public void subtractNode() { + } + + /** + * {@inheritDoc} + */ + @Override + public void processPath() { + } + +} diff --git a/src/core/org/apache/jmeter/engine/event/LoopIterationEvent.java b/src/core/org/apache/jmeter/engine/event/LoopIterationEvent.java new file mode 100644 index 00000000000..b07bd34ef99 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/event/LoopIterationEvent.java @@ -0,0 +1,55 @@ +/* + * 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.engine.event; + +import org.apache.jmeter.testelement.TestElement; + +/** + * An iteration event provides information about the iteration number and the + * source of the event. + */ +public class LoopIterationEvent { + private final int iteration; + + private final TestElement source; + + public LoopIterationEvent(TestElement source, int iter) { + iteration = iter; + this.source = source; + } + + /** + * Returns the iteration. + * + * @return int + */ + public int getIteration() { + return iteration; + } + + /** + * Returns the source. + * + * @return TestElement + */ + public TestElement getSource() { + return source; + } + +} diff --git a/src/core/org/apache/jmeter/engine/event/LoopIterationListener.java b/src/core/org/apache/jmeter/engine/event/LoopIterationListener.java new file mode 100644 index 00000000000..c9841bc9275 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/event/LoopIterationListener.java @@ -0,0 +1,31 @@ +/* + * 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.engine.event; + +/** + * Allows a class to receive loop iteration start events. + */ +public interface LoopIterationListener { + /** + * Called when a loop iteration is about to start. + * + * @param iterEvent the event + */ + void iterationStart(LoopIterationEvent iterEvent); +} diff --git a/src/core/org/apache/jmeter/engine/package-info.java b/src/core/org/apache/jmeter/engine/package-info.java new file mode 100644 index 00000000000..cf7a3b5ea84 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + * + */ + +/** + * This package contains the interfaces and classes that are used to run JMeter tests. + */ + +package org.apache.jmeter.engine; diff --git a/src/core/org/apache/jmeter/engine/util/AbstractTransformer.java b/src/core/org/apache/jmeter/engine/util/AbstractTransformer.java new file mode 100644 index 00000000000..01c2a33a45d --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/AbstractTransformer.java @@ -0,0 +1,51 @@ +/* + * 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. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; + +abstract class AbstractTransformer implements ValueTransformer { + + private CompoundVariable masterFunction; + + private Map variables; + + /** {@inheritDoc} */ + @Override + public void setMasterFunction(CompoundVariable variable) { + masterFunction = variable; + } + + protected CompoundVariable getMasterFunction() { + return masterFunction; + } + + public Map getVariables() { + return variables; + } + + /** {@inheritDoc} */ + @Override + public void setVariables(Map map) { + variables = map; + } +} diff --git a/src/core/org/apache/jmeter/engine/util/CompoundVariable.java b/src/core/org/apache/jmeter/engine/util/CompoundVariable.java new file mode 100644 index 00000000000..0eb1244a876 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/CompoundVariable.java @@ -0,0 +1,241 @@ +/* + * 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.engine.util; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.functions.Function; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +/** + * CompoundFunction. + * + */ +public class CompoundVariable implements Function { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private String rawParameters; + + private static final FunctionParser functionParser = new FunctionParser(); + + // Created during class init; not modified thereafter + private static final Map> functions = + new HashMap>(); + + private boolean hasFunction, isDynamic; + + private String permanentResults; + + private LinkedList compiledComponents = new LinkedList(); + + static { + try { + final String contain = // Classnames must contain this string [.functions.] + JMeterUtils.getProperty("classfinder.functions.contain"); // $NON-NLS-1$ + final String notContain = // Classnames must not contain this string [.gui.] + JMeterUtils.getProperty("classfinder.functions.notContain"); // $NON-NLS-1$ + if (contain!=null){ + log.info("Note: Function class names must contain the string: '"+contain+"'"); + } + if (notContain!=null){ + log.info("Note: Function class names must not contain the string: '"+notContain+"'"); + } + List classes = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), + new Class[] { Function.class }, true, contain, notContain); + Iterator iter = classes.iterator(); + while (iter.hasNext()) { + Function tempFunc = (Function) Class.forName(iter.next()).newInstance(); + String referenceKey = tempFunc.getReferenceKey(); + if (referenceKey.length() > 0) { // ignore self + functions.put(referenceKey, tempFunc.getClass()); + // Add alias for original StringFromFile name (had only one underscore) + if (referenceKey.equals("__StringFromFile")){//$NON-NLS-1$ + functions.put("_StringFromFile", tempFunc.getClass());//$NON-NLS-1$ + } + } + } + final int functionCount = functions.size(); + if (functionCount == 0){ + log.warn("Did not find any functions"); + } else { + log.debug("Function count: "+functionCount); + } + } catch (Exception err) { + log.error("", err); + } + } + + public CompoundVariable() { + hasFunction = false; + } + + public CompoundVariable(String parameters) { + this(); + try { + setParameters(parameters); + } catch (InvalidVariableException e) { + // TODO should level be more than debug ? + if(log.isDebugEnabled()) { + log.debug("Invalid variable:"+ parameters, e); + } + } + } + + public String execute() { + if (isDynamic || permanentResults == null) { + JMeterContext context = JMeterContextService.getContext(); + SampleResult previousResult = context.getPreviousResult(); + Sampler currentSampler = context.getCurrentSampler(); + return execute(previousResult, currentSampler); + } + return permanentResults; // $NON-NLS-1$ + } + + /** + * Allows the retrieval of the original String prior to it being compiled. + * + * @return String + */ + public String getRawParameters() { + return rawParameters; + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) { + if (compiledComponents == null || compiledComponents.size() == 0) { + return ""; // $NON-NLS-1$ + } + StringBuilder results = new StringBuilder(); + for (Object item : compiledComponents) { + if (item instanceof Function) { + try { + results.append(((Function) item).execute(previousResult, currentSampler)); + } catch (InvalidVariableException e) { + // TODO should level be more than debug ? + if(log.isDebugEnabled()) { + log.debug("Invalid variable:"+item, e); + } + } + } else if (item instanceof SimpleVariable) { + results.append(((SimpleVariable) item).toString()); + } else { + results.append(item); + } + } + if (!isDynamic) { + permanentResults = results.toString(); + } + return results.toString(); + } + + @SuppressWarnings("unchecked") // clone will produce correct type + public CompoundVariable getFunction() { + CompoundVariable func = new CompoundVariable(); + func.compiledComponents = (LinkedList) compiledComponents.clone(); + func.rawParameters = rawParameters; + func.hasFunction = hasFunction; + func.isDynamic = isDynamic; + return func; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return new LinkedList(); + } + + public void clear() { + // TODO should this also clear isDynamic, rawParameters, permanentResults? + hasFunction = false; + compiledComponents.clear(); + } + + public void setParameters(String parameters) throws InvalidVariableException { + this.rawParameters = parameters; + if (parameters == null || parameters.length() == 0) { + return; + } + + compiledComponents = functionParser.compileString(parameters); + if (compiledComponents.size() > 1 || !(compiledComponents.get(0) instanceof String)) { + hasFunction = true; + } + permanentResults = null; // To be calculated and cached on first execution + isDynamic = false; + for (Object item : compiledComponents) { + if (item instanceof Function || item instanceof SimpleVariable) { + isDynamic = true; + break; + } + } + } + + static Object getNamedFunction(String functionName) throws InvalidVariableException { + if (functions.containsKey(functionName)) { + try { + return ((Class) functions.get(functionName)).newInstance(); + } catch (Exception e) { + log.error("", e); // $NON-NLS-1$ + throw new InvalidVariableException(e); + } + } + return new SimpleVariable(functionName); + } + + // For use by FunctionHelper + public static Class getFunctionClass(String className) { + return functions.get(className); + } + + // For use by FunctionHelper + public static String[] getFunctionNames() { + return functions.keySet().toArray(new String[functions.size()]); + } + + public boolean hasFunction() { + return hasFunction; + } + + // Dummy methods needed by Function interface + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return ""; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + } +} diff --git a/src/core/org/apache/jmeter/engine/util/ConfigMergabilityIndicator.java b/src/core/org/apache/jmeter/engine/util/ConfigMergabilityIndicator.java new file mode 100644 index 00000000000..e14f6e73221 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/ConfigMergabilityIndicator.java @@ -0,0 +1,37 @@ +/* + * 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.engine.util; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.threads.TestCompiler; + +/** + * Interface that gives a hint about the merge policy to apply between Samplers and Config elements + * @see TestCompiler#configureWithConfigElements + * @since 2.7 + */ +public interface ConfigMergabilityIndicator { + + /** + * Does configElement apply to Sampler + * @param configElement {@link ConfigTestElement} + * @return boolean + */ + boolean applies(ConfigTestElement configElement); +} diff --git a/src/core/org/apache/jmeter/engine/util/FunctionParser.java b/src/core/org/apache/jmeter/engine/util/FunctionParser.java new file mode 100644 index 00000000000..830a3bbaa12 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/FunctionParser.java @@ -0,0 +1,249 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 25, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.io.IOException; +import java.io.StringReader; +import java.util.LinkedList; + +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.functions.Function; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Parses function / variable references of the form + * ${functionName[([var[,var...]])]} + * and + * ${variableName} + */ +class FunctionParser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Compile a general string into a list of elements for a CompoundVariable. + * + * Calls {@link #makeFunction(StringReader)} if it detects an unescaped "${". + * + * Removes escapes from '$', ',' and '\'. + * + * @param value string containing the function / variable references (if any) + * + * @return list of Strings or Objects representing functions + * @throws InvalidVariableException when evaluation of variables fail + */ + LinkedList compileString(String value) throws InvalidVariableException { + StringReader reader = new StringReader(value); + LinkedList result = new LinkedList(); + StringBuilder buffer = new StringBuilder(); + char previous = ' '; // TODO - why use space? + char[] current = new char[1]; + try { + while (reader.read(current) == 1) { + if (current[0] == '\\') { // Handle escapes + previous = current[0]; + if (reader.read(current) == 0) { + break; + } + // Keep the '\' unless it is one of the escapable chars '$' ',' or '\' + // N.B. This method is used to parse function parameters, so must treat ',' as special + if (current[0] != '$' && current[0] != ',' && current[0] != '\\') { + buffer.append(previous); // i.e. '\\' + } + previous = ' '; + buffer.append(current[0]); + continue; + } else if (current[0] == '{' && previous == '$') {// found "${" + buffer.deleteCharAt(buffer.length() - 1); + if (buffer.length() > 0) {// save leading text + result.add(buffer.toString()); + buffer.setLength(0); + } + result.add(makeFunction(reader)); + previous = ' '; + } else { + buffer.append(current[0]); + previous = current[0]; + } + } + if (buffer.length() > 0) { + result.add(buffer.toString()); + } + } catch (IOException e) { + log.error("Error parsing function: " + value, e); + result.clear(); + result.add(value); + } + if (result.size() == 0) { + result.add(""); + } + return result; + } + + /** + * Compile a string into a function or SimpleVariable. + * + * Called by {@link #compileString(String)} when that has detected "${". + * + * Calls {@link CompoundVariable#getNamedFunction(String)} if it detects: + * '(' - start of parameter list + * '}' - end of function call + * + * @param reader points to input after the "${" + * @return the function or variable object (or a String) + * @throws InvalidVariableException when evaluation of variables fail + */ + Object makeFunction(StringReader reader) throws InvalidVariableException { + char[] current = new char[1]; + char previous = ' '; // TODO - why use space? + StringBuilder buffer = new StringBuilder(); + Object function; + try { + while (reader.read(current) == 1) { + if (current[0] == '\\') { + if (reader.read(current) == 0) { + break; + } + previous = ' '; + buffer.append(current[0]); + continue; + } else if (current[0] == '(' && previous != ' ') { + String funcName = buffer.toString(); + function = CompoundVariable.getNamedFunction(funcName); + if (function instanceof Function) { + ((Function) function).setParameters(parseParams(reader)); + if (reader.read(current) == 0 || current[0] != '}') { + reader.reset();// set to start of string + char []cb = new char[100]; + int nbRead = reader.read(cb); + throw new InvalidVariableException + ("Expected } after "+funcName+" function call in "+new String(cb, 0, nbRead)); + } + if (function instanceof TestStateListener) { + StandardJMeterEngine.register((TestStateListener) function); + } + return function; + } else { // Function does not exist, so treat as per missing variable + buffer.append(current[0]); + } + continue; + } else if (current[0] == '}') {// variable, or function with no parameter list + function = CompoundVariable.getNamedFunction(buffer.toString()); + if (function instanceof Function){// ensure that setParameters() is called. + ((Function) function).setParameters(new LinkedList()); + } + buffer.setLength(0); + return function; + } else { + buffer.append(current[0]); + previous = current[0]; + } + } + } catch (IOException e) { + log.error("Error parsing function: " + buffer.toString(), e); + return null; + } + log.warn("Probably an invalid function string: " + buffer.toString()); + return buffer.toString(); + } + + /** + * Compile a String into a list of parameters, each made into a + * CompoundVariable. + * + * Parses strings of the following form: + *
    + *
  • text)
  • + *
  • text,text)
  • + *
  • + *
+ * @param reader a StringReader pointing to the current input location, just after "(" + * @return a list of CompoundVariable elements + * @throws InvalidVariableException when evaluation of variables fail + */ + LinkedList parseParams(StringReader reader) throws InvalidVariableException { + LinkedList result = new LinkedList(); + StringBuilder buffer = new StringBuilder(); + char[] current = new char[1]; + char previous = ' '; + int functionRecursion = 0; + int parenRecursion = 0; + try { + while (reader.read(current) == 1) { + if (current[0] == '\\') { // Process escaped characters + buffer.append(current[0]); // Store the \ + if (reader.read(current) == 0) { + break; // end of buffer + } + previous = ' '; + buffer.append(current[0]); // store the following character + continue; + } else if (current[0] == ',' && functionRecursion == 0) { + CompoundVariable param = new CompoundVariable(); + param.setParameters(buffer.toString()); + buffer.setLength(0); + result.add(param); + } else if (current[0] == ')' && functionRecursion == 0 && parenRecursion == 0) { + // Detect functionName() so this does not generate empty string as the parameter + if (buffer.length() == 0 && result.isEmpty()){ + return result; + } + // Normal exit occurs here + CompoundVariable param = new CompoundVariable(); + param.setParameters(buffer.toString()); + buffer.setLength(0); + result.add(param); + return result; + } else if (current[0] == '{' && previous == '$') { + buffer.append(current[0]); + previous = current[0]; + functionRecursion++; + } else if (current[0] == '}' && functionRecursion > 0) { + buffer.append(current[0]); + previous = current[0]; + functionRecursion--; + } else if (current[0] == ')' && functionRecursion == 0 && parenRecursion > 0) { + buffer.append(current[0]); + previous = current[0]; + parenRecursion--; + } else if (current[0] == '(' && functionRecursion == 0) { + buffer.append(current[0]); + previous = current[0]; + parenRecursion++; + } else { + buffer.append(current[0]); + previous = current[0]; + } + } + } catch (IOException e) {// Should not happen with StringReader + log.error("Error parsing function: " + buffer.toString(), e); + } + // Dropped out, i.e. did not find closing ')' + log.warn("Probably an invalid function string: " + buffer.toString()); + CompoundVariable var = new CompoundVariable(); + var.setParameters(buffer.toString()); + result.add(var); + return result; + } +} diff --git a/src/core/org/apache/jmeter/engine/util/NoConfigMerge.java b/src/core/org/apache/jmeter/engine/util/NoConfigMerge.java new file mode 100644 index 00000000000..e9860030e7f --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/NoConfigMerge.java @@ -0,0 +1,31 @@ +/* + * 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.engine.util; + +import org.apache.jmeter.threads.TestCompiler; + +/** + * Implement this method-less interface to indicate that this ConfigElement should not be merged. + * Otherwise, the default behavior is to merge the element with every sampler in scope. + * + * @see TestCompiler#configureSampler(org.apache.jmeter.samplers.Sampler) + * @version $Revision$ + * @since 2.7 + */ +public interface NoConfigMerge { +} diff --git a/src/core/org/apache/jmeter/engine/util/NoThreadClone.java b/src/core/org/apache/jmeter/engine/util/NoThreadClone.java new file mode 100644 index 00000000000..b3c855c288d --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/NoThreadClone.java @@ -0,0 +1,32 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 23, 2003 + */ +package org.apache.jmeter.engine.util; + +/** + * Implement this method-less interface to indicate your test element should not + * be cloned for each thread in a test run. Otherwise, the default behavior is + * to clone every test element for each thread. + * + * @version $Revision$ + */ +public interface NoThreadClone { +} diff --git a/src/core/org/apache/jmeter/engine/util/ReplaceFunctionsWithStrings.java b/src/core/org/apache/jmeter/engine/util/ReplaceFunctionsWithStrings.java new file mode 100644 index 00000000000..5a4a44ef141 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/ReplaceFunctionsWithStrings.java @@ -0,0 +1,111 @@ +/* + * 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. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.StringUtilities; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.regex.MalformedPatternException; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternCompiler; +import org.apache.oro.text.regex.PatternMatcher; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.StringSubstitution; +import org.apache.oro.text.regex.Util; + +/** + * Transforms strings into variable references (in spite of the name, which + * suggests the opposite!) + * + */ +public class ReplaceFunctionsWithStrings extends AbstractTransformer { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Functions are wrapped in ${ and } + private static final String FUNCTION_REF_PREFIX = "${"; //$NON-NLS-1$ + + private static final String FUNCTION_REF_SUFFIX = "}"; //$NON-NLS-1$ + + private final boolean regexMatch;// Should we match using regexes? + + public ReplaceFunctionsWithStrings(CompoundVariable masterFunction, Map variables) { + this(masterFunction, variables, false); + } + + public ReplaceFunctionsWithStrings(CompoundVariable masterFunction, Map variables, boolean regexMatch) { + super(); + setMasterFunction(masterFunction); + setVariables(variables); + this.regexMatch = regexMatch; + } + + @Override + public JMeterProperty transformValue(JMeterProperty prop) throws InvalidVariableException { + PatternMatcher pm = JMeterUtils.getMatcher(); + Pattern pattern = null; + PatternCompiler compiler = new Perl5Compiler(); + String input = prop.getStringValue(); + if(input == null) { + return prop; + } + for(Entry entry : getVariables().entrySet()){ + String key = entry.getKey(); + String value = entry.getValue(); + if (regexMatch) { + try { + pattern = compiler.compile(constructPattern(value)); + input = Util.substitute(pm, pattern, + new StringSubstitution(FUNCTION_REF_PREFIX + key + FUNCTION_REF_SUFFIX), + input, Util.SUBSTITUTE_ALL); + } catch (MalformedPatternException e) { + log.warn("Malformed pattern " + value); + } + } else { + input = StringUtilities.substitute(input, value, FUNCTION_REF_PREFIX + key + FUNCTION_REF_SUFFIX); + } + } + return new StringProperty(prop.getName(), input); + } + + /** + * Normal regexes will be surrounded by boundary character matches to make life easier for users. + * If a user doesn't want that behaviour, he can prevent the modification by giving a regex, that + * starts and ends with a parenthesis. + * + * @param value given by user + * @return regex surrounded by boundary character matches, if value is not included in parens + */ + private String constructPattern(String value) { + if (value.startsWith("(") && value.endsWith(")")) { + return value; + } + return "\\b(" + value + ")\\b"; + } + +} diff --git a/src/core/org/apache/jmeter/engine/util/ReplaceStringWithFunctions.java b/src/core/org/apache/jmeter/engine/util/ReplaceStringWithFunctions.java new file mode 100644 index 00000000000..188bc53132d --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/ReplaceStringWithFunctions.java @@ -0,0 +1,53 @@ +/* + * 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. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.property.FunctionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; + +/** + * Replaces a String containing functions with their Function properties equivalent, example: + * ${__time()}_${__threadNum()}_${__machineName()} will become a FunctionProperty of + * a CompoundVariable containing 3 functions + */ +public class ReplaceStringWithFunctions extends AbstractTransformer { + public ReplaceStringWithFunctions(CompoundVariable masterFunction, Map variables) { + super(); + setMasterFunction(masterFunction); + setVariables(variables); + } + + @Override + public JMeterProperty transformValue(JMeterProperty prop) throws InvalidVariableException { + JMeterProperty newValue = prop; + getMasterFunction().clear(); + getMasterFunction().setParameters(prop.getStringValue()); + if (getMasterFunction().hasFunction()) { + newValue = new FunctionProperty(prop.getName(), getMasterFunction().getFunction()); + } + return newValue; + } + +} diff --git a/src/core/org/apache/jmeter/engine/util/SimpleVariable.java b/src/core/org/apache/jmeter/engine/util/SimpleVariable.java new file mode 100644 index 00000000000..d3f3222a7c3 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/SimpleVariable.java @@ -0,0 +1,69 @@ +/* + * 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.engine.util; + +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class SimpleVariable { + + private String name; + + public SimpleVariable(String name) { + this.name = name; + } + + public SimpleVariable() { + this.name = ""; //$NON-NLS-1$ + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * @see org.apache.jmeter.functions.Function#execute + */ + @Override + public String toString() { + String ret = null; + JMeterVariables vars = getVariables(); + + if (vars != null) { + ret = vars.get(name); + } + + if (ret == null) { + return "${" + name + "}"; + } + + return ret; + } + + private JMeterVariables getVariables() { + JMeterContext context = JMeterContextService.getContext(); + return context.getVariables(); + } + +} diff --git a/src/core/org/apache/jmeter/engine/util/UndoVariableReplacement.java b/src/core/org/apache/jmeter/engine/util/UndoVariableReplacement.java new file mode 100644 index 00000000000..c8ac8ce0fa1 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/UndoVariableReplacement.java @@ -0,0 +1,52 @@ +/* + * 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. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.StringUtilities; + +/** + * Replaces ${key} by value extracted from + * {@link org.apache.jmeter.threads.JMeterVariables JMeterVariables} if any + */ +public class UndoVariableReplacement extends AbstractTransformer { + public UndoVariableReplacement(CompoundVariable masterFunction, Map variables) { + super(); + setMasterFunction(masterFunction); + setVariables(variables); + } + + @Override + public JMeterProperty transformValue(JMeterProperty prop) throws InvalidVariableException { + String input = prop.getStringValue(); + for (Map.Entry entry : getVariables().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + input = StringUtilities.substitute(input, "${" + key + "}", value); + } + return new StringProperty(prop.getName(), input); + } +} diff --git a/src/core/org/apache/jmeter/engine/util/ValueReplacer.java b/src/core/org/apache/jmeter/engine/util/ValueReplacer.java new file mode 100644 index 00000000000..7ee90aef7a6 --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/ValueReplacer.java @@ -0,0 +1,199 @@ +/* + * 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.engine.util; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.MultiProperty; +import org.apache.jmeter.testelement.property.NumberProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Perfom replacement of ${variable} references. + */ +public class ValueReplacer { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final CompoundVariable masterFunction = new CompoundVariable(); + + private Map variables = new HashMap(); + + public ValueReplacer() { + } + + /** + * Constructor which couples the given {@link TestPlan} to this by means of the user defined variables + * @param tp {@link TestPlan} from which we will take the user defined variables as variables map + */ + public ValueReplacer(TestPlan tp) { + setUserDefinedVariables(tp.getUserDefinedVariables()); + } + + boolean containsKey(String k){ + return variables.containsKey(k); + } + + /** + * Set this {@link ValueReplacer}'s variable map + * @param variables Map which stores the variables + */ + public void setUserDefinedVariables(Map variables) { + this.variables = variables; + } + + /** + * Replaces TestElement StringProperties containing functions with their Function properties equivalent, example: + * ${__time()}_${__threadNum()}_${__machineName()} will become a FunctionProperty of + * a CompoundVariable containing 3 functions + * @param el {@link TestElement} in which the values should be replaced + * @throws InvalidVariableException when transforming of the variables goes awry and + * the used transformer throws an {@link InvalidVariableException} + */ + public void replaceValues(TestElement el) throws InvalidVariableException { + Collection newProps = replaceValues(el.propertyIterator(), new ReplaceStringWithFunctions(masterFunction, + variables)); + setProperties(el, newProps); + } + + private void setProperties(TestElement el, Collection newProps) { + el.clear(); + for (JMeterProperty jmp : newProps) { + el.setProperty(jmp); + } + } + + /** + * Transforms strings into variable references + * @param el {@link TestElement} in which the we will look for strings, that can be replaced by variable references + * @throws InvalidVariableException when transforming of the strings goes awry and + * the used transformer throws an {@link InvalidVariableException} + */ + public void reverseReplace(TestElement el) throws InvalidVariableException { + Collection newProps = replaceValues(el.propertyIterator(), new ReplaceFunctionsWithStrings(masterFunction, + variables)); + setProperties(el, newProps); + } + + /** + * Transforms strings into variable references using regexp matching if regexMatch is true + * @param el {@link TestElement} in which the we will look for strings, that can be replaced by variable references + * @param regexMatch when true variable substitution will be done in regexp matching mode + * @throws InvalidVariableException when transforming of the strings goes awry and + * the used transformer throws an {@link InvalidVariableException} + */ + public void reverseReplace(TestElement el, boolean regexMatch) throws InvalidVariableException { + Collection newProps = replaceValues(el.propertyIterator(), new ReplaceFunctionsWithStrings(masterFunction, + variables, regexMatch)); + setProperties(el, newProps); + } + + /** + * Replaces ${key} by value extracted from variables if any + * @param el {@link TestElement} in which values should be replaced + * @throws InvalidVariableException when transforming of the variables goes awry and + * the used transformer throws an {@link InvalidVariableException} + */ + public void undoReverseReplace(TestElement el) throws InvalidVariableException { + Collection newProps = replaceValues(el.propertyIterator(), new UndoVariableReplacement(masterFunction, + variables)); + setProperties(el, newProps); + } + + /** + * Add a variable to this replacer's variables map + * @param name Name of the variable + * @param value Value of the variable + */ + public void addVariable(String name, String value) { + variables.put(name, value); + } + + /** + * Add all the given variables to this replacer's variables map. + * + * @param vars + * A map of variable name-value pairs (String-to-String). + */ + public void addVariables(Map vars) { + variables.putAll(vars); + } + + /** + * Replaces a {@link StringProperty} containing functions with their Function properties equivalent. + *

For example: + * ${__time()}_${__threadNum()}_${__machineName()} will become a + * {@link org.apache.jmeter.testelement.property.FunctionProperty} of + * a {@link CompoundVariable} containing three functions + * @param iter the {@link PropertyIterator} over all properties, in which the values should be replaced + * @param transform the {@link ValueTransformer}, that should do transformation + * @return a new {@link Collection} with all the transformed {@link JMeterProperty}s + * @throws InvalidVariableException when transform throws an {@link InvalidVariableException} while transforming a value + */ + private Collection replaceValues(PropertyIterator iter, ValueTransformer transform) throws InvalidVariableException { + List props = new LinkedList(); + while (iter.hasNext()) { + JMeterProperty val = iter.next(); + if (log.isDebugEnabled()) { + log.debug("About to replace in property of type: " + val.getClass() + ": " + val); + } + if (val instanceof StringProperty) { + // Must not convert TestElement.gui_class etc + if (!val.getName().equals(TestElement.GUI_CLASS) && + !val.getName().equals(TestElement.TEST_CLASS)) { + val = transform.transformValue(val); + if (log.isDebugEnabled()) { + log.debug("Replacement result: " + val); + } + } + } else if (val instanceof NumberProperty) { + val = transform.transformValue(val); + if (log.isDebugEnabled()) { + log.debug("Replacement result: " + val); + } + } else if (val instanceof MultiProperty) { + MultiProperty multiVal = (MultiProperty) val; + Collection newValues = replaceValues(multiVal.iterator(), transform); + multiVal.clear(); + for (JMeterProperty jmp : newValues) { + multiVal.addProperty(jmp); + } + if (log.isDebugEnabled()) { + log.debug("Replacement result: " + multiVal); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Won't replace " + val); + } + } + props.add(val); + } + return props; + } +} diff --git a/src/core/org/apache/jmeter/engine/util/ValueTransformer.java b/src/core/org/apache/jmeter/engine/util/ValueTransformer.java new file mode 100644 index 00000000000..9afd870e4ff --- /dev/null +++ b/src/core/org/apache/jmeter/engine/util/ValueTransformer.java @@ -0,0 +1,57 @@ +/* + * 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. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.property.JMeterProperty; + +interface ValueTransformer { + /** + * Transform the given property and return the new version. + * + * @param property + * Property to be transformed + * @return the transformed property + * @throws InvalidVariableException + * if something went wrong while computing variables or + * functions + */ + JMeterProperty transformValue(JMeterProperty property) throws InvalidVariableException; + + /** + * Set the master function for the value transformer. This handles + * converting strings to functions. + * + * @param masterFunction Function to be used for the transformation + */ + void setMasterFunction(CompoundVariable masterFunction); + + /** + * Set the variable names and values used to reverse replace functions with + * strings, and undo functions to raw values. + * + * @param vars Map of names and values to be used for the transformation + */ + void setVariables(Map vars); +} diff --git a/src/core/org/apache/jmeter/exceptions/IllegalUserActionException.java b/src/core/org/apache/jmeter/exceptions/IllegalUserActionException.java new file mode 100644 index 00000000000..09ec6cce82d --- /dev/null +++ b/src/core/org/apache/jmeter/exceptions/IllegalUserActionException.java @@ -0,0 +1,42 @@ +/* + * 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.exceptions; + +/** + */ +public class IllegalUserActionException extends Exception { + private static final long serialVersionUID = 240L; + + /** + * @deprecated - use IllegalUserActionException(String) + */ + @Deprecated // Needed for serialisation testing + public IllegalUserActionException() { + super(); + } + + public IllegalUserActionException(String name) { + super(name); + } + + public IllegalUserActionException(String name, Throwable t) { + super(name, t); + } + +} diff --git a/src/core/org/apache/jmeter/functions/AbstractFunction.java b/src/core/org/apache/jmeter/functions/AbstractFunction.java new file mode 100644 index 00000000000..0d9d98f9d54 --- /dev/null +++ b/src/core/org/apache/jmeter/functions/AbstractFunction.java @@ -0,0 +1,143 @@ +/* + * 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.util.Collection; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +/** + * Provides common methods for all functions + */ +public abstract class AbstractFunction implements Function { + + /** + *

+ * N.B. execute() should be synchronized if function is operating with non-thread-safe + * objects (e.g. operates with files). + *

+ * JMeter ensures setParameters() happens-before execute(): setParameters is executed in main thread, + * and worker threads are started after that. + * @see Function#execute(SampleResult, Sampler) + */ + @Override + abstract public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException; + + public String execute() throws InvalidVariableException { + JMeterContext context = JMeterContextService.getContext(); + SampleResult previousResult = context.getPreviousResult(); + Sampler currentSampler = context.getCurrentSampler(); + return execute(previousResult, currentSampler); + } + + /** + * Note: This is always called even if no parameters are provided + * (versions of JMeter after 2.3.1) + * + * @see Function#setParameters(Collection) + */ + @Override + abstract public void setParameters(Collection parameters) throws InvalidVariableException; + + /** + * @see Function#getReferenceKey() + */ + @Override + abstract public String getReferenceKey(); + + /** + * Gives access to the JMeter variables for the current thread. + * + * @return a pointer to the JMeter variables. + */ + protected JMeterVariables getVariables() { + return JMeterContextService.getContext().getVariables(); + } + + /** + * Utility method to check parameter counts. + * + * @param parameters collection of parameters + * @param min minimum number of parameters allowed + * @param max maximum number of parameters allowed + * + * @throws InvalidVariableException if the number of parameters is incorrect + */ + protected void checkParameterCount(Collection parameters, int min, int max) + throws InvalidVariableException + { + int num = parameters.size(); + if ((num > max) || (num < min)) { + throw new InvalidVariableException( + getReferenceKey() + + " called with wrong number of parameters. Actual: "+num+ + ( + min==max ? + ". Expected: "+min+"." + : ". Expected: >= "+min+" and <= "+max + ) + ); + } + } + + /** + * Utility method to check parameter counts. + * + * @param parameters collection of parameters + * @param count number of parameters expected + * + * @throws InvalidVariableException if the number of parameters is incorrect + */ + protected void checkParameterCount(Collection parameters, int count) + throws InvalidVariableException + { + int num = parameters.size(); + if (num != count) { + throw new InvalidVariableException( + getReferenceKey() + + " called with wrong number of parameters. Actual: "+num+". Expected: "+count+"." + ); + } + } + + /** + * Utility method to check parameter counts. + * + * @param parameters collection of parameters + * @param minimum number of parameters expected + * + * @throws InvalidVariableException if the number of parameters is incorrect + */ + protected void checkMinParameterCount(Collection parameters, int minimum) + throws InvalidVariableException + { + int num = parameters.size(); + if (num < minimum) { + throw new InvalidVariableException( + getReferenceKey() + + " called with wrong number of parameters. Actual: "+num+". Expected at least: "+minimum+"." + ); + } + } +} diff --git a/src/core/org/apache/jmeter/functions/Function.java b/src/core/org/apache/jmeter/functions/Function.java new file mode 100644 index 00000000000..8155e355aff --- /dev/null +++ b/src/core/org/apache/jmeter/functions/Function.java @@ -0,0 +1,76 @@ +/* + * 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.util.Collection; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; + +/** + * Methods that a function must implement + */ +public interface Function { + /** + * Given the previous SampleResult and the current Sampler, return a string + * to use as a replacement value for the function call. Assume + * "setParameter" was previously called. + * + * This method must be threadsafe - multiple threads will be using the same + * object. + * @param previousResult The previous {@link SampleResult} + * @param currentSampler The current {@link Sampler} + * @return The replacement value, which was generated by the function + * @throws InvalidVariableException - when the variables for the function call can't be evaluated + */ + String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException; + + /** + * A collection of the parameters used to configure your function. Each + * parameter is a CompoundVariable and can be resolved by calling the + * execute() method of the CompoundVariable (which should be done at + * execution.) + * + * @param parameters The parameters for the function call + * @throws InvalidVariableException - when the variables for the function call can't be evaluated + */ + void setParameters(Collection parameters) throws InvalidVariableException; + + /** + * Return the name of your function. Convention is to prepend "__" to the + * name (ie "__regexFunction") + * @return The name of the funtion + */ + String getReferenceKey(); + + /** + * Return a list of strings briefly describing each parameter your function + * takes. Please use JMeterUtils.getResString(resource_name) to grab a + * resource string. Otherwise, your help text will be difficult to + * internationalize. + * + * This list is not optional. If you don't wish to write help, you must at + * least return a List containing the correct number of blank strings, one + * for each argument. + * @return List with brief descriptions for each parameter the function takes + */ + List getArgumentDesc(); +} diff --git a/src/core/org/apache/jmeter/functions/InvalidVariableException.java b/src/core/org/apache/jmeter/functions/InvalidVariableException.java new file mode 100644 index 00000000000..04570320d4f --- /dev/null +++ b/src/core/org/apache/jmeter/functions/InvalidVariableException.java @@ -0,0 +1,38 @@ +/* + * 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; + +public class InvalidVariableException extends Exception { + private static final long serialVersionUID = 240L; + + public InvalidVariableException() { + } + + public InvalidVariableException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidVariableException(Throwable cause) { + super(cause); + } + + public InvalidVariableException(String msg) { + super(msg); + } +} diff --git a/src/core/org/apache/jmeter/functions/gui/FunctionHelper.java b/src/core/org/apache/jmeter/functions/gui/FunctionHelper.java new file mode 100644 index 00000000000..19e379849d9 --- /dev/null +++ b/src/core/org/apache/jmeter/functions/gui/FunctionHelper.java @@ -0,0 +1,188 @@ +/* + * 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.gui; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.functions.Function; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.Help; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextField; + +public class FunctionHelper extends JDialog implements ActionListener, ChangeListener, LocaleChangeListener { + private static final long serialVersionUID = 240L; + + private JLabeledChoice functionList; + + private ArgumentsPanel parameterPanel; + + private JLabeledTextField cutPasteFunction; + + public FunctionHelper() { + super((JFrame) null, JMeterUtils.getResString("function_helper_title"), false); //$NON-NLS-1$ + init(); + JMeterUtils.addLocaleChangeListener(this); + } + + /** + * Allow Dialog to be closed by ESC key + */ + @Override + protected JRootPane createRootPane() { + JRootPane rootPane = new JRootPane(); + javax.swing.Action escapeAction = new AbstractAction("ESCAPE") { + + private static final long serialVersionUID = -4036804004190858925L; + + @Override + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }; + rootPane.getActionMap().put(escapeAction.getValue(Action.NAME), escapeAction); + InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + inputMap.put(KeyStrokes.ESC, escapeAction.getValue(Action.NAME)); + return rootPane; + } + + private void init() { + parameterPanel = new ArgumentsPanel(JMeterUtils.getResString("function_params"), false); //$NON-NLS-1$ + initializeFunctionList(); + this.getContentPane().setLayout(new BorderLayout(10, 10)); + JPanel comboPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + comboPanel.add(functionList); + JButton helpButton = new JButton(JMeterUtils.getResString("help")); //$NON-NLS-1$ + helpButton.addActionListener(new HelpListener()); + comboPanel.add(helpButton); + this.getContentPane().add(comboPanel, BorderLayout.NORTH); + this.getContentPane().add(parameterPanel, BorderLayout.CENTER); + JPanel resultsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + cutPasteFunction = new JLabeledTextField(JMeterUtils.getResString("cut_paste_function"), 35); //$NON-NLS-1$ + resultsPanel.add(cutPasteFunction); + JButton generateButton = new JButton(JMeterUtils.getResString("generate")); //$NON-NLS-1$ + generateButton.addActionListener(this); + resultsPanel.add(generateButton); + this.getContentPane().add(resultsPanel, BorderLayout.SOUTH); + + this.pack(); + ComponentUtil.centerComponentInWindow(this); + } + + private void initializeFunctionList() { + String[] functionNames = CompoundVariable.getFunctionNames(); + Arrays.sort(functionNames, new Comparator() { + @Override + public int compare(String o1, String o2) { + return o1.compareToIgnoreCase(o2); + } + }); + functionList = new JLabeledChoice(JMeterUtils.getResString("choose_function"), functionNames); //$NON-NLS-1$ + functionList.addChangeListener(this); + } + + @Override + public void stateChanged(ChangeEvent event) { + try { + Arguments args = new Arguments(); + Function function = CompoundVariable.getFunctionClass(functionList.getText()).newInstance(); + List argumentDesc = function.getArgumentDesc(); + for (String help : argumentDesc) { + args.addArgument(help, ""); //$NON-NLS-1$ + } + parameterPanel.configure(args); + parameterPanel.revalidate(); + getContentPane().remove(parameterPanel); + this.pack(); + getContentPane().add(parameterPanel, BorderLayout.CENTER); + this.pack(); + this.validate(); + this.repaint(); + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } + } + + @Override + public void actionPerformed(ActionEvent e) { + StringBuilder functionCall = new StringBuilder("${"); + functionCall.append(functionList.getText()); + Arguments args = (Arguments) parameterPanel.createTestElement(); + if (args.getArguments().size() > 0) { + functionCall.append("("); + PropertyIterator iter = args.iterator(); + boolean first = true; + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + if (!first) { + functionCall.append(","); + } + functionCall.append(arg.getValue()); + first = false; + } + functionCall.append(")"); + } + functionCall.append("}"); + cutPasteFunction.setText(functionCall.toString()); + } + + private class HelpListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String[] source = new String[] { Help.HELP_FUNCTIONS, functionList.getText() }; + ActionEvent helpEvent = new ActionEvent(source, e.getID(), "help"); //$NON-NLS-1$ + ActionRouter.getInstance().actionPerformed(helpEvent); + } + } + + @Override + public void localeChanged(LocaleChangeEvent event) { + setTitle(JMeterUtils.getResString("function_helper_title")); //$NON-NLS-1$ + this.getContentPane().removeAll(); // so we can add them again in init + init(); + } +} diff --git a/src/core/org/apache/jmeter/functions/util/ArgumentDecoder.java b/src/core/org/apache/jmeter/functions/util/ArgumentDecoder.java new file mode 100644 index 00000000000..5da4d982ca5 --- /dev/null +++ b/src/core/org/apache/jmeter/functions/util/ArgumentDecoder.java @@ -0,0 +1,41 @@ +/* + * 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.util; + +import org.apache.oro.text.perl.Perl5Util; + +/** + * Decodes an Argument by replacing '\x' with 'x' + */ +public final class ArgumentDecoder { + private static final Perl5Util util = new Perl5Util(); + + private static final String expression = "s#[\\\\](.)#$1#g"; // $NON-NLS-1$ + +// TODO does not appear to be used + public static String decode(String s) { + return util.substitute(expression, s); + } + + /** + * Prevent instantiation of utility class. + */ + private ArgumentDecoder() { + } +} diff --git a/src/core/org/apache/jmeter/functions/util/ArgumentEncoder.java b/src/core/org/apache/jmeter/functions/util/ArgumentEncoder.java new file mode 100644 index 00000000000..2f452afd5c2 --- /dev/null +++ b/src/core/org/apache/jmeter/functions/util/ArgumentEncoder.java @@ -0,0 +1,41 @@ +/* + * 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.util; + +import org.apache.oro.text.perl.Perl5Util; + +/** + * Encode an Argument + */ +public final class ArgumentEncoder { + private static final Perl5Util util = new Perl5Util(); + + private static final String expression = "s#([${}(),\\\\])#\\$1#g"; + + // TODO does not appear to be used + public static String encode(String s) { + return util.substitute(expression, s); + } + + /** + * Prevent instantiation of utility class. + */ + private ArgumentEncoder() { + } +} diff --git a/src/core/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java b/src/core/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java new file mode 100644 index 00000000000..eefa214411d --- /dev/null +++ b/src/core/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java @@ -0,0 +1,341 @@ +/* + * 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; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Font; +import java.util.Locale; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.border.Border; + +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Printable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This abstract class takes care of the most basic functions necessary to + * create a viable JMeter GUI component. It extends JPanel and implements + * JMeterGUIComponent. This abstract class is, in turn, extended by several + * other abstract classes that create different classes of GUI components for + * JMeter (Visualizers, Timers, Samplers, Modifiers, Controllers, etc). + * + * @see org.apache.jmeter.gui.JMeterGUIComponent + * @see org.apache.jmeter.config.gui.AbstractConfigGui + * @see org.apache.jmeter.assertions.gui.AbstractAssertionGui + * @see org.apache.jmeter.control.gui.AbstractControllerGui + * @see org.apache.jmeter.timers.gui.AbstractTimerGui + * @see org.apache.jmeter.visualizers.gui.AbstractVisualizer + * @see org.apache.jmeter.samplers.gui.AbstractSamplerGui + * + */ +public abstract class AbstractJMeterGuiComponent extends JPanel implements JMeterGUIComponent, Printable { + private static final long serialVersionUID = 240L; + + /** Logging */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** Flag indicating whether or not this component is enabled. */ + private boolean enabled = true; + + /** A GUI panel containing the name of this component. */ + protected NamePanel namePanel; + // used by AbstractReportGui + + private final CommentPanel commentPanel; + + /** + * When constructing a new component, this takes care of basic tasks like + * setting up the Name Panel and assigning the class's static label as the + * name to start. + */ + public AbstractJMeterGuiComponent() { + namePanel = new NamePanel(); + commentPanel=new CommentPanel(); + initGui(); + } + + /** + * Provides a default implementation for setting the name property. It's unlikely + * developers will need to override. + */ + @Override + public void setName(String name) { + namePanel.setName(name); + } + + /** + * Provides a default implementation for setting the comment property. It's + * unlikely developers will need to override. + * + * @param comment + * The comment for the property + */ + public void setComment(String comment) { + commentPanel.setText(comment); + } + + /** + * Provides a default implementation for the enabled property. It's unlikely + * developers will need to override. + */ + @Override + public boolean isEnabled() { + return enabled; + } + + /** + * Provides a default implementation for the enabled property. It's unlikely + * developers will need to override. + */ + @Override + public void setEnabled(boolean e) { + log.debug("Setting enabled: " + e); + enabled = e; + } + + /** + * Provides a default implementation for the name property. It's unlikely + * developers will need to override. + */ + @Override + public String getName() { + if (getNamePanel() != null) { + return getNamePanel().getName(); + } + return ""; // $NON-NLS-1$ + } + + /** + * Provides a default implementation for the comment property. It's unlikely + * developers will need to override. + * + * @return The comment for the property + */ + public String getComment() { + if (getCommentPanel() != null) { + return getCommentPanel().getText(); + } + return ""; // $NON-NLS-1$ + } + + /** + * Provides the Name Panel for extending classes. Extending classes are free + * to place it as desired within the component, or not at all. Most + * components place the NamePanel automatically by calling + * {@link #makeTitlePanel()} instead of directly calling this method. + * + * @return a NamePanel containing the name of this component + */ + protected NamePanel getNamePanel() { + return namePanel; + } + + private CommentPanel getCommentPanel(){ + return commentPanel; + } + /** + * Provides a label containing the title for the component. Subclasses + * typically place this label at the top of their GUI. The title is set to + * the name returned from the component's + * {@link JMeterGUIComponent#getStaticLabel() getStaticLabel()} method. Most + * components place this label automatically by calling + * {@link #makeTitlePanel()} instead of directly calling this method. + * + * @return a JLabel which subclasses can add to their GUI + */ + protected Component createTitleLabel() { + JLabel titleLabel = new JLabel(getStaticLabel()); + Font curFont = titleLabel.getFont(); + titleLabel.setFont(curFont.deriveFont((float) curFont.getSize() + 4)); + return titleLabel; + } + + /** + * A newly created gui component can be initialized with the contents of a + * Test Element object by calling this method. The component is responsible + * for querying the Test Element object for the relevant information to + * display in its GUI. + *

+ * AbstractJMeterGuiComponent provides a partial implementation of this + * method, setting the name of the component and its enabled status. + * Subclasses should override this method, performing their own + * configuration as needed, but also calling this super-implementation. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + setName(element.getName()); + enabled = element.isEnabled(); + getCommentPanel().setText(element.getComment()); + } + + /** + * Provides a default implementation that resets the name field to the value of + * getStaticLabel(), reset comment and sets enabled to true. Your GUI may need more things + * cleared, in which case you should override, clear the extra fields, and + * still call super.clearGui(). + */ + @Override + public void clearGui() { + initGui(); + enabled = true; + } + + // helper method - also used by constructor + private void initGui() { + setName(getStaticLabel()); + commentPanel.clearGui(); + } + + /** + * This provides a convenience for extenders when they implement the + * {@link JMeterGUIComponent#modifyTestElement(TestElement)} method. This + * method will set the name, gui class, and test class for the created Test + * Element. It should be called by every extending class when + * creating/modifying Test Elements, as that will best assure consistent + * behavior. + * + * @param mc + * the TestElement being created. + */ + protected void configureTestElement(TestElement mc) { + mc.setName(getName()); + + mc.setProperty(new StringProperty(TestElement.GUI_CLASS, this.getClass().getName())); + + mc.setProperty(new StringProperty(TestElement.TEST_CLASS, mc.getClass().getName())); + + // This stores the state of the TestElement + log.debug("setting element to enabled: " + enabled); + mc.setEnabled(enabled); + mc.setComment(getComment()); + } + + /** + * Create a standard title section for JMeter components. This includes the + * title for the component and the Name Panel allowing the user to change + * the name for the component. This method is typically added to the top of + * the component at the beginning of the component's init method. + * + * @return a panel containing the component title and name panel + */ + protected Container makeTitlePanel() { + VerticalPanel titlePanel = new VerticalPanel(); + titlePanel.add(createTitleLabel()); + VerticalPanel contentPanel = new VerticalPanel(); + contentPanel.setBorder(BorderFactory.createEtchedBorder()); + contentPanel.add(getNamePanel()); + contentPanel.add(getCommentPanel()); + titlePanel.add(contentPanel); + return titlePanel; + } + + /** + * Create a top-level Border which can be added to JMeter components. + * Components typically set this as their border in their init method. It + * simply provides a nice spacing between the GUI components used and the + * edges of the window in which they appear. + * + * @return a Border for JMeter components + */ + protected Border makeBorder() { + return BorderFactory.createEmptyBorder(10, 10, 5, 10); + } + + /** + * Create a scroll panel that sets it's preferred size to it's minimum size. + * Explicitly for scroll panes that live inside other scroll panes, or + * within containers that stretch components to fill the area they exist in. + * Use this for any component you would put in a scroll pane (such as + * TextAreas, tables, JLists, etc). It is here for convenience and to avoid + * duplicate code. JMeter displays best if you follow this custom. + * + * @param comp + * the component which should be placed inside the scroll pane + * @return a JScrollPane containing the specified component + */ + protected JScrollPane makeScrollPane(Component comp) { + JScrollPane pane = new JScrollPane(comp); + pane.setPreferredSize(pane.getMinimumSize()); + return pane; + } + + /** + * Create a scroll panel that sets it's preferred size to it's minimum size. + * Explicitly for scroll panes that live inside other scroll panes, or + * within containers that stretch components to fill the area they exist in. + * Use this for any component you would put in a scroll pane (such as + * TextAreas, tables, JLists, etc). It is here for convenience and to avoid + * duplicate code. JMeter displays best if you follow this custom. + * + * @see javax.swing.ScrollPaneConstants + * + * @param comp + * the component which should be placed inside the scroll pane + * @param verticalPolicy + * the vertical scroll policy + * @param horizontalPolicy + * the horizontal scroll policy + * @return a JScrollPane containing the specified component + */ + protected JScrollPane makeScrollPane(Component comp, int verticalPolicy, int horizontalPolicy) { + JScrollPane pane = new JScrollPane(comp, verticalPolicy, horizontalPolicy); + pane.setPreferredSize(pane.getMinimumSize()); + return pane; + } + + @Override + public String getStaticLabel() { + return JMeterUtils.getResString(getLabelResource()); + } + + /** + * Compute Anchor value to find reference in documentation for a particular component + * @return String anchor + */ + @Override + public String getDocAnchor() { + // Ensure we use default bundle + String label = JMeterUtils.getResString(getLabelResource(), new Locale("","")); + return label.replace(' ', '_'); + } + + /** + * Subclasses need to over-ride this method, if they wish to return + * something other than the Visualizer itself. + * + * @return this object + */ + @Override + public JComponent getPrintableComponent() { + return this; + } +} diff --git a/src/core/org/apache/jmeter/gui/AbstractScopedJMeterGuiComponent.java b/src/core/org/apache/jmeter/gui/AbstractScopedJMeterGuiComponent.java new file mode 100644 index 00000000000..83617a817c7 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/AbstractScopedJMeterGuiComponent.java @@ -0,0 +1,143 @@ +/* + * 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; + +import javax.swing.JPanel; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.util.ScopePanel; + + +public abstract class AbstractScopedJMeterGuiComponent extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + private ScopePanel scopePanel; + + @Override + public void clearGui(){ + super.clearGui(); + if (scopePanel != null) { + scopePanel.clearGui(); + } + } + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most assertion + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + @Override + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultAssertionMenu(); + } + + /** + * Create the scope settings panel. + * + * @return the scope settings panel + */ + protected JPanel createScopePanel() { + return createScopePanel(false); + } + + /** + * Create the scope settings panel. + * @param enableVariable set true to enable the variable panel + * @return the scope settings panel + */ + protected JPanel createScopePanel(boolean enableVariable) { + return createScopePanel(enableVariable, true, true); + } + + /** + * Create the scope settings panel. + * @param enableVariable set true to enable the variable panel + * @param enableParentAndSubsamples set true to enable the parent and sub-samples + * @param enableSubsamplesOnly set true to enable the sub-samples only + * @return the scope settings panel + */ + protected JPanel createScopePanel(boolean enableVariable, boolean enableParentAndSubsamples, boolean enableSubsamplesOnly) { + scopePanel = new ScopePanel(enableVariable, enableParentAndSubsamples, enableSubsamplesOnly); + return scopePanel; + } + + /** + * Save the scope settings in the test element. + * + * @param testElement + * the test element to save the settings into + */ + protected void saveScopeSettings(AbstractScopedTestElement testElement) { + if (scopePanel.isScopeParent()){ + testElement.setScopeParent(); + } else if (scopePanel.isScopeChildren()){ + testElement.setScopeChildren(); + } else if (scopePanel.isScopeAll()) { + testElement.setScopeAll(); + } else if (scopePanel.isScopeVariable()) { + testElement.setScopeVariable(scopePanel.getVariable()); + } else { + throw new IllegalArgumentException("Unexpected scope panel state"); + } + } + + /** + * Show the scope settings from the test element. + * + * @param testElement + * the test element from which the settings should be shown + */ + protected void showScopeSettings(AbstractScopedTestElement testElement) { + showScopeSettings(testElement, false); + } + + /** + * Show the scope settings from the test element with variable scope + * + * @param testElement + * the test element from which the settings should be shown + * @param enableVariableButton + * set true to enable the variable panel + */ + protected void showScopeSettings(AbstractScopedTestElement testElement, + boolean enableVariableButton) { + String scope = testElement.fetchScope(); + if (testElement.isScopeParent(scope)) { + scopePanel.setScopeParent(enableVariableButton); + } else if (testElement.isScopeChildren(scope)){ + scopePanel.setScopeChildren(enableVariableButton); + } else if (testElement.isScopeAll(scope)){ + scopePanel.setScopeAll(enableVariableButton); + } else if (testElement.isScopeVariable(scope)){ + scopePanel.setScopeVariable(testElement.getVariableName()); + } else { + throw new IllegalArgumentException("Invalid scope: "+scope); + } + } + + +} diff --git a/src/core/org/apache/jmeter/gui/ClearGui.java b/src/core/org/apache/jmeter/gui/ClearGui.java new file mode 100644 index 00000000000..b943bd8e901 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/ClearGui.java @@ -0,0 +1,32 @@ +/* + * 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; + +public interface ClearGui { + + /** + * Clear the gui and return it to initial default values. This is necessary + * because most gui classes are instantiated just once and re-used for + * multiple test element objects and thus they need to be cleared between + * uses. + */ + void clearGui(); + // N.B. originally called clear() + // @see also Clearable +} diff --git a/src/core/org/apache/jmeter/gui/CommentPanel.java b/src/core/org/apache/jmeter/gui/CommentPanel.java new file mode 100644 index 00000000000..92b67bcd4bf --- /dev/null +++ b/src/core/org/apache/jmeter/gui/CommentPanel.java @@ -0,0 +1,74 @@ +/* + * 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; + +import java.awt.BorderLayout; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Generic comment panel for Test Elements + * + */ +public class CommentPanel extends JPanel { + private static final long serialVersionUID = 240L; + + /** A text field containing the comment. */ + private JTextArea commentField; + + /** + * Create a new NamePanel with the default name. + */ + public CommentPanel() { + init(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + + commentField = new JTextArea(); + JLabel commentLabel = new JLabel(JMeterUtils.getResString("testplan_comments")); //$NON-NLS-1$ + commentLabel.setLabelFor(commentField); + + JPanel commentPanel = new JPanel(); + commentPanel.setLayout(new BorderLayout(0, 5)); + commentPanel.add(commentLabel,BorderLayout.WEST); + commentPanel.add(commentField,BorderLayout.CENTER); + add(commentPanel); + } + + public void setText(String comment) { + this.commentField.setText(comment); + } + + public String getText() { + return this.commentField.getText(); + } + + public void clearGui() { + commentField.setText(""); // $NON-NLS-1$ + } +} diff --git a/src/core/org/apache/jmeter/gui/GUIFactory.java b/src/core/org/apache/jmeter/gui/GUIFactory.java new file mode 100644 index 00000000000..7f7bcd4481f --- /dev/null +++ b/src/core/org/apache/jmeter/gui/GUIFactory.java @@ -0,0 +1,174 @@ +/* + * 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; + +import java.util.HashMap; +import java.util.Map; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; + +import org.apache.jmeter.testbeans.gui.TestBeanGUI; + +/** + * Provides a way to register and retrieve GUI classes and icons. + * + */ +public final class GUIFactory { + /** A Map from String to JMeterGUIComponent of registered GUI classes. */ + private static final Map GUI_MAP = new HashMap(); + + /** A Map from String to ImageIcon of registered icons. */ + private static final Map ICON_MAP = new HashMap(); + + /** A Map from String to ImageIcon of registered icons. */ + private static final Map DISABLED_ICON_MAP = new HashMap(); + + /** + * Prevent instantiation since this is a static utility class. + */ + private GUIFactory() { + } + + /** + * Get an icon which has previously been registered for this class object. + * + * @param elementClass + * the class object which we want to get an icon for + * + * @return the associated icon, or null if this class or its superclass has + * not been registered + */ + public static ImageIcon getIcon(Class elementClass) { + return getIcon(elementClass, true); + + } + + /** + * Get icon/disabledicon which has previously been registered for this class + * object. + * + * @param elementClass + * the class object which we want to get an icon for + * @param enabled - + * is icon enabled + * + * @return the associated icon, or null if this class or its superclass has + * not been registered + */ + public static ImageIcon getIcon(Class elementClass, boolean enabled) { + String key = elementClass.getName(); + ImageIcon icon = (enabled ? ICON_MAP.get(key) : DISABLED_ICON_MAP.get(key)); + + if (icon != null) { + return icon; + } + + if (elementClass.getSuperclass() != null) { + return getIcon(elementClass.getSuperclass(), enabled); + } + + return null; + } + + /** + * Get a component instance which has previously been registered for this + * class object. + * + * @param elementClass + * the class object which we want to get an instance of + * + * @return an instance of the class, or null if this class or its superclass + * has not been registered + */ + public static JComponent getGUI(Class elementClass) { + // TODO: This method doesn't appear to be used. + String key = elementClass.getName(); + JComponent gui = (JComponent) GUI_MAP.get(key); + + if (gui != null) { + return gui; + } + + if (elementClass.getSuperclass() != null) { + return getGUI(elementClass.getSuperclass()); + } + + return null; + } + + /** + * Register an icon so that it can later be retrieved via + * {@link #getIcon(Class)}. The key should match the fully-qualified class + * name for the class used as the parameter when retrieving the icon. + * + * @param key + * the name which can be used to retrieve this icon later + * @param icon + * the icon to store + */ + public static void registerIcon(String key, ImageIcon icon) { + ICON_MAP.put(key, icon); + } + + /** + * Register an icon so that it can later be retrieved via + * {@link #getIcon(Class)}. The key should match the fully-qualified class + * name for the class used as the parameter when retrieving the icon. + * + * @param key + * the name which can be used to retrieve this icon later + * @param icon + * the icon to store + */ + public static void registerDisabledIcon(String key, ImageIcon icon) { + DISABLED_ICON_MAP.put(key, icon); + } + + /** + * Register a GUI class so that it can later be retrieved via + * {@link #getGUI(Class)}. The key should match the fully-qualified class + * name for the class used as the parameter when retrieving the GUI. + * + * @param key + * the name which can be used to retrieve this GUI later + * @param guiClass + * the class object for the GUI component + * @param testClass + * the class of the objects edited by this GUI + * + * @throws InstantiationException + * if an instance of the GUI class can not be instantiated + * @throws IllegalAccessException + * if access rights do not permit an instance of the GUI class + * to be created + */ + public static void registerGUI(String key, Class guiClass, Class testClass) throws InstantiationException, + IllegalAccessException { + // TODO: This method doesn't appear to be used. + JMeterGUIComponent gui; + + if (guiClass == TestBeanGUI.class) { + gui = new TestBeanGUI(testClass); + } else { + gui = (JMeterGUIComponent) guiClass.newInstance(); + } + GUI_MAP.put(key, gui); + } +} diff --git a/src/core/org/apache/jmeter/gui/GuiPackage.java b/src/core/org/apache/jmeter/gui/GuiPackage.java new file mode 100644 index 00000000000..d7946354682 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/GuiPackage.java @@ -0,0 +1,867 @@ +/* + * 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; + +import java.awt.Component; +import java.awt.event.MouseEvent; +import java.beans.Introspector; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPopupMenu; +import javax.swing.JToolBar; +import javax.swing.SwingUtilities; + +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.UndoHistory.HistoryListener; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.JMeterToolBar; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TestBeanGUI; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * GuiPackage is a static class that provides convenient access to information + * about the current state of JMeter's GUI. Any GUI class can grab a handle to + * GuiPackage by calling the static method {@link #getInstance()} and then use + * it to query the GUI about it's state. When actions, for instance, need to + * affect the GUI, they typically use GuiPackage to get access to different + * parts of the GUI. + * + */ +public final class GuiPackage implements LocaleChangeListener, HistoryListener { + /** Logging. */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** Singleton instance. */ + private static GuiPackage guiPack; + + /** + * Flag indicating whether or not parts of the tree have changed since they + * were last saved. + */ + private boolean dirty = false; + + /** + * Map from TestElement to JMeterGUIComponent, mapping the nodes in the tree + * to their corresponding GUI components. + */ + private Map nodesToGui = new HashMap(); + + /** + * Map from Class to JMeterGUIComponent, mapping the Class of a GUI + * component to an instance of that component. + */ + private Map, JMeterGUIComponent> guis = new HashMap, JMeterGUIComponent>(); + + /** + * Map from Class to TestBeanGUI, mapping the Class of a TestBean to an + * instance of TestBeanGUI to be used to edit such components. + */ + private Map, JMeterGUIComponent> testBeanGUIs = new HashMap, JMeterGUIComponent>(); + + /** The currently selected node in the tree. */ + private JMeterTreeNode currentNode = null; + + private boolean currentNodeUpdated = false; + + /** The model for JMeter's test tree. */ + private final JMeterTreeModel treeModel; + + /** The listener for JMeter's test tree. */ + private final JMeterTreeListener treeListener; + + /** The main JMeter frame. */ + private MainFrame mainFrame; + + /** The main JMeter toolbar. */ + private JToolBar toolbar; + + /** The menu item toolbar. */ + private JCheckBoxMenuItem menuToolBar; + + /** + * The LoggerPanel menu item + */ + private JCheckBoxMenuItem menuItemLoggerPanel; + + /** + * Logger Panel reference + */ + private LoggerPanel loggerPanel; + + /** + * History for tree states + */ + private UndoHistory undoHistory = new UndoHistory(); + + /** + * Private constructor to permit instantiation only from within this class. + * Use {@link #getInstance()} to retrieve a singleton instance. + */ + private GuiPackage(JMeterTreeModel treeModel, JMeterTreeListener treeListener) { + this.treeModel = treeModel; + if(undoHistory.isEnabled()) { + this.treeModel.addTreeModelListener(undoHistory); + } + this.treeListener = treeListener; + } + + /** + * Retrieve the singleton GuiPackage instance. + * + * @return the GuiPackage instance (may be null, e.g in non-Gui mode) + */ + public static GuiPackage getInstance() { + return guiPack; + } + + /** + * Register as listener of: + * - UndoHistory + * - Locale Changes + */ + public void registerAsListener() { + if(undoHistory.isEnabled()) { + this.undoHistory.registerHistoryListener(this); + } + JMeterUtils.addLocaleChangeListener(this); + } + + /** + * When GuiPackage is requested for the first time, it should be given + * handles to JMeter's Tree Listener and TreeModel. + * + * @param listener + * the TreeListener for JMeter's test tree + * @param treeModel + * the model for JMeter's test tree + * + * @return GuiPackage + */ + public static GuiPackage getInstance(JMeterTreeListener listener, JMeterTreeModel treeModel) { + if (guiPack == null) { + guiPack = new GuiPackage(treeModel, listener); + guiPack.undoHistory.add(treeModel, "Created"); + } + return guiPack; + } + + /** + * Get a JMeterGUIComponent for the specified test element. If the GUI has + * already been created, that instance will be returned. Otherwise, if a GUI + * component of the same type has been created, and the component is not + * marked as an {@link UnsharedComponent}, that shared component will be + * returned. Otherwise, a new instance of the component will be created. The + * TestElement's GUI_CLASS property will be used to determine the + * appropriate type of GUI component to use. + * + * @param node + * the test element which this GUI is being created for + * + * @return the GUI component corresponding to the specified test element + */ + public JMeterGUIComponent getGui(TestElement node) { + String testClassName = node.getPropertyAsString(TestElement.TEST_CLASS); + String guiClassName = node.getPropertyAsString(TestElement.GUI_CLASS); + try { + Class testClass; + if (testClassName.equals("")) { // $NON-NLS-1$ + testClass = node.getClass(); + } else { + testClass = Class.forName(testClassName); + } + Class guiClass = null; + if (!guiClassName.equals("")) { // $NON-NLS-1$ + guiClass = Class.forName(guiClassName); + } + return getGui(node, guiClass, testClass); + } catch (ClassNotFoundException e) { + log.error("Could not get GUI for " + node, e); + return null; + } + } + + /** + * Get a JMeterGUIComponent for the specified test element. If the GUI has + * already been created, that instance will be returned. Otherwise, if a GUI + * component of the same type has been created, and the component is not + * marked as an {@link UnsharedComponent}, that shared component will be + * returned. Otherwise, a new instance of the component will be created. + * + * @param node + * the test element which this GUI is being created for + * @param guiClass + * the fully qualified class name of the GUI component which will + * be created if it doesn't already exist + * @param testClass + * the fully qualified class name of the test elements which have + * to be edited by the returned GUI component + * + * @return the GUI component corresponding to the specified test element + */ + public JMeterGUIComponent getGui(TestElement node, Class guiClass, Class testClass) { + try { + JMeterGUIComponent comp = nodesToGui.get(node); + if (comp == null) { + comp = getGuiFromCache(guiClass, testClass); + nodesToGui.put(node, comp); + } + log.debug("Gui retrieved = " + comp); + return comp; + } catch (Exception e) { + log.error("Problem retrieving gui", e); + return null; + } + } + + /** + * Remove a test element from the tree. This removes the reference to any + * associated GUI component. + * + * @param node + * the test element being removed + */ + public void removeNode(TestElement node) { + nodesToGui.remove(node); + } + + /** + * Convenience method for grabbing the gui for the current node. + * + * @return the GUI component associated with the currently selected node + */ + public JMeterGUIComponent getCurrentGui() { + try { + updateCurrentNode(); + TestElement curNode = treeListener.getCurrentNode().getTestElement(); + JMeterGUIComponent comp = getGui(curNode); + comp.clearGui(); + log.debug("Updating gui to new node"); + comp.configure(curNode); + currentNodeUpdated = false; + return comp; + } catch (Exception e) { + log.error("Problem retrieving gui", e); + return null; + } + } + + /** + * Find the JMeterTreeNode for a certain TestElement object. + * + * @param userObject + * the test element to search for + * @return the tree node associated with the test element + */ + public JMeterTreeNode getNodeOf(TestElement userObject) { + return treeModel.getNodeOf(userObject); + } + + /** + * Create a TestElement corresponding to the specified GUI class. + * + * @param guiClass + * the fully qualified class name of the GUI component or a + * TestBean class for TestBeanGUIs. + * @param testClass + * the fully qualified class name of the test elements edited by + * this GUI component. + * @return the test element corresponding to the specified GUI class. + */ + public TestElement createTestElement(Class guiClass, Class testClass) { + try { + JMeterGUIComponent comp = getGuiFromCache(guiClass, testClass); + comp.clearGui(); + TestElement node = comp.createTestElement(); + nodesToGui.put(node, comp); + return node; + } catch (Exception e) { + log.error("Problem retrieving gui", e); + return null; + } + } + + /** + * Create a TestElement for a GUI or TestBean class. + *

+ * This is a utility method to help actions do with one single String + * parameter. + * + * @param objClass + * the fully qualified class name of the GUI component or of the + * TestBean subclass for which a TestBeanGUI is wanted. + * @return the test element corresponding to the specified GUI class. + */ + public TestElement createTestElement(String objClass) { + JMeterGUIComponent comp; + Class c; + try { + c = Class.forName(objClass); + if (TestBean.class.isAssignableFrom(c)) { + comp = getGuiFromCache(TestBeanGUI.class, c); + } else { + comp = getGuiFromCache(c, null); + } + comp.clearGui(); + TestElement node = comp.createTestElement(); + nodesToGui.put(node, comp); + return node; + } catch (NoClassDefFoundError e) { + log.error("Problem retrieving gui for " + objClass, e); + String msg="Cannot find class: "+e.getMessage(); + JOptionPane.showMessageDialog(null, + msg, + "Missing jar? See log file." , + JOptionPane.ERROR_MESSAGE); + throw new RuntimeException(e.toString(), e); // Probably a missing jar + } catch (ClassNotFoundException e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString(), e); // Programming error: bail out. + } catch (InstantiationException e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString(), e); // Programming error: bail out. + } catch (IllegalAccessException e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString(), e); // Programming error: bail out. + } + } + + /** + * Get an instance of the specified JMeterGUIComponent class. If an instance + * of the GUI class has previously been created and it is not marked as an + * {@link UnsharedComponent}, that shared instance will be returned. + * Otherwise, a new instance of the component will be created, and shared + * components will be cached for future retrieval. + * + * @param guiClass + * the fully qualified class name of the GUI component. This + * class must implement JMeterGUIComponent. + * @param testClass + * the fully qualified class name of the test elements edited by + * this GUI component. This class must implement TestElement. + * @return an instance of the specified class + * + * @throws InstantiationException + * if an instance of the object cannot be created + * @throws IllegalAccessException + * if access rights do not allow the default constructor to be + * called + * @throws ClassNotFoundException + * if the specified GUI class cannot be found + */ + private JMeterGUIComponent getGuiFromCache(Class guiClass, Class testClass) throws InstantiationException, + IllegalAccessException { + JMeterGUIComponent comp; + if (guiClass == TestBeanGUI.class) { + comp = testBeanGUIs.get(testClass); + if (comp == null) { + comp = new TestBeanGUI(testClass); + testBeanGUIs.put(testClass, comp); + } + } else { + comp = guis.get(guiClass); + if (comp == null) { + comp = (JMeterGUIComponent) guiClass.newInstance(); + if (!(comp instanceof UnsharedComponent)) { + guis.put(guiClass, comp); + } + } + } + return comp; + } + + /** + * Update the GUI for the currently selected node. The GUI component is + * configured to reflect the settings in the current tree node. + * + */ + public void updateCurrentGui() { + updateCurrentNode(); + currentNode = treeListener.getCurrentNode(); + TestElement element = currentNode.getTestElement(); + JMeterGUIComponent comp = getGui(element); + comp.configure(element); + currentNodeUpdated = false; + } + + /** + * This method should be called in order for GuiPackage to change the + * current node. This will save any changes made to the earlier node before + * choosing the new node. + */ + public void updateCurrentNode() { + try { + if (currentNode != null && !currentNodeUpdated) { + log.debug("Updating current node " + currentNode.getName()); + JMeterGUIComponent comp = getGui(currentNode.getTestElement()); + TestElement el = currentNode.getTestElement(); + int before = getTestElementCheckSum(el); + comp.modifyTestElement(el); + int after = getTestElementCheckSum(el); + if (before != after) { + currentNode.nameChanged(); // Bug 50221 - ensure label is updated + } + } + // The current node is now updated + currentNodeUpdated = true; + currentNode = treeListener.getCurrentNode(); + } catch (Exception e) { + log.error("Problem retrieving gui", e); + } + } + + public JMeterTreeNode getCurrentNode() { + return treeListener.getCurrentNode(); + } + + public TestElement getCurrentElement() { + return getCurrentNode().getTestElement(); + } + + /** + * The dirty property is a flag that indicates whether there are parts of + * JMeter's test tree that the user has not saved since last modification. + * Various (@link Command actions) set this property when components are + * modified/created/saved. + * + * @param dirty + * the new value of the dirty flag + */ + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + /** + * Retrieves the state of the 'dirty' property, a flag that indicates if + * there are test tree components that have been modified since they were + * last saved. + * + * @return true if some tree components have been modified since they were + * last saved, false otherwise + */ + public boolean isDirty() { + return dirty; + } + + /** + * Add a subtree to the currently selected node. + * + * @param subTree + * the subtree to add. + * + * @return the resulting subtree starting with the currently selected node + * + * @throws IllegalUserActionException + * if a subtree cannot be added to the currently selected node + */ + public HashTree addSubTree(HashTree subTree) throws IllegalUserActionException { + HashTree hashTree = treeModel.addSubTree(subTree, treeListener.getCurrentNode()); + undoHistory.clear(); + undoHistory.add(this.treeModel, "Loaded tree"); + return hashTree; + } + + /** + * Get the currently selected subtree. + * + * @return the subtree of the currently selected node + */ + public HashTree getCurrentSubTree() { + return treeModel.getCurrentSubTree(treeListener.getCurrentNode()); + } + + /** + * Get the model for JMeter's test tree. + * + * @return the JMeter tree model + */ + /* + * TODO consider removing this method, and providing method wrappers instead. + * This would allow the Gui package to do any additional clearups if required, + * as has been done with clearTestPlan() + */ + public JMeterTreeModel getTreeModel() { + return treeModel; + } + + /** + * Get a ValueReplacer for the test tree. + * + * @return a ValueReplacer configured for the test tree + */ + public ValueReplacer getReplacer() { + return new ValueReplacer((TestPlan) ((JMeterTreeNode) getTreeModel().getTestPlan().getArray()[0]) + .getTestElement()); + } + + /** + * Set the main JMeter frame. + * + * @param newMainFrame + * the new JMeter main frame + */ + public void setMainFrame(MainFrame newMainFrame) { + mainFrame = newMainFrame; + } + + /** + * Get the main JMeter frame. + * + * @return the main JMeter frame + */ + public MainFrame getMainFrame() { + return mainFrame; + } + + /** + * Get the listener for JMeter's test tree. + * + * @return the JMeter test tree listener + */ + public JMeterTreeListener getTreeListener() { + return treeListener; + } + + /** + * Set the main JMeter toolbar. + * + * @param newToolbar + * the new JMeter main toolbar + */ + public void setMainToolbar(JToolBar newToolbar) { + toolbar = newToolbar; + } + + /** + * Get the main JMeter toolbar. + * + * @return the main JMeter toolbar + */ + public JToolBar getMainToolbar() { + return toolbar; + } + + /** + * Set the menu item toolbar. + * + * @param newMenuToolBar + * the new menu item toolbar + */ + public void setMenuItemToolbar(JCheckBoxMenuItem newMenuToolBar) { + menuToolBar = newMenuToolBar; + } + + /** + * Get the menu item toolbar. + * + * @return the menu item toolbar + */ + public JCheckBoxMenuItem getMenuItemToolbar() { + return menuToolBar; + } + + /** + * Display the specified popup menu with the source component and location + * from the specified mouse event. + * + * @param e + * the mouse event causing this popup to be displayed + * @param popup + * the popup menu to display + */ + public void displayPopUp(MouseEvent e, JPopupMenu popup) { + displayPopUp((Component) e.getSource(), e, popup); + } + + /** + * Display the specified popup menu at the location specified by a mouse + * event with the specified source component. + * + * @param invoker + * the source component + * @param e + * the mouse event causing this popup to be displayed + * @param popup + * the popup menu to display + */ + public void displayPopUp(Component invoker, MouseEvent e, JPopupMenu popup) { + if (popup != null) { + log.debug("Showing pop up for " + invoker + " at x,y = " + e.getX() + "," + e.getY()); + + popup.pack(); + popup.show(invoker, e.getX(), e.getY()); + popup.setVisible(true); + popup.requestFocusInWindow(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void localeChanged(LocaleChangeEvent event) { + // FIrst make sure we save the content of the current GUI (since we + // will flush it away): + updateCurrentNode(); + + // Forget about all GUIs we've created so far: we'll need to re-created + // them all! + guis = new HashMap, JMeterGUIComponent>(); + nodesToGui = new HashMap(); + testBeanGUIs = new HashMap, JMeterGUIComponent>(); + + // BeanInfo objects also contain locale-sensitive data -- flush them + // away: + Introspector.flushCaches(); + + // Now put the current GUI in place. [This code was copied from the + // EditCommand action -- we can't just trigger the action because that + // would populate the current node with the contents of the new GUI -- + // which is empty.] + MainFrame mf = getMainFrame(); // Fetch once + if (mf == null) // Probably caused by unit testing on headless system + { + log.warn("Mainframe is null"); + } else { + mf.setMainPanel((javax.swing.JComponent) getCurrentGui()); + mf.setEditMenu(getTreeListener().getCurrentNode().createPopupMenu()); + } + } + + private String testPlanFile; + + private final List stoppables = Collections.synchronizedList(new ArrayList()); + + /** + * Sets the filepath of the current test plan. It's shown in the main frame + * title and used on saving. + * + * @param f + * The filepath of the current test plan + */ + public void setTestPlanFile(String f) { + testPlanFile = f; + getMainFrame().setExtendedFrameTitle(testPlanFile); + // Enable file revert action if a file is used + getMainFrame().setFileRevertEnabled(f != null); + getMainFrame().setProjectFileLoaded(f); + + try { + FileServer.getFileServer().setBasedir(testPlanFile); + } catch (IllegalStateException e1) { + log.error("Failure setting file server's base dir", e1); + } + } + + public String getTestPlanFile() { + return testPlanFile; + } + + /** + * Clears the test plan and associated objects. + * Clears the test plan file name. + */ + public void clearTestPlan() { + getTreeModel().clearTestPlan(); + nodesToGui.clear(); + setTestPlanFile(null); + undoHistory.clear(); + undoHistory.add(this.treeModel, "Initial Tree"); + } + + /** + * Clears the test plan element and associated object + * + * @param element to clear + */ + public void clearTestPlan(TestElement element) { + getTreeModel().clearTestPlan(element); + removeNode(element); + undoHistory.clear(); + undoHistory.add(this.treeModel, "Initial Tree"); + } + + public static void showErrorMessage(final String message, final String title){ + showMessage(message,title,JOptionPane.ERROR_MESSAGE); + } + + public static void showInfoMessage(final String message, final String title){ + showMessage(message,title,JOptionPane.INFORMATION_MESSAGE); + } + + public static void showWarningMessage(final String message, final String title){ + showMessage(message,title,JOptionPane.WARNING_MESSAGE); + } + + public static void showMessage(final String message, final String title, final int type){ + if (guiPack == null) { + return ; + } + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + JOptionPane.showMessageDialog(null,message,title,type); + } + }); + + } + + /** + * Unregister stoppable + * @param stoppable Stoppable + */ + public void unregister(Stoppable stoppable) { + for (Iterator iterator = stoppables .iterator(); iterator.hasNext();) { + Stoppable stopable = iterator.next(); + if(stopable == stoppable) + { + iterator.remove(); + } + } + } + + /** + * Register process to stop on reload + * + * @param stoppable + * The {@link Stoppable} to be registered + */ + public void register(Stoppable stoppable) { + stoppables.add(stoppable); + } + + /** + * + * @return copy of list of {@link Stoppable}s + */ + public List getStoppables() { + ArrayList list = new ArrayList(); + list.addAll(stoppables); + return list; + } + + /** + * Set the menu item LoggerPanel. + * @param menuItemLoggerPanel The menu item LoggerPanel + */ + public void setMenuItemLoggerPanel(JCheckBoxMenuItem menuItemLoggerPanel) { + this.menuItemLoggerPanel = menuItemLoggerPanel; + } + + /** + * Get the menu item LoggerPanel. + * + * @return the menu item LoggerPanel + */ + public JCheckBoxMenuItem getMenuItemLoggerPanel() { + return menuItemLoggerPanel; + } + + /** + * @param loggerPanel LoggerPanel + */ + public void setLoggerPanel(LoggerPanel loggerPanel) { + this.loggerPanel = loggerPanel; + } + + /** + * @return the loggerPanel + */ + public LoggerPanel getLoggerPanel() { + return loggerPanel; + } + + /** + * Navigate back and forward through undo history + * + * @param offset int + */ + public void goInHistory(int offset) { + undoHistory.moveInHistory(offset, this.treeModel); + } + + /** + * @return true if history contains redo item + */ + public boolean canRedo() { + return undoHistory.canRedo(); + } + + /** + * @return true if history contains undo item + */ + public boolean canUndo() { + return undoHistory.canUndo(); + } + + /** + * Compute checksum of TestElement to detect changes + * the method calculates properties checksum to detect testelement + * modifications + * TODO would be better to override hashCode for TestElement, but I decided not to touch it + * + * @param el {@link TestElement} + * @return int checksum + */ + private int getTestElementCheckSum(TestElement el) { + int ret = el.getClass().hashCode(); + PropertyIterator it = el.propertyIterator(); + while (it.hasNext()) { + JMeterProperty obj = it.next(); + if (obj instanceof TestElementProperty) { + ret ^= getTestElementCheckSum(((TestElementProperty) obj) + .getElement()); + } else { + ret ^= obj.getName().hashCode(); + ret ^= obj.getStringValue().hashCode(); + } + } + return ret; + } + + /** + * Called when history changes, it updates toolbar + */ + @Override + public void notifyChangeInHistory(UndoHistory history) { + ((JMeterToolBar)toolbar).updateUndoRedoIcons(history.canUndo(), history.canRedo()); + } + +} diff --git a/src/core/org/apache/jmeter/gui/JMeterFileFilter.java b/src/core/org/apache/jmeter/gui/JMeterFileFilter.java new file mode 100644 index 00000000000..5c140d207ec --- /dev/null +++ b/src/core/org/apache/jmeter/gui/JMeterFileFilter.java @@ -0,0 +1,113 @@ +/* + * 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; + +import java.io.File; +import java.util.Arrays; + +/** + * A file filter which allows files to be filtered based on a list of allowed + * extensions. + * + * Optionally returns directories. + * + */ +public class JMeterFileFilter extends javax.swing.filechooser.FileFilter implements java.io.FileFilter { + /** The list of extensions allowed by this filter. */ + private final String[] exts; + + private final boolean allowDirs; // Should we allow directories? + + /** + * Create a new JMeter file filter which allows the specified extensions. If + * the array of extensions contains no elements, any file will be allowed. + * + * This constructor will also return all directories + * + * @param extensions + * non-null array of allowed file extensions + */ + public JMeterFileFilter(String[] extensions) { + this(extensions,true); + } + + /** + * Create a new JMeter file filter which allows the specified extensions. If + * the array of extensions contains no elements, any file will be allowed. + * + * @param extensions non-null array of allowed file extensions + * @param allow should directories be returned ? + */ + public JMeterFileFilter(String[] extensions, boolean allow) { + exts = extensions; + allowDirs = allow; + } + + /** + * Determine if the specified file is allowed by this filter. The file will + * be allowed if it is a directory, or if the end of the filename matches + * one of the extensions allowed by this filter. The filename is converted + * to lower-case before making the comparison. + * + * @param f + * the File being tested + * + * @return true if the file should be allowed, false otherwise + */ + @Override + public boolean accept(File f) { + return (allowDirs && f.isDirectory()) || accept(f.getName().toLowerCase()); + // TODO - why lower case? OK to use the default Locale? + } + + /** + * Determine if the specified filename is allowed by this filter. The file + * will be allowed if the end of the filename matches one of the extensions + * allowed by this filter. The comparison is case-sensitive. If no + * extensions were provided for this filter, the file will always be + * allowed. + * + * @param filename + * the filename to test + * @return true if the file should be allowed, false otherwise + */ + public boolean accept(String filename) { + if (exts.length == 0) { + return true; + } + + for (int i = 0; i < exts.length; i++) { + if (filename.endsWith(exts[i])) { + return true; + } + } + + return false; + } + + /** + * Get a description for this filter. + * + * @return a description for this filter + */ + @Override + public String getDescription() { + return "JMeter " + Arrays.asList(exts).toString(); + } +} diff --git a/src/core/org/apache/jmeter/gui/JMeterGUIComponent.java b/src/core/org/apache/jmeter/gui/JMeterGUIComponent.java new file mode 100644 index 00000000000..17dce6329fa --- /dev/null +++ b/src/core/org/apache/jmeter/gui/JMeterGUIComponent.java @@ -0,0 +1,179 @@ +/* + * 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; + +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.testelement.TestElement; + +/** + * Implementing this interface indicates that the class is a JMeter GUI + * Component. A JMeter GUI Component is essentially the GUI display code + * associated with a JMeter Test Element. The writer of the component must take + * care to make the component be consistent with the rest of JMeter's GUI look + * and feel and behavior. Use of the provided abstract classes is highly + * recommended to make this task easier. + * + * @see AbstractJMeterGuiComponent + * @see org.apache.jmeter.config.gui.AbstractConfigGui + * @see org.apache.jmeter.assertions.gui.AbstractAssertionGui + * @see org.apache.jmeter.control.gui.AbstractControllerGui + * @see org.apache.jmeter.timers.gui.AbstractTimerGui + * @see org.apache.jmeter.visualizers.gui.AbstractVisualizer + * @see org.apache.jmeter.samplers.gui.AbstractSamplerGui + * + */ + +public interface JMeterGUIComponent extends ClearGui { + + /** + * Sets the name of the JMeter GUI Component. The name of the component is + * used in the Test Tree as the name of the tree node. + * + * @param name + * the name of the component + */ + void setName(String name); + + /** + * Gets the name of the JMeter GUI component. The name of the component is + * used in the Test Tree as the name of the tree node. + * + * @return the name of the component + */ + String getName(); + + /** + * Get the component's label. This label is used in drop down lists that + * give the user the option of choosing one type of component in a list of + * many. It should therefore be a descriptive name for the end user to see. + * It must be unique to the class. + * + * It is also used by Help to find the appropriate location in the + * documentation. + * + * Normally getLabelResource() should be overridden instead of + * this method; the definition of this method in AbstractJMeterGuiComponent + * is intended for general use. + * + * @see #getLabelResource() + * @return GUI label for the component. + */ + String getStaticLabel(); + + /** + * Get the component's resource name, which getStaticLabel uses to derive + * the component's label in the local language. The resource name is fixed, + * and does not vary with the selected language. + * + * Normally this method should be overriden in preference to overriding + * getStaticLabel(). However where the resource name is not available or required, + * getStaticLabel() may be overridden instead. + * + * @return the resource name + */ + String getLabelResource(); + + /** + * Get the component's document anchor name. Used by Help to find the + * appropriate location in the documentation + * + * @return Document anchor (#ref) for the component. + */ + String getDocAnchor(); + + /** + * JMeter test components are separated into a model and a GUI + * representation. The model holds the data and the GUI displays it. The GUI + * class is responsible for knowing how to create and initialize with data + * the model class that it knows how to display, and this method is called + * when new test elements are created. + * + * @return the Test Element object that the GUI component represents. + */ + TestElement createTestElement(); + + /** + * GUI components are responsible for populating TestElements they create + * with the data currently held in the GUI components. This method should + * overwrite whatever data is currently in the TestElement as it is called + * after a user has filled out the form elements in the gui with new + * information. + * + * @param element + * the TestElement to modify + */ + void modifyTestElement(TestElement element); + + /** + * Test GUI elements can be disabled, in which case they do not become part + * of the test when run. + * + * @return true if the element should be part of the test run, false + * otherwise + */ + boolean isEnabled(); + + /** + * Set whether this component is enabled. + * + * @param enabled + * true for enabled, false for disabled. + */ + void setEnabled(boolean enabled); + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + * + * @return a JPopupMenu appropriate for the component. + */ + JPopupMenu createPopupMenu(); + + /** + * The GUI must be able to extract the data from the TestElement and update + * all GUI fields to represent those data. This method is called to allow + * JMeter to show the user the GUI that represents the test element's data. + * + * @param element + * the TestElement to configure + */ + void configure(TestElement element); + + /** + * This is the list of add menu categories this gui component will be + * available under. For instance, if this represents a Controller, then the + * MenuFactory.CONTROLLERS category should be in the returned collection. + * When a user right-clicks on a tree element and looks through the "add" + * menu, which category your GUI component shows up in is determined by + * which categories are returned by this method. Most GUI's belong to only + * one category, but it is possible for a component to exist in multiple + * categories. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + * + * @see org.apache.jmeter.gui.util.MenuFactory + */ + Collection getMenuCategories(); +} diff --git a/src/core/org/apache/jmeter/gui/LoggerPanel.java b/src/core/org/apache/jmeter/gui/LoggerPanel.java new file mode 100644 index 00000000000..a578716420b --- /dev/null +++ b/src/core/org/apache/jmeter/gui/LoggerPanel.java @@ -0,0 +1,127 @@ +/* + * 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; + +import java.awt.BorderLayout; +import java.awt.Insets; + +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; + +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.LogEvent; +import org.apache.log.LogTarget; +import org.apache.log.format.PatternFormatter; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; + +/** + * Panel that shows log events + */ +public class LoggerPanel extends JPanel implements LogTarget { + + private static final long serialVersionUID = 6911128494402594429L; + + private final JTextArea textArea; + + private final PatternFormatter format; + + // Limit length of log content + private static final int LOGGER_PANEL_MAX_LENGTH = + JMeterUtils.getPropDefault("jmeter.loggerpanel.maxlength", 80000); // $NON-NLS-1$ + + // Make panel handle event even if closed + private static final boolean LOGGER_PANEL_RECEIVE_WHEN_CLOSED = + JMeterUtils.getPropDefault("jmeter.loggerpanel.enable_when_closed", true); // $NON-NLS-1$ + + /** + * Pane for display JMeter log file + */ + public LoggerPanel() { + textArea = init(); + format = new PatternFormatter(LoggingManager.DEFAULT_PATTERN + "\n"); // $NON-NLS-1$ + } + + private JTextArea init() { + this.setLayout(new BorderLayout()); + final JScrollPane areaScrollPane; + final JTextArea jTextArea; + + if (JMeterUtils.getPropDefault("loggerpanel.usejsyntaxtext", true)) { + // JSyntax Text Area + JSyntaxTextArea jSyntaxTextArea = new JSyntaxTextArea(15, 80, true); + jSyntaxTextArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE); + jSyntaxTextArea.setCodeFoldingEnabled(false); + jSyntaxTextArea.setAntiAliasingEnabled(false); + jSyntaxTextArea.setEditable(false); + jSyntaxTextArea.setLineWrap(false); + jSyntaxTextArea.setLanguage("text"); + jSyntaxTextArea.setMargin(new Insets(2, 2, 2, 2)); // space between borders and text + areaScrollPane = new JTextScrollPane(jSyntaxTextArea); + jTextArea = jSyntaxTextArea; + } else { + // Plain text area + jTextArea = new JTextArea(15, 80); + areaScrollPane = new JScrollPane(jTextArea); + } + + areaScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + areaScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + this.add(areaScrollPane, BorderLayout.CENTER); + return jTextArea; + } + + /* (non-Javadoc) + * @see org.apache.log.LogTarget#processEvent(org.apache.log.LogEvent) + */ + @Override + public void processEvent(final LogEvent logEvent) { + if(!LOGGER_PANEL_RECEIVE_WHEN_CLOSED && !GuiPackage.getInstance().getMenuItemLoggerPanel().getModel().isSelected()) { + return; + } + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + synchronized (textArea) { + textArea.append(format.format(logEvent)); + int currentLength = textArea.getText().length(); + // If LOGGER_PANEL_MAX_LENGTH is 0, it means all log events are kept + if(LOGGER_PANEL_MAX_LENGTH != 0 && currentLength> LOGGER_PANEL_MAX_LENGTH) { + textArea.setText(textArea.getText().substring(Math.max(0, currentLength-LOGGER_PANEL_MAX_LENGTH), + currentLength)); + } + textArea.setCaretPosition(textArea.getText().length()); + } + } + }); + } + + /** + * Clear panel content + */ + public void clear() { + this.textArea.setText(""); // $NON-NLS-1$ + } +} diff --git a/src/core/org/apache/jmeter/gui/MainFrame.java b/src/core/org/apache/jmeter/gui/MainFrame.java new file mode 100644 index 00000000000..29185be49b5 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/MainFrame.java @@ -0,0 +1,829 @@ +/* + * 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; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Insets; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetListener; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DropMode; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTree; +import javax.swing.MenuElement; +import javax.swing.SwingUtilities; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.LoadDraggedFile; +import org.apache.jmeter.gui.tree.JMeterCellRenderer; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeTransferHandler; +import org.apache.jmeter.gui.util.EscapeDialog; +import org.apache.jmeter.gui.util.JMeterMenuBar; +import org.apache.jmeter.gui.util.JMeterToolBar; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.LogEvent; +import org.apache.log.LogTarget; +import org.apache.log.Logger; +import org.apache.log.Priority; + +/** + * The main JMeter frame, containing the menu bar, test tree, and an area for + * JMeter component GUIs. + * + */ +public class MainFrame extends JFrame implements TestStateListener, Remoteable, DropTargetListener, Clearable, ActionListener { + + private static final long serialVersionUID = 240L; + + // This is used to keep track of local (non-remote) tests + // The name is chosen to be an unlikely host-name + public static final String LOCAL = "*local*"; // $NON-NLS-1$ + + // The application name + private static final String DEFAULT_APP_NAME = "Apache JMeter"; // $NON-NLS-1$ + + // The default title for the Menu bar + private static final String DEFAULT_TITLE = DEFAULT_APP_NAME + + " (" + JMeterUtils.getJMeterVersion() + ")"; // $NON-NLS-1$ $NON-NLS-2$ + + // Allow display/hide toolbar + private static final boolean DISPLAY_TOOLBAR = + JMeterUtils.getPropDefault("jmeter.toolbar.display", true); // $NON-NLS-1$ + + // Allow display/hide LoggerPanel + private static final boolean DISPLAY_LOGGER_PANEL = + JMeterUtils.getPropDefault("jmeter.loggerpanel.display", false); // $NON-NLS-1$ + + // Allow display/hide Log Error/Fatal counter + private static final boolean DISPLAY_ERROR_FATAL_COUNTER = + JMeterUtils.getPropDefault("jmeter.errorscounter.display", true); // $NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** The menu bar. */ + private JMeterMenuBar menuBar; + + /** The main panel where components display their GUIs. */ + private JScrollPane mainPanel; + + /** The panel where the test tree is shown. */ + private JScrollPane treePanel; + + /** The LOG panel. */ + private LoggerPanel logPanel; + + /** The test tree. */ + private JTree tree; + + /** An image which is displayed when a test is running. */ + private final ImageIcon runningIcon = JMeterUtils.getImage("thread.enabled.gif");// $NON-NLS-1$ + + /** An image which is displayed when a test is not currently running. */ + private final ImageIcon stoppedIcon = JMeterUtils.getImage("thread.disabled.gif");// $NON-NLS-1$ + + /** An image which is displayed to indicate FATAL, ERROR or WARNING. */ + private final ImageIcon warningIcon = JMeterUtils.getImage("warning.png");// $NON-NLS-1$ + + /** The button used to display the running/stopped image. */ + private JButton runningIndicator; + + /** The set of currently running hosts. */ + private final Set hosts = new HashSet(); + + /** A message dialog shown while JMeter threads are stopping. */ + private JDialog stoppingMessage; + + private JLabel totalThreads; + private JLabel activeThreads; + + private JMeterToolBar toolbar; + + /** + * Indicator for Log errors and Fatals + */ + private JButton warnIndicator; + /** + * Counter + */ + private JLabel errorsOrFatalsLabel; + /** + * LogTarget that receives ERROR or FATAL + */ + private transient ErrorsAndFatalsCounterLogTarget errorsAndFatalsCounterLogTarget; + + /** + * Create a new JMeter frame. + * + * @param treeModel + * the model for the test tree + * @param treeListener + * the listener for the test tree + */ + public MainFrame(TreeModel treeModel, JMeterTreeListener treeListener) { + + // TODO: Make the running indicator its own class instead of a JButton + runningIndicator = new JButton(stoppedIcon); + runningIndicator.setMargin(new Insets(0, 0, 0, 0)); + runningIndicator.setBorder(BorderFactory.createEmptyBorder()); + + totalThreads = new JLabel("0"); // $NON-NLS-1$ + totalThreads.setToolTipText(JMeterUtils.getResString("total_threads_tooltip")); // $NON-NLS-1$ + + activeThreads = new JLabel("0"); // $NON-NLS-1$ + activeThreads.setToolTipText(JMeterUtils.getResString("active_threads_tooltip")); // $NON-NLS-1$ + + warnIndicator = new JButton(warningIcon); + warnIndicator.setMargin(new Insets(0, 0, 0, 0)); + // Transparent JButton with no border + warnIndicator.setOpaque(false); + warnIndicator.setContentAreaFilled(false); + warnIndicator.setBorderPainted(false); + warnIndicator.setCursor(new Cursor(Cursor.HAND_CURSOR)); + warnIndicator.setToolTipText(JMeterUtils.getResString("error_indicator_tooltip")); // $NON-NLS-1$ + warnIndicator.addActionListener(this); + + errorsOrFatalsLabel = new JLabel("0"); // $NON-NLS-1$ + errorsOrFatalsLabel.setToolTipText(JMeterUtils.getResString("error_indicator_tooltip")); // $NON-NLS-1$ + + tree = makeTree(treeModel, treeListener); + + GuiPackage.getInstance().setMainFrame(this); + init(); + initTopLevelDndHandler(); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + } + + /** + * Default constructor for the JMeter frame. This constructor will not + * properly initialize the tree, so don't use it. + * + * @deprecated Do not use - only needed for JUnit tests + */ + @Deprecated + public MainFrame() { + } + + // MenuBar related methods + // TODO: Do we really need to have all these menubar methods duplicated + // here? Perhaps we can make the menu bar accessible through GuiPackage? + + /** + * Specify whether or not the File|Load menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setFileLoadEnabled(boolean enabled) { + menuBar.setFileLoadEnabled(enabled); + } + + /** + * Specify whether or not the File|Save menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setFileSaveEnabled(boolean enabled) { + menuBar.setFileSaveEnabled(enabled); + } + + /** + * Specify whether or not the File|Revert item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setFileRevertEnabled(boolean enabled) { + menuBar.setFileRevertEnabled(enabled); + } + + /** + * Specify the project file that was just loaded + * + * @param file - the full path to the file that was loaded + */ + public void setProjectFileLoaded(String file) { + menuBar.setProjectFileLoaded(file); + } + + /** + * Set the menu that should be used for the Edit menu. + * + * @param menu + * the new Edit menu + */ + public void setEditMenu(JPopupMenu menu) { + menuBar.setEditMenu(menu); + } + + /** + * Specify whether or not the Edit menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setEditEnabled(boolean enabled) { + menuBar.setEditEnabled(enabled); + } + + /** + * Set the menu that should be used for the Edit|Add menu. + * + * @param menu + * the new Edit|Add menu + */ + public void setEditAddMenu(JMenu menu) { + menuBar.setEditAddMenu(menu); + } + + /** + * Specify whether or not the Edit|Add menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setEditAddEnabled(boolean enabled) { + menuBar.setEditAddEnabled(enabled); + } + + /** + * Close the currently selected menu. + */ + public void closeMenu() { + if (menuBar.isSelected()) { + MenuElement[] menuElement = menuBar.getSubElements(); + if (menuElement != null) { + for (MenuElement element : menuElement) { + JMenu menu = (JMenu) element; + if (menu.isSelected()) { + menu.setPopupMenuVisible(false); + menu.setSelected(false); + break; + } + } + } + } + } + + /** + * Show a dialog indicating that JMeter threads are stopping on a particular + * host. + * + * @param host + * the host where JMeter threads are stopping + */ + public void showStoppingMessage(String host) { + if (stoppingMessage != null){ + stoppingMessage.dispose(); + } + stoppingMessage = new EscapeDialog(this, JMeterUtils.getResString("stopping_test_title"), true); //$NON-NLS-1$ + String label = JMeterUtils.getResString("stopping_test"); //$NON-NLS-1 + if (!StringUtils.isEmpty(host)) { + label = label + JMeterUtils.getResString("stopping_test_host")+ ": " + host; + } + JLabel stopLabel = new JLabel(label); //$NON-NLS-1$$NON-NLS-2$ + stopLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + stoppingMessage.getContentPane().add(stopLabel); + stoppingMessage.pack(); + ComponentUtil.centerComponentInComponent(this, stoppingMessage); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (stoppingMessage != null) { // TODO - how can this be null? + stoppingMessage.setVisible(true); + } + } + }); + } + + public void updateCounts() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + activeThreads.setText(Integer.toString(JMeterContextService.getNumberOfThreads())); + totalThreads.setText(Integer.toString(JMeterContextService.getTotalThreads())); + } + }); + } + + public void setMainPanel(JComponent comp) { + mainPanel.setViewportView(comp); + } + + public JTree getTree() { + return tree; + } + + // TestStateListener implementation + + /** + * Called when a test is started on the local system. This implementation + * sets the running indicator and ensures that the menubar is enabled and in + * the running state. + */ + @Override + public void testStarted() { + testStarted(LOCAL); + menuBar.setEnabled(true); + } + + /** + * Called when a test is started on a specific host. This implementation + * sets the running indicator and ensures that the menubar is in the running + * state. + * + * @param host + * the host where the test is starting + */ + @Override + public void testStarted(String host) { + hosts.add(host); + runningIndicator.setIcon(runningIcon); + activeThreads.setText("0"); // $NON-NLS-1$ + totalThreads.setText("0"); // $NON-NLS-1$ + menuBar.setRunning(true, host); + if (LOCAL.equals(host)) { + toolbar.setLocalTestStarted(true); + } else { + toolbar.setRemoteTestStarted(true); + } + } + + /** + * Called when a test is ended on the local system. This implementation + * disables the menubar, stops the running indicator, and closes the + * stopping message dialog. + */ + @Override + public void testEnded() { + testEnded(LOCAL); + menuBar.setEnabled(false); + } + + /** + * Called when a test is ended on the remote system. This implementation + * stops the running indicator and closes the stopping message dialog. + * + * @param host + * the host where the test is ending + */ + @Override + public void testEnded(String host) { + hosts.remove(host); + if (hosts.size() == 0) { + runningIndicator.setIcon(stoppedIcon); + JMeterContextService.endTest(); + } + menuBar.setRunning(false, host); + if (LOCAL.equals(host)) { + toolbar.setLocalTestStarted(false); + } else { + toolbar.setRemoteTestStarted(false); + } + if (stoppingMessage != null) { + stoppingMessage.dispose(); + stoppingMessage = null; + } + } + + /** + * Create the GUI components and layout. + */ + private void init() { + menuBar = new JMeterMenuBar(); + setJMenuBar(menuBar); + JPanel all = new JPanel(new BorderLayout()); + all.add(createToolBar(), BorderLayout.NORTH); + + JSplitPane treeAndMain = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + + treePanel = createTreePanel(); + treeAndMain.setLeftComponent(treePanel); + + JSplitPane topAndDown = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + topAndDown.setOneTouchExpandable(true); + topAndDown.setDividerLocation(0.8); + topAndDown.setResizeWeight(.8); + topAndDown.setContinuousLayout(true); + topAndDown.setBorder(null); // see bug jdk 4131528 + if (!DISPLAY_LOGGER_PANEL) { + topAndDown.setDividerSize(0); + } + mainPanel = createMainPanel(); + + logPanel = createLoggerPanel(); + if (DISPLAY_ERROR_FATAL_COUNTER) { + errorsAndFatalsCounterLogTarget = new ErrorsAndFatalsCounterLogTarget(); + LoggingManager.addLogTargetToRootLogger(new LogTarget[]{ + logPanel, + errorsAndFatalsCounterLogTarget + }); + } else { + LoggingManager.addLogTargetToRootLogger(new LogTarget[]{ + logPanel + }); + } + + topAndDown.setTopComponent(mainPanel); + topAndDown.setBottomComponent(logPanel); + + treeAndMain.setRightComponent(topAndDown); + + treeAndMain.setResizeWeight(.2); + treeAndMain.setContinuousLayout(true); + all.add(treeAndMain, BorderLayout.CENTER); + + getContentPane().add(all); + + tree.setSelectionRow(1); + addWindowListener(new WindowHappenings()); + // Building is complete, register as listener + GuiPackage.getInstance().registerAsListener(); + setTitle(DEFAULT_TITLE); + setIconImage(JMeterUtils.getImage("icon-apache.png").getImage());// $NON-NLS-1$ + setWindowTitle(); // define AWT WM_CLASS string + } + + + /** + * Support for Test Plan Dnd + * see BUG 52281 (when JDK6 will be minimum JDK target) + */ + public void initTopLevelDndHandler() { + new DropTarget(this, this); + } + + public void setExtendedFrameTitle(String fname) { + // file New operation may set to null, so just return app name + if (fname == null) { + setTitle(DEFAULT_TITLE); + return; + } + + // allow for windows / chars in filename + String temp = fname.replace('\\', '/'); // $NON-NLS-1$ // $NON-NLS-2$ + String simpleName = temp.substring(temp.lastIndexOf('/') + 1);// $NON-NLS-1$ + setTitle(simpleName + " (" + fname + ") - " + DEFAULT_TITLE); // $NON-NLS-1$ // $NON-NLS-2$ + } + + /** + * Create the JMeter tool bar pane containing the running indicator. + * + * @return a panel containing the running indicator + */ + private Component createToolBar() { + Box toolPanel = new Box(BoxLayout.X_AXIS); + // add the toolbar + this.toolbar = JMeterToolBar.createToolbar(DISPLAY_TOOLBAR); + GuiPackage guiInstance = GuiPackage.getInstance(); + guiInstance.setMainToolbar(toolbar); + guiInstance.getMenuItemToolbar().getModel().setSelected(DISPLAY_TOOLBAR); + toolPanel.add(toolbar); + + toolPanel.add(Box.createRigidArea(new Dimension(10, 15))); + toolPanel.add(Box.createGlue()); + + if (DISPLAY_ERROR_FATAL_COUNTER) { + toolPanel.add(errorsOrFatalsLabel); + toolPanel.add(warnIndicator); + toolPanel.add(Box.createRigidArea(new Dimension(20, 15))); + } + toolPanel.add(activeThreads); + toolPanel.add(new JLabel(" / ")); + toolPanel.add(totalThreads); + toolPanel.add(Box.createRigidArea(new Dimension(10, 15))); + toolPanel.add(runningIndicator); + return toolPanel; + } + + /** + * Create the panel where the GUI representation of the test tree is + * displayed. The tree should already be created before calling this method. + * + * @return a scroll pane containing the test tree GUI + */ + private JScrollPane createTreePanel() { + JScrollPane treeP = new JScrollPane(tree); + treeP.setMinimumSize(new Dimension(100, 0)); + return treeP; + } + + /** + * Create the main panel where components can display their GUIs. + * + * @return the main scroll pane + */ + private JScrollPane createMainPanel() { + return new JScrollPane(); + } + + /** + * Create at the down of the left a Console for Log events + * @return {@link LoggerPanel} + */ + private LoggerPanel createLoggerPanel() { + LoggerPanel loggerPanel = new LoggerPanel(); + loggerPanel.setMinimumSize(new Dimension(0, 100)); + loggerPanel.setPreferredSize(new Dimension(0, 150)); + GuiPackage guiInstance = GuiPackage.getInstance(); + guiInstance.setLoggerPanel(loggerPanel); + guiInstance.getMenuItemLoggerPanel().getModel().setSelected(DISPLAY_LOGGER_PANEL); + loggerPanel.setVisible(DISPLAY_LOGGER_PANEL); + return loggerPanel; + } + + /** + * Create and initialize the GUI representation of the test tree. + * + * @param treeModel + * the test tree model + * @param treeListener + * the test tree listener + * + * @return the initialized test tree GUI + */ + private JTree makeTree(TreeModel treeModel, JMeterTreeListener treeListener) { + JTree treevar = new JTree(treeModel) { + private static final long serialVersionUID = 240L; + + @Override + public String getToolTipText(MouseEvent event) { + TreePath path = this.getPathForLocation(event.getX(), event.getY()); + if (path != null) { + Object treeNode = path.getLastPathComponent(); + if (treeNode instanceof DefaultMutableTreeNode) { + Object testElement = ((DefaultMutableTreeNode) treeNode).getUserObject(); + if (testElement instanceof TestElement) { + String comment = ((TestElement) testElement).getComment(); + if (comment != null && comment.length() > 0) { + return comment; + } + } + } + } + return null; + } + }; + treevar.setToolTipText(""); + treevar.setCellRenderer(getCellRenderer()); + treevar.setRootVisible(false); + treevar.setShowsRootHandles(true); + + treeListener.setJTree(treevar); + treevar.addTreeSelectionListener(treeListener); + treevar.addMouseListener(treeListener); + treevar.addKeyListener(treeListener); + + // enable drag&drop, install a custom transfer handler + treevar.setDragEnabled(true); + treevar.setDropMode(DropMode.ON_OR_INSERT); + treevar.setTransferHandler(new JMeterTreeTransferHandler()); + + return treevar; + } + + /** + * Create the tree cell renderer used to draw the nodes in the test tree. + * + * @return a renderer to draw the test tree nodes + */ + private TreeCellRenderer getCellRenderer() { + DefaultTreeCellRenderer rend = new JMeterCellRenderer(); + rend.setFont(new Font("Dialog", Font.PLAIN, 11)); + return rend; + } + + /** + * A window adapter used to detect when the main JMeter frame is being + * closed. + */ + private static class WindowHappenings extends WindowAdapter { + /** + * Called when the main JMeter frame is being closed. Sends a + * notification so that JMeter can react appropriately. + * + * @param event + * the WindowEvent to handle + */ + @Override + public void windowClosing(WindowEvent event) { + ActionRouter.getInstance().actionPerformed(new ActionEvent(this, event.getID(), ActionNames.EXIT)); + } + } + + @Override + public void dragEnter(DropTargetDragEvent dtde) { + // NOOP + } + + @Override + public void dragExit(DropTargetEvent dte) { + // NOOP + } + + @Override + public void dragOver(DropTargetDragEvent dtde) { + // NOOP + } + + /** + * Handler of Top level Dnd + */ + @Override + public void drop(DropTargetDropEvent dtde) { + try { + Transferable tr = dtde.getTransferable(); + DataFlavor[] flavors = tr.getTransferDataFlavors(); + for (DataFlavor flavor : flavors) { + // Check for file lists specifically + if (flavor.isFlavorJavaFileListType()) { + dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); + try { + openJmxFilesFromDragAndDrop(tr); + } finally { + dtde.dropComplete(true); + } + return; + } + } + } catch (UnsupportedFlavorException e) { + log.warn("Dnd failed" , e); + } catch (IOException e) { + log.warn("Dnd failed" , e); + } + + } + + public boolean openJmxFilesFromDragAndDrop(Transferable tr) throws UnsupportedFlavorException, IOException { + @SuppressWarnings("unchecked") + List files = (List) + tr.getTransferData(DataFlavor.javaFileListFlavor); + if (files.isEmpty()) { + return false; + } + File file = files.get(0); + if (!file.getName().endsWith(".jmx")) { + log.warn("Importing file:" + file.getName()+ "from DnD failed because file extension does not end with .jmx"); + return false; + } + + ActionEvent fakeEvent = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ActionNames.OPEN); + LoadDraggedFile.loadProject(fakeEvent, file); + + return true; + } + + @Override + public void dropActionChanged(DropTargetDragEvent dtde) { + // NOOP + } + + /** + * + */ + public final class ErrorsAndFatalsCounterLogTarget implements LogTarget, Clearable { + public AtomicInteger errorOrFatal = new AtomicInteger(0); + + @Override + public void processEvent(LogEvent event) { + if(event.getPriority().equals(Priority.ERROR) || + event.getPriority().equals(Priority.FATAL_ERROR)) { + final int newValue = errorOrFatal.incrementAndGet(); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + errorsOrFatalsLabel.setText(Integer.toString(newValue)); + } + }); + } + } + + @Override + public void clearData() { + errorOrFatal.set(0); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + errorsOrFatalsLabel.setText(Integer.toString(errorOrFatal.get())); + } + }); + } + } + + + @Override + public void clearData() { + logPanel.clear(); + if (DISPLAY_ERROR_FATAL_COUNTER) { + errorsAndFatalsCounterLogTarget.clearData(); + } + } + + /** + * Handles click on warnIndicator + */ + @Override + public void actionPerformed(ActionEvent event) { + if (event.getSource() == warnIndicator) { + ActionRouter.getInstance().doActionNow(new ActionEvent(event.getSource(), event.getID(), ActionNames.LOGGER_PANEL_ENABLE_DISABLE)); + } + } + + /** + * Define AWT window title (WM_CLASS string) (useful on Gnome 3 / Linux) + */ + private void setWindowTitle() { + Class xtoolkit = Toolkit.getDefaultToolkit().getClass(); + if (xtoolkit.getName().equals("sun.awt.X11.XToolkit")) { // $NON-NLS-1$ + try { + final Field awtAppClassName = xtoolkit.getDeclaredField("awtAppClassName"); // $NON-NLS-1$ + awtAppClassName.setAccessible(true); + awtAppClassName.set(null, DEFAULT_APP_NAME); + } catch (NoSuchFieldException nsfe) { + log.warn("Error awt title: " + nsfe); // $NON-NLS-1$ + } catch (IllegalAccessException iae) { + log.warn("Error awt title: " + iae); // $NON-NLS-1$ + } + } + } + + /** + * Update Undo/Redo icons state + * + * @param canUndo + * Flag whether the undo button should be enabled + * @param canRedo + * Flag whether the redo button should be enabled + */ + public void updateUndoRedoIcons(boolean canUndo, boolean canRedo) { + toolbar.updateUndoRedoIcons(canUndo, canRedo); + } +} diff --git a/src/core/org/apache/jmeter/gui/NamePanel.java b/src/core/org/apache/jmeter/gui/NamePanel.java new file mode 100644 index 00000000000..10fa502363e --- /dev/null +++ b/src/core/org/apache/jmeter/gui/NamePanel.java @@ -0,0 +1,141 @@ +/* + * 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; + +import java.awt.BorderLayout; +import java.util.Collection; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JTextField; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; + +public class NamePanel extends JPanel implements JMeterGUIComponent { + private static final long serialVersionUID = 240L; + + /** A text field containing the name. */ + private final JTextField nameField = new JTextField(15); + + + /** + * Create a new NamePanel with the default name. + */ + public NamePanel() { + setName(getStaticLabel()); + init(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + /** The label for the text field. */ + JLabel nameLabel = new JLabel(JMeterUtils.getResString("name")); // $NON-NLS-1$ + nameLabel.setName("name"); + nameLabel.setLabelFor(nameField); + + add(nameLabel, BorderLayout.WEST); + add(nameField, BorderLayout.CENTER); + } + + @Override + public void clearGui() { + setName(getStaticLabel()); + } + + /** + * Get the currently displayed name. + * + * @return the current name + */ + @Override + public String getName() { + if (nameField != null) { + return nameField.getText(); + } + return ""; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void setName(String name) { + super.setName(name); + nameField.setText(name); + } + + /** {@inheritDoc} */ + @Override + public void configure(TestElement testElement) { + setName(testElement.getName()); + } + + /** {@inheritDoc} */ + @Override + public JPopupMenu createPopupMenu() { + return null; + } + + /** {@inheritDoc} */ + @Override + public String getStaticLabel() { + return JMeterUtils.getResString(getLabelResource()); + } + + /** {@inheritDoc} */ + @Override + public String getLabelResource() { + return "root"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public Collection getMenuCategories() { + return null; + } + + /** {@inheritDoc} */ + @Override + public TestElement createTestElement() { + WorkBench wb = new WorkBench(); + modifyTestElement(wb); + return wb; + } + + /** {@inheritDoc} */ + @Override + public void modifyTestElement(TestElement wb) { + wb.setName(getName()); + wb.setProperty(new StringProperty(TestElement.GUI_CLASS, this.getClass().getName())); + wb.setProperty(new StringProperty(TestElement.TEST_CLASS, WorkBench.class.getName())); + } + + /** + * {@inheritDoc} + */ + @Override + public String getDocAnchor() { + return null; + } +} diff --git a/src/core/org/apache/jmeter/gui/OnErrorPanel.java b/src/core/org/apache/jmeter/gui/OnErrorPanel.java new file mode 100644 index 00000000000..c0a0d8bcbca --- /dev/null +++ b/src/core/org/apache/jmeter/gui/OnErrorPanel.java @@ -0,0 +1,115 @@ +/* + * 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; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import org.apache.jmeter.testelement.OnErrorTestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class OnErrorPanel extends JPanel { + private static final long serialVersionUID = 240L; + + // Sampler error action buttons + private JRadioButton continueBox; + + private JRadioButton startNextThreadLoopBox; + + private JRadioButton stopThrdBox; + + private JRadioButton stopTestBox; + + private JRadioButton stopTestNowBox; + + private JPanel createOnErrorPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("sampler_on_error_action"))); //$NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + + continueBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_continue")); //$NON-NLS-1$ + group.add(continueBox); + continueBox.setSelected(true); + panel.add(continueBox); + + startNextThreadLoopBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_start_next_loop")); //$NON-NLS-1$ + group.add(startNextThreadLoopBox); + panel.add(startNextThreadLoopBox); + + stopThrdBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_thread")); //$NON-NLS-1$ + group.add(stopThrdBox); + panel.add(stopThrdBox); + + stopTestBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test")); //$NON-NLS-1$ + group.add(stopTestBox); + panel.add(stopTestBox); + + stopTestNowBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test_now")); //$NON-NLS-1$ + group.add(stopTestNowBox); + panel.add(stopTestNowBox); + + return panel; + } + + /** + * Create a new NamePanel with the default name. + */ + public OnErrorPanel() { + init(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + add(createOnErrorPanel()); + } + + public void configure(int errorAction) { + stopTestNowBox.setSelected(errorAction == OnErrorTestElement.ON_ERROR_STOPTEST_NOW); + startNextThreadLoopBox.setSelected(errorAction == OnErrorTestElement.ON_ERROR_START_NEXT_THREAD_LOOP); + stopTestBox.setSelected(errorAction == OnErrorTestElement.ON_ERROR_STOPTEST); + stopThrdBox.setSelected(errorAction == OnErrorTestElement.ON_ERROR_STOPTHREAD); + continueBox.setSelected(errorAction == OnErrorTestElement.ON_ERROR_CONTINUE); + } + + public int getOnErrorSetting() { + if (stopTestNowBox.isSelected()) { + return OnErrorTestElement.ON_ERROR_STOPTEST_NOW; + } + if (stopTestBox.isSelected()) { + return OnErrorTestElement.ON_ERROR_STOPTEST; + } + if (stopThrdBox.isSelected()) { + return OnErrorTestElement.ON_ERROR_STOPTHREAD; + } + if (startNextThreadLoopBox.isSelected()) { + return OnErrorTestElement.ON_ERROR_START_NEXT_THREAD_LOOP; + } + + // Defaults to continue + return OnErrorTestElement.ON_ERROR_CONTINUE; + } +} diff --git a/src/core/org/apache/jmeter/gui/SavePropertyDialog.java b/src/core/org/apache/jmeter/gui/SavePropertyDialog.java new file mode 100644 index 00000000000..fed2f14c0d1 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/SavePropertyDialog.java @@ -0,0 +1,157 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 15, 2004 + */ +package org.apache.jmeter.gui; + +import java.awt.BorderLayout; +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JPanel; + +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.log.Logger; + +/** + * Generates Configure pop-up dialogue for Listeners from all methods in SampleSaveConfiguration + * with the signature "boolean saveXXX()". + * There must be a corresponding "void setXXX(boolean)" method, and a property save_XXX which is + * used to name the field on the dialogue. + * + */ +public class SavePropertyDialog extends JDialog implements ActionListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + private static final Map functors = new HashMap(); + + private static final String NAME_SAVE_PFX = "save"; // $NON-NLS-1$ i.e. boolean saveXXX() + private static final String NAME_SET_PREFIX = "set"; // $NON-NLS-1$ i.e. void setXXX(boolean) + private static final String RESOURCE_PREFIX = "save_"; // $NON-NLS-1$ e.g. save_XXX property + private static final int NAME_SAVE_PFX_LEN = NAME_SAVE_PFX.length(); + + private SampleSaveConfiguration saveConfig; + + public SavePropertyDialog(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + /** + * @param owner The {@link Frame} from which the dialog is displayed + * @param title The string to be used as a title of this dialog + * @param modal specifies whether the dialog should be modal + * @param s The details, which sample attributes are to be saved + * @throws java.awt.HeadlessException - when run headless + */ + public SavePropertyDialog(Frame owner, String title, boolean modal, SampleSaveConfiguration s) + // throws HeadlessException + { + super(owner, title, modal); + saveConfig = s; + log.debug("SampleSaveConfiguration = " + saveConfig);// $NON-NLS-1$ + initDialog(); + } + + private int countMethods(Method[] m) { + int count = 0; + for (int i = 0; i < m.length; i++) { + if (m[i].getName().startsWith(NAME_SAVE_PFX)) { + count++; + } + } + return count; + } + + private void initDialog() { + this.getContentPane().setLayout(new BorderLayout()); + Method[] methods = SampleSaveConfiguration.class.getMethods(); + int x = (countMethods(methods) / 3) + 1; + log.debug("grid panel is " + 3 + " by " + x); + JPanel checkPanel = new JPanel(new GridLayout(x, 3)); + for (int i = 0; i < methods.length; i++) { + String name = methods[i].getName(); + if (name.startsWith(NAME_SAVE_PFX) && methods[i].getParameterTypes().length == 0) { + try { + name = name.substring(NAME_SAVE_PFX_LEN); + JCheckBox check = new JCheckBox( + JMeterUtils.getResString(RESOURCE_PREFIX + name) + ,((Boolean) methods[i].invoke(saveConfig, new Object[0])).booleanValue()); + checkPanel.add(check, BorderLayout.NORTH); + check.addActionListener(this); + String actionCommand = NAME_SET_PREFIX + name; // $NON-NLS-1$ + check.setActionCommand(actionCommand); + if (!functors.containsKey(actionCommand)) { + functors.put(actionCommand, new Functor(actionCommand)); + } + } catch (IllegalAccessException e) { + log.warn("Problem creating save config dialog", e); + } catch (InvocationTargetException e) { + log.warn("Problem creating save config dialog", e); + } + } + } + getContentPane().add(checkPanel, BorderLayout.NORTH); + JButton exit = new JButton(JMeterUtils.getResString("done")); // $NON-NLS-1$ + this.getContentPane().add(exit, BorderLayout.SOUTH); + exit.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + dispose(); + } + }); + } + + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + Functor f = functors.get(action); + f.invoke(saveConfig, new Object[] { + Boolean.valueOf(((JCheckBox) e.getSource()).isSelected()) }); + } + + /** + * @return Returns the saveConfig. + */ + public SampleSaveConfiguration getSaveConfig() { + return saveConfig; + } + + /** + * @param saveConfig + * The saveConfig to set. + */ + public void setSaveConfig(SampleSaveConfiguration saveConfig) { + this.saveConfig = saveConfig; + } +} diff --git a/src/core/org/apache/jmeter/gui/Searchable.java b/src/core/org/apache/jmeter/gui/Searchable.java new file mode 100644 index 00000000000..f3d99252fd8 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/Searchable.java @@ -0,0 +1,40 @@ +/* + * 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; + +import java.util.List; + +/** + * Interface for nodes that are searchable. + *

+ * A {@link Searchable} component will get asked for tokens, that should be used + * in a search. These tokens will then be matched against a user given search + * string. + */ +public interface Searchable { + /** + * Get a list of all tokens that should be visible to searching + * + * @return List of searchable tokens + * @throws Exception + * when something fails while getting the searchable tokens + */ + List getSearchableTokens() + throws Exception; +} diff --git a/src/core/org/apache/jmeter/gui/ServerPanel.java b/src/core/org/apache/jmeter/gui/ServerPanel.java new file mode 100644 index 00000000000..d8a6b3af5ef --- /dev/null +++ b/src/core/org/apache/jmeter/gui/ServerPanel.java @@ -0,0 +1,182 @@ +/* + * 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; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Common server panel implementation for use with HTTP, TCP etc samplers + */ +public class ServerPanel extends JPanel { + + private static final long serialVersionUID = -2749091243070619669L; + + private JTextField domain; + + private JTextField port; + + private JTextField connectTimeOut; + + private JTextField responseTimeOut; + + /** + * create the target server panel. + *

    + *
  • Server IP
  • + *
  • Server Port
  • + *
  • Connect Timeout
  • + *
  • Response Timeout
  • + *
+ */ + public ServerPanel() { + init(); + } + + /** + * clear all the fields + */ + public void clear() { + domain.setText(""); + port.setText(""); + connectTimeOut.setText(""); + responseTimeOut.setText(""); + } + + public String getServer(){ + return domain.getText(); + } + + public void setServer(String value){ + domain.setText(value); + } + + public String getPort(){ + return port.getText(); + } + + public void setPort(String value){ + port.setText(value); + } + + public String getConnectTimeout(){ + return connectTimeOut.getText(); + } + + public void setConnectTimeout(String value){ + connectTimeOut.setText(value); + } + + public String getResponseTimeout(){ + return responseTimeOut.getText(); + } + + public void setResponseTimeout(String value){ + responseTimeOut.setText(value); + } + + private void init() { + setLayout(new BorderLayout(5, 0)); + // Target server panel + JPanel webServerPanel = new HorizontalPanel(); + webServerPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("target_server"))); // $NON-NLS-1$ + final JPanel domainPanel = getDomainPanel(); + final JPanel portPanel = getPortPanel(); + webServerPanel.add(domainPanel, BorderLayout.CENTER); + webServerPanel.add(portPanel, BorderLayout.EAST); + + JPanel timeOut = new HorizontalPanel(); + timeOut.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_server_timeout_title"))); // $NON-NLS-1$ + final JPanel connPanel = getConnectTimeOutPanel(); + final JPanel reqPanel = getResponseTimeOutPanel(); + timeOut.add(connPanel); + timeOut.add(reqPanel); + + JPanel webServerTimeoutPanel = new VerticalPanel(); + webServerTimeoutPanel.add(webServerPanel, BorderLayout.CENTER); + webServerTimeoutPanel.add(timeOut, BorderLayout.EAST); + + JPanel bigPanel = new VerticalPanel(); + bigPanel.add(webServerTimeoutPanel); + + add(bigPanel); + } + + private JPanel getDomainPanel() { + domain = new JTextField(20); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + label.setLabelFor(domain); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(domain, BorderLayout.CENTER); + return panel; + } + + private JPanel getPortPanel() { + port = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(port); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(port, BorderLayout.CENTER); + + return panel; + } + + private JPanel getConnectTimeOutPanel() { + connectTimeOut = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_timeout_connect")); // $NON-NLS-1$ + label.setLabelFor(connectTimeOut); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(connectTimeOut, BorderLayout.CENTER); + + return panel; + } + + private JPanel getResponseTimeOutPanel() { + responseTimeOut = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_timeout_response")); // $NON-NLS-1$ + label.setLabelFor(responseTimeOut); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(responseTimeOut, BorderLayout.CENTER); + + return panel; + } + +} diff --git a/src/core/org/apache/jmeter/gui/Stoppable.java b/src/core/org/apache/jmeter/gui/Stoppable.java new file mode 100644 index 00000000000..7d29464db7d --- /dev/null +++ b/src/core/org/apache/jmeter/gui/Stoppable.java @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * Interface that identifies processes to stop on close or load of new project files + */ +public interface Stoppable { + + /** + * Stop server + */ + void stopServer(); +} diff --git a/src/core/org/apache/jmeter/gui/UndoHistory.java b/src/core/org/apache/jmeter/gui/UndoHistory.java new file mode 100644 index 00000000000..2965355f7ad --- /dev/null +++ b/src/core/org/apache/jmeter/gui/UndoHistory.java @@ -0,0 +1,370 @@ +/* + * 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; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JTree; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; + +import org.apache.jmeter.gui.action.UndoCommand; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class serves storing Test Tree state and navigating through it + * to give the undo/redo ability for test plan changes + * + * @since 2.12 + */ +public class UndoHistory implements TreeModelListener, Serializable { + /** + * + */ + private static final long serialVersionUID = -974269825492906010L; + + /** + * Interface to be implemented by components interested in UndoHistory + */ + public interface HistoryListener { + void notifyChangeInHistory(UndoHistory history); + } + + /** + * Avoid storing too many elements + * + * @param Class that should be held in this container + */ + private static class LimitedArrayList extends ArrayList { + /** + * + */ + private static final long serialVersionUID = -6574380490156356507L; + private int limit; + + public LimitedArrayList(int limit) { + this.limit = limit; + } + + @Override + public boolean add(T item) { + if (this.size() + 1 > limit) { + this.remove(0); + } + return super.add(item); + } + } + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * temporary storage for GUI tree expansion state + */ + private ArrayList savedExpanded = new ArrayList(); + + /** + * temporary storage for GUI tree selected row + */ + private int savedSelected = 0; + + private static final int INITIAL_POS = -1; + private int position = INITIAL_POS; + + private static final int HISTORY_SIZE = JMeterUtils.getPropDefault("undo.history.size", 0); + + private List history = new LimitedArrayList(HISTORY_SIZE); + + /** + * flag to prevent recursive actions + */ + private boolean working = false; + + /** + * History listeners + */ + private List listeners = new ArrayList(); + + public UndoHistory() { + } + + /** + * Clears the undo history + */ + public void clear() { + if (working) { + return; + } + log.debug("Clearing undo history"); + history.clear(); + position = INITIAL_POS; + notifyListeners(); + } + + /** + * Add tree model copy to the history + *

+ * This method relies on the rule that the record in history made AFTER + * change has been made to test plan + * + * @param treeModel JMeterTreeModel + * @param comment String + */ + public void add(JMeterTreeModel treeModel, String comment) { + if(!isEnabled()) { + log.debug("undo.history.size is set to 0, undo/redo feature is disabled"); + return; + } + + // don't add element if we are in the middle of undo/redo or a big loading + if (working) { + log.debug("Not adding history because of noop"); + return; + } + + JMeterTreeNode root = (JMeterTreeNode) treeModel.getRoot(); + if (root.getChildCount() < 1) { + log.debug("Not adding history because of no children"); + return; + } + + String name = root.getName(); + + log.debug("Adding history element " + name + ": " + comment); + + working = true; + // get test plan tree + HashTree tree = treeModel.getCurrentSubTree((JMeterTreeNode) treeModel.getRoot()); + // first clone to not convert original tree + tree = (HashTree) tree.getTree(tree.getArray()[0]).clone(); + + position++; + while (history.size() > position) { + log.debug("Removing further record, position: " + position + ", size: " + history.size()); + history.remove(history.size() - 1); + } + + // cloning is required because we need to immute stored data + HashTree copy = UndoCommand.convertAndCloneSubTree(tree); + + history.add(new UndoHistoryItem(copy, comment)); + + log.debug("Added history element, position: " + position + ", size: " + history.size()); + working = false; + notifyListeners(); + } + + /** + * Goes through undo history, changing GUI + * + * @param offset the direction to go to, usually -1 for undo or 1 for redo + * @param acceptorModel TreeModel to accept the changes + */ + public void moveInHistory(int offset, JMeterTreeModel acceptorModel) { + log.debug("Moving history from position " + position + " with step " + offset + ", size is " + history.size()); + if (offset < 0 && !canUndo()) { + log.warn("Can't undo, we're already on the last record"); + return; + } + + if (offset > 0 && !canRedo()) { + log.warn("Can't redo, we're already on the first record"); + return; + } + + if (history.isEmpty()) { + log.warn("Can't proceed, the history is empty"); + return; + } + + position += offset; + + final GuiPackage guiInstance = GuiPackage.getInstance(); + + // save tree expansion and selection state before changing the tree + saveTreeState(guiInstance); + + // load the tree + loadHistoricalTree(acceptorModel, guiInstance); + + // load tree UI state + restoreTreeState(guiInstance); + + log.debug("Current position " + position + ", size is " + history.size()); + + // refresh the all ui + guiInstance.updateCurrentGui(); + guiInstance.getMainFrame().repaint(); + notifyListeners(); + } + + /** + * Load the undo item into acceptorModel tree + * + * @param acceptorModel tree to accept the data + * @param guiInstance {@link GuiPackage} to be used + */ + private void loadHistoricalTree(JMeterTreeModel acceptorModel, GuiPackage guiInstance) { + HashTree newModel = history.get(position).getTree(); + acceptorModel.removeTreeModelListener(this); + working = true; + try { + guiInstance.getTreeModel().clearTestPlan(); + guiInstance.addSubTree(newModel); + } catch (Exception ex) { + log.error("Failed to load from history", ex); + } + acceptorModel.addTreeModelListener(this); + working = false; + } + + /** + * @return true if remaing items + */ + public boolean canRedo() { + return position < history.size() - 1; + } + + /** + * @return true if not at first element + */ + public boolean canUndo() { + return position > INITIAL_POS + 1; + } + + /** + * Record the changes in the node as the undo step + * + * @param tme {@link TreeModelEvent} with event details + */ + @Override + public void treeNodesChanged(TreeModelEvent tme) { + String name = ((JMeterTreeNode) tme.getTreePath().getLastPathComponent()).getName(); + log.debug("Nodes changed " + name); + final JMeterTreeModel sender = (JMeterTreeModel) tme.getSource(); + add(sender, "Node changed " + name); + } + + /** + * Record adding nodes as the undo step + * + * @param tme {@link TreeModelEvent} with event details + */ + @Override + public void treeNodesInserted(TreeModelEvent tme) { + String name = ((JMeterTreeNode) tme.getTreePath().getLastPathComponent()).getName(); + log.debug("Nodes inserted " + name); + final JMeterTreeModel sender = (JMeterTreeModel) tme.getSource(); + add(sender, "Add " + name); + } + + /** + * Record deleting nodes as the undo step + * + * @param tme {@link TreeModelEvent} with event details + */ + @Override + public void treeNodesRemoved(TreeModelEvent tme) { + String name = ((JMeterTreeNode) tme.getTreePath().getLastPathComponent()).getName(); + log.debug("Nodes removed: " + name); + add((JMeterTreeModel) tme.getSource(), "Remove " + name); + } + + /** + * Record some other change + * + * @param tme {@link TreeModelEvent} with event details + */ + @Override + public void treeStructureChanged(TreeModelEvent tme) { + log.debug("Nodes struct changed"); + add((JMeterTreeModel) tme.getSource(), "Complex Change"); + } + + /** + * Save tree expanded and selected state + * + * @param guiPackage {@link GuiPackage} to be used + */ + private void saveTreeState(GuiPackage guiPackage) { + savedExpanded.clear(); + + MainFrame mainframe = guiPackage.getMainFrame(); + if (mainframe != null) { + final JTree tree = mainframe.getTree(); + savedSelected = tree.getMinSelectionRow(); + + for (int rowN = 0; rowN < tree.getRowCount(); rowN++) { + if (tree.isExpanded(rowN)) { + savedExpanded.add(rowN); + } + } + } + } + + /** + * Restore tree expanded and selected state + * + * @param guiInstance GuiPackage to be used + */ + private void restoreTreeState(GuiPackage guiInstance) { + final JTree tree = guiInstance.getMainFrame().getTree(); + + if (savedExpanded.size() > 0) { + for (int rowN : savedExpanded) { + tree.expandRow(rowN); + } + } else { + tree.expandRow(0); + } + tree.setSelectionRow(savedSelected); + } + + /** + * + * @return true if history is enabled + */ + boolean isEnabled() { + return HISTORY_SIZE > 0; + } + + /** + * Register HistoryListener + * @param listener to add to our listeners + */ + public void registerHistoryListener(HistoryListener listener) { + listeners.add(listener); + } + + /** + * Notify listener + */ + private void notifyListeners() { + for (HistoryListener listener : listeners) { + listener.notifyChangeInHistory(this); + } + } + +} diff --git a/src/core/org/apache/jmeter/gui/UndoHistoryItem.java b/src/core/org/apache/jmeter/gui/UndoHistoryItem.java new file mode 100644 index 00000000000..cc1cf7392ef --- /dev/null +++ b/src/core/org/apache/jmeter/gui/UndoHistoryItem.java @@ -0,0 +1,71 @@ +/* + * 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; + +import org.apache.jorphan.collections.HashTree; + +import java.io.Serializable; + +/** + * Undo history item + * @since 2.12 + */ +public class UndoHistoryItem implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -8683007040160205040L; + private final HashTree tree; + // TODO: find a way to show this comment in menu item and toolbar tooltip + private final String comment; + + /** + * This constructor is for Unit test purposes only + * @deprecated DO NOT USE + */ + @Deprecated + public UndoHistoryItem() { + tree = null; + comment = null; + } + + /** + * @param copy HashTree + * @param acomment String + */ + public UndoHistoryItem(HashTree copy, String acomment) { + tree = copy; + comment = acomment; + } + + /** + * @return {@link org.apache.jorphan.collections.HashTree} + */ + public HashTree getTree() { + return tree; + } + + /** + * @return String comment + */ + public String getComment() { + return comment; + } +} diff --git a/src/core/org/apache/jmeter/gui/UnsharedComponent.java b/src/core/org/apache/jmeter/gui/UnsharedComponent.java new file mode 100644 index 00000000000..90af109822b --- /dev/null +++ b/src/core/org/apache/jmeter/gui/UnsharedComponent.java @@ -0,0 +1,28 @@ +/* + * 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; + +/** + * Marker interface indicating that an instance of a component cannot be shared. + * The GUI instance will be shared among all test elements of a given type if + * the GUI component class does not implement this interface. + * + */ +public interface UnsharedComponent { +} diff --git a/src/core/org/apache/jmeter/gui/action/AboutCommand.java b/src/core/org/apache/jmeter/gui/action/AboutCommand.java new file mode 100644 index 00000000000..fedc5777b44 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/AboutCommand.java @@ -0,0 +1,126 @@ +/* + * 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.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.util.EscapeDialog; +import org.apache.jmeter.util.JMeterUtils; + +/** + * About Command. It may be extended in the future to add a list of installed + * protocols, config options, etc. + * + */ +public class AboutCommand implements Command { + private static final Set commandSet; + + private static JDialog about; + + static { + HashSet commands = new HashSet(); + commands.add(ActionNames.ABOUT); + commandSet = Collections.unmodifiableSet(commands); + } + + /** + * Handle the "about" action by displaying the "About Apache JMeter..." + * dialog box. The Dialog Box is NOT modal, because those should be avoided + * if at all possible. + */ + @Override + public void doAction(ActionEvent e) { + if (e.getActionCommand().equals(ActionNames.ABOUT)) { + this.about(); + } + } + + /** + * Provide the list of Action names that are available in this command. + */ + @Override + public Set getActionNames() { + return AboutCommand.commandSet; + } + + /** + * Called by about button. Raises about dialog. Currently the about box has + * the product image and the copyright notice. The dialog box is centered + * over the MainFrame. + */ + void about() { + JFrame mainFrame = GuiPackage.getInstance().getMainFrame(); + if (about == null) { + about = new EscapeDialog(mainFrame, "About Apache JMeter...", false); + about.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + about.setVisible(false); + } + }); + + JLabel jmeter = new JLabel(JMeterUtils.getImage("jmeter.jpg")); + JLabel copyright = new JLabel(JMeterUtils.getJMeterCopyright(), SwingConstants.CENTER); + JLabel rights = new JLabel("All Rights Reserved.", SwingConstants.CENTER); + JLabel version = new JLabel("Apache JMeter Version " + JMeterUtils.getJMeterVersion(), SwingConstants.CENTER); + JPanel infos = new JPanel(); + infos.setOpaque(false); + infos.setLayout(new GridLayout(0, 1)); + infos.setBorder(new EmptyBorder(5, 5, 5, 5)); + infos.add(copyright); + infos.add(rights); + infos.add(version); + Container panel = about.getContentPane(); + panel.setLayout(new BorderLayout()); + panel.setBackground(Color.white); + panel.add(jmeter, BorderLayout.NORTH); + panel.add(infos, BorderLayout.SOUTH); + } + + // NOTE: these lines center the about dialog in the + // current window. Some older Swing versions have + // a bug in getLocationOnScreen() and they may not + // make this behave properly. + Point p = mainFrame.getLocationOnScreen(); + Dimension d1 = mainFrame.getSize(); + Dimension d2 = about.getSize(); + about.setLocation(p.x + (d1.width - d2.width) / 2, p.y + (d1.height - d2.height) / 2); + about.pack(); + about.setVisible(true); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/AbstractAction.java b/src/core/org/apache/jmeter/gui/action/AbstractAction.java new file mode 100644 index 00000000000..d732914036e --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/AbstractAction.java @@ -0,0 +1,62 @@ +/* + * 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.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public abstract class AbstractAction implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + } + + /** + * @see Command#getActionNames() + */ + @Override + abstract public Set getActionNames(); + + /** + * @param e the event that led to the call of this method + */ + protected void popupShouldSave(ActionEvent e) { + log.debug("popupShouldSave"); + if (GuiPackage.getInstance().getTestPlanFile() == null) { + if (JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("should_save"), //$NON-NLS-1$ + JMeterUtils.getResString("warning"), //$NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(),ActionNames.SAVE)); + } + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/ActionNames.java b/src/core/org/apache/jmeter/gui/action/ActionNames.java new file mode 100644 index 00000000000..e77c4c75bfe --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/ActionNames.java @@ -0,0 +1,105 @@ +/* + * 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; + +/* + * Collect all the action names together in one place. + * This helps to ensure that there are no duplicates + * + * + */ +public final class ActionNames { + + public static final String ABOUT = "about"; // $NON-NLS-1$ + public static final String ACTION_SHUTDOWN = "shutdown"; // $NON-NLS-1$ + public static final String ACTION_START = "start"; // $NON-NLS-1$ + public static final String ACTION_START_NO_TIMERS = "start_no_timers"; // $NON-NLS-1$ + public static final String ACTION_STOP = "stop"; // $NON-NLS-1$ + public static final String ADD = "Add"; // $NON-NLS-1$ + public static final String ADD_ALL = "add_all"; // $NON-NLS-1$ + public static final String ADD_PARENT = "Add Parent"; // $NON-NLS-1$ + public static final String ANALYZE_FILE = "Analyze File"; // $NON-NLS-1$ + public static final String CHANGE_LANGUAGE = "change_language"; // $NON-NLS-1$ + public static final String CHANGE_PARENT = "Change Parent"; // $NON-NLS-1$ + public static final String CHECK_DIRTY = "check_dirty"; // $NON-NLS-1$ + public static final String CHECK_REMOVE = "check_remove"; // $NON-NLS-1$ + public static final String CLEAR = "action.clear"; // $NON-NLS-1$ + public static final String CLEAR_ALL = "action.clear_all"; // $NON-NLS-1$ + public static final String CLOSE = "close"; // $NON-NLS-1$ + public static final String COLLAPSE_ALL = "collapse all"; // $NON-NLS-1$ + public static final String COPY = "Copy"; // $NON-NLS-1$ + public static final String CUT = "Cut"; // $NON-NLS-1$ + public static final String DEBUG_ON = "debug_on"; // $NON-NLS-1$ + public static final String DEBUG_OFF = "debug_off"; // $NON-NLS-1$ + public static final String DISABLE = "disable"; // $NON-NLS-1$ + /** Copy, then paste afterwards */ + public static final String DUPLICATE = "duplicate"; // $NON-NLS-1$ + public static final String EDIT = "edit"; // $NON-NLS-1$ + public static final String ENABLE = "enable"; // $NON-NLS-1$ + public static final String EXIT = "exit"; // $NON-NLS-1$ + public static final String EXPAND_ALL = "expand all"; // $NON-NLS-1$ + public static final String FUNCTIONS = "functions"; // $NON-NLS-1$ + public static final String HELP = "help"; // $NON-NLS-1$ + public static final String HEAP_DUMP = "heap_dump"; // $NON-NLS-1$ + public static final String LAF_PREFIX = "laf:"; // Look and Feel prefix + public static final String LOGGER_PANEL_ENABLE_DISABLE = "logger_panel_enable_disable"; // $NON-NLS-1$ + public static final String MERGE = "merge"; // $NON-NLS-1$ + public static final String OPEN = "open"; // $NON-NLS-1$ + public static final String OPEN_RECENT = "open_recent"; // $NON-NLS-1$ + public static final String TEMPLATES = "templates"; // $NON-NLS-1$ + public static final String PASTE = "Paste"; // $NON-NLS-1$ + public static final String REMOTE_EXIT = "remote_exit"; // $NON-NLS-1$ + public static final String REMOTE_EXIT_ALL = "remote_exit_all"; // $NON-NLS-1$ + public static final String REMOTE_SHUT = "remote_shut"; // $NON-NLS-1$ + public static final String REMOTE_SHUT_ALL = "remote_shut_all"; // $NON-NLS-1$ + public static final String REMOTE_START = "remote_start"; // $NON-NLS-1$ + public static final String REMOTE_START_ALL = "remote_start_all"; // $NON-NLS-1$ + public static final String REMOTE_STOP = "remote_stop"; // $NON-NLS-1$ + public static final String REMOTE_STOP_ALL = "remote_stop_all"; // $NON-NLS-1$ + public static final String REMOVE = "remove"; // $NON-NLS-1$ + public static final String RESET_GUI = "reset_gui"; // $NON-NLS-1$ + public static final String REVERT_PROJECT = "revert_project"; // $NON-NLS-1$ + public static final String SAVE = "save"; // $NON-NLS-1$ + public static final String SAVE_ALL_AS = "save_all_as"; // $NON-NLS-1$ + public static final String SAVE_AS = "save_as"; // $NON-NLS-1$ + public static final String SAVE_AS_TEST_FRAGMENT = "save_as_test_fragment"; // $NON-NLS-1$ + public static final String SAVE_GRAPHICS = "save_graphics"; // $NON-NLS-1$ + public static final String SAVE_GRAPHICS_ALL= "save_graphics_all"; // $NON-NLS-1$ + public static final String SSL_MANAGER = "sslManager"; // $NON-NLS-1$ + public static final String STOP_THREAD = "stop_thread"; // $NON-NLS-1$ + public static final String SUB_TREE_LOADED = "sub_tree_loaded"; // $NON-NLS-1$ + public static final String SUB_TREE_MERGED = "sub_tree_merged"; // $NON-NLS-1$ + public static final String SUB_TREE_SAVED = "sub_tree_saved"; // $NON-NLS-1$ + public static final String TOGGLE = "toggle"; // $NON-NLS-1$ enable/disable + public static final String TOOLBAR = "toolbar"; // $NON-NLS-1$ + public static final String WHAT_CLASS = "what_class"; // $NON-NLS-1$ + public static final String SEARCH_TREE = "search_tree"; // $NON-NLS-1$ + public static final String SEARCH_RESET = "search_reset"; // $NON-NLS-1$ + public static final String MOVE_UP = "move_up"; // $NON-NLS-1$ + public static final String MOVE_DOWN = "move_down"; // $NON-NLS-1$ + public static final String MOVE_LEFT = "move_left"; // $NON-NLS-1$ + public static final String MOVE_RIGHT = "move_right"; // $NON-NLS-1$ + public static final String UNDO = "undo"; // $NON-NLS-1$ + public static final String REDO = "redo"; // $NON-NLS-1$ + + // Prevent instantiation + private ActionNames(){ + + } +} diff --git a/src/core/org/apache/jmeter/gui/action/ActionRouter.java b/src/core/org/apache/jmeter/gui/action/ActionRouter.java new file mode 100644 index 00000000000..6e6d5ed5024 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/ActionRouter.java @@ -0,0 +1,338 @@ +/* + * 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.HeadlessException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.SwingUtilities; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +public final class ActionRouter implements ActionListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Object LOCK = new Object(); + + private static volatile ActionRouter router; + + private Map> commands = new HashMap>(); + + private final Map> preActionListeners = + new HashMap>(); + + private final Map> postActionListeners = + new HashMap>(); + + private ActionRouter() { + } + + @Override + public void actionPerformed(final ActionEvent e) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + performAction(e); + } + + }); + } + + private void performAction(final ActionEvent e) { + String actionCommand = e.getActionCommand(); + try { + try { + GuiPackage.getInstance().updateCurrentGui(); + } catch (Exception err){ + log.error("performAction(" + actionCommand + ") updateCurrentGui() on" + e.toString() + " caused", err); + JMeterUtils.reportErrorToUser("Problem updating GUI - see log file for details"); + } + for (Command c : commands.get(actionCommand)) { + try { + preActionPerformed(c.getClass(), e); + c.doAction(e); + postActionPerformed(c.getClass(), e); + } catch (IllegalUserActionException err) { + String msg = err.getMessage(); + if (msg == null) { + msg = err.toString(); + } + Throwable t = err.getCause(); + if (t != null) { + String cause = t.getMessage(); + if (cause == null) { + cause = t.toString(); + } + msg = msg + "\n" + cause; + } + JMeterUtils.reportErrorToUser(msg); + } catch (Exception err) { + log.error("Error processing "+c.toString(), err); + } + } + } catch (NullPointerException er) { + log.error("performAction(" + actionCommand + ") " + e.toString() + " caused", er); + JMeterUtils.reportErrorToUser("Sorry, this feature (" + actionCommand + ") not yet implemented"); + } + } + + /** + * To execute an action immediately in the current thread. + * + * @param e + * the action to execute + */ + public void doActionNow(ActionEvent e) { + performAction(e); + } + + /** + * Get the set of {@link Command}s registered under the name + * actionName + * + * @param actionName + * The name the {@link Command}s were registered + * @return a set with all registered {@link Command}s for + * actionName + */ + public Set getAction(String actionName) { + Set set = new HashSet(); + for (Command c : commands.get(actionName)) { + try { + set.add(c); + } catch (Exception err) { + log.error("Could not add Command", err); + } + } + return set; + } + + /** + * Get the {@link Command} registered under the name actionName, + * that is of {@link Class} actionClass + * + * @param actionName + * The name the {@link Command}s were registered + * @param actionClass + * The class the {@link Command}s should be equal to + * @return The registered {@link Command} for actionName, or + * null if none could be found + */ + public Command getAction(String actionName, Class actionClass) { + for (Command com : commands.get(actionName)) { + if (com.getClass().equals(actionClass)) { + return com; + } + } + return null; + } + + /** + * Get the {@link Command} registered under the name actionName + * , which class names are equal to className + * + * @param actionName + * The name the {@link Command}s were registered + * @param className + * The name of the class the {@link Command}s should be equal to + * @return The {@link Command} for actionName or + * null if none could be found + */ + public Command getAction(String actionName, String className) { + for (Command com : commands.get(actionName)) { + if (com.getClass().getName().equals(className)) { + return com; + } + } + return null; + } + + /** + * Allows an ActionListener to receive notification of a command being + * executed prior to the actual execution of the command. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.gui.action.Command. + * @param listener + * the ActionListener to receive the notifications + */ + public void addPreActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = preActionListeners.get(action.getName()); + if (set == null) { + set = new HashSet(); + } + set.add(listener); + preActionListeners.put(action.getName(), set); + } + } + + /** + * Allows an ActionListener to be removed from receiving notifications of a + * command being executed prior to the actual execution of the command. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.gui.action.Command. + * @param listener + * the ActionListener to receive the notifications + */ + public void removePreActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = preActionListeners.get(action.getName()); + if (set != null) { + set.remove(listener); + preActionListeners.put(action.getName(), set); + } + } + } + + /** + * Allows an ActionListener to receive notification of a command being + * executed after the command has executed. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.gui.action.Command. + * @param listener + * The {@link ActionListener} to be registered + */ + public void addPostActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = postActionListeners.get(action.getName()); + if (set == null) { + set = new HashSet(); + } + set.add(listener); + postActionListeners.put(action.getName(), set); + } + } + + /** + * Allows an ActionListener to be removed from receiving notifications of a + * command being executed after the command has executed. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.gui.action.Command. + * @param listener The {@link ActionListener} that should be deregistered + */ + public void removePostActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = postActionListeners.get(action.getName()); + if (set != null) { + set.remove(listener); + postActionListeners.put(action.getName(), set); + } + } + } + + protected void preActionPerformed(Class action, ActionEvent e) { + if (action != null) { + Set listenerSet = preActionListeners.get(action.getName()); + if (listenerSet != null && listenerSet.size() > 0) { + ActionListener[] listeners = listenerSet.toArray(new ActionListener[listenerSet.size()]); + for (ActionListener listener : listeners) { + listener.actionPerformed(e); + } + } + } + } + + protected void postActionPerformed(Class action, ActionEvent e) { + if (action != null) { + Set listenerSet = postActionListeners.get(action.getName()); + if (listenerSet != null && listenerSet.size() > 0) { + ActionListener[] listeners = listenerSet.toArray(new ActionListener[listenerSet.size()]); + for (ActionListener listener : listeners) { + listener.actionPerformed(e); + } + } + } + } + + private void populateCommandMap() { + try { + List listClasses = ClassFinder.findClassesThatExtend( + JMeterUtils.getSearchPaths(), // strPathsOrJars - pathnames or jarfiles to search for classes + // classNames - required parent class(es) or annotations + new Class[] {Class.forName("org.apache.jmeter.gui.action.Command") }, // $NON-NLS-1$ + false, // innerClasses - should we include inner classes? + null, // contains - classname should contain this string + // Ignore the classes which are specific to the reporting tool + "org.apache.jmeter.report.gui", // $NON-NLS-1$ // notContains - classname should not contain this string + false); // annotations - true if classnames are annotations + commands = new HashMap>(listClasses.size()); + if (listClasses.isEmpty()) { + log.fatalError("!!!!!Uh-oh, didn't find any action handlers!!!!!"); + throw new JMeterError("No action handlers found - check JMeterHome and libraries"); + } + for (String strClassName : listClasses) { + Class commandClass = Class.forName(strClassName); + Command command = (Command) commandClass.newInstance(); + for (String commandName : command.getActionNames()) { + Set commandObjects = commands.get(commandName); + if (commandObjects == null) { + commandObjects = new HashSet(); + commands.put(commandName, commandObjects); + } + commandObjects.add(command); + } + } + } catch (HeadlessException e){ + log.warn(e.toString()); + } catch (Exception e) { + log.error("exception finding action handlers", e); + } + } + + /** + * Gets the Instance attribute of the ActionRouter class + * + * @return The Instance value + */ + public static ActionRouter getInstance() { + if (router == null) { + synchronized (LOCK) { + if(router == null) { + router = new ActionRouter(); + router.populateCommandMap(); + } + } + } + return router; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/AddParent.java b/src/core/org/apache/jmeter/gui/action/AddParent.java new file mode 100644 index 00000000000..dd43910b529 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/AddParent.java @@ -0,0 +1,83 @@ +/* + * 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.Component; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Add Parent menu command + */ +public class AddParent implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.ADD_PARENT); + } + + public AddParent() { + } + + @Override + public void doAction(ActionEvent e) { + String name = ((Component) e.getSource()).getName(); + GuiPackage guiPackage = GuiPackage.getInstance(); + try { + guiPackage.updateCurrentNode(); + TestElement controller = guiPackage.createTestElement(name); + addParentToTree(controller); + } catch (Exception err) { + log.error("", err); + } + + } + + @Override + public Set getActionNames() { + return commands; + } + + protected void addParentToTree(TestElement newParent) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeNode newNode = new JMeterTreeNode(newParent, guiPackage.getTreeModel()); + JMeterTreeNode currentNode = guiPackage.getTreeListener().getCurrentNode(); + JMeterTreeNode parentNode = (JMeterTreeNode) currentNode.getParent(); + int index = parentNode.getIndex(currentNode); + guiPackage.getTreeModel().insertNodeInto(newNode, parentNode, index); + JMeterTreeNode[] nodes = guiPackage.getTreeListener().getSelectedNodes(); + for (JMeterTreeNode node : nodes) { + moveNode(guiPackage, node, newNode); + } + } + + private void moveNode(GuiPackage guiPackage, JMeterTreeNode node, JMeterTreeNode newParentNode) { + guiPackage.getTreeModel().removeNodeFromParent(node); + guiPackage.getTreeModel().insertNodeInto(node, newParentNode, newParentNode.getChildCount()); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/AddToTree.java b/src/core/org/apache/jmeter/gui/action/AddToTree.java new file mode 100644 index 00000000000..822d2288c53 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/AddToTree.java @@ -0,0 +1,91 @@ +/* + * 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.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JComponent; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class AddToTree implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commandSet; + + static { + HashSet commands = new HashSet(); + commands.add(ActionNames.ADD); + commandSet = Collections.unmodifiableSet(commands); + } + + public AddToTree() { + } + + /** + * Gets the Set of actions this Command class responds to. + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commandSet; + } + + /** + * Adds the specified class to the current node of the tree. + */ + @Override + public void doAction(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + try { + guiPackage.updateCurrentNode(); + TestElement testElement = guiPackage.createTestElement(((JComponent) e.getSource()).getName()); + JMeterTreeNode parentNode = guiPackage.getCurrentNode(); + JMeterTreeNode node = guiPackage.getTreeModel().addComponent(testElement, parentNode); + guiPackage.getMainFrame().getTree().setSelectionPath(new TreePath(node.getPath())); + } + catch (IllegalUserActionException err) { + log.error("", err); // $NON-NLS-1$ + String msg = err.getMessage(); + if (msg == null) { + msg = err.toString(); + } + JMeterUtils.reportErrorToUser(msg); + } + catch (Exception err) { + log.error("", err); // $NON-NLS-1$ + String msg = err.getMessage(); + if (msg == null) { + msg = err.toString(); + } + JMeterUtils.reportErrorToUser(msg); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Analyze.java b/src/core/org/apache/jmeter/gui/action/Analyze.java new file mode 100644 index 00000000000..def9d7a185e --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Analyze.java @@ -0,0 +1,59 @@ +/* + * 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.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JFileChooser; + +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.reporters.FileReporter; +import org.apache.jmeter.util.JMeterUtils; + +public class Analyze implements Command { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.ANALYZE_FILE); + } + + public Analyze() { + } + + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + FileReporter analyzer = new FileReporter(); + final JFileChooser chooser = FileDialoger.promptToOpenFile(new String[] { ".jtl" }); //$NON-NLS-1$ + if (chooser != null) { + try { + analyzer.init(chooser.getSelectedFile().getPath()); + } catch (IOException err) { + JMeterUtils.reportErrorToUser("The file you selected could not be analyzed"); + } + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/ChangeLanguage.java b/src/core/org/apache/jmeter/gui/action/ChangeLanguage.java new file mode 100644 index 00000000000..369fe995e19 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/ChangeLanguage.java @@ -0,0 +1,73 @@ +/* + * 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.Component; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +/** + * @version $Revision$ + */ +public class ChangeLanguage implements Command { + private static final Set commands = new HashSet(); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + static { + commands.add(ActionNames.CHANGE_LANGUAGE); + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + String locale = ((Component) e.getSource()).getName(); + Locale loc; + + int sep = locale.indexOf('_'); + if (sep > 0) { + loc = new Locale(locale.substring(0, sep), locale.substring(sep + 1)); + } else { + loc = new Locale(locale, ""); + } + log.debug("Changing locale to " + loc.toString()); + try { + JMeterUtils.setLocale(loc); + } catch (JMeterError err) { + JMeterUtils.reportErrorToUser(err.toString()); + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/ChangeParent.java b/src/core/org/apache/jmeter/gui/action/ChangeParent.java new file mode 100644 index 00000000000..1ade698730f --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/ChangeParent.java @@ -0,0 +1,91 @@ +/* + * 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.Component; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Add Parent menu command + */ +public class ChangeParent implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CHANGE_PARENT); + } + + public ChangeParent() { + } + + @Override + public void doAction(ActionEvent e) { + String name = ((Component) e.getSource()).getName(); + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeNode currentNode = guiPackage.getTreeListener().getCurrentNode(); + if (!(currentNode.getUserObject() instanceof Controller)) { + Toolkit.getDefaultToolkit().beep(); + return; + } + try { + guiPackage.updateCurrentNode(); + TestElement controller = guiPackage.createTestElement(name); + changeParent(controller, guiPackage, currentNode); + } catch (Exception err) { + Toolkit.getDefaultToolkit().beep(); + log.error("Failed to change parent", err); + } + + } + + @Override + public Set getActionNames() { + return commands; + } + + private void changeParent(TestElement newParent, GuiPackage guiPackage, JMeterTreeNode currentNode) { + JMeterTreeModel treeModel = guiPackage.getTreeModel(); + JMeterTreeNode newNode = new JMeterTreeNode(newParent, treeModel); + JMeterTreeNode parentNode = (JMeterTreeNode) currentNode.getParent(); + int index = parentNode.getIndex(currentNode); + treeModel.insertNodeInto(newNode, parentNode, index); + treeModel.removeNodeFromParent(currentNode); + int childCount = currentNode.getChildCount(); + for (int i = 0; i < childCount; i++) { + // Using index 0 is voluntary as child is removed in next step and added to new parent + JMeterTreeNode node = (JMeterTreeNode) currentNode.getChildAt(0); + treeModel.removeNodeFromParent(node); + treeModel.insertNodeInto(node, newNode, newNode.getChildCount()); + } + } + +} diff --git a/src/core/org/apache/jmeter/gui/action/CheckDirty.java b/src/core/org/apache/jmeter/gui/action/CheckDirty.java new file mode 100644 index 00000000000..df0edaa81fc --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/CheckDirty.java @@ -0,0 +1,170 @@ +/* + * 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.awt.event.ActionListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.collections.ListedHashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Check if the TestPlan has been changed since it was last saved + * + */ +public class CheckDirty extends AbstractAction implements HashTreeTraverser, ActionListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Map previousGuiItems; + + private boolean checkMode = false; + + private boolean removeMode = false; + + private boolean dirty = false; + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CHECK_DIRTY); + commands.add(ActionNames.SUB_TREE_SAVED); + commands.add(ActionNames.SUB_TREE_MERGED); + commands.add(ActionNames.SUB_TREE_LOADED); + commands.add(ActionNames.ADD_ALL); + commands.add(ActionNames.CHECK_REMOVE); + } + + public CheckDirty() { + previousGuiItems = new HashMap(); + ActionRouter.getInstance().addPreActionListener(ExitCommand.class, this); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals(ActionNames.EXIT)) { + doAction(e); + } + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(ActionNames.SUB_TREE_SAVED)) { + HashTree subTree = (HashTree) e.getSource(); + subTree.traverse(this); + } else if (action.equals(ActionNames.SUB_TREE_LOADED)) { + ListedHashTree addTree = (ListedHashTree) e.getSource(); + addTree.traverse(this); + } else if (action.equals(ActionNames.ADD_ALL)) { + previousGuiItems.clear(); + GuiPackage.getInstance().getTreeModel().getTestPlan().traverse(this); + } else if (action.equals(ActionNames.CHECK_REMOVE)) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeNode[] nodes = guiPackage.getTreeListener().getSelectedNodes(); + removeMode = true; + try { + for (int i = nodes.length - 1; i >= 0; i--) { + guiPackage.getTreeModel().getCurrentSubTree(nodes[i]).traverse(this); + } + } finally { + removeMode = false; + } + } + // If we are merging in another test plan, we know the test plan is dirty now + if(action.equals(ActionNames.SUB_TREE_MERGED)) { + dirty = true; + } + else { + dirty = false; + checkMode = true; + try { + HashTree wholeTree = GuiPackage.getInstance().getTreeModel().getTestPlan(); + wholeTree.traverse(this); + } finally { + checkMode = false; + } + } + GuiPackage.getInstance().setDirty(dirty); + } + + /** + * The tree traverses itself depth-first, calling addNode for each + * object it encounters as it goes. + */ + @Override + public void addNode(Object node, HashTree subTree) { + log.debug("Node is class:" + node.getClass()); + JMeterTreeNode treeNode = (JMeterTreeNode) node; + if (checkMode) { + // Only check if we have not found any differences so far + if(!dirty) { + if (previousGuiItems.containsKey(treeNode)) { + if (!previousGuiItems.get(treeNode).equals(treeNode.getTestElement())) { + dirty = true; + } + } else { + dirty = true; + } + } + } else if (removeMode) { + previousGuiItems.remove(treeNode); + } else { + previousGuiItems.put(treeNode, (TestElement) treeNode.getTestElement().clone()); + } + } + + /** + * Indicates traversal has moved up a step, and the visitor should remove + * the top node from it's stack structure. + */ + @Override + public void subtractNode() { + } + + /** + * Process path is called when a leaf is reached. If a visitor wishes to + * generate Lists of path elements to each leaf, it should keep a Stack data + * structure of nodes passed to it with addNode, and removing top items for + * every subtractNode() call. + */ + @Override + public void processPath() { + } + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Clear.java b/src/core/org/apache/jmeter/gui/action/Clear.java new file mode 100644 index 00000000000..3d995dac8b2 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Clear.java @@ -0,0 +1,84 @@ +/* + * 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.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Handles the following actions: + * - Clear (Data) + * - Clear All (Data) + * - Reset (Clear GUI) + */ +public class Clear implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CLEAR); + commands.add(ActionNames.CLEAR_ALL); + commands.add(ActionNames.RESET_GUI); + } + + public Clear() { + } + + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + final String actionCommand = e.getActionCommand(); + if (actionCommand.equals(ActionNames.CLEAR)) { + JMeterGUIComponent guiComp = guiPackage.getCurrentGui(); + if (guiComp instanceof Clearable){ + ((Clearable) guiComp).clearData(); + } + } else if (actionCommand.equals(ActionNames.RESET_GUI)) { + JMeterGUIComponent guiComp = guiPackage.getCurrentGui(); + guiComp.clearGui(); + } else { + guiPackage.getMainFrame().clearData(); + for (JMeterTreeNode node : guiPackage.getTreeModel().getNodesOfType(Clearable.class)) { + JMeterGUIComponent guiComp = guiPackage.getGui(node.getTestElement()); + if (guiComp instanceof Clearable){ + Clearable item = (Clearable) guiComp; + try { + item.clearData(); + } catch (Exception ex) { + log.error("Can't clear: "+node+" "+guiComp, ex); + } + } + } + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Close.java b/src/core/org/apache/jmeter/gui/action/Close.java new file mode 100644 index 00000000000..9c5dcbb1336 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Close.java @@ -0,0 +1,108 @@ +/* + * 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.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; +import javax.swing.JTree; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.util.FocusRequester; +import org.apache.jmeter.util.JMeterUtils; + +/** + * This command clears the existing test plan, allowing the creation of a New + * test plan. + * + */ +public class Close implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CLOSE); + } + + /** + * Constructor for the Close object. + */ + public Close() { + } + + /** + * Gets the ActionNames attribute of the Close object. + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * This method performs the actual command processing. + * + * @param e + * the generic UI action event + */ + @Override + public void doAction(ActionEvent e) { + performAction(e); + } + + /** + * Helper routine to allow action to be shared by LOAD. + * + * @param e event + * @return true if Close was not cancelled + */ + static boolean performAction(ActionEvent e){ + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.CHECK_DIRTY)); + GuiPackage guiPackage = GuiPackage.getInstance(); + if (guiPackage.isDirty()) { + int response; + if ((response=JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("cancel_new_to_save"), // $NON-NLS-1$ + JMeterUtils.getResString("save?"), // $NON-NLS-1$ + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE)) == JOptionPane.YES_OPTION) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.SAVE)); + } + if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) { + return false; // Don't clear the plan + } + } + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.STOP_THREAD)); + closeProject(e); + return true; + } + + static void closeProject(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + + guiPackage.clearTestPlan(); + JTree tree = guiPackage.getTreeListener().getJTree(); + tree.setSelectionRow(0); + FocusRequester.requestFocus(tree); + ActionRouter.getInstance().actionPerformed(new ActionEvent(e.getSource(), e.getID(), ActionNames.ADD_ALL)); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/CollapseExpand.java b/src/core/org/apache/jmeter/gui/action/CollapseExpand.java new file mode 100644 index 00000000000..cb20e897ff9 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/CollapseExpand.java @@ -0,0 +1,79 @@ +/* + * 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.HashSet; +import java.util.Set; + +import javax.swing.JTree; + +import org.apache.jmeter.gui.GuiPackage; + +/** + * Processes the Collapse All and Expand All options. + * + */ +public class CollapseExpand implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.COLLAPSE_ALL); + commands.add(ActionNames.EXPAND_ALL); + } + + /** + * Constructor for the Close object. + */ + public CollapseExpand() { + } + + /** + * Gets the ActionNames attribute of the Close object. + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * This method performs the actual command processing. + * + * @param e + * the generic UI action event + */ + @Override + public void doAction(ActionEvent e) { + boolean collapse=ActionNames.COLLAPSE_ALL.equals(e.getActionCommand()); + GuiPackage guiInstance = GuiPackage.getInstance(); + JTree jTree = guiInstance.getMainFrame().getTree(); + if (collapse){ + for (int i = jTree.getRowCount() - 1; i >= 0; i--) { + jTree.collapseRow(i); + } + return; + } + for(int i = 0; i < jTree.getRowCount(); i++) { + jTree.expandRow(i); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Command.java b/src/core/org/apache/jmeter/gui/action/Command.java new file mode 100644 index 00000000000..d3f60eaac19 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Command.java @@ -0,0 +1,30 @@ +/* + * 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.Set; + +import org.apache.jmeter.exceptions.IllegalUserActionException; + +public interface Command { + void doAction(ActionEvent e) throws IllegalUserActionException; + + Set getActionNames(); +} diff --git a/src/core/org/apache/jmeter/gui/action/Copy.java b/src/core/org/apache/jmeter/gui/action/Copy.java new file mode 100644 index 00000000000..01315d1604e --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Copy.java @@ -0,0 +1,147 @@ +/* + * 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.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterTreeNodeTransferable; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Copy menu command + */ +public class Copy extends AbstractAction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final HashSet commands = new HashSet(); + + static { + commands.add(ActionNames.COPY); + } + + /* + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + JMeterTreeListener treeListener = GuiPackage.getInstance().getTreeListener(); + JMeterTreeNode[] nodes = treeListener.getSelectedNodes(); + nodes = keepOnlyAncestors(nodes); + nodes = cloneTreeNodes(nodes); + setCopiedNodes(nodes); + } + + public static JMeterTreeNode[] getCopiedNodes() { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + if (clipboard.isDataFlavorAvailable(JMeterTreeNodeTransferable.JMETER_TREE_NODE_ARRAY_DATA_FLAVOR)) { + try { + return (JMeterTreeNode[]) clipboard.getData(JMeterTreeNodeTransferable.JMETER_TREE_NODE_ARRAY_DATA_FLAVOR); + } catch (Exception ex) { + log.error("Clipboard node read error:" + ex.getMessage(), ex); + JOptionPane.showMessageDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("clipboard_node_read_error")+":\n" + ex.getLocalizedMessage(), //$NON-NLS-1$ //$NON-NLS-2$ + JMeterUtils.getResString("error_title"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$ + } + } + return null; + } + + public static JMeterTreeNode cloneTreeNode(JMeterTreeNode node) { + JMeterTreeNode treeNode = (JMeterTreeNode) node.clone(); + treeNode.setUserObject(((TestElement) node.getUserObject()).clone()); + cloneChildren(treeNode, node); + return treeNode; + } + + /** + * If a child and one of its ancestors are selected : only keep the ancestor + * @param currentNodes JMeterTreeNode[] + * @return JMeterTreeNode[] + */ + static JMeterTreeNode[] keepOnlyAncestors(JMeterTreeNode[] currentNodes) { + List nodes = new ArrayList(); + for (int i = 0; i < currentNodes.length; i++) { + boolean exclude = false; + for (int j = 0; j < currentNodes.length; j++) { + if(i!=j && currentNodes[i].isNodeAncestor(currentNodes[j])) { + exclude = true; + break; + } + } + + if(!exclude) { + nodes.add(currentNodes[i]); + } + } + + return nodes.toArray(new JMeterTreeNode[nodes.size()]); + } + + public static void setCopiedNodes(JMeterTreeNode nodes[]) { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + try { + JMeterTreeNodeTransferable transferable = new JMeterTreeNodeTransferable(); + transferable.setTransferData(nodes); + clipboard.setContents(transferable, null); + } catch (Exception ex) { + log.error("Clipboard node read error:" + ex.getMessage(), ex); + JOptionPane.showMessageDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("clipboard_node_read_error")+":\n" + ex.getLocalizedMessage(), //$NON-NLS-1$ //$NON-NLS-2$ + JMeterUtils.getResString("error_title"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$ + } + } + + public static JMeterTreeNode[] cloneTreeNodes(JMeterTreeNode nodes[]) { + JMeterTreeNode treeNodes[] = new JMeterTreeNode[nodes.length]; + for (int i = 0; i < nodes.length; i++) { + treeNodes[i] = cloneTreeNode(nodes[i]); + } + return treeNodes; + } + + private static void cloneChildren(JMeterTreeNode to, JMeterTreeNode from) { + Enumeration enumFrom = from.children(); + while (enumFrom.hasMoreElements()) { + JMeterTreeNode child = (JMeterTreeNode) enumFrom.nextElement(); + JMeterTreeNode childClone = (JMeterTreeNode) child.clone(); + childClone.setUserObject(((TestElement) child.getUserObject()).clone()); + to.add(childClone); + cloneChildren((JMeterTreeNode) to.getLastChild(), child); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/CreateFunctionDialog.java b/src/core/org/apache/jmeter/gui/action/CreateFunctionDialog.java new file mode 100644 index 00000000000..febe94056de --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/CreateFunctionDialog.java @@ -0,0 +1,52 @@ +/* + * 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.HashSet; +import java.util.Set; + +import org.apache.jmeter.functions.gui.FunctionHelper; + +public class CreateFunctionDialog extends AbstractAction { + private final FunctionHelper helper; + + private static final Set commands; + static { + commands = new HashSet(); + commands.add(ActionNames.FUNCTIONS); + } + + public CreateFunctionDialog() { + helper = new FunctionHelper(); + } + + /** + * Provide the list of Action names that are available in this command. + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent arg0) { + helper.setVisible(true); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Cut.java b/src/core/org/apache/jmeter/gui/action/Cut.java new file mode 100644 index 00000000000..a6c1086b5ab --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Cut.java @@ -0,0 +1,61 @@ +/* + * 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.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; + +/** + * Implements the Cut menu item command + */ +public class Cut extends AbstractAction { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CUT); + } + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + GuiPackage guiPack = GuiPackage.getInstance(); + JMeterTreeNode[] currentNodes = guiPack.getTreeListener().getSelectedNodes(); + + currentNodes = Copy.keepOnlyAncestors(currentNodes); + Copy.setCopiedNodes(currentNodes); + for (JMeterTreeNode currentNode : currentNodes) { + guiPack.getTreeModel().removeNodeFromParent(currentNode); + } + guiPack.getMainFrame().repaint(); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Duplicate.java b/src/core/org/apache/jmeter/gui/action/Duplicate.java new file mode 100644 index 00000000000..d1d92416e07 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Duplicate.java @@ -0,0 +1,68 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 9, 2003 + * + * Clones a JMeterTreeNode + */ +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; + +/** + * Implements the Duplicate menu command + */ +public class Duplicate extends AbstractAction { + + private static final HashSet commands = new HashSet(); + + static { + commands.add(ActionNames.DUPLICATE); + } + + /* + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + GuiPackage instance = GuiPackage.getInstance(); + JMeterTreeListener treeListener = instance.getTreeListener(); + JMeterTreeNode[] copiedNodes = Copy.cloneTreeNodes(treeListener.getSelectedNodes()); + JMeterTreeNode currentNode = treeListener.getCurrentNode(); + JMeterTreeNode parentNode = (JMeterTreeNode) currentNode.getParent(); + JMeterTreeModel treeModel = instance.getTreeModel(); + for (JMeterTreeNode copiedNode : copiedNodes) { + int index = parentNode.getIndex(currentNode) + 1; + treeModel.insertNodeInto(copiedNode, parentNode, index); + } + instance.getMainFrame().repaint(); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/EditCommand.java b/src/core/org/apache/jmeter/gui/action/EditCommand.java new file mode 100644 index 00000000000..eeb6285f1ec --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/EditCommand.java @@ -0,0 +1,64 @@ +/* + * 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.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.NamePanel; + +/** + * Implements the Edit menu item. + */ +public class EditCommand implements Command { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.EDIT); + } + + public EditCommand() { + } + + @Override + public void doAction(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterGUIComponent currentGui = guiPackage.getCurrentGui(); + guiPackage.getMainFrame().setMainPanel((javax.swing.JComponent) currentGui); + guiPackage.getMainFrame().setEditMenu(guiPackage.getTreeListener().getCurrentNode().createPopupMenu()); + // TODO: I believe the following code (to the end of the method) is + // obsolete, + // since NamePanel no longer seems to be the GUI for any component: + if (!(currentGui instanceof NamePanel)) { + guiPackage.getMainFrame().setFileLoadEnabled(true); + guiPackage.getMainFrame().setFileSaveEnabled(true); + } else { + guiPackage.getMainFrame().setFileLoadEnabled(false); + guiPackage.getMainFrame().setFileSaveEnabled(false); + } + } + + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/EnableComponent.java b/src/core/org/apache/jmeter/gui/action/EnableComponent.java new file mode 100644 index 00000000000..d9823da74f4 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/EnableComponent.java @@ -0,0 +1,87 @@ +/* + * 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.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Enable menu item. + */ +public class EnableComponent implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.ENABLE); + commands.add(ActionNames.DISABLE); + commands.add(ActionNames.TOGGLE); + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + JMeterTreeNode[] nodes = GuiPackage.getInstance().getTreeListener().getSelectedNodes(); + + if (e.getActionCommand().equals(ActionNames.ENABLE)) { + log.debug("enabling currently selected gui objects"); + enableComponents(nodes, true); + } else if (e.getActionCommand().equals(ActionNames.DISABLE)) { + log.debug("disabling currently selected gui objects"); + enableComponents(nodes, false); + } else if (e.getActionCommand().equals(ActionNames.TOGGLE)) { + log.debug("toggling currently selected gui objects"); + toggleComponents(nodes); + } + } + + private void enableComponents(JMeterTreeNode[] nodes, boolean enable) { + GuiPackage pack = GuiPackage.getInstance(); + for (JMeterTreeNode node : nodes) { + node.setEnabled(enable); + pack.getGui(node.getTestElement()).setEnabled(enable); + } + } + + private void toggleComponents(JMeterTreeNode[] nodes) { + GuiPackage pack = GuiPackage.getInstance(); + for (int i = 0; i < nodes.length; i++) { + boolean enable = !nodes[i].isEnabled(); + nodes[i].setEnabled(enable); + pack.getGui(nodes[i].getTestElement()).setEnabled(enable); + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/ExitCommand.java b/src/core/org/apache/jmeter/gui/action/ExitCommand.java new file mode 100644 index 00000000000..558e01aad9d --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/ExitCommand.java @@ -0,0 +1,80 @@ +/* + * 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.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; + +public class ExitCommand implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.EXIT); + } + + /** + * Constructor for the ExitCommand object + */ + public ExitCommand() { + } + + /** + * Gets the ActionNames attribute of the ExitCommand object + * + * @return The ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * Description of the Method + * + * @param e + * Description of Parameter + */ + @Override + public void doAction(ActionEvent e) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.CHECK_DIRTY)); + if (GuiPackage.getInstance().isDirty()) { + int chosenOption = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), JMeterUtils + .getResString("cancel_exit_to_save"), // $NON-NLS-1$ + JMeterUtils.getResString("save?"), // $NON-NLS-1$ + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + if (chosenOption == JOptionPane.NO_OPTION) { + System.exit(0); + } else if (chosenOption == JOptionPane.YES_OPTION) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.SAVE)); + if (!GuiPackage.getInstance().isDirty()) { + System.exit(0); + } + } + } else { + System.exit(0); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Help.java b/src/core/org/apache/jmeter/gui/action/Help.java new file mode 100644 index 00000000000..eda2f49403c --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Help.java @@ -0,0 +1,113 @@ +/* + * 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.Frame; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JDialog; +import javax.swing.JScrollPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.util.EscapeDialog; +import org.apache.jmeter.swing.HtmlPane; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Help menu item. + */ +public class Help implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + private static final String HELP_DOCS = "file:///" // $NON-NLS-1$ + + JMeterUtils.getJMeterHome() + + "/printable_docs/usermanual/"; // $NON-NLS-1$ + + private static final String HELP_PAGE = HELP_DOCS + "component_reference.html"; // $NON-NLS-1$ + + public static final String HELP_FUNCTIONS = HELP_DOCS + "functions.html"; // $NON-NLS-1$ + + private static JDialog helpWindow; + + private static final HtmlPane helpDoc; + + private static final JScrollPane scroller; + + static { + commands.add(ActionNames.HELP); + helpDoc = new HtmlPane(); + scroller = new JScrollPane(helpDoc); + helpDoc.setEditable(false); + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + if (helpWindow == null) { + helpWindow = new EscapeDialog(new Frame(),// independent frame to + // allow it to be overlaid + // by the main frame + JMeterUtils.getResString("help"),//$NON-NLS-1$ + false); + helpWindow.getContentPane().setLayout(new GridLayout(1, 1)); + helpWindow.getContentPane().removeAll(); + helpWindow.getContentPane().add(scroller); + ComponentUtil.centerComponentInWindow(helpWindow, 60); + } + helpWindow.setVisible(true); // set the window visible immediately + /* + * This means that a new page will be shown before rendering is complete, + * however the correct location will be displayed. + * Attempts to use a "page" PropertyChangeListener to detect when the page + * has been loaded failed to work any better. + */ + StringBuilder url=new StringBuilder(); + if (e.getSource() instanceof String[]) { + String[] source = (String[]) e.getSource(); + url.append(source[0]).append('#').append(source[1]); + } else { + url.append(HELP_PAGE).append('#').append(GuiPackage.getInstance().getTreeListener().getCurrentNode().getDocAnchor()); + } + try { + helpDoc.setPage(url.toString()); // N.B. this only reloads if necessary (ignores the reference) + } catch (IOException ioe) { + log.error(ioe.toString()); + JMeterUtils.reportErrorToUser("Problem loading a help page - see log for details"); + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/KeyStrokes.java b/src/core/org/apache/jmeter/gui/action/KeyStrokes.java new file mode 100644 index 00000000000..5590c5c8617 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/KeyStrokes.java @@ -0,0 +1,89 @@ +/* + * 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.Toolkit; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import javax.swing.KeyStroke; + +/* + * Collect all the keystrokes together in one place. + * This helps to ensure that there are no duplicates. + */ +public final class KeyStrokes { + // Prevent instantiation + private KeyStrokes(){ + } + + // Bug 47064 - fixes for Mac LAF + private static final int CONTROL_MASK =Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + + public static final KeyStroke COPY = KeyStroke.getKeyStroke(KeyEvent.VK_C, CONTROL_MASK); + public static final KeyStroke DUPLICATE = KeyStroke.getKeyStroke(KeyEvent.VK_C, CONTROL_MASK | InputEvent.SHIFT_DOWN_MASK); + public static final KeyStroke DEBUG_OFF = KeyStroke.getKeyStroke(KeyEvent.VK_D, CONTROL_MASK); + public static final KeyStroke DEBUG_ON = KeyStroke.getKeyStroke(KeyEvent.VK_D, CONTROL_MASK | InputEvent.SHIFT_DOWN_MASK); + public static final KeyStroke CLEAR_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_E, CONTROL_MASK); + public static final KeyStroke CLEAR = KeyStroke.getKeyStroke(KeyEvent.VK_E, CONTROL_MASK | InputEvent.SHIFT_DOWN_MASK); + public static final KeyStroke ESC = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + public static final KeyStroke ENTER = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); + public static final KeyStroke FUNCTIONS = KeyStroke.getKeyStroke(KeyEvent.VK_F1, CONTROL_MASK | InputEvent.SHIFT_DOWN_MASK); + public static final KeyStroke SAVE_GRAPHICS = KeyStroke.getKeyStroke(KeyEvent.VK_G, CONTROL_MASK); + public static final KeyStroke SAVE_GRAPHICS_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_G, CONTROL_MASK | InputEvent.SHIFT_DOWN_MASK); + public static final KeyStroke HELP = KeyStroke.getKeyStroke(KeyEvent.VK_H, CONTROL_MASK); + public static final KeyStroke CLOSE = KeyStroke.getKeyStroke(KeyEvent.VK_L, CONTROL_MASK); + public static final KeyStroke SSL_MANAGER = KeyStroke.getKeyStroke(KeyEvent.VK_M, CONTROL_MASK); + public static final KeyStroke OPEN = KeyStroke.getKeyStroke(KeyEvent.VK_O, CONTROL_MASK); + public static final KeyStroke EXIT = KeyStroke.getKeyStroke(KeyEvent.VK_Q, CONTROL_MASK); + public static final KeyStroke ACTION_START = KeyStroke.getKeyStroke(KeyEvent.VK_R, CONTROL_MASK); + public static final KeyStroke REMOTE_START_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_R, CONTROL_MASK | InputEvent.SHIFT_DOWN_MASK); + public static final KeyStroke SAVE = KeyStroke.getKeyStroke(KeyEvent.VK_S, CONTROL_MASK); + public static final KeyStroke SAVE_ALL_AS = KeyStroke.getKeyStroke(KeyEvent.VK_S, CONTROL_MASK | InputEvent.SHIFT_DOWN_MASK); + public static final KeyStroke SEARCH_TREE = KeyStroke.getKeyStroke(KeyEvent.VK_F, CONTROL_MASK); + public static final KeyStroke TOGGLE = KeyStroke.getKeyStroke(KeyEvent.VK_T, CONTROL_MASK); + public static final KeyStroke PASTE = KeyStroke.getKeyStroke(KeyEvent.VK_V, CONTROL_MASK); + public static final KeyStroke WHAT_CLASS = KeyStroke.getKeyStroke(KeyEvent.VK_W, CONTROL_MASK); + public static final KeyStroke CUT = KeyStroke.getKeyStroke(KeyEvent.VK_X, CONTROL_MASK); + public static final KeyStroke REMOTE_STOP_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.ALT_DOWN_MASK); + public static final KeyStroke REMOTE_SHUT_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.ALT_DOWN_MASK); + + public static final KeyStroke REMOVE = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0); + public static final KeyStroke ACTION_STOP = KeyStroke.getKeyStroke(KeyEvent.VK_PERIOD, CONTROL_MASK); + public static final KeyStroke ACTION_SHUTDOWN = KeyStroke.getKeyStroke(KeyEvent.VK_COMMA, CONTROL_MASK); + public static final KeyStroke COLLAPSE_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, CONTROL_MASK); + // VK_PLUS + CTRL_DOWN_MASK did not work... + public static final KeyStroke EXPAND_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, CONTROL_MASK | InputEvent.SHIFT_DOWN_MASK); + public static final KeyStroke ALT_UP_ARROW = KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK); + public static final KeyStroke ALT_DOWN_ARROW = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK); + public static final KeyStroke ALT_LEFT_ARROW = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.ALT_DOWN_MASK); + public static final KeyStroke ALT_RIGHT_ARROW = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.ALT_DOWN_MASK); + + /** + * Check if an event matches the KeyStroke definition. + * + * @param e event + * @param k keystroke + * @return true if event matches the keystroke definition + */ + public static boolean matches(KeyEvent e, KeyStroke k){ + final int modifiersEx = e.getModifiersEx() | e.getModifiers();// Hack to get full modifier value + return e.getKeyCode() == k.getKeyCode() && modifiersEx == k.getModifiers(); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Load.java b/src/core/org/apache/jmeter/gui/action/Load.java new file mode 100644 index 00000000000..730bfa290fc --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Load.java @@ -0,0 +1,246 @@ +/* + * 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.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JFileChooser; +import javax.swing.JTree; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.FocusRequester; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.converters.ConversionException; + +/** + * Handles the Open (load a new file) and Merge commands. + * + */ +public class Load implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final boolean expandTree = JMeterUtils.getPropDefault("onload.expandtree", false); //$NON-NLS-1$ + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.OPEN); + commands.add(ActionNames.MERGE); + } + + public Load() { + super(); + } + + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(final ActionEvent e) { + final JFileChooser chooser = FileDialoger.promptToOpenFile(new String[] { ".jmx" }); //$NON-NLS-1$ + if (chooser == null) { + return; + } + final File selectedFile = chooser.getSelectedFile(); + if (selectedFile != null) { + final boolean merging = e.getActionCommand().equals(ActionNames.MERGE); + // We must ask the user if it is ok to close current project + if (!merging) { // i.e. it is OPEN + if (!Close.performAction(e)) { + return; + } + } + loadProjectFile(e, selectedFile, merging); + } + } + + /** + * Loads or merges a file into the current GUI, reporting any errors to the user. + * If the file is a complete test plan, sets the GUI test plan file name + * + * @param e the event that triggered the action + * @param f the file to load + * @param merging if true, then try to merge the file into the current GUI. + */ + static void loadProjectFile(final ActionEvent e, final File f, final boolean merging) { + loadProjectFile(e, f, merging, true); + } + + /** + * Loads or merges a file into the current GUI, reporting any errors to the user. + * If the file is a complete test plan, sets the GUI test plan file name + * + * @param e the event that triggered the action + * @param f the file to load + * @param merging if true, then try to merge the file into the current GUI. + * @param setDetails if true, then set the file details (if not merging) + */ + static void loadProjectFile(final ActionEvent e, final File f, final boolean merging, final boolean setDetails) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.STOP_THREAD)); + + final GuiPackage guiPackage = GuiPackage.getInstance(); + if (f != null) { + try { + if (merging) { + log.info("Merging file: " + f); + } else { + log.info("Loading file: " + f); + // TODO should this be done even if not a full test plan? + // and what if load fails? + if (setDetails) { + FileServer.getFileServer().setBaseForScript(f); + } + } + final HashTree tree = SaveService.loadTree(f); + final boolean isTestPlan = insertLoadedTree(e.getID(), tree, merging); + + // don't change name if merging + if (!merging && isTestPlan && setDetails) { + // TODO should setBaseForScript be called here rather than + // above? + guiPackage.setTestPlanFile(f.getAbsolutePath()); + } + } catch (NoClassDefFoundError ex) {// Allow for missing optional jars + reportError("Missing jar file", ex, true); + } catch (ConversionException ex) { + log.warn("Could not convert file "+ex); + JMeterUtils.reportErrorToUser(SaveService.CEtoString(ex)); + } catch (IOException ex) { + reportError("Error reading file: ", ex, false); + } catch (Exception ex) { + reportError("Unexpected error", ex, true); + } + FileDialoger.setLastJFCDirectory(f.getParentFile().getAbsolutePath()); + guiPackage.updateCurrentGui(); + guiPackage.getMainFrame().repaint(); + } + } + + /** + * Inserts (or merges) the tree into the GUI. + * Does not check if the previous tree has been saved. + * Clears the existing GUI test plan if we are inserting a complete plan. + * @param id the id for the ActionEvent that is created + * @param tree the tree to load + * @param merging true if the tree is to be merged; false if it is to replace the existing tree + * @return true if the loaded tree was a full test plan + * @throws IllegalUserActionException if the tree cannot be merged at the selected position or the tree is empty + */ + // Does not appear to be used externally; called by #loadProjectFile() + public static boolean insertLoadedTree(final int id, final HashTree tree, final boolean merging) throws IllegalUserActionException { + // convertTree(tree); + if (tree == null) { + throw new IllegalUserActionException("Empty TestPlan or error reading test plan - see log file"); + } + final boolean isTestPlan = tree.getArray()[0] instanceof TestPlan; + + // If we are loading a new test plan, initialize the tree with the testplan node we are loading + final GuiPackage guiInstance = GuiPackage.getInstance(); + if(isTestPlan && !merging) { + // Why does this not call guiInstance.clearTestPlan() ? + // Is there a reason for not clearing everything? + guiInstance.clearTestPlan((TestElement)tree.getArray()[0]); + } + + if (merging){ // Check if target of merge is reasonable + final TestElement te = (TestElement)tree.getArray()[0]; + if (!(te instanceof WorkBench || te instanceof TestPlan)){// These are handled specially by addToTree + final boolean ok = MenuFactory.canAddTo(guiInstance.getCurrentNode(), te); + if (!ok){ + String name = te.getName(); + String className = te.getClass().getName(); + className = className.substring(className.lastIndexOf('.')+1); + throw new IllegalUserActionException("Can't merge "+name+" ("+className+") here"); + } + } + } + final HashTree newTree = guiInstance.addSubTree(tree); + guiInstance.updateCurrentGui(); + guiInstance.getMainFrame().getTree().setSelectionPath( + new TreePath(((JMeterTreeNode) newTree.getArray()[0]).getPath())); + final HashTree subTree = guiInstance.getCurrentSubTree(); + // Send different event wether we are merging a test plan into another test plan, + // or loading a testplan from scratch + ActionEvent actionEvent = + new ActionEvent(subTree.get(subTree.getArray()[subTree.size() - 1]), id, + merging ? ActionNames.SUB_TREE_MERGED : ActionNames.SUB_TREE_LOADED); + + ActionRouter.getInstance().actionPerformed(actionEvent); + final JTree jTree = guiInstance.getMainFrame().getTree(); + if (expandTree && !merging) { // don't automatically expand when merging + for(int i = 0; i < jTree.getRowCount(); i++) { + jTree.expandRow(i); + } + } else { + jTree.expandRow(0); + } + jTree.setSelectionPath(jTree.getPathForRow(1)); + FocusRequester.requestFocus(jTree); + return isTestPlan; + } + + /** + * Inserts the tree into the GUI. + * Does not check if the previous tree has been saved. + * Clears the existing GUI test plan if we are inserting a complete plan. + * @param id the id for the ActionEvent that is created + * @param tree the tree to load + * @return true if the loaded tree was a full test plan + * @throws IllegalUserActionException if the tree cannot be merged at the selected position or the tree is empty + */ + // Called by JMeter#startGui() + public static boolean insertLoadedTree(final int id, final HashTree tree) throws IllegalUserActionException { + return insertLoadedTree(id, tree, false); + } + + // Helper method to simplify code + private static void reportError(final String reason, final Throwable ex, final boolean stackTrace) { + if (stackTrace) { + log.warn(reason, ex); + } else { + log.warn(reason + ex); + } + String msg = ex.getMessage(); + if (msg == null) { + msg = "Unexpected error - see log for details"; + } + JMeterUtils.reportErrorToUser(msg); + } + +} diff --git a/src/core/org/apache/jmeter/gui/action/LoadDraggedFile.java b/src/core/org/apache/jmeter/gui/action/LoadDraggedFile.java new file mode 100644 index 00000000000..8cdda5ba072 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/LoadDraggedFile.java @@ -0,0 +1,40 @@ +/* + * 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.io.File; + +/** + * Handles the loading of a file from a Drag and Drop action. + */ +public class LoadDraggedFile { + + /** + * Loads dragged file asking before for save if current open file is dirty. + * @param e {@link ActionEvent} + * @param file File to Load + */ + public static void loadProject(ActionEvent e, File file) { + if(!Close.performAction(e)) { + return; + } + Load.loadProjectFile(e, file, false); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/LoadRecentProject.java b/src/core/org/apache/jmeter/gui/action/LoadRecentProject.java new file mode 100644 index 00000000000..62941a305c1 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/LoadRecentProject.java @@ -0,0 +1,260 @@ +/* + * 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.awt.event.KeyEvent; +import java.io.File; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.prefs.Preferences; + +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.JSeparator; + +/** + * Handles the loading of recent files, and also the content and + * visibility of menu items for loading the recent files + */ +public class LoadRecentProject extends Load { + /** Prefix for the user preference key */ + private static final String USER_PREFS_KEY = "recent_file_"; //$NON-NLS-1$ + /** The number of menu items used for recent files */ + private static final int NUMBER_OF_MENU_ITEMS = 9; + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.OPEN_RECENT); + } + + private static final Preferences prefs = Preferences.userNodeForPackage(LoadRecentProject.class); + // Note: Windows user preferences are stored relative to: HKEY_CURRENT_USER\Software\JavaSoft\Prefs + + public LoadRecentProject() { + super(); + } + + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + // We must ask the user if it is ok to close current project + if (!Close.performAction(e)) { + return; + } + // Load the file for this recent file command + loadProjectFile(e, getRecentFile(e), false); + } + + /** + * Get the recent file for the menu item + */ + private File getRecentFile(ActionEvent e) { + JMenuItem menuItem = (JMenuItem)e.getSource(); + // Get the preference for the recent files + return new File(getRecentFile(Integer.parseInt(menuItem.getName()))); + } + + /** + * Get the menu items to add to the menu bar, to get recent file functionality + * + * @return a List of JMenuItem and a JSeparator, representing recent files + */ + public static List getRecentFileMenuItems() { + LinkedList menuItems = new LinkedList(); + // Get the preference for the recent files + for(int i = 0; i < NUMBER_OF_MENU_ITEMS; i++) { + // Create the menu item + JMenuItem recentFile = new JMenuItem(); + // Use the index as the name, used when processing the action + recentFile.setName(Integer.toString(i)); + recentFile.addActionListener(ActionRouter.getInstance()); + recentFile.setActionCommand(ActionNames.OPEN_RECENT); + // Set the KeyStroke to use + int shortKey = getShortcutKey(i); + if(shortKey >= 0) { + recentFile.setMnemonic(shortKey); + } + // Add the menu item + menuItems.add(recentFile); + } + // Add separator as the last item + JSeparator separator = new JSeparator(); + separator.setVisible(false); + menuItems.add(separator); + + // Update menu items to reflect recent files + updateMenuItems(menuItems); + + return menuItems; + } + + /** + * Update the content and visibility of the menu items for recent files + * + * @param menuItems the JMenuItem and JSeparator to update + * @param loadedFileName the file name of the project file that has just + * been loaded + */ + public static void updateRecentFileMenuItems(List menuItems, String loadedFileName) { + // Get the preference for the recent files + + LinkedList newRecentFiles = new LinkedList(); + // Check if the new file is already in the recent list + boolean alreadyExists = false; + for(int i = 0; i < NUMBER_OF_MENU_ITEMS; i++) { + String recentFilePath = getRecentFile(i); + if(!loadedFileName.equals(recentFilePath)) { + newRecentFiles.add(recentFilePath); + } + else { + alreadyExists = true; + } + } + // Add the new file at the start of the list + newRecentFiles.add(0, loadedFileName); + // Remove the last item from the list if it was a brand new file + if(!alreadyExists) { + newRecentFiles.removeLast(); + } + // Store the recent files + for(int i = 0; i < NUMBER_OF_MENU_ITEMS; i++) { + String fileName = newRecentFiles.get(i); + if(fileName != null) { + setRecentFile(i, fileName); + } + } + // Update menu items to reflect recent files + updateMenuItems(menuItems); + } + + /** + * Set the content and visibility of menu items and menu separator, + * based on the recent file stored user preferences. + */ + private static void updateMenuItems(List menuItems) { + // Assume no recent files + boolean someRecentFiles = false; + // Update the menu items + for(int i = 0; i < NUMBER_OF_MENU_ITEMS; i++) { + // Get the menu item + JMenuItem recentFile = (JMenuItem)menuItems.get(i); + + // Find and set the file for this recent file command + String recentFilePath = getRecentFile(i); + if(recentFilePath != null) { + File file = new File(recentFilePath); + StringBuilder sb = new StringBuilder(60); + if (i<9) { + sb.append(i+1).append(" "); //$NON-NLS-1$ + } + sb.append(getMenuItemDisplayName(file)); + recentFile.setText(sb.toString()); + recentFile.setToolTipText(recentFilePath); + recentFile.setEnabled(true); + recentFile.setVisible(true); + // At least one recent file menu item is visible + someRecentFiles = true; + } + else { + recentFile.setEnabled(false); + recentFile.setVisible(false); + } + } + // If there are some recent files, we must make the separator visisble + // The separator is the last item in the list + JSeparator separator = (JSeparator)menuItems.get(menuItems.size() - 1); + separator.setVisible(someRecentFiles); + } + + /** + * Get the name to display in the menu item, it will chop the file name + * if it is too long to display in the menu bar + */ + private static String getMenuItemDisplayName(File file) { + // Limit the length of the menu text if needed + final int maxLength = 40; + String menuText = file.getName(); + if(menuText.length() > maxLength) { + menuText = "..." + menuText.substring(menuText.length() - maxLength, menuText.length()); //$NON-NLS-1$ + } + return menuText; + } + + /** + * Get the KeyEvent to use as shortcut key for menu item + */ + private static int getShortcutKey(int index) { + int shortKey = -1; + switch(index+1) { + case 1: + shortKey = KeyEvent.VK_1; + break; + case 2: + shortKey = KeyEvent.VK_2; + break; + case 3: + shortKey = KeyEvent.VK_3; + break; + case 4: + shortKey = KeyEvent.VK_4; + break; + case 5: + shortKey = KeyEvent.VK_5; + break; + case 6: + shortKey = KeyEvent.VK_6; + break; + case 7: + shortKey = KeyEvent.VK_7; + break; + case 8: + shortKey = KeyEvent.VK_8; + break; + case 9: + shortKey = KeyEvent.VK_9; + break; + default: + break; + } + return shortKey; + } + + /** + * Get the full path to the recent file where index 0 is the most recent + * @param index the index of the recent file + * @return full path to the recent file at index + */ + public static String getRecentFile(int index) { + return prefs.get(USER_PREFS_KEY + index, null); + } + + /** + * Set the full path to the recent file where index 0 is the most recent + */ + private static void setRecentFile(int index, String fileName) { + prefs.put(USER_PREFS_KEY + index, fileName); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java b/src/core/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java new file mode 100644 index 00000000000..cd6901729d7 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java @@ -0,0 +1,82 @@ +/* + * 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.HashSet; +import java.util.Set; + +import javax.swing.JSplitPane; +import javax.swing.UIManager; + +import org.apache.jmeter.gui.GuiPackage; + +/** + * Hide / unhide LoggerPanel. + * + */ +public class LoggerPanelEnableDisable implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.LOGGER_PANEL_ENABLE_DISABLE); + } + + /** + * Constructor for object. + */ + public LoggerPanelEnableDisable() { + } + + /** + * Gets the ActionNames attribute of the action + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * This method performs the actual command processing. + * + * @param e + * the generic UI action event + */ + @Override + public void doAction(ActionEvent e) { + GuiPackage guiInstance = GuiPackage.getInstance(); + JSplitPane splitPane = ((JSplitPane)guiInstance.getLoggerPanel().getParent()); + if (ActionNames.LOGGER_PANEL_ENABLE_DISABLE.equals(e.getActionCommand())) { + if (!guiInstance.getLoggerPanel().isVisible()) { + splitPane.setDividerSize(UIManager.getInt("SplitPane.dividerSize")); + guiInstance.getLoggerPanel().setVisible(true); + splitPane.setDividerLocation(0.8); + guiInstance.getMenuItemLoggerPanel().getModel().setSelected(true); + } else { + guiInstance.getLoggerPanel().clear(); + guiInstance.getLoggerPanel().setVisible(false); + splitPane.setDividerSize(0); + guiInstance.getMenuItemLoggerPanel().getModel().setSelected(false); + } + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/LookAndFeelCommand.java b/src/core/org/apache/jmeter/gui/action/LookAndFeelCommand.java new file mode 100644 index 00000000000..841de53c8e9 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/LookAndFeelCommand.java @@ -0,0 +1,157 @@ +/* + * 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.Dialog; +import java.awt.Frame; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.prefs.Preferences; + +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +import org.apache.jmeter.gui.util.JMeterMenuBar; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Look and Feel menu item. + */ +public class LookAndFeelCommand implements Command { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String JMETER_LAF = "jmeter.laf"; // $NON-NLS-1$ + + private static final Set commands = new HashSet(); + + private static final Preferences PREFS = Preferences.userNodeForPackage(LookAndFeelCommand.class); + // Note: Windows user preferences are stored relative to: HKEY_CURRENT_USER\Software\JavaSoft\Prefs + + /** Prefix for the user preference key */ + private static final String USER_PREFS_KEY = "laf"; //$NON-NLS-1$ + + static { + UIManager.LookAndFeelInfo[] lfs = JMeterMenuBar.getAllLAFs(); + for (int i = 0; i < lfs.length; i++) { + commands.add(ActionNames.LAF_PREFIX + lfs[i].getClassName()); + } + String jMeterLaf = getJMeterLaf(); + if (log.isInfoEnabled()) { + ArrayList names=new ArrayList(); + for(UIManager.LookAndFeelInfo laf : lfs) { + if (laf.getClassName().equals(jMeterLaf)) { + names.add(laf.getName()); + } + } + if (names.size() > 0) { + log.info("Using look and feel: "+jMeterLaf+ " " +names.toString()); + } else { + log.info("Using look and feel: "+jMeterLaf); + } + } + } + + /** + * Get LookAndFeel classname from the following properties: + *

    + *
  • User preferences key: "laf"
  • + *
  • jmeter.laf.<os.name> - lowercased; spaces replaced by '_'
  • + *
  • jmeter.laf.<os.family> - lowercased.
  • + *
  • jmeter.laf
  • + *
  • UIManager.getCrossPlatformLookAndFeelClassName()
  • + *
+ * @return LAF classname + */ + public static String getJMeterLaf(){ + String laf = PREFS.get(USER_PREFS_KEY, null); + if (laf != null) { + return checkLafName(laf); + } + + String osName = System.getProperty("os.name") // $NON-NLS-1$ + .toLowerCase(Locale.ENGLISH); + // Spaces are not allowed in property names read from files + laf = JMeterUtils.getProperty(JMETER_LAF+"."+osName.replace(' ', '_')); + if (laf != null) { + return checkLafName(laf); + } + String[] osFamily = osName.split("\\s"); // e.g. windows xp => windows + laf = JMeterUtils.getProperty(JMETER_LAF+"."+osFamily[0]); + if (laf != null) { + return checkLafName(laf); + } + laf = JMeterUtils.getProperty(JMETER_LAF); + if (laf != null) { + return checkLafName(laf); + } + return UIManager.getCrossPlatformLookAndFeelClassName(); + } + + // Check if LAF is a built-in one + private static String checkLafName(String laf){ + if (JMeterMenuBar.SYSTEM_LAF.equalsIgnoreCase(laf)){ + return UIManager.getSystemLookAndFeelClassName(); + } + if (JMeterMenuBar.CROSS_PLATFORM_LAF.equalsIgnoreCase(laf)){ + return UIManager.getCrossPlatformLookAndFeelClassName(); + } + return laf; + } + + public LookAndFeelCommand() { + } + + @Override + public void doAction(ActionEvent ev) { + try { + String className = ev.getActionCommand().substring(ActionNames.LAF_PREFIX.length()).replace('/', '.'); + UIManager.setLookAndFeel(className); + for (Window w : Window.getWindows()) { + SwingUtilities.updateComponentTreeUI(w); + if (w.isDisplayable() && + (w instanceof Frame ? !((Frame)w).isResizable() : + w instanceof Dialog ? !((Dialog)w).isResizable() : + true)) { + w.pack(); + } + } + PREFS.put(USER_PREFS_KEY, className); + } catch (javax.swing.UnsupportedLookAndFeelException e) { + JMeterUtils.reportErrorToUser("Look and Feel unavailable:" + e.toString()); + } catch (InstantiationException e) { + JMeterUtils.reportErrorToUser("Look and Feel unavailable:" + e.toString()); + } catch (ClassNotFoundException e) { + JMeterUtils.reportErrorToUser("Look and Feel unavailable:" + e.toString()); + } catch (IllegalAccessException e) { + JMeterUtils.reportErrorToUser("Look and Feel unavailable:" + e.toString()); + } + } + + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Move.java b/src/core/org/apache/jmeter/gui/action/Move.java new file mode 100644 index 00000000000..fb07fd6407d --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Move.java @@ -0,0 +1,156 @@ +/* + * 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.Toolkit; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JTree; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; + +public class Move extends AbstractAction { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.MOVE_DOWN); + commands.add(ActionNames.MOVE_UP); + commands.add(ActionNames.MOVE_LEFT); + commands.add(ActionNames.MOVE_RIGHT); + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + JMeterTreeListener treeListener = GuiPackage.getInstance() + .getTreeListener(); + + if (treeListener.getSelectedNodes().length != 1) { + // we can only move a single node + return; + } + + JMeterTreeNode currentNode = treeListener.getCurrentNode(); + JMeterTreeNode parentNode = getParentNode(currentNode); + + if (parentNode != null) { + String action = e.getActionCommand(); + int index = parentNode.getIndex(currentNode); + + if (ActionNames.MOVE_UP.equals(action)) { + if (index > 0) { + // we stay within the same parent node + int newIndx = index - 1; + moveAndSelectNode(currentNode, parentNode, newIndx); + } + } else if (ActionNames.MOVE_DOWN.equals(action)) { + if (index < parentNode.getChildCount() - 1) { + // we stay within the same parent node + int newIndx = index + 1; + moveAndSelectNode(currentNode, parentNode, newIndx); + } + } else if (ActionNames.MOVE_LEFT.equals(action)) { + JMeterTreeNode parentParentNode = getParentNode(parentNode); + // move to the parent + if (parentParentNode != null + && canAddTo(parentParentNode, currentNode)) { + moveAndSelectNode(currentNode, parentParentNode, + parentParentNode.getIndex(parentNode)); + } + } else if (ActionNames.MOVE_RIGHT.equals(action)) { + JMeterTreeNode after = (JMeterTreeNode) parentNode + .getChildAfter(currentNode); + if (after != null && canAddTo(after, currentNode)) { + // move as a child of the next sibling + moveAndSelectNode(currentNode, after, 0); + } + // Commented as per sebb + // http://mail-archives.apache.org/mod_mbox/jmeter-dev/201307.mbox/%3CCAOGo0VZ0z3GMbfsq_gSB%2Bp7nTUqLng6Gy2ecvYbD8_AKb-Dt5w%40mail.gmail.com%3E + /* + else { + // move as a sibling of the parent + JMeterTreeNode parentParentNode = getParentNode(parentNode); + after = (JMeterTreeNode) parentParentNode + .getChildAfter(parentNode); + if (after != null + && canAddTo(parentParentNode, currentNode)) { + moveAndSelectNode(currentNode, parentParentNode, + parentParentNode.getIndex(after)); + } + } + */ + } + } + + GuiPackage.getInstance().getMainFrame().repaint(); + } + + private JMeterTreeNode getParentNode(JMeterTreeNode currentNode) { + JMeterTreeNode parentNode = (JMeterTreeNode) currentNode.getParent(); + TestElement te = currentNode.getTestElement(); + if (te instanceof TestPlan || te instanceof WorkBench) { + parentNode = null; // So elements can only be added as children + } + return parentNode; + } + + private static boolean canAddTo(JMeterTreeNode parentNode, + JMeterTreeNode node) { + boolean ok = MenuFactory.canAddTo(parentNode, + new JMeterTreeNode[] { node }); + if (!ok) { + Toolkit.getDefaultToolkit().beep(); + } + return ok; + } + + private static void moveAndSelectNode(JMeterTreeNode currentNode, + JMeterTreeNode parentNode, int newIndx) { + GuiPackage guiInstance = GuiPackage.getInstance(); + guiInstance.getTreeModel().removeNodeFromParent(currentNode); + guiInstance.getTreeModel().insertNodeInto(currentNode, parentNode, + newIndx); + + // select the node + TreeNode[] nodes = guiInstance.getTreeModel() + .getPathToRoot(currentNode); + JTree jTree = guiInstance.getMainFrame().getTree(); + jTree.setSelectionPath(new TreePath(nodes)); + } + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Paste.java b/src/core/org/apache/jmeter/gui/action/Paste.java new file mode 100644 index 00000000000..7b0a4d121e2 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Paste.java @@ -0,0 +1,95 @@ +/* + * 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.Toolkit; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Places a copied JMeterTreeNode under the selected node. + * + */ +public class Paste extends AbstractAction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.PASTE); + } + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + JMeterTreeNode draggedNodes[] = Copy.getCopiedNodes(); + if (draggedNodes == null) { + Toolkit.getDefaultToolkit().beep(); + return; + } + JMeterTreeListener treeListener = GuiPackage.getInstance().getTreeListener(); + JMeterTreeNode currentNode = treeListener.getCurrentNode(); + if (MenuFactory.canAddTo(currentNode, draggedNodes)) { + for (JMeterTreeNode draggedNode : draggedNodes) { + if (draggedNode != null) { + addNode(currentNode, draggedNode); + } + } + } else { + Toolkit.getDefaultToolkit().beep(); + } + GuiPackage.getInstance().getMainFrame().repaint(); + } + + private void addNode(JMeterTreeNode parent, JMeterTreeNode node) { + try { + // Add this node + JMeterTreeNode newNode = GuiPackage.getInstance().getTreeModel().addComponent(node.getTestElement(), parent); + // Add all the child nodes of the node we are adding + for(int i = 0; i < node.getChildCount(); i++) { + addNode(newNode, (JMeterTreeNode)node.getChildAt(i)); + } + } + catch (IllegalUserActionException iuae) { + log.error("", iuae); // $NON-NLS-1$ + JMeterUtils.reportErrorToUser(iuae.getMessage()); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/RawTextSearcher.java b/src/core/org/apache/jmeter/gui/action/RawTextSearcher.java new file mode 100644 index 00000000000..c584f177559 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/RawTextSearcher.java @@ -0,0 +1,81 @@ +/* + * 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.util.List; + +import org.apache.commons.lang3.StringUtils; + +/** + * Searcher implementation that searches text as is + */ +public class RawTextSearcher implements Searcher { + private boolean caseSensitive; + private String textToSearch; + + + /** + * Constructor + * @param caseSensitive is search case sensitive + * @param textToSearch Text to search + */ + public RawTextSearcher(boolean caseSensitive, String textToSearch) { + super(); + this.caseSensitive = caseSensitive; + if(caseSensitive) { + this.textToSearch = textToSearch; + } else { + this.textToSearch = textToSearch.toLowerCase(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean search(List textTokens) { + boolean result = false; + for (String searchableToken : textTokens) { + if(!StringUtils.isEmpty(searchableToken)) { + if(caseSensitive) { + result = searchableToken.indexOf(textToSearch)>=0; + } else { + result = searchableToken.toLowerCase().indexOf(textToSearch)>=0; + } + if (result) { + return result; + } + } + } + return false; + } + + /** + * Returns true if searchedTextLowerCase is in value + * @param value string in which the search will be done + * @param searchedTextLowerCase string which will be searched for + * @return true if searchedTextLowerCase is in value + */ + protected boolean testField(String value, String searchedTextLowerCase) { + if(!StringUtils.isEmpty(value)) { + return value.toLowerCase().indexOf(searchedTextLowerCase)>=0; + } + return false; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/RegexpSearcher.java b/src/core/org/apache/jmeter/gui/action/RegexpSearcher.java new file mode 100644 index 00000000000..7b17982c2fd --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/RegexpSearcher.java @@ -0,0 +1,72 @@ +/* + * 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.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; + +/** + * Regexp search implementation + */ +public class RegexpSearcher implements Searcher { + private boolean caseSensitive; + private Pattern pattern; + + + /** + * Constructor + * @param caseSensitive is search case sensitive + * @param regexp Regexp to search + */ + public RegexpSearcher(boolean caseSensitive, String regexp) { + super(); + this.caseSensitive = caseSensitive; + String newRegexp = ".*"+regexp+".*"; + if(caseSensitive) { + pattern = Pattern.compile(newRegexp); + } else { + pattern = Pattern.compile(newRegexp.toLowerCase()); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean search(List textTokens) { + for (String searchableToken : textTokens) { + if(!StringUtils.isEmpty(searchableToken)) { + Matcher matcher = null; + if(caseSensitive) { + matcher = pattern.matcher(searchableToken); + } else { + matcher = pattern.matcher(searchableToken.toLowerCase()); + } + if(matcher.find()) { + return true; + } + } + } + return false; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/RemoteStart.java b/src/core/org/apache/jmeter/gui/action/RemoteStart.java new file mode 100644 index 00000000000..19f2305ea58 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/RemoteStart.java @@ -0,0 +1,121 @@ +/* + * 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.Component; +import java.awt.event.ActionEvent; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; +import org.apache.jmeter.JMeter; +import org.apache.jmeter.engine.DistributedRunner; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.threads.RemoteThreadsListenerTestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class RemoteStart extends AbstractAction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String LOCAL_HOST = "127.0.0.1"; // $NON-NLS-1$ + + private static final String REMOTE_HOSTS = "remote_hosts"; // $NON-NLS-1$ jmeter.properties + + private static final String REMOTE_HOSTS_SEPARATOR = ","; // $NON-NLS-1$ + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.REMOTE_START); + commands.add(ActionNames.REMOTE_STOP); + commands.add(ActionNames.REMOTE_SHUT); + commands.add(ActionNames.REMOTE_START_ALL); + commands.add(ActionNames.REMOTE_STOP_ALL); + commands.add(ActionNames.REMOTE_SHUT_ALL); + commands.add(ActionNames.REMOTE_EXIT); + commands.add(ActionNames.REMOTE_EXIT_ALL); + } + + private DistributedRunner distributedRunner = new DistributedRunner(); + + public RemoteStart() { + } + + @Override + public void doAction(ActionEvent e) { + String name = ((Component) e.getSource()).getName(); + if (name != null) { + name = name.trim(); + } + String action = e.getActionCommand(); + if (action.equals(ActionNames.REMOTE_STOP)) { + GuiPackage.getInstance().getMainFrame().showStoppingMessage(name); + distributedRunner.stop(Arrays.asList(name)); + } else if (action.equals(ActionNames.REMOTE_SHUT)) { + GuiPackage.getInstance().getMainFrame().showStoppingMessage(name); + distributedRunner.shutdown(Arrays.asList(name)); + } else if (action.equals(ActionNames.REMOTE_START)) { + popupShouldSave(e); + distributedRunner.init(Arrays.asList(name), getTestTree()); + distributedRunner.start(Arrays.asList(name)); + } else if (action.equals(ActionNames.REMOTE_START_ALL)) { + popupShouldSave(e); + distributedRunner.init(getRemoteHosts(), getTestTree()); + distributedRunner.start(); + } else if (action.equals(ActionNames.REMOTE_STOP_ALL)) { + distributedRunner.stop(getRemoteHosts()); + } else if (action.equals(ActionNames.REMOTE_SHUT_ALL)) { + distributedRunner.shutdown(getRemoteHosts()); + } else if (action.equals(ActionNames.REMOTE_EXIT)) { + distributedRunner.exit(Arrays.asList(name)); + } else if (action.equals(ActionNames.REMOTE_EXIT_ALL)) { + distributedRunner.exit(getRemoteHosts()); + } + } + + private List getRemoteHosts() { + String remote_hosts_string = JMeterUtils.getPropDefault(REMOTE_HOSTS, LOCAL_HOST); + StringTokenizer st = new StringTokenizer(remote_hosts_string, REMOTE_HOSTS_SEPARATOR); + List list = new LinkedList(); + while (st.hasMoreElements()) + list.add((String) st.nextElement()); + return list; + } + + @Override + public Set getActionNames() { + return commands; + } + + private HashTree getTestTree() { + GuiPackage gui = GuiPackage.getInstance(); + HashTree testTree = gui.getTreeModel().getTestPlan(); + JMeter.convertSubTree(testTree); + testTree.add(testTree.getArray()[0], gui.getMainFrame()); + // Used for remote notification of threads start/stop,see BUG 54152 + testTree.add(testTree.getArray()[0], new RemoteThreadsListenerTestElement()); + return testTree; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Remove.java b/src/core/org/apache/jmeter/gui/action/Remove.java new file mode 100644 index 00000000000..c741b7977c3 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Remove.java @@ -0,0 +1,97 @@ +/* + * 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.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implements the Remove menu item. + */ +public class Remove implements Command { + + private static final Set commands = new HashSet(); + + // Whether to skip the delete confirmation dialogue + private static final boolean SKIP_CONFIRM = JMeterUtils.getPropDefault("confirm.delete.skip", false); // $NON-NLS-1$ + + static { + commands.add(ActionNames.REMOVE); + } + + /** + * Constructor for the Remove object + */ + public Remove() { + } + + /** + * Gets the ActionNames attribute of the Remove object. + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + + int isConfirm = SKIP_CONFIRM ? JOptionPane.YES_OPTION : + JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("remove_confirm_msg"),// $NON-NLS-1$ + JMeterUtils.getResString("remove_confirm_title"), // $NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (isConfirm == JOptionPane.YES_OPTION) { + // TODO - removes the nodes from the CheckDirty map - should it be done later, in case some can't be removed? + ActionRouter.getInstance().actionPerformed(new ActionEvent(e.getSource(), e.getID(), ActionNames.CHECK_REMOVE)); + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeNode[] nodes = guiPackage.getTreeListener().getSelectedNodes(); + TreePath newTreePath = // Save parent node for later + guiPackage.getTreeListener().removedSelectedNode(); + for (int i = nodes.length - 1; i >= 0; i--) { + removeNode(nodes[i]); + } + guiPackage.getTreeListener().getJTree().setSelectionPath(newTreePath); + guiPackage.updateCurrentGui(); + } + } + + private static void removeNode(JMeterTreeNode node) { + TestElement testElement = node.getTestElement(); + if (testElement.canRemove()) { + GuiPackage.getInstance().getTreeModel().removeNodeFromParent(node); + GuiPackage.getInstance().removeNode(testElement); + } else { + String message = testElement.getClass().getName() + " is busy"; + JOptionPane.showMessageDialog(null, message, "Cannot remove item", JOptionPane.ERROR_MESSAGE); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/ResetSearchCommand.java b/src/core/org/apache/jmeter/gui/action/ResetSearchCommand.java new file mode 100644 index 00000000000..0da2649fa00 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/ResetSearchCommand.java @@ -0,0 +1,68 @@ +/* + * 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.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.Searchable; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; + +/** + * Reset Search + */ +public class ResetSearchCommand extends AbstractAction { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.SEARCH_RESET); + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeModel jMeterTreeModel = guiPackage.getTreeModel(); + for (JMeterTreeNode jMeterTreeNode : jMeterTreeModel.getNodesOfType(Searchable.class)) { + if (jMeterTreeNode.getUserObject() instanceof Searchable){ + List matchingNodes = jMeterTreeNode.getPathToThreadGroup(); + for (JMeterTreeNode jMeterTreeNode2 : matchingNodes) { + jMeterTreeNode2.setMarkedBySearch(false); + } + } + } + GuiPackage.getInstance().getMainFrame().repaint(); + } + + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/RevertProject.java b/src/core/org/apache/jmeter/gui/action/RevertProject.java new file mode 100644 index 00000000000..4d278ee4101 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/RevertProject.java @@ -0,0 +1,77 @@ +/* + * 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.io.File; +import java.util.HashSet; +import java.util.Set; +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Handles the Revert Project command. + * + */ +public class RevertProject implements Command { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.REVERT_PROJECT); + } + + public RevertProject() { + super(); + } + + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + // Get the file name of the current project + String projectFile = GuiPackage.getInstance().getTestPlanFile(); + // Check if the user has loaded any file + if(projectFile == null) { + return; + } + + // Check if the user wants to drop any changes + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.CHECK_DIRTY)); + GuiPackage guiPackage = GuiPackage.getInstance(); + if (guiPackage.isDirty()) { + // Check if the user wants to revert + int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("cancel_revert_project"), // $NON-NLS-1$ + JMeterUtils.getResString("revert_project?"), // $NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if(response == JOptionPane.YES_OPTION) { + // Close the current project + Close.closeProject(e); + // Reload the project + Load.loadProjectFile(e, new File(projectFile), false); + } + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/SSLManagerCommand.java b/src/core/org/apache/jmeter/gui/action/SSLManagerCommand.java new file mode 100644 index 00000000000..32b54ea5f16 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/SSLManagerCommand.java @@ -0,0 +1,135 @@ +/* + * 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.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileFilter; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.SSLManager; + +// +/** + * SSL Manager Command. The SSL Manager provides a mechanism to change your + * client authentication if required by the server. If you have JSSE 1.0.2 + * installed, you can select your client identity from a list of installed keys. + * You can also change your keystore. JSSE 1.0.2 allows you to export a PKCS#12 + * key from Netscape 4.04 or higher and use it in a read only format. You must + * supply a password that is greater than six characters due to limitations in + * the keytool program--and possibly the rest of the system. + *

+ * By selecting a *.p12 file as your keystore (your PKCS#12) format file, you + * can have a whopping one key keystore. The advantage is that you can test a + * connection using the assigned Certificate from a Certificate Authority. + *

+ * TODO ? + * N.B. The present implementation does not seem to allow selection of keys, + * it only allows a change of keystore at run-time, or to provide one if not + * already defined via the property. + * + */ +public class SSLManagerCommand implements Command { + private static final Set commandSet; + static { + HashSet commands = new HashSet(); + commands.add(ActionNames.SSL_MANAGER); + commandSet = Collections.unmodifiableSet(commands); + } + + /** + * Handle the "sslmanager" action by displaying the "SSL CLient Manager" + * dialog box. The Dialog Box is NOT modal, because those should be avoided + * if at all possible. + */ + @Override + public void doAction(ActionEvent e) { + if (e.getActionCommand().equals(ActionNames.SSL_MANAGER)) { + this.sslManager(); + } + } + + /** + * Provide the list of Action names that are available in this command. + */ + @Override + public Set getActionNames() { + return SSLManagerCommand.commandSet; + } + + /** + * Called by sslManager button. Raises sslManager dialog. + * I.e. a FileChooser for PCSI12 (.p12|.P12) files. + */ + private void sslManager() { + SSLManager.reset(); + + JFileChooser keyStoreChooser = new JFileChooser(System.getProperty("user.dir")); //$NON-NLS-1$ + keyStoreChooser.addChoosableFileFilter(new AcceptPKCS12FileFilter()); + keyStoreChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + int retVal = keyStoreChooser.showOpenDialog(GuiPackage.getInstance().getMainFrame()); + + if (JFileChooser.APPROVE_OPTION == retVal) { + File selectedFile = keyStoreChooser.getSelectedFile(); + try { + System.setProperty(SSLManager.JAVAX_NET_SSL_KEY_STORE, selectedFile.getCanonicalPath()); + } catch (IOException e) { + //Ignored + } + } + + SSLManager.getInstance(); + } + + /** + * Internal class to add a PKCS12 file format filter for JFileChooser. + */ + static private class AcceptPKCS12FileFilter extends FileFilter { + /** + * Get the description that shows up in JFileChooser filter menu. + * + * @return description + */ + @Override + public String getDescription() { + return JMeterUtils.getResString("pkcs12_desc"); //$NON-NLS-1$ + } + + /** + * Tests to see if the file ends with "*.p12" or "*.P12". + * + * @param testFile + * file to test + * @return true if file is accepted, false otherwise + */ + @Override + public boolean accept(File testFile) { + return testFile.isDirectory() + || testFile.getName().endsWith(".p12") //$NON-NLS-1$ + || testFile.getName().endsWith(".P12"); //$NON-NLS-1$ + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Save.java b/src/core/org/apache/jmeter/gui/action/Save.java new file mode 100644 index 00000000000..b33f4f072e3 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Save.java @@ -0,0 +1,224 @@ +/* + * 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.io.File; +import java.io.FileOutputStream; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Set; + +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; + +import org.apache.commons.io.FilenameUtils; +import org.apache.jmeter.control.gui.TestFragmentControllerGui; +import org.apache.jmeter.engine.TreeCloner; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Save the current test plan; implements: + * Save + * Save TestPlan As + * Save (Selection) As + */ +public class Save implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String JMX_FILE_EXTENSION = ".jmx"; // $NON-NLS-1$ + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.SAVE_AS); // Save (Selection) As + commands.add(ActionNames.SAVE_AS_TEST_FRAGMENT); // Save as Test Fragment + commands.add(ActionNames.SAVE_ALL_AS); // Save TestPlan As + commands.add(ActionNames.SAVE); // Save + } + + /** + * Constructor for the Save object. + */ + public Save() { + } + + /** + * Gets the ActionNames attribute of the Save object. + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) throws IllegalUserActionException { + HashTree subTree = null; + boolean fullSave = false; // are we saving the whole tree? + if (!commands.contains(e.getActionCommand())) { + throw new IllegalUserActionException("Invalid user command:" + e.getActionCommand()); + } + if (e.getActionCommand().equals(ActionNames.SAVE_AS)) { + JMeterTreeNode[] nodes = GuiPackage.getInstance().getTreeListener().getSelectedNodes(); + if (nodes.length > 1){ + JMeterUtils.reportErrorToUser( + JMeterUtils.getResString("save_as_error"), // $NON-NLS-1$ + JMeterUtils.getResString("save_as")); // $NON-NLS-1$ + return; + } + subTree = GuiPackage.getInstance().getCurrentSubTree(); + } + else if (e.getActionCommand().equals(ActionNames.SAVE_AS_TEST_FRAGMENT)) { + JMeterTreeNode[] nodes = GuiPackage.getInstance().getTreeListener().getSelectedNodes(); + if(checkAcceptableForTestFragment(nodes)) { + subTree = GuiPackage.getInstance().getCurrentSubTree(); + // Create Test Fragment node + TestElement element = GuiPackage.getInstance().createTestElement(TestFragmentControllerGui.class.getName()); + HashTree hashTree = new ListedHashTree(); + HashTree tfTree = hashTree.add(new JMeterTreeNode(element, null)); + for (int i = 0; i < nodes.length; i++) { + // Clone deeply current node + TreeCloner cloner = new TreeCloner(false); + GuiPackage.getInstance().getTreeModel().getCurrentSubTree(nodes[i]).traverse(cloner); + // Add clone to tfTree + tfTree.add(cloner.getClonedTree()); + } + + subTree = hashTree; + + } else { + JMeterUtils.reportErrorToUser( + JMeterUtils.getResString("save_as_test_fragment_error"), // $NON-NLS-1$ + JMeterUtils.getResString("save_as_test_fragment")); // $NON-NLS-1$ + return; + } + } else { + fullSave = true; + HashTree testPlan = GuiPackage.getInstance().getTreeModel().getTestPlan(); + // If saveWorkBench + JMeterTreeNode workbenchNode = (JMeterTreeNode) ((JMeterTreeNode) GuiPackage.getInstance().getTreeModel().getRoot()).getChildAt(1); + if (((WorkBench)workbenchNode.getUserObject()).getSaveWorkBench()) { + HashTree workbench = GuiPackage.getInstance().getTreeModel().getWorkBench(); + testPlan.add(workbench); + } + subTree = testPlan; + } + + String updateFile = GuiPackage.getInstance().getTestPlanFile(); + if (!ActionNames.SAVE.equals(e.getActionCommand()) || updateFile == null) { + JFileChooser chooser = FileDialoger.promptToSaveFile(updateFile == null ? GuiPackage.getInstance().getTreeListener() + .getCurrentNode().getName() + + JMX_FILE_EXTENSION : updateFile); + if (chooser == null) { + return; + } + updateFile = chooser.getSelectedFile().getAbsolutePath(); + // Make sure the file ends with proper extension + if(FilenameUtils.getExtension(updateFile).equals("")) { + updateFile = updateFile + JMX_FILE_EXTENSION; + } + // Check if the user is trying to save to an existing file + File f = new File(updateFile); + if(f.exists()) { + int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("save_overwrite_existing_file"), // $NON-NLS-1$ + JMeterUtils.getResString("save?"), // $NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.NO_OPTION) { + return ; // Do not save, user does not want to overwrite + } + } + + if (!e.getActionCommand().equals(ActionNames.SAVE_AS)) { + GuiPackage.getInstance().setTestPlanFile(updateFile); + } + } + + try { + convertSubTree(subTree); + } catch (Exception err) { + log.warn("Error converting subtree "+err); + } + + FileOutputStream ostream = null; + try { + ostream = new FileOutputStream(updateFile); + SaveService.saveTree(subTree, ostream); + if (fullSave) { // Only update the stored copy of the tree for a full save + subTree = GuiPackage.getInstance().getTreeModel().getTestPlan(); // refetch, because convertSubTree affects it + ActionRouter.getInstance().doActionNow(new ActionEvent(subTree, e.getID(), ActionNames.SUB_TREE_SAVED)); + } + } catch (Throwable ex) { + log.error("Error saving tree:", ex); + if (ex instanceof Error){ + throw (Error) ex; + } + if (ex instanceof RuntimeException){ + throw (RuntimeException) ex; + } + throw new IllegalUserActionException("Couldn't save test plan to file: " + updateFile, ex); + } finally { + JOrphanUtils.closeQuietly(ostream); + } + GuiPackage.getInstance().updateCurrentGui(); + } + + /** + * Check nodes does not contain a node of type TestPlan or ThreadGroup + * @param nodes + */ + private static final boolean checkAcceptableForTestFragment(JMeterTreeNode[] nodes) { + for (int i = 0; i < nodes.length; i++) { + Object userObject = nodes[i].getUserObject(); + if(userObject instanceof org.apache.jmeter.threads.ThreadGroup || + userObject instanceof TestPlan) { + return false; + } + } + return true; + } + + // package protected to allow access from test code + void convertSubTree(HashTree tree) { + Iterator iter = new LinkedList(tree.list()).iterator(); + while (iter.hasNext()) { + JMeterTreeNode item = (JMeterTreeNode) iter.next(); + convertSubTree(tree.getTree(item)); + TestElement testElement = item.getTestElement(); // requires JMeterTreeNode + tree.replaceKey(item, testElement); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/SaveGraphics.java b/src/core/org/apache/jmeter/gui/action/SaveGraphics.java new file mode 100644 index 00000000000..cf6787624a6 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/SaveGraphics.java @@ -0,0 +1,130 @@ +/* + * 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.io.File; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.save.SaveGraphicsService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Printable; + +/** + * SaveGraphics action is meant to be a generic reusable Action. The class will + * use GUIPackage to get the current gui. Once it does, it checks to see if the + * element implements Printable interface. If it does, it call getPrintable() to + * get the JComponent. By default, it will use SaveGraphicsService to save a PNG + * file if no extension is provided. If either .png or .tif is in the filename, + * it will call SaveGraphicsService to save in the format. + */ +public class SaveGraphics implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.SAVE_GRAPHICS); + commands.add(ActionNames.SAVE_GRAPHICS_ALL); + } + + private static final String[] extensions + = { SaveGraphicsService.TIFF_EXTENSION, SaveGraphicsService.PNG_EXTENSION }; + + /** + * Constructor for the Save object. + */ + public SaveGraphics() { + } + + /** + * Gets the ActionNames attribute of the Save object. + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) throws IllegalUserActionException { + if (!commands.contains(e.getActionCommand())) { + throw new IllegalUserActionException("Invalid user command:" + e.getActionCommand()); + } + if (e.getActionCommand().equals(ActionNames.SAVE_GRAPHICS)) { + JMeterGUIComponent component = GuiPackage.getInstance().getCurrentGui(); + // get the JComponent from the visualizer + if (component instanceof Printable) { + JComponent comp = ((Printable) component).getPrintableComponent(); + saveImage(comp); + } + } + if (e.getActionCommand().equals(ActionNames.SAVE_GRAPHICS_ALL)) { + JMeterGUIComponent component = GuiPackage.getInstance().getCurrentGui(); + JComponent comp=((JComponent) component).getRootPane(); + saveImage(comp); + } + } + + private void saveImage(JComponent comp){ + + String filename; + JFileChooser chooser = FileDialoger.promptToSaveFile(GuiPackage.getInstance().getTreeListener() + .getCurrentNode().getName(), extensions); + if (chooser == null) { + return; + } + // Get the string given from the choose and check + // the file extension. + filename = chooser.getSelectedFile().getAbsolutePath(); + if (filename != null) { + File f = new File(filename); + if(f.exists()) { + int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("save_overwrite_existing_file"), // $NON-NLS-1$ + JMeterUtils.getResString("save?"), // $NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.NO_OPTION) { + return ; // Do not save, user does not want to overwrite + } + } + SaveGraphicsService save = new SaveGraphicsService(); + String ext = filename.substring(filename.length() - 4); + String name = filename.substring(0, filename.length() - 4); + if (ext.equals(SaveGraphicsService.PNG_EXTENSION)) { + save.saveJComponent(name, SaveGraphicsService.PNG, comp); + } else if (ext.equals(SaveGraphicsService.TIFF_EXTENSION)) { + save.saveJComponent(name, SaveGraphicsService.TIFF, comp); + } else { + save.saveJComponent(filename, SaveGraphicsService.PNG, comp); + } + } + + } +} diff --git a/src/core/org/apache/jmeter/gui/action/SearchTreeCommand.java b/src/core/org/apache/jmeter/gui/action/SearchTreeCommand.java new file mode 100644 index 00000000000..c7c33093a9c --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/SearchTreeCommand.java @@ -0,0 +1,54 @@ +/* + * 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.HashSet; +import java.util.Set; + +/** + * Search nodes for a text + * TODO Enhance search dialog to select kind of nodes .... + */ +public class SearchTreeCommand extends AbstractAction { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.SEARCH_TREE); + } + + private SearchTreeDialog dialog = new SearchTreeDialog(); + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + dialog.setVisible(true); + } + + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/SearchTreeDialog.java b/src/core/org/apache/jmeter/gui/action/SearchTreeDialog.java new file mode 100644 index 00000000000..944c15c5d7a --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/SearchTreeDialog.java @@ -0,0 +1,230 @@ +/* + * 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.BorderLayout; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.JTree; +import javax.swing.tree.TreePath; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.Searchable; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * FIXME Why is searchTF not getting focus correctly after having been setVisible(false) once + */ +public class SearchTreeDialog extends JDialog implements ActionListener { + + private static final long serialVersionUID = -4436834972710248247L; + + private static final Logger logger = LoggingManager.getLoggerForClass(); + + private JButton searchButton; + + private JLabeledTextField searchTF; + + private JCheckBox isRegexpCB; + + private JCheckBox isCaseSensitiveCB; + + private JButton cancelButton; + + /** + * Store last search + */ + private transient String lastSearch = null; + + private JButton searchAndExpandButton; + + public SearchTreeDialog() { + super((JFrame) null, JMeterUtils.getResString("search_tree_title"), true); //$NON-NLS-1$ + init(); + } + + @Override + protected JRootPane createRootPane() { + JRootPane rootPane = new JRootPane(); + // Hide Window on ESC + Action escapeAction = new AbstractAction("ESCAPE") { + + private static final long serialVersionUID = -6543764044868772971L; + + @Override + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }; + // Do search on Enter + Action enterAction = new AbstractAction("ENTER") { + + private static final long serialVersionUID = -3661361497864527363L; + + @Override + public void actionPerformed(ActionEvent actionEvent) { + doSearch(actionEvent); + } + }; + ActionMap actionMap = rootPane.getActionMap(); + actionMap.put(escapeAction.getValue(Action.NAME), escapeAction); + actionMap.put(enterAction.getValue(Action.NAME), enterAction); + InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + inputMap.put(KeyStrokes.ESC, escapeAction.getValue(Action.NAME)); + inputMap.put(KeyStrokes.ENTER, enterAction.getValue(Action.NAME)); + + return rootPane; + } + + private void init() { + this.getContentPane().setLayout(new BorderLayout(10,10)); + + searchTF = new JLabeledTextField(JMeterUtils.getResString("search_text_field"), 20); //$NON-NLS-1$ + if(!StringUtils.isEmpty(lastSearch)) { + searchTF.setText(lastSearch); + } + isRegexpCB = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_regexp"), false); //$NON-NLS-1$ + isCaseSensitiveCB = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_case"), false); //$NON-NLS-1$ + Font font = new Font("SansSerif", Font.PLAIN, 10); // reduce font + isRegexpCB.setFont(font); + isCaseSensitiveCB.setFont(font); + + JPanel searchCriterionPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + searchCriterionPanel.add(isCaseSensitiveCB); + searchCriterionPanel.add(isRegexpCB); + + JPanel searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.Y_AXIS)); + searchPanel.setBorder(BorderFactory.createEmptyBorder(7, 3, 3, 3)); + searchPanel.add(searchTF, BorderLayout.NORTH); + searchPanel.add(searchCriterionPanel, BorderLayout.CENTER); + JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + + searchButton = new JButton(JMeterUtils.getResString("search")); //$NON-NLS-1$ + searchButton.addActionListener(this); + searchAndExpandButton = new JButton(JMeterUtils.getResString("search_expand")); //$NON-NLS-1$ + searchAndExpandButton.addActionListener(this); + cancelButton = new JButton(JMeterUtils.getResString("cancel")); //$NON-NLS-1$ + cancelButton.addActionListener(this); + buttonsPanel.add(searchButton); + buttonsPanel.add(searchAndExpandButton); + buttonsPanel.add(cancelButton); + searchPanel.add(buttonsPanel, BorderLayout.SOUTH); + this.getContentPane().add(searchPanel); + searchTF.requestFocusInWindow(); + + this.pack(); + ComponentUtil.centerComponentInWindow(this); + } + + /** + * Do search + * @param e {@link ActionEvent} + */ + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource()==cancelButton) { + searchTF.requestFocusInWindow(); + this.setVisible(false); + return; + } + doSearch(e); + } + + /** + * @param e {@link ActionEvent} + */ + private void doSearch(ActionEvent e) { + boolean expand = e.getSource()==searchAndExpandButton; + String wordToSearch = searchTF.getText(); + if (StringUtils.isEmpty(wordToSearch)) { + return; + } else { + this.lastSearch = wordToSearch; + } + + // reset previous result + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.SEARCH_RESET)); + // do search + Searcher searcher = null; + if (isRegexpCB.isSelected()) { + searcher = new RegexpSearcher(isCaseSensitiveCB.isSelected(), searchTF.getText()); + } else { + searcher = new RawTextSearcher(isCaseSensitiveCB.isSelected(), searchTF.getText()); + } + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeModel jMeterTreeModel = guiPackage.getTreeModel(); + Set nodes = new HashSet(); + for (JMeterTreeNode jMeterTreeNode : jMeterTreeModel.getNodesOfType(Searchable.class)) { + try { + if (jMeterTreeNode.getUserObject() instanceof Searchable){ + Searchable searchable = (Searchable) jMeterTreeNode.getUserObject(); + List matchingNodes = jMeterTreeNode.getPathToThreadGroup(); + List searchableTokens = searchable.getSearchableTokens(); + boolean result = searcher.search(searchableTokens); + if (result) { + nodes.addAll(matchingNodes); + } + } + } catch (Exception ex) { + logger.error("Error occured searching for word:"+ wordToSearch, ex); + } + } + GuiPackage guiInstance = GuiPackage.getInstance(); + JTree jTree = guiInstance.getMainFrame().getTree(); + + for (Iterator iterator = nodes.iterator(); iterator.hasNext();) { + JMeterTreeNode jMeterTreeNode = iterator.next(); + jMeterTreeNode.setMarkedBySearch(true); + if (expand) { + jTree.expandPath(new TreePath(jMeterTreeNode.getPath())); + } + } + GuiPackage.getInstance().getMainFrame().repaint(); + searchTF.requestFocusInWindow(); + this.setVisible(false); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/Searcher.java b/src/core/org/apache/jmeter/gui/action/Searcher.java new file mode 100644 index 00000000000..5ad2ec735dc --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Searcher.java @@ -0,0 +1,34 @@ +/* + * 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.util.List; + +/** + * Search algorithm + */ +public interface Searcher { + + /** + * Implements the search + * @param textTokens List of content to be searched + * @return true if search on textTokens is successful + */ + boolean search(List textTokens); +} diff --git a/src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java b/src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java new file mode 100644 index 00000000000..7cb13045393 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java @@ -0,0 +1,243 @@ +/* + * 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.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.HeadlessException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.JScrollPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.action.template.Template; +import org.apache.jmeter.gui.action.template.TemplateManager; +import org.apache.jmeter.swing.HtmlPane; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Dialog used for Templates selection + * @since 2.10 + */ +public class SelectTemplatesDialog extends JDialog implements ChangeListener, ActionListener, HyperlinkListener { + + private static final long serialVersionUID = -4436834972710248247L; + + // Minimal dimensions for dialog box + private static final int MINIMAL_BOX_WIDTH = 500; + private static final int MINIMAL_BOX_HEIGHT = 300; + + private Font FONT_SMALL = new Font("SansSerif", Font.PLAIN, 10); // $NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final JLabeledChoice templateList = new JLabeledChoice(JMeterUtils.getResString("template_choose"), false); //$NON-NLS-1$; + + private final HtmlPane helpDoc = new HtmlPane(); + + private final JButton reloadTemplateButton = new JButton(JMeterUtils.getResString("template_reload")); //$NON-NLS-1$; + + private final JButton applyTemplateButton = new JButton(); + + private final JButton cancelButton = new JButton(JMeterUtils.getResString("cancel")); //$NON-NLS-1$; + + private final JScrollPane scroller = new JScrollPane(helpDoc); + + public SelectTemplatesDialog() { + super((JFrame) null, JMeterUtils.getResString("template_title"), true); //$NON-NLS-1$ + init(); + } + + @Override + protected JRootPane createRootPane() { + JRootPane rootPane = new JRootPane(); + // Hide Window on ESC + Action escapeAction = new AbstractAction("ESCAPE") { //$NON-NLS-1$ + /** + * + */ + private static final long serialVersionUID = -6543764044868772971L; + + @Override + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }; + // Do search on Enter + Action enterAction = new AbstractAction("ENTER") { //$NON-NLS-1$ + + private static final long serialVersionUID = -3661361497864527363L; + + @Override + public void actionPerformed(final ActionEvent actionEvent) { + checkDirtyAndLoad(actionEvent); + } + }; + ActionMap actionMap = rootPane.getActionMap(); + actionMap.put(escapeAction.getValue(Action.NAME), escapeAction); + actionMap.put(enterAction.getValue(Action.NAME), enterAction); + InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + inputMap.put(KeyStrokes.ESC, escapeAction.getValue(Action.NAME)); + inputMap.put(KeyStrokes.ENTER, enterAction.getValue(Action.NAME)); + + return rootPane; + } + + /** + * Check if existing Test Plan has been modified and ask user + * what he wants to do if test plan is dirty + * @param actionEvent {@link ActionEvent} + */ + private void checkDirtyAndLoad(final ActionEvent actionEvent) + throws HeadlessException { + final String selectedTemplate = templateList.getText(); + final Template template = TemplateManager.getInstance().getTemplateByName(selectedTemplate); + if (template == null) { + return; + } + final boolean isTestPlan = template.isTestPlan(); + // Check if the user wants to drop any changes + if (isTestPlan) { + ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.CHECK_DIRTY)); + GuiPackage guiPackage = GuiPackage.getInstance(); + if (guiPackage.isDirty()) { + // Check if the user wants to create from template + int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("cancel_new_from_template"), // $NON-NLS-1$ + JMeterUtils.getResString("template_load?"), // $NON-NLS-1$ + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + if(response == JOptionPane.YES_OPTION) { + ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.SAVE)); + } + if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) { + return; // Don't clear the plan + } + } + } + ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.STOP_THREAD)); + final File parent = template.getParent(); + final File fileToCopy = parent != null + ? new File(parent, template.getFileName()) + : new File(JMeterUtils.getJMeterHome(), template.getFileName()); + Load.loadProjectFile(actionEvent, fileToCopy, !isTestPlan, false); + this.setVisible(false); + } + + private void init() { + templateList.setValues(TemplateManager.getInstance().getTemplateNames()); + templateList.addChangeListener(this); + reloadTemplateButton.addActionListener(this); + reloadTemplateButton.setFont(FONT_SMALL); + this.getContentPane().setLayout(new BorderLayout(10, 0)); + + JPanel templateBar = new JPanel(new BorderLayout()); + templateBar.add(templateList, BorderLayout.CENTER); + JPanel reloadBtnBar = new JPanel(); + reloadBtnBar.add(reloadTemplateButton); + templateBar.add(reloadBtnBar, BorderLayout.EAST); + this.getContentPane().add(templateBar, BorderLayout.NORTH); + helpDoc.setContentType("text/html"); //$NON-NLS-1$ + helpDoc.setEditable(false); + helpDoc.addHyperlinkListener(this); + this.getContentPane().add(scroller, BorderLayout.CENTER); + + applyTemplateButton.addActionListener(this); + cancelButton.addActionListener(this); + + // Bottom buttons bar + JPanel actionBtnBar = new JPanel(new FlowLayout()); + actionBtnBar.add(applyTemplateButton); + actionBtnBar.add(cancelButton); + this.getContentPane().add(actionBtnBar, BorderLayout.SOUTH); + + this.pack(); + this.setMinimumSize(new Dimension(MINIMAL_BOX_WIDTH, MINIMAL_BOX_HEIGHT)); + ComponentUtil.centerComponentInWindow(this, 50); // center position and 50% of screen size + populateTemplatePage(); + } + + /** + * Do search + * @param e {@link ActionEvent} + */ + @Override + public void actionPerformed(ActionEvent e) { + final Object source = e.getSource(); + if (source == cancelButton) { + this.setVisible(false); + return; + } else if (source == applyTemplateButton) { + checkDirtyAndLoad(e); + } else if (source == reloadTemplateButton) { + templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames()); + } + } + + @Override + public void stateChanged(ChangeEvent event) { + populateTemplatePage(); + } + + private void populateTemplatePage() { + String selectedTemplate = templateList.getText(); + Template template = TemplateManager.getInstance().getTemplateByName(selectedTemplate); + helpDoc.setText(template.getDescription()); + applyTemplateButton.setText(template.isTestPlan() + ? JMeterUtils.getResString("template_create_from") + : JMeterUtils.getResString("template_merge_from") ); + } + + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + if (java.awt.Desktop.isDesktopSupported()) { + try { + java.awt.Desktop.getDesktop().browse(e.getURL().toURI()); + } catch (Exception ex) { + log.error("Error opening URL in browser:"+e.getURL()); + } + } + } + } + +} diff --git a/src/core/org/apache/jmeter/gui/action/Start.java b/src/core/org/apache/jmeter/gui/action/Start.java new file mode 100644 index 00000000000..621207275fd --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/Start.java @@ -0,0 +1,133 @@ +/* + * 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.HashSet; +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.testelement.TestPlan; +import org.apache.jmeter.timers.Timer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class Start extends AbstractAction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.ACTION_START); + commands.add(ActionNames.ACTION_START_NO_TIMERS); + commands.add(ActionNames.ACTION_STOP); + commands.add(ActionNames.ACTION_SHUTDOWN); + } + + 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 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(); + } + } + } + + /** + * Start JMeter engine + * @param noTimer ignore timers + */ + private void startEngine(boolean ignoreTimer) { + GuiPackage gui = GuiPackage.getInstance(); + HashTree testTree = gui.getTreeModel().getTestPlan(); + JMeter.convertSubTree(testTree); + testTree.add(testTree.getArray()[0], gui.getMainFrame()); + log.debug("test plan before cloning is running version: " + + ((TestPlan) testTree.getArray()[0]).isRunningVersion()); + TreeCloner cloner = cloneTree(testTree, ignoreTimer); + engine = new StandardJMeterEngine(); + engine.configure(cloner.getClonedTree()); + try { + engine.runTest(); + } catch (JMeterEngineException e) { + JOptionPane.showMessageDialog(gui.getMainFrame(), e.getMessage(), + JMeterUtils.getResString("error_occurred"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$ + } + log.debug("test plan after cloning and running test is running version: " + + ((TestPlan) testTree.getArray()[0]).isRunningVersion()); + } + + /** + * 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; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/StopStoppables.java b/src/core/org/apache/jmeter/gui/action/StopStoppables.java new file mode 100644 index 00000000000..9f7de47b05b --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/StopStoppables.java @@ -0,0 +1,76 @@ +/* + * 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.awt.event.ActionListener; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.Stoppable; + +/** + * Stops stopables (Proxy, Mirror) + * @since 2.5.1 + */ +public class StopStoppables extends AbstractAction implements ActionListener { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.STOP_THREAD); + } + + /** + * + */ + public StopStoppables() { + super(); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.action.AbstractAction#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + /* (non-Javadoc) + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + @Override + public void actionPerformed(ActionEvent e) { + + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.action.AbstractAction#doAction(java.awt.event.ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + GuiPackage instance = GuiPackage.getInstance(); + List stopables = instance.getStoppables(); + for (Stoppable element : stopables) { + instance.unregister(element); + element.stopServer(); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/TemplatesCommand.java b/src/core/org/apache/jmeter/gui/action/TemplatesCommand.java new file mode 100644 index 00000000000..d578cecfc44 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/TemplatesCommand.java @@ -0,0 +1,58 @@ +/* + * 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.HashSet; +import java.util.Set; + +/** + * Open Templates + * @since 2.10 + */ +public class TemplatesCommand extends AbstractAction { + + private static final Set commands = new HashSet(); + + // Ensure the dialog is only created when it is first needed + // In turn this avoids scanning the templates until first needed + static class IODH { + private static final SelectTemplatesDialog dialog = new SelectTemplatesDialog(); + } + + static { + commands.add(ActionNames.TEMPLATES); + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + IODH.dialog.setVisible(true); + } + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/ToolBar.java b/src/core/org/apache/jmeter/gui/action/ToolBar.java new file mode 100644 index 00000000000..fbead3e7472 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/ToolBar.java @@ -0,0 +1,69 @@ +/* + * 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.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; + +/** + * Hide / unhide toolbar. + * + */ +public class ToolBar implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.TOOLBAR); + } + + /** + * Constructor for object. + */ + public ToolBar() { + } + + /** + * Gets the ActionNames attribute of the action + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * This method performs the actual command processing. + * + * @param e + * the generic UI action event + */ + @Override + public void doAction(ActionEvent e) { + if (ActionNames.TOOLBAR.equals(e.getActionCommand())) { + GuiPackage guiInstance = GuiPackage.getInstance(); + final boolean isSelected = guiInstance.getMenuItemToolbar().getModel().isSelected(); + guiInstance.getMainToolbar().setVisible(isSelected); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/action/UndoCommand.java b/src/core/org/apache/jmeter/gui/action/UndoCommand.java new file mode 100644 index 00000000000..8f464d35069 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/UndoCommand.java @@ -0,0 +1,81 @@ +/* + * 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.HashSet; +import java.util.Set; + +import org.apache.jmeter.engine.TreeCloner; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jorphan.collections.HashTree; + +/** + * Menu command to serve Undo/Redo + * @since 2.12 + */ +public class UndoCommand implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.UNDO); + commands.add(ActionNames.REDO); + } + + @Override + public void doAction(ActionEvent e) throws IllegalUserActionException { + GuiPackage guiPackage = GuiPackage.getInstance(); + final String command = e.getActionCommand(); + + if (command.equals(ActionNames.UNDO)) { + guiPackage.goInHistory(-1); + } else if (command.equals(ActionNames.REDO)) { + guiPackage.goInHistory(1); + } else { + throw new IllegalArgumentException("Wrong action called: " + command); + } + } + + /** + * @return Set of all action names + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * wrapper to use package-visible method + * and clone tree for saving + * + * @param tree to be converted and cloned + * @return converted and cloned tree + */ + public static HashTree convertAndCloneSubTree(HashTree tree) { + Save executor = new Save(); + executor.convertSubTree(tree); + + // convert before clone + TreeCloner cloner = new TreeCloner(false); + tree.traverse(cloner); + return cloner.getClonedTree(); + } +} diff --git a/src/core/org/apache/jmeter/gui/action/What.java b/src/core/org/apache/jmeter/gui/action/What.java new file mode 100644 index 00000000000..e1310456371 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/What.java @@ -0,0 +1,89 @@ +/* + * 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.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.HeapDumper; +import org.apache.log.Logger; + +/** + * + * Debug class to show details of the currently selected object + * Currently shows TestElement and GUI class names + * + * Also enables/disables debug for the test element. + * + */ +public class What implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commandSet; + + static { + HashSet commands = new HashSet(); + commands.add(ActionNames.WHAT_CLASS); + commands.add(ActionNames.DEBUG_ON); + commands.add(ActionNames.DEBUG_OFF); + commands.add(ActionNames.HEAP_DUMP); + commandSet = Collections.unmodifiableSet(commands); + } + + + @Override + public void doAction(ActionEvent e) throws IllegalUserActionException { + JMeterTreeNode node= GuiPackage.getInstance().getTreeListener().getCurrentNode(); + TestElement te = (TestElement)node.getUserObject(); + if (ActionNames.WHAT_CLASS.equals(e.getActionCommand())){ + String guiClassName = te.getPropertyAsString(TestElement.GUI_CLASS); + System.out.println(te.getClass().getName()); + System.out.println(guiClassName); + log.info("TestElement:"+te.getClass().getName()+", guiClassName:"+guiClassName); + } else if (ActionNames.DEBUG_ON.equals(e.getActionCommand())){ + LoggingManager.setPriorityFullName("DEBUG",te.getClass().getName());//$NON-NLS-1$ + } else if (ActionNames.DEBUG_OFF.equals(e.getActionCommand())){ + LoggingManager.setPriorityFullName("INFO",te.getClass().getName());//$NON-NLS-1$ + } else if (ActionNames.HEAP_DUMP.equals(e.getActionCommand())){ + try { + String s = HeapDumper.dumpHeap(); + JOptionPane.showMessageDialog(null, "Created "+s, "HeapDump", JOptionPane.INFORMATION_MESSAGE); + } catch (Exception ex) { + JOptionPane.showMessageDialog(null, ex.toString(), "HeapDump", JOptionPane.ERROR_MESSAGE); + } + } + } + + /** + * Provide the list of Action names that are available in this command. + */ + @Override + public Set getActionNames() { + return commandSet; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/template/Template.java b/src/core/org/apache/jmeter/gui/action/template/Template.java new file mode 100644 index 00000000000..52a38d62346 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/template/Template.java @@ -0,0 +1,81 @@ +/* + * 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.template; + +import java.io.File; + +/** + * Template Bean + * @since 2.10 + */ +public class Template { + private boolean isTestPlan; + private String name; + private String fileName; + private String description; + private transient File parent; // for relative links + /** + * @return the name + */ + public String getName() { + return name; + } + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + /** + * @return the relativeFileName + */ + public String getFileName() { + return fileName; + } + /** + * @param relativeFileName the relativeFileName to set + */ + public void setFileName(String relativeFileName) { + fileName = relativeFileName; + } + /** + * @return the description + */ + public String getDescription() { + return description; + } + /** + * @param description the description to set + */ + public void setDescription(String description) { + this.description = description; + } + public boolean isTestPlan() { + return isTestPlan; + } + public void setTestPlan(boolean isTestPlan) { + this.isTestPlan = isTestPlan; + } + public File getParent() { + return parent; + } + public void setParent(File parent) { + this.parent = parent; + } +} diff --git a/src/core/org/apache/jmeter/gui/action/template/TemplateManager.java b/src/core/org/apache/jmeter/gui/action/template/TemplateManager.java new file mode 100644 index 00000000000..6c991b9cffc --- /dev/null +++ b/src/core/org/apache/jmeter/gui/action/template/TemplateManager.java @@ -0,0 +1,141 @@ +/* + * 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.template; + +import java.io.File; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; + +/** + * Manages Test Plan templates + * @since 2.10 + */ +public class TemplateManager { + // Created by XStream reading templates.xml + private static class Templates { + /* + * N.B. Must use LinkedHashMap for field type + * XStream creates a plain HashMap if one uses Map as the field type. + */ + private final LinkedHashMap templates = new LinkedHashMap(); + } + private static final String TEMPLATE_FILES = JMeterUtils.getPropDefault("template.files", // $NON-NLS-1$ + "/bin/templates/templates.xml"); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final TemplateManager SINGLETON = new TemplateManager(); + + private final Map allTemplates; + + private final XStream xstream = initXStream(); + + public static final TemplateManager getInstance() { + return SINGLETON; + } + + private TemplateManager() { + allTemplates = readTemplates(); + } + + private XStream initXStream() { + XStream xstream = new XStream(new DomDriver()); + xstream.alias("template", Template.class); + xstream.alias("templates", Templates.class); + xstream.useAttributeFor(Template.class, "isTestPlan"); + + // templates i + xstream.addImplicitMap(Templates.class, + // field TemplateManager#templates + "templates", // $NON-NLS-1$ + Template.class, + // field Template#name + "name" // $NON-NLS-1$ + ); + + return xstream; + } + + public void addTemplate(Template template) { + allTemplates.put(template.getName(), template); + } + + /** + * Resets the template Map by re-reading the template files. + * + * @return this + */ + public TemplateManager reset() { + allTemplates.clear(); + allTemplates.putAll(readTemplates()); + return this; + } + + /** + * @return the templates names + */ + public String[] getTemplateNames() { + return allTemplates.keySet().toArray(new String[allTemplates.size()]); + } + + private Map readTemplates() { + final Map temps = new LinkedHashMap(); + + final String[] templateFiles = TEMPLATE_FILES.split(","); + for (String templateFile : templateFiles) { + if(!StringUtils.isEmpty(templateFile)) { + final File f = new File(JMeterUtils.getJMeterHome(), templateFile); + try { + if(f.exists() && f.canRead()) { + log.info("Reading templates from:"+f.getAbsolutePath()); + final File parent = f.getParentFile(); + final LinkedHashMap templates = ((Templates) xstream.fromXML(f)).templates; + for(Template t : templates.values()) { + if (!t.getFileName().startsWith("/")) { + t.setParent(parent); + } + } + temps.putAll(templates); + } else { + log.warn("Ignoring template file:'"+f.getAbsolutePath()+"' as it does not exist or is not readable"); + } + } catch(Exception ex) { + log.warn("Ignoring template file:'"+f.getAbsolutePath()+"', an error occured parsing the file", ex); + } + } + } + return temps; + } + + /** + * @param selectedTemplate Template name + * @return {@link Template} + */ + public Template getTemplateByName(String selectedTemplate) { + return allTemplates.get(selectedTemplate); + } +} diff --git a/src/core/org/apache/jmeter/gui/plugin/MenuCreator.java b/src/core/org/apache/jmeter/gui/plugin/MenuCreator.java new file mode 100644 index 00000000000..a7768550566 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/plugin/MenuCreator.java @@ -0,0 +1,60 @@ +/* + * 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.plugin; + +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.MenuElement; + +/** + * @since 2.10 + */ +public interface MenuCreator { + public enum MENU_LOCATION { + FILE, + EDIT, + RUN, + OPTIONS, + HELP, + SEARCH + } + + /** + * MenuItems to be added in location menu + * @param location in top menu + * @return array of {@link JMenuItem} + */ + JMenuItem[] getMenuItemsAtLocation(MENU_LOCATION location); + + /** + * @return array of JMenu to be put as top level menu between Options and Help + */ + JMenu[] getTopLevelMenus(); + + /** + * @param menu MenuElement + * @return true if menu was concerned by Locale change + */ + boolean localeChanged(MenuElement menu); + + /** + * Update Top Level menu on Locale Change + */ + void localeChanged(); +} diff --git a/src/core/org/apache/jmeter/gui/tree/JMeterCellRenderer.java b/src/core/org/apache/jmeter/gui/tree/JMeterCellRenderer.java new file mode 100644 index 00000000000..e0a1a62e990 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/tree/JMeterCellRenderer.java @@ -0,0 +1,72 @@ +/* + * 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.tree; + +import java.awt.Color; +import java.awt.Component; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeCellRenderer; + +/** + * Class to render the test tree - sets the enabled/disabled versions of the icons + */ +public class JMeterCellRenderer extends DefaultTreeCellRenderer { + private static final long serialVersionUID = 240L; + + public JMeterCellRenderer() { + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, + boolean leaf, int row, boolean p_hasFocus) { + JMeterTreeNode node = (JMeterTreeNode) value; + super.getTreeCellRendererComponent(tree, node.getName(), sel, expanded, leaf, row, + p_hasFocus); + boolean enabled = node.isEnabled(); + ImageIcon ic = node.getIcon(enabled); + if (ic != null) { + if (enabled) { + setIcon(ic); + } else { + setDisabledIcon(ic); + } + } else { + if (!enabled)// i.e. no disabled icon found + { + // Must therefore set the enabled icon so there is at least some + // icon + ic = node.getIcon(); + if (ic != null) { + setIcon(ic); + } + } + } + this.setEnabled(enabled); + if(node.isMarkedBySearch()) { + setBorder(BorderFactory.createLineBorder(Color.red)); + } + else { + setBorder(null); + } + return this; + } +} diff --git a/src/core/org/apache/jmeter/gui/tree/JMeterTreeListener.java b/src/core/org/apache/jmeter/gui/tree/JMeterTreeListener.java new file mode 100644 index 00000000000..a34c9fde246 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/tree/JMeterTreeListener.java @@ -0,0 +1,247 @@ +/* + * 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.tree; + +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.JPopupMenu; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.MainFrame; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JMeterTreeListener implements TreeSelectionListener, MouseListener, KeyListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private TreePath currentPath; + + private ActionListener actionHandler; + + private JMeterTreeModel model; + + private JTree tree; + + /** + * Constructor for the JMeterTreeListener object. + * + * @param model + * The {@link JMeterTreeModel} for this listener + */ + public JMeterTreeListener(JMeterTreeModel model) { + this.model = model; + } + + /** + * Constructor for the {@link JMeterTreeListener} object + */ + public JMeterTreeListener() { + } + + /** + * Set the {@link JMeterTreeModel} for this listener + * @param m The {@link JMeterTreeModel} to be used + */ + public void setModel(JMeterTreeModel m) { + model = m; + } + + /** + * Sets the ActionHandler attribute of the JMeterTreeListener object. + * + * @param ah + * the new ActionHandler value + */ + public void setActionHandler(ActionListener ah) { + actionHandler = ah; + } + + /** + * Sets the JTree attribute of the JMeterTreeListener object. + * + * @param tree + * the new JTree value + */ + public void setJTree(JTree tree) { + this.tree = tree; + } + + /** + * Sets the EndWindow attribute of the JMeterTreeListener object. + * + * @param window + * the new EndWindow value + */ + public void setEndWindow(Container window) { + // endWindow = window; + } + + /** + * Gets the JTree attribute of the JMeterTreeListener object. + * + * @return tree the current JTree value. + */ + public JTree getJTree() { + return tree; + } + + /** + * Gets the CurrentNode attribute of the JMeterTreeListener object. + * + * @return the CurrentNode value + */ + public JMeterTreeNode getCurrentNode() { + if (currentPath != null) { + if (currentPath.getLastPathComponent() != null) { + return (JMeterTreeNode) currentPath.getLastPathComponent(); + } + return (JMeterTreeNode) currentPath.getParentPath().getLastPathComponent(); + } + return (JMeterTreeNode) model.getRoot(); + } + + public JMeterTreeNode[] getSelectedNodes() { + TreePath[] paths = tree.getSelectionPaths(); + if (paths == null) { + return new JMeterTreeNode[] { getCurrentNode() }; + } + JMeterTreeNode[] nodes = new JMeterTreeNode[paths.length]; + for (int i = 0; i < paths.length; i++) { + nodes[i] = (JMeterTreeNode) paths[i].getLastPathComponent(); + } + + return nodes; + } + + public TreePath removedSelectedNode() { + currentPath = currentPath.getParentPath(); + return currentPath; + } + + @Override + public void valueChanged(TreeSelectionEvent e) { + log.debug("value changed, updating currentPath"); + currentPath = e.getNewLeadSelectionPath(); + // Call requestFocusInWindow to ensure current component loses focus and + // all values are correctly saved + // see https://bz.apache.org/bugzilla/show_bug.cgi?id=55103 + // see https://bz.apache.org/bugzilla/show_bug.cgi?id=55459 + tree.requestFocusInWindow(); + actionHandler.actionPerformed(new ActionEvent(this, 3333, ActionNames.EDIT)); // $NON-NLS-1$ + } + + @Override + public void mouseClicked(MouseEvent ev) { + } + + @Override + public void mouseReleased(MouseEvent e) { + GuiPackage.getInstance().getMainFrame().repaint(); + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mousePressed(MouseEvent e) { + // Get the Main Frame. + MainFrame mainFrame = GuiPackage.getInstance().getMainFrame(); + // Close any Main Menu that is open + mainFrame.closeMenu(); + int selRow = tree.getRowForLocation(e.getX(), e.getY()); + if (tree.getPathForLocation(e.getX(), e.getY()) != null) { + log.debug("mouse pressed, updating currentPath"); + currentPath = tree.getPathForLocation(e.getX(), e.getY()); + } + if (selRow != -1) { + if (isRightClick(e)) { + if (tree.getSelectionCount() < 2) { + tree.setSelectionPath(currentPath); + } + log.debug("About to display pop-up"); + displayPopUp(e); + } + } + } + + @Override + public void mouseExited(MouseEvent ev) { + } + + @Override + public void keyPressed(KeyEvent e) { + String actionName = null; + + if (KeyStrokes.matches(e, KeyStrokes.COPY)) { + actionName = ActionNames.COPY; + } else if (KeyStrokes.matches(e, KeyStrokes.PASTE)) { + actionName = ActionNames.PASTE; + } else if (KeyStrokes.matches(e, KeyStrokes.CUT)) { + actionName = ActionNames.CUT; + } else if (KeyStrokes.matches(e, KeyStrokes.DUPLICATE)) { + actionName = ActionNames.DUPLICATE; + } else if (KeyStrokes.matches(e, KeyStrokes.ALT_UP_ARROW)) { + actionName = ActionNames.MOVE_UP; + } else if (KeyStrokes.matches(e, KeyStrokes.ALT_DOWN_ARROW)) { + actionName = ActionNames.MOVE_DOWN; + } else if (KeyStrokes.matches(e, KeyStrokes.ALT_LEFT_ARROW)) { + actionName = ActionNames.MOVE_LEFT; + } else if (KeyStrokes.matches(e, KeyStrokes.ALT_RIGHT_ARROW)) { + actionName = ActionNames.MOVE_RIGHT; + } + + if (actionName != null) { + final ActionRouter actionRouter = ActionRouter.getInstance(); + actionRouter.doActionNow(new ActionEvent(e.getSource(), e.getID(), actionName)); + e.consume(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyTyped(KeyEvent e) { + } + + private boolean isRightClick(MouseEvent e) { + return e.isPopupTrigger() || (InputEvent.BUTTON2_MASK & e.getModifiers()) > 0 || (InputEvent.BUTTON3_MASK == e.getModifiers()); + } + + private void displayPopUp(MouseEvent e) { + JPopupMenu pop = getCurrentNode().createPopupMenu(); + GuiPackage.getInstance().displayPopUp(e, pop); + } +} diff --git a/src/core/org/apache/jmeter/gui/tree/JMeterTreeModel.java b/src/core/org/apache/jmeter/gui/tree/JMeterTreeModel.java new file mode 100644 index 00000000000..0ecd843f3c8 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/tree/JMeterTreeModel.java @@ -0,0 +1,281 @@ +/* + * 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.tree; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.tree.DefaultTreeModel; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.control.gui.TestPlanGui; +import org.apache.jmeter.control.gui.WorkBenchGui; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; + +public class JMeterTreeModel extends DefaultTreeModel { + + private static final long serialVersionUID = 240L; + + public JMeterTreeModel(TestElement tp, TestElement wb) { + super(new JMeterTreeNode(wb, null)); + initTree(tp,wb); + } + + public JMeterTreeModel() { + this(new TestPlanGui().createTestElement(),new WorkBenchGui().createTestElement()); +// super(new JMeterTreeNode(new WorkBenchGui().createTestElement(), null)); +// TestElement tp = new TestPlanGui().createTestElement(); +// initTree(tp); + } + + /** + * Hack to allow TreeModel to be used in non-GUI and headless mode. + * + * @deprecated - only for use by JMeter class! + * @param o - dummy + */ + @Deprecated + public JMeterTreeModel(Object o) { + this(new TestPlan(),new WorkBench()); +// super(new JMeterTreeNode(new WorkBench(), null)); +// TestElement tp = new TestPlan(); +// initTree(tp, new WorkBench()); + } + + /** + * Returns a list of tree nodes that hold objects of the given class type. + * If none are found, an empty list is returned. + * @param type The type of nodes, which are to be collected + * @return a list of tree nodes of the given type, or an empty list + */ + public List getNodesOfType(Class type) { + List nodeList = new LinkedList(); + traverseAndFind(type, (JMeterTreeNode) this.getRoot(), nodeList); + return nodeList; + } + + /** + * Get the node for a given TestElement object. + * @param userObject The object to be found in this tree + * @return the node corresponding to the userObject + */ + public JMeterTreeNode getNodeOf(TestElement userObject) { + return traverseAndFind(userObject, (JMeterTreeNode) getRoot()); + } + + /** + * Adds the sub tree at the given node. Returns a boolean indicating whether + * the added sub tree was a full test plan. + * + * @param subTree + * The {@link HashTree} which is to be inserted into + * current + * @param current + * The node in which the subTree is to be inserted. + * Will be overridden, when an instance of {@link TestPlan} or + * {@link WorkBench} is found in the subtree. + * @return newly created sub tree now found at current + * @throws IllegalUserActionException + * when current is not an instance of + * {@link AbstractConfigGui} and no instance of {@link TestPlan} + * or {@link WorkBench} could be found in the + * subTree + */ + public HashTree addSubTree(HashTree subTree, JMeterTreeNode current) throws IllegalUserActionException { + Iterator iter = subTree.list().iterator(); + while (iter.hasNext()) { + TestElement item = (TestElement) iter.next(); + if (item instanceof TestPlan) { + TestPlan tp = (TestPlan) item; + current = (JMeterTreeNode) ((JMeterTreeNode) getRoot()).getChildAt(0); + final TestPlan userObject = (TestPlan) current.getUserObject(); + userObject.addTestElement(item); + userObject.setName(item.getName()); + userObject.setFunctionalMode(tp.isFunctionalMode()); + userObject.setSerialized(tp.isSerialized()); + addSubTree(subTree.getTree(item), current); + } else if (item instanceof WorkBench) { + current = (JMeterTreeNode) ((JMeterTreeNode) getRoot()).getChildAt(1); + final TestElement testElement = ((TestElement) current.getUserObject()); + testElement.addTestElement(item); + testElement.setName(item.getName()); + addSubTree(subTree.getTree(item), current); + } else { + addSubTree(subTree.getTree(item), addComponent(item, current)); + } + } + return getCurrentSubTree(current); + } + + /** + * Add a {@link TestElement} to a {@link JMeterTreeNode} + * @param component The {@link TestElement} to be used as data for the newly created note + * @param node The {@link JMeterTreeNode} into which the newly created node is to be inserted + * @return new {@link JMeterTreeNode} for the given component + * @throws IllegalUserActionException + * when the user object for the node is not an instance + * of {@link AbstractConfigGui} + */ + public JMeterTreeNode addComponent(TestElement component, JMeterTreeNode node) throws IllegalUserActionException { + if (node.getUserObject() instanceof AbstractConfigGui) { + throw new IllegalUserActionException("This node cannot hold sub-elements"); + } + + GuiPackage guiPackage = GuiPackage.getInstance(); + if (guiPackage != null) { + // The node can be added in non GUI mode at startup + guiPackage.updateCurrentNode(); + JMeterGUIComponent guicomp = guiPackage.getGui(component); + guicomp.configure(component); + guicomp.modifyTestElement(component); + guiPackage.getCurrentGui(); // put the gui object back + // to the way it was. + } + JMeterTreeNode newNode = new JMeterTreeNode(component, this); + + // This check the state of the TestElement and if returns false it + // disable the loaded node + try { + newNode.setEnabled(component.isEnabled()); + } catch (Exception e) { // TODO - can this ever happen? + newNode.setEnabled(true); + } + + this.insertNodeInto(newNode, node, node.getChildCount()); + return newNode; + } + + public void removeNodeFromParent(JMeterTreeNode node) { + if (!(node.getUserObject() instanceof TestPlan) && !(node.getUserObject() instanceof WorkBench)) { + super.removeNodeFromParent(node); + } + } + + private void traverseAndFind(Class type, JMeterTreeNode node, List nodeList) { + if (type.isInstance(node.getUserObject())) { + nodeList.add(node); + } + Enumeration enumNode = node.children(); + while (enumNode.hasMoreElements()) { + JMeterTreeNode child = enumNode.nextElement(); + traverseAndFind(type, child, nodeList); + } + } + + private JMeterTreeNode traverseAndFind(TestElement userObject, JMeterTreeNode node) { + if (userObject == node.getUserObject()) { + return node; + } + Enumeration enumNode = node.children(); + while (enumNode.hasMoreElements()) { + JMeterTreeNode child = enumNode.nextElement(); + JMeterTreeNode result = traverseAndFind(userObject, child); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Get the current sub tree for a {@link JMeterTreeNode} + * @param node The {@link JMeterTreeNode} from which the sub tree is to be taken + * @return newly copied sub tree + */ + public HashTree getCurrentSubTree(JMeterTreeNode node) { + ListedHashTree hashTree = new ListedHashTree(node); + Enumeration enumNode = node.children(); + while (enumNode.hasMoreElements()) { + JMeterTreeNode child = enumNode.nextElement(); + hashTree.add(node, getCurrentSubTree(child)); + } + return hashTree; + } + + /** + * Get the {@link TestPlan} from the root of this tree + * @return The {@link TestPlan} found at the root of this tree + */ + public HashTree getTestPlan() { + return getCurrentSubTree((JMeterTreeNode) ((JMeterTreeNode) this.getRoot()).getChildAt(0)); + } + + /** + * Get the {@link WorkBench} from the root of this tree + * @return The {@link WorkBench} found at the root of this tree + */ + public HashTree getWorkBench() { + return getCurrentSubTree((JMeterTreeNode) ((JMeterTreeNode) this.getRoot()).getChildAt(1)); + } + + /** + * Clear the test plan, and use default node for test plan and workbench. + * + * N.B. Should only be called by {@link GuiPackage#clearTestPlan()} + */ + public void clearTestPlan() { + TestElement tp = new TestPlanGui().createTestElement(); + clearTestPlan(tp); + } + + /** + * Clear the test plan, and use specified node for test plan and default node for workbench + * + * N.B. Should only be called by {@link GuiPackage#clearTestPlan(TestElement)} + * + * @param testPlan the node to use as the testplan top node + */ + public void clearTestPlan(TestElement testPlan) { + // Remove the workbench and testplan nodes + int children = getChildCount(getRoot()); + while (children > 0) { + JMeterTreeNode child = (JMeterTreeNode)getChild(getRoot(), 0); + super.removeNodeFromParent(child); + children = getChildCount(getRoot()); + } + // Init the tree + initTree(testPlan,new WorkBenchGui().createTestElement()); // Assumes this is only called from GUI mode + } + + /** + * Initialize the model with nodes for testplan and workbench. + * + * @param tp the element to use as testplan + * @param wb the element to use as workbench + */ + private void initTree(TestElement tp, TestElement wb) { + // Insert the test plan node + insertNodeInto(new JMeterTreeNode(tp, this), (JMeterTreeNode) getRoot(), 0); + // Insert the workbench node + insertNodeInto(new JMeterTreeNode(wb, this), (JMeterTreeNode) getRoot(), 1); + // Let others know that the tree content has changed. + // This should not be necessary, but without it, nodes are not shown when the user + // uses the Close menu item + nodeStructureChanged((JMeterTreeNode)getRoot()); + } +} diff --git a/src/core/org/apache/jmeter/gui/tree/JMeterTreeNode.java b/src/core/org/apache/jmeter/gui/tree/JMeterTreeNode.java new file mode 100644 index 00000000000..cd6f9c9bd27 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/tree/JMeterTreeNode.java @@ -0,0 +1,203 @@ +/* + * 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.tree; + +import java.awt.Image; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; + +import org.apache.jmeter.gui.GUIFactory; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JMeterTreeNode extends DefaultMutableTreeNode implements NamedTreeNode { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int TEST_PLAN_LEVEL = 1; + + // See Bug 54648 + private transient JMeterTreeModel treeModel; + + private boolean markedBySearch; + + public JMeterTreeNode() {// Allow serializable test to work + // TODO: is the serializable test necessary now that JMeterTreeNode is + // no longer a GUI component? + this(null, null); + } + + public JMeterTreeNode(TestElement userObj, JMeterTreeModel treeModel) { + super(userObj); + this.treeModel = treeModel; + } + + public boolean isEnabled() { + return getTestElement().isEnabled(); + } + + public void setEnabled(boolean enabled) { + getTestElement().setEnabled(enabled); + treeModel.nodeChanged(this); + } + + /** + * Return nodes to level 2 + * @return {@link List} of {@link JMeterTreeNode}s + */ + public List getPathToThreadGroup() { + List nodes = new ArrayList(); + if(treeModel != null) { + TreeNode[] nodesToRoot = treeModel.getPathToRoot(this); + for (TreeNode node : nodesToRoot) { + JMeterTreeNode jMeterTreeNode = (JMeterTreeNode) node; + int level = jMeterTreeNode.getLevel(); + if(level testClass = testElement.getClass(); + try { + Image img = Introspector.getBeanInfo(testClass).getIcon(BeanInfo.ICON_COLOR_16x16); + // If icon has not been defined, then use GUI_CLASS property + if (img == null) { + Object clazz = Introspector.getBeanInfo(testClass).getBeanDescriptor() + .getValue(TestElement.GUI_CLASS); + if (clazz == null) { + log.warn("getIcon(): Can't obtain GUI class from " + testClass.getName()); + return null; + } + return GUIFactory.getIcon(Class.forName((String) clazz), enabled); + } + return new ImageIcon(img); + } catch (IntrospectionException e1) { + log.error("Can't obtain icon for class "+testElement, e1); + throw new org.apache.jorphan.util.JMeterError(e1); + } + } + return GUIFactory.getIcon(Class.forName(testElement.getPropertyAsString(TestElement.GUI_CLASS)), + enabled); + } catch (ClassNotFoundException e) { + log.warn("Can't get icon for class " + testElement, e); + return null; + } + } + + public Collection getMenuCategories() { + try { + return GuiPackage.getInstance().getGui(getTestElement()).getMenuCategories(); + } catch (Exception e) { + log.error("Can't get popup menu for gui", e); + return null; + } + } + + public JPopupMenu createPopupMenu() { + try { + return GuiPackage.getInstance().getGui(getTestElement()).createPopupMenu(); + } catch (Exception e) { + log.error("Can't get popup menu for gui", e); + return null; + } + } + + public TestElement getTestElement() { + return (TestElement) getUserObject(); + } + + public String getStaticLabel() { + return GuiPackage.getInstance().getGui((TestElement) getUserObject()).getStaticLabel(); + } + + public String getDocAnchor() { + return GuiPackage.getInstance().getGui((TestElement) getUserObject()).getDocAnchor(); + } + + /** {@inheritDoc} */ + @Override + public void setName(String name) { + ((TestElement) getUserObject()).setName(name); + } + + /** {@inheritDoc} */ + @Override + public String getName() { + return ((TestElement) getUserObject()).getName(); + } + + /** {@inheritDoc} */ + @Override + public void nameChanged() { + if (treeModel != null) { // may be null during startup + treeModel.nodeChanged(this); + } + } + + // Override in order to provide type safety + @Override + @SuppressWarnings("unchecked") + public Enumeration children() { + return super.children(); + } +} diff --git a/src/core/org/apache/jmeter/gui/tree/JMeterTreeTransferHandler.java b/src/core/org/apache/jmeter/gui/tree/JMeterTreeTransferHandler.java new file mode 100644 index 00000000000..e117262dd31 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/tree/JMeterTreeTransferHandler.java @@ -0,0 +1,316 @@ +/* + * 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.tree; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.JTree; +import javax.swing.TransferHandler; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JMeterTreeTransferHandler extends TransferHandler { + + private static final long serialVersionUID = 8560957372186260765L; + + private static final Logger LOG = LoggingManager.getLoggerForClass(); + + private DataFlavor nodeFlavor; + private DataFlavor[] jMeterTreeNodeDataFlavors = new DataFlavor[1]; + + // hold the nodes that should be removed on drop + private List nodesForRemoval = null; + + public JMeterTreeTransferHandler() { + try { + // only allow a drag&drop inside the current jvm + String jvmLocalFlavor = DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + JMeterTreeNode[].class.getName() + "\""; + nodeFlavor = new DataFlavor(jvmLocalFlavor); + jMeterTreeNodeDataFlavors[0] = nodeFlavor; + } + catch (ClassNotFoundException e) { + LOG.error("Class Not Found", e); + } + } + + + @Override + public int getSourceActions(JComponent c) { + return MOVE; + } + + + @Override + protected Transferable createTransferable(JComponent c) { + this.nodesForRemoval = null; + JTree tree = (JTree) c; + TreePath[] paths = tree.getSelectionPaths(); + if (paths != null) { + + // sort the selected tree path by row + sortTreePathByRow(paths, tree); + + // if child and a parent are selected : only keep the parent + boolean[] toRemove = new boolean[paths.length]; + int size = paths.length; + for (int i = 0; i < paths.length; i++) { + for (int j = 0; j < paths.length; j++) { + if(i!=j && ((JMeterTreeNode)paths[i].getLastPathComponent()).isNodeAncestor((JMeterTreeNode)paths[j].getLastPathComponent())) { + toRemove[i] = true; + size--; + break; + } + } + } + + // remove unneeded nodes + JMeterTreeNode[] nodes = new JMeterTreeNode[size]; + size = 0; + for (int i = 0; i < paths.length; i++) { + if(!toRemove[i]) { + JMeterTreeNode node = (JMeterTreeNode) paths[i].getLastPathComponent(); + nodes[size++] = node; + } + } + + return new NodesTransferable(nodes); + } + + return null; + } + + + private static void sortTreePathByRow(TreePath[] paths, final JTree tree) { + Comparator cp = new Comparator() { + + @Override + public int compare(TreePath o1, TreePath o2) { + int row1 = tree.getRowForPath(o1); + int row2 = tree.getRowForPath(o2); + + return (row1 0 + && target.isNodeAncestor(node)) { + return false; + } + } + + // re-use node association logic + return MenuFactory.canAddTo(target, nodes); + } + + + @Override + public boolean importData(TransferHandler.TransferSupport support) { + if (!canImport(support)) { + return false; + } + + // deal with the jmx files + GuiPackage guiInstance = GuiPackage.getInstance(); + DataFlavor[] flavors = support.getDataFlavors(); + Transferable t = support.getTransferable(); + for (DataFlavor flavor : flavors) { + // Check for file lists specifically + if (flavor.isFlavorJavaFileListType()) { + try { + return guiInstance.getMainFrame().openJmxFilesFromDragAndDrop(t); + } + catch (Exception e) { + LOG.error("Drop file failed", e); + } + return false; + } + } + + // Extract transfer data. + JMeterTreeNode[] nodes = getDraggedNodes(t); + + if(nodes == null || nodes.length == 0) { + return false; + } + + // Get drop location and mode + JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation(); + TreePath dest = dl.getPath(); + JMeterTreeNode target = (JMeterTreeNode) dest.getLastPathComponent(); + + nodesForRemoval = new ArrayList(); + int index = dl.getChildIndex(); + TreePath[] pathsToSelect = new TreePath[nodes.length]; + int pathPosition = 0; + JMeterTreeModel treeModel = guiInstance.getTreeModel(); + for (JMeterTreeNode node : nodes) { + + if (index == -1) { // drop mode == DropMode.ON + index = target.getChildCount(); + } + + // Insert a clone of the node, the original one will be removed by the exportDone method + // the children are not cloned but moved to the cloned node + // working on the original node would be harder as + // you'll have to deal with the insertion index offset if you re-order a node inside a parent + JMeterTreeNode copy = (JMeterTreeNode) node.clone(); + + // first copy the children as the call to copy.add will modify the collection we're iterating on + Enumeration enumFrom = node.children(); + List tmp = new ArrayList(); + while (enumFrom.hasMoreElements()) { + JMeterTreeNode child = (JMeterTreeNode) enumFrom.nextElement(); + tmp.add(child); + } + + for (JMeterTreeNode jMeterTreeNode : tmp) { + copy.add(jMeterTreeNode); + } + treeModel.insertNodeInto(copy, target, index++); + nodesForRemoval.add(node); + pathsToSelect[pathPosition++] = new TreePath(treeModel.getPathToRoot(copy)); + } + + TreePath treePath = new TreePath(target.getPath()); + // expand the destination node + JTree tree = (JTree) support.getComponent(); + tree.expandPath(treePath); + tree.setSelectionPaths(pathsToSelect); + return true; + } + + + private JMeterTreeNode[] getDraggedNodes(Transferable t) { + JMeterTreeNode[] nodes = null; + try { + nodes = (JMeterTreeNode[]) t.getTransferData(nodeFlavor); + } + catch (Exception e) { + LOG.error("Unsupported Flavor in Transferable", e); + } + return nodes; + } + + + private class NodesTransferable implements Transferable { + JMeterTreeNode[] nodes; + + public NodesTransferable(JMeterTreeNode[] nodes) { + this.nodes = nodes; + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) { + throw new UnsupportedFlavorException(flavor); + } + return nodes; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return jMeterTreeNodeDataFlavors; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return nodeFlavor.equals(flavor); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/tree/NamedTreeNode.java b/src/core/org/apache/jmeter/gui/tree/NamedTreeNode.java new file mode 100644 index 00000000000..7bc01c73bc9 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/tree/NamedTreeNode.java @@ -0,0 +1,26 @@ +/* + * 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.tree; + +public interface NamedTreeNode { + + void setName(String name); + String getName(); + void nameChanged(); +} diff --git a/src/core/org/apache/jmeter/gui/util/ButtonPanel.java b/src/core/org/apache/jmeter/gui/util/ButtonPanel.java new file mode 100644 index 00000000000..8a20349aa56 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/ButtonPanel.java @@ -0,0 +1,125 @@ +/* + * 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.util; + +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JPanel; + +import org.apache.jmeter.util.JMeterUtils; + +// TODO - does not appear to be used + +public class ButtonPanel extends JPanel { + private static final long serialVersionUID = 240L; + + public static final int ADD_BUTTON = 1; + + public static final int EDIT_BUTTON = 2; + + public static final int DELETE_BUTTON = 3; + + public static final int LOAD_BUTTON = 4; + + public static final int SAVE_BUTTON = 5; + + private JButton add, delete, edit, load, save; + + public ButtonPanel() { + init(); + } + + public void addButtonListener(int button, ActionListener listener) { + switch (button) { + case ADD_BUTTON: + add.addActionListener(listener); + break; + case EDIT_BUTTON: + edit.addActionListener(listener); + break; + case DELETE_BUTTON: + delete.addActionListener(listener); + break; + case LOAD_BUTTON: + load.addActionListener(listener); + break; + case SAVE_BUTTON: + save.addActionListener(listener); + break; + default: + throw new IllegalStateException("Unexpected button id: " + button); + } + } + + /* + * NOTUSED private void initButtonMap() { } + */ + private void init() { + add = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + add.setActionCommand("Add"); + edit = new JButton(JMeterUtils.getResString("edit")); // $NON-NLS-1$ + edit.setActionCommand("Edit"); + delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + delete.setActionCommand("Delete"); + load = new JButton(JMeterUtils.getResString("load")); // $NON-NLS-1$ + load.setActionCommand("Load"); + save = new JButton(JMeterUtils.getResString("save")); // $NON-NLS-1$ + save.setActionCommand("Save"); + Dimension d = delete.getPreferredSize(); + add.setPreferredSize(d); + edit.setPreferredSize(d); + // close.setPreferredSize(d); + load.setPreferredSize(d); + save.setPreferredSize(d); + GridBagLayout g = new GridBagLayout(); + this.setLayout(g); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.gridwidth = 1; + c.gridheight = 1; + c.gridx = 1; + c.gridy = 1; + g.setConstraints(add, c); + this.add(add); + c.gridx = 2; + c.gridy = 1; + g.setConstraints(edit, c); + this.add(edit); + c.gridx = 3; + c.gridy = 1; + g.setConstraints(delete, c); + this.add(delete); + /* + * c.gridx = 1; c.gridy = 2; g.setConstraints(close, c); + * panel.add(close); + */ + c.gridx = 2; + c.gridy = 2; + g.setConstraints(load, c); + this.add(load); + c.gridx = 3; + c.gridy = 2; + g.setConstraints(save, c); + this.add(save); + } +} diff --git a/src/core/org/apache/jmeter/gui/util/EscapeDialog.java b/src/core/org/apache/jmeter/gui/util/EscapeDialog.java new file mode 100644 index 00000000000..b9fef679414 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/EscapeDialog.java @@ -0,0 +1,64 @@ +/* + * 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.util; + +import java.awt.Frame; +import java.awt.event.ActionEvent; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JRootPane; + +import org.apache.jmeter.gui.action.KeyStrokes; + +public class EscapeDialog extends JDialog { + + private static final long serialVersionUID = 1319421816741139938L; + + public EscapeDialog() { + super(); + } + + public EscapeDialog(Frame frame, String title, boolean modal) { + super(frame, title, modal); + } + + @Override + protected JRootPane createRootPane() { + JRootPane rootPane = new JRootPane(); + Action escapeAction = new AbstractAction("ESCAPE") { + /** + * + */ + private static final long serialVersionUID = 2208129319916921772L; + + @Override + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + }; + InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + inputMap.put(KeyStrokes.ESC, escapeAction.getValue(Action.NAME)); + rootPane.getActionMap().put(escapeAction.getValue(Action.NAME), escapeAction); + return rootPane; + } +} diff --git a/src/core/org/apache/jmeter/gui/util/FileDialoger.java b/src/core/org/apache/jmeter/gui/util/FileDialoger.java new file mode 100644 index 00000000000..88fc4186d52 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/FileDialoger.java @@ -0,0 +1,231 @@ +/* + * 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.util; + +import java.io.File; + +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileFilter; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterFileFilter; + +/** + * Class implementing a file open dialogue + */ +public final class FileDialoger { + /** + * The last directory visited by the user while choosing Files. + */ + private static String lastJFCDirectory = null; + + private static JFileChooser jfc = new JFileChooser(); + + /** + * Prevent instantiation of utility class. + */ + private FileDialoger() { + } + + /** + * Prompts the user to choose a file from their filesystems for our own + * devious uses. This method maintains the last directory the user visited + * before dismissing the dialog. This does NOT imply they actually chose a + * file from that directory, only that they closed the dialog there. It is + * the caller's responsibility to check to see if the selected file is + * non-null. + * + * @return the JFileChooser that interacted with the user, after they are + * finished using it - null if no file was chosen + */ + public static JFileChooser promptToOpenFile() { + return promptToOpenFile((String)null); + } + + /** + * Prompts the user to choose a file from their filesystems for our own + * devious uses. This method maintains the last directory the user visited + * before dismissing the dialog. This does NOT imply they actually chose a + * file from that directory, only that they closed the dialog there. It is + * the caller's responsibility to check to see if the selected file is + * non-null. + * @param existingFileName The name of a file with path. If the filename points + * to an existing file, the directory in which it lies, will be used + * as the starting point for the returned JFileChooser. + * + * @return the JFileChooser that interacted with the user, after they are + * finished using it - null if no file was chosen + */ + public static JFileChooser promptToOpenFile(String existingFileName) { + return promptToOpenFile(new String[0], existingFileName); + } + + /** + * Prompts the user to choose a file from their filesystems for our own + * devious uses. This method maintains the last directory the user visited + * before dismissing the dialog. This does NOT imply they actually chose a + * file from that directory, only that they closed the dialog there. It is + * the caller's responsibility to check to see if the selected file is + * non-null. + * @param exts The list of allowed file extensions. If empty, any + * file extension is allowed + * + * @return the JFileChooser that interacted with the user, after they are + * finished using it - null if no file was chosen + */ + public static JFileChooser promptToOpenFile(String[] exts) { + return promptToOpenFile(exts, null); + } + + /** + * Prompts the user to choose a file from their filesystems for our own + * devious uses. This method maintains the last directory the user visited + * before dismissing the dialog. This does NOT imply they actually chose a + * file from that directory, only that they closed the dialog there. It is + * the caller's responsibility to check to see if the selected file is + * non-null. + * @param exts The list of allowed file extensions. If empty, any + * file extension is allowed + * @param existingFileName The name of a file with path. If the filename points + * to an existing file, the directory in which it lies, will be used + * as the starting point for the returned JFileChooser. + * + * @return the JFileChooser that interacted with the user, after they are + * finished using it - null if no file was chosen + */ + public static JFileChooser promptToOpenFile(String[] exts, String existingFileName) { + // JFileChooser jfc = null; + if(!StringUtils.isEmpty(existingFileName)) { + File existingFileStart = new File(existingFileName); + if(existingFileStart.exists() && existingFileStart.canRead()) { + jfc.setCurrentDirectory(new File(existingFileName)); + } + } + else if (lastJFCDirectory == null) { + String start = System.getProperty("user.dir", ""); //$NON-NLS-1$//$NON-NLS-2$ + + if (start.length() > 0) { + jfc.setCurrentDirectory(new File(start)); + } + } + clearFileFilters(); + if(exts != null && exts.length > 0) { + JMeterFileFilter currentFilter = new JMeterFileFilter(exts); + jfc.addChoosableFileFilter(currentFilter); + jfc.setAcceptAllFileFilterUsed(true); + jfc.setFileFilter(currentFilter); + } + if(lastJFCDirectory==null) { + lastJFCDirectory = System.getProperty("user.dir", ""); //$NON-NLS-1$//$NON-NLS-2$ + } + jfc.setCurrentDirectory(new File(lastJFCDirectory)); + int retVal = jfc.showOpenDialog(GuiPackage.getInstance().getMainFrame()); + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + + if (retVal == JFileChooser.APPROVE_OPTION) { + return jfc; + } + return null; + } + + private static void clearFileFilters() { + FileFilter[] filters = jfc.getChoosableFileFilters(); + for (FileFilter filter : filters) { + jfc.removeChoosableFileFilter(filter); + } + } + + /** + * Prompts the user to choose a file from their filesystems for our own + * devious uses. This method maintains the last directory the user visited + * before dismissing the dialog. This does NOT imply they actually chose a + * file from that directory, only that they closed the dialog there. It is + * the caller's responsibility to check to see if the selected file is + * non-null. + * @param filename The name of a file with path. If the filename points + * to an existing file, the directory in which it lies, will be used + * as the starting point for the returned JFileChooser. + * + * @return the JFileChooser that interacted with the user, after they are + * finished using it - null if no file was chosen + * @see #promptToOpenFile() + */ + public static JFileChooser promptToSaveFile(String filename) { + return promptToSaveFile(filename, null); + } + + /** + * Get a JFileChooser with a new FileFilter. + * + * @param filename file name + * @param extensions list of extensions + * @return the FileChooser - null if no file was chosen + */ + public static JFileChooser promptToSaveFile(String filename, String[] extensions) { + if (lastJFCDirectory == null) { + String start = System.getProperty("user.dir", "");//$NON-NLS-1$//$NON-NLS-2$ + if (start.length() > 0) { + jfc = new JFileChooser(new File(start)); + } + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + } + String ext = ".jmx";//$NON-NLS-1$ + if (filename != null) { + jfc.setDialogTitle(filename); + jfc.setSelectedFile(filename.lastIndexOf(File.separator) > 0 ? + new File(filename) : + new File(lastJFCDirectory, filename)); + int i = -1; + if ((i = filename.lastIndexOf('.')) > -1) {//$NON-NLS-1$ + ext = filename.substring(i); + } + } + clearFileFilters(); + if (extensions != null) { + jfc.addChoosableFileFilter(new JMeterFileFilter(extensions)); + } else { + jfc.addChoosableFileFilter(new JMeterFileFilter(new String[] { ext })); + } + + int retVal = jfc.showSaveDialog(GuiPackage.getInstance().getMainFrame()); + jfc.setDialogTitle(null); + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + if (retVal == JFileChooser.APPROVE_OPTION) { + return jfc; + } + return null; + } + + /** + * + * @return The last directory visited by the user while choosing Files + */ + public static String getLastJFCDirectory() { + return lastJFCDirectory; + } + + /** + * + * @param lastJFCDirectory The last directory visited by the user while choosing Files + */ + public static void setLastJFCDirectory(String lastJFCDirectory) { + FileDialoger.lastJFCDirectory = lastJFCDirectory; + } +} diff --git a/src/core/org/apache/jmeter/gui/util/FileListPanel.java b/src/core/org/apache/jmeter/gui/util/FileListPanel.java new file mode 100644 index 00000000000..f7e5ab0d060 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/FileListPanel.java @@ -0,0 +1,221 @@ +/* + * 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.util; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterFileFilter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +public class FileListPanel extends JPanel implements ActionListener { + + private static final long serialVersionUID = 1L; + + private JTable files = null; + + private transient ObjectTableModel tableModel = null; + + private static final String ACTION_BROWSE = "browse"; // $NON-NLS-1$ + + private static final String LABEL_LIBRARY = "library"; // $NON-NLS-1$ + + private JButton browse = new JButton(JMeterUtils.getResString(ACTION_BROWSE)); + + private JButton clear = new JButton(JMeterUtils.getResString("clear")); // $NON-NLS-1$ + + private JButton delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + + private List listeners = new LinkedList(); + + private String title; + + private String filetype; + + /** + * Constructor for the FilePanel object. + */ + public FileListPanel() { + title = ""; // $NON-NLS-1$ + init(); + } + + public FileListPanel(String title) { + this.title = title; + init(); + } + + public FileListPanel(String title, String filetype) { + this.title = title; + this.filetype = filetype; + init(); + } + + /** + * Constructor for the FilePanel object. + * @param l The changelistener for this panel + * @param title The title of this panel + */ + public FileListPanel(ChangeListener l, String title) { + this.title = title; + init(); + listeners.add(l); + } + + public void addChangeListener(ChangeListener l) { + listeners.add(l); + } + + private void init() { + this.setLayout(new BorderLayout(0, 5)); + setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 5)); + JLabel jtitle = new JLabel(title); + + HorizontalPanel buttons = new HorizontalPanel(); + buttons.add(jtitle); + buttons.add(browse); + buttons.add(delete); + buttons.add(clear); + add(buttons,BorderLayout.NORTH); + + this.initializeTableModel(); + files = new JTable(tableModel); + files.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + files.revalidate(); + + JScrollPane scrollpane = new JScrollPane(files); + scrollpane.setPreferredSize(new Dimension(100,80)); + add(scrollpane,BorderLayout.CENTER); + + browse.setActionCommand(ACTION_BROWSE); // $NON-NLS-1$ + browse.addActionListener(this); + clear.addActionListener(this); + delete.addActionListener(this); + //this.setPreferredSize(new Dimension(400,150)); + } + + /** + * If the gui needs to enable/disable the FilePanel, call the method. + * + * @param enable Flag whether FilePanel should be enabled + */ + public void enableFile(boolean enable) { + browse.setEnabled(enable); + files.setEnabled(false); + } + + /** + * Add a single file to the table + * @param f The name of the file to be added + */ + public void addFilename(String f) { + tableModel.addRow(f); + } + + /** + * clear the files from the table + */ + public void clearFiles() { + tableModel.clearData(); + } + + public void setFiles(String[] files) { + this.clearFiles(); + for (String file : files) { + addFilename(file); + } + } + + public String[] getFiles() { + String[] _files = new String[tableModel.getRowCount()]; + for (int idx=0; idx < _files.length; idx++) { + _files[idx] = (String)tableModel.getValueAt(idx,0); + } + return _files; + } + + protected void deleteFile() { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + + int rowSelected = files.getSelectedRow(); + if (rowSelected >= 0) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + } + } + + private void fireFileChanged() { + for (ChangeListener cl : listeners) { + cl.stateChanged(new ChangeEvent(this)); + } + } + + protected void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { JMeterUtils.getResString(LABEL_LIBRARY) }, + new Functor[0] , new Functor[0] , // i.e. bypass the Functors + new Class[] { String.class }); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == clear) { + this.clearFiles(); + } else if (e.getActionCommand().equals(ACTION_BROWSE)) { + JFileChooser chooser = new JFileChooser(); + String start = System.getProperty("user.dir", ""); // $NON-NLS-1$ // $NON-NLS-2$ + chooser.setCurrentDirectory(new File(start)); + chooser.setFileFilter(new JMeterFileFilter(new String[] { filetype })); + chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + chooser.setMultiSelectionEnabled(true); + chooser.showOpenDialog(GuiPackage.getInstance().getMainFrame()); + File[] cfiles = chooser.getSelectedFiles(); + if (cfiles != null) { + for (int idx=0; idx < cfiles.length; idx++) { + this.addFilename(cfiles[idx].getPath()); + } + fireFileChanged(); + } + } else if (e.getSource() == delete) { + this.deleteFile(); + } else { + fireFileChanged(); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/FilePanel.java b/src/core/org/apache/jmeter/gui/util/FilePanel.java new file mode 100644 index 00000000000..eb5fad28229 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/FilePanel.java @@ -0,0 +1,61 @@ +/* + * 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.util; + +import javax.swing.BorderFactory; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.util.JMeterUtils; + +public class FilePanel extends FilePanelEntry { + private static final long serialVersionUID = 240L; + + private final String title; + + public FilePanel() { + this("", (String) null); + } + + public FilePanel(String title) { + this(title, (String) null); + } + + public FilePanel(String title, String filetype) { + super(JMeterUtils.getResString("file_visualizer_filename"), filetype); // $NON-NLS-1$ + this.title = title; + init(); + } + + public FilePanel(ChangeListener l, String title) { + super(JMeterUtils.getResString("file_visualizer_filename"), l); // $NON-NLS-1$ + this.title = title; + init(); + } + + public FilePanel(String resString, String[] exts) { + super(JMeterUtils.getResString("file_visualizer_filename"), exts); // $NON-NLS-1$ + title = resString; + init(); + } + + private void init() { + setBorder(BorderFactory.createTitledBorder(title)); + } + +} diff --git a/src/core/org/apache/jmeter/gui/util/FilePanelEntry.java b/src/core/org/apache/jmeter/gui/util/FilePanelEntry.java new file mode 100644 index 00000000000..2597ac9fbf3 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/FilePanelEntry.java @@ -0,0 +1,151 @@ +/* + * 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.util; + +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.util.JMeterUtils; + +public class FilePanelEntry extends HorizontalPanel implements ActionListener { + private static final long serialVersionUID = 280L; + + private final Font FONT_SMALL = new Font("SansSerif", Font.PLAIN, 10); //$NON-NLS-1$ + + private final JTextField filename = new JTextField(10); + + private final JLabel label; + + private final JButton browse = new JButton(JMeterUtils.getResString("browse")); //$NON-NLS-1$ + + private static final String ACTION_BROWSE = "browse"; //$NON-NLS-1$ + + private final List listeners = new LinkedList(); + + private final String[] filetypes; + + // Mainly needed for unit test Serialisable tests + public FilePanelEntry() { + this(JMeterUtils.getResString("file_visualizer_filename")); //$NON-NLS-1$ + } + + public FilePanelEntry(String label) { + this(label, (ChangeListener) null); + } + + public FilePanelEntry(String label, String ... exts) { + this(label, (ChangeListener) null, exts); + } + + public FilePanelEntry(String label, ChangeListener listener, String ... exts) { + this.label = new JLabel(label); + if (listener != null) { + listeners.add(listener); + } + if (exts != null) { + this.filetypes = new String[exts.length]; + System.arraycopy(exts, 0, this.filetypes, 0, exts.length); + } else { + this.filetypes = null; + } + init(); + } + + public final void addChangeListener(ChangeListener l) { + listeners.add(l); + } + + private void init() { + add(label); + add(filename); + filename.addActionListener(this); + browse.setFont(FONT_SMALL); + add(browse); + browse.setActionCommand(ACTION_BROWSE); + browse.addActionListener(this); + + } + + public void clearGui(){ + filename.setText(""); // $NON-NLS-1$ + } + + /** + * If the gui needs to enable/disable the FilePanel, call the method. + * + * @param enable The Flag whether the {@link FilePanel} should be enabled + */ + public void enableFile(boolean enable) { + browse.setEnabled(enable); + filename.setEnabled(enable); + } + + /** + * Gets the filename attribute of the FilePanel object. + * + * @return the filename value + */ + public String getFilename() { + return filename.getText(); + } + + /** + * Sets the filename attribute of the FilePanel object. + * + * @param f + * the new filename value + */ + public void setFilename(String f) { + filename.setText(f); + } + + private void fireFileChanged() { + for (ChangeListener cl : listeners) { + cl.stateChanged(new ChangeEvent(this)); + } + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals(ACTION_BROWSE)) { + JFileChooser chooser; + if(filetypes == null || filetypes.length == 0){ + chooser = FileDialoger.promptToOpenFile(filename.getText()); + } else { + chooser = FileDialoger.promptToOpenFile(filetypes, filename.getText()); + } + if (chooser != null && chooser.getSelectedFile() != null) { + filename.setText(chooser.getSelectedFile().getPath()); + fireFileChanged(); + } + } else { + fireFileChanged(); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/FocusRequester.java b/src/core/org/apache/jmeter/gui/util/FocusRequester.java new file mode 100644 index 00000000000..adcd7770c81 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/FocusRequester.java @@ -0,0 +1,40 @@ +/* + * 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.util; + +import java.awt.Component; + +import javax.swing.SwingUtilities; + +/* + * Note: This helper class appeared in JavaWorld in June 2001 + * (http://www.javaworld.com) and was originally written by Michael Daconta. + * It has since been simplified. + */ +public class FocusRequester { + + public static void requestFocus(final Component comp) { + SwingUtilities.invokeLater(new Runnable(){ + @Override + public void run() { + comp.requestFocusInWindow(); + } + }); + } +} diff --git a/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java b/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java new file mode 100644 index 00000000000..925308caba8 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java @@ -0,0 +1,90 @@ +/* + * 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.util; + +import java.awt.Component; +import java.text.MessageFormat; + +import javax.swing.JTable; +import javax.swing.SwingConstants; +import javax.swing.UIManager; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.JTableHeader; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Renders items in a JTable by converting from resource names. + */ +public class HeaderAsPropertyRenderer extends DefaultTableCellRenderer { + + private static final long serialVersionUID = 240L; + private Object[][] columnsMsgParameters; + + /** + * + */ + public HeaderAsPropertyRenderer() { + this(null); + } + + /** + * @param columnsMsgParameters Optional parameters of i18n keys + */ + public HeaderAsPropertyRenderer(Object[][] columnsMsgParameters) { + super(); + this.columnsMsgParameters = columnsMsgParameters; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) { + if (table != null) { + JTableHeader header = table.getTableHeader(); + if (header != null){ + setForeground(header.getForeground()); + setBackground(header.getBackground()); + setFont(header.getFont()); + } + setText(getText(value, row, column)); + setBorder(UIManager.getBorder("TableHeader.cellBorder")); + setHorizontalAlignment(SwingConstants.CENTER); + } + return this; + } + + /** + * Get the text for the value as the translation of the resource name. + * + * @param value value for which to get the translation + * @param column index which column message parameters should be used + * @param row not used + * @return the text + */ + protected String getText(Object value, int row, int column) { + if (value == null){ + return ""; + } + if(columnsMsgParameters != null && columnsMsgParameters[column] != null) { + return MessageFormat.format(JMeterUtils.getResString(value.toString()), columnsMsgParameters[column]); + } else { + return JMeterUtils.getResString(value.toString()); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/HorizontalPanel.java b/src/core/org/apache/jmeter/gui/util/HorizontalPanel.java new file mode 100644 index 00000000000..b5fdbd0d16b --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/HorizontalPanel.java @@ -0,0 +1,75 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 25, 2003 + * + */ +package org.apache.jmeter.gui.util; + +import java.awt.Color; +import java.awt.BorderLayout; +import java.awt.Component; + +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JPanel; + +public class HorizontalPanel extends JPanel { + private static final long serialVersionUID = 240L; + + private final Box subPanel = Box.createHorizontalBox(); + + private final float verticalAlign; + + private final int hgap; + + public HorizontalPanel() { + this(5, CENTER_ALIGNMENT); + } + + public HorizontalPanel(Color bk) { + this(); + subPanel.setBackground(bk); + this.setBackground(bk); + } + + public HorizontalPanel(int hgap, float verticalAlign) { + super(new BorderLayout()); + add(subPanel, BorderLayout.CENTER); + this.hgap = hgap; + this.verticalAlign = verticalAlign; + } + + /** + * {@inheritDoc} + */ + @Override + public Component add(Component c) { + // This won't work right if we remove components. But we don't, so I'm + // not going to worry about it right now. + if (hgap > 0 && subPanel.getComponentCount() > 0) { + subPanel.add(Box.createHorizontalStrut(hgap)); + } + + if (c instanceof JComponent) { + ((JComponent) c).setAlignmentY(verticalAlign); + } + return subPanel.add(c); + } +} diff --git a/src/core/org/apache/jmeter/gui/util/IconToolbarBean.java b/src/core/org/apache/jmeter/gui/util/IconToolbarBean.java new file mode 100644 index 00000000000..da901ebad6a --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/IconToolbarBean.java @@ -0,0 +1,103 @@ +/* + * 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.util; + +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public final class IconToolbarBean { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ICON_FIELD_SEP = ","; //$NON-NLS-1$ + + private final String i18nKey; + + private final String actionName; + + private final String iconPath; + + private final String iconPathPressed; + + /** + * Constructor to transform a line value (from icon set file) to a icon bean for toolbar. + * @param strToSplit - the line value (i18n key, ActionNames ID, icon path, optional icon pressed path) + * @throws JMeterException if error in parsing. + */ + IconToolbarBean(final String strToSplit, final String iconSize) throws IllegalArgumentException { + if (strToSplit == null) { + throw new IllegalArgumentException("Icon definition must not be null"); //$NON-NLS-1$ + } + final String tmp[] = strToSplit.split(ICON_FIELD_SEP); + if (tmp.length > 2) { + this.i18nKey = tmp[0]; + this.actionName = tmp[1]; + this.iconPath = tmp[2].replace("", iconSize); //$NON-NLS-1$ + this.iconPathPressed = (tmp.length > 3) ? tmp[3].replace("", iconSize) : this.iconPath; //$NON-NLS-1$ + } else { + throw new IllegalArgumentException("Incorrect argument format - expected at least 2 fields separated by " + ICON_FIELD_SEP); + } + } + + /** + * Resolve action name ID declared in icon set file to ActionNames value + * @return the resolve actionName + */ + public String getActionNameResolve() { + final String aName; + try { + aName = (String) (ActionNames.class.getField(this.actionName).get(null)); + } catch (Exception e) { + log.warn("Toolbar icon Action names error: " + this.actionName + ", use unknown action."); //$NON-NLS-1$ + return this.actionName; // return unknown action names for display error msg + } + return aName; + } + + /** + * @return the i18nKey + */ + public String getI18nKey() { + return i18nKey; + } + + /** + * @return the actionName + */ + public String getActionName() { + return actionName; + } + + /** + * @return the iconPath + */ + public String getIconPath() { + return iconPath; + } + + /** + * @return the iconPathPressed + */ + public String getIconPathPressed() { + return iconPathPressed; + } + +} diff --git a/src/core/org/apache/jmeter/gui/util/JDateField.java b/src/core/org/apache/jmeter/gui/util/JDateField.java new file mode 100644 index 00000000000..f4405369e21 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/JDateField.java @@ -0,0 +1,211 @@ +/* + * 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.util; + +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +import javax.swing.JTextField; + +/** + * This is Date mask control. Using this control we can pop up our date in the + * text field. And this control is Devloped basically for JDK1.3 and lower + * version support. This control is similer to JSpinner control this is + * available in JDK1.4 and above only. + *

+ * This will set the date "yyyy/MM/dd HH:mm:ss" in this format only. + *

+ * + */ +public class JDateField extends JTextField { + + private static final long serialVersionUID = 240L; + + // Datefields are not thread-safe + private final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // $NON-NLS-1$ + + /* + * The following array must agree with dateFormat + * + * It is used to translate the positions in the buffer to the values used by + * the Calendar class for the field id. + * + * Current format: MM/DD/YYYY HH:MM:SS 01234567890123456789 ^buffer + * positions + */ + private static final int fieldPositions[] = { + Calendar.YEAR, // Y + Calendar.YEAR, // Y + Calendar.YEAR, // Y + Calendar.YEAR, // Y + Calendar.YEAR, // sp + Calendar.MONTH, // M + Calendar.MONTH, // M + Calendar.MONTH, // / + Calendar.DAY_OF_MONTH, // D + Calendar.DAY_OF_MONTH, // D + Calendar.DAY_OF_MONTH, // / + Calendar.HOUR_OF_DAY, // H + Calendar.HOUR_OF_DAY, // H + Calendar.HOUR_OF_DAY, // : + Calendar.MINUTE, // M + Calendar.MINUTE, // M + Calendar.MINUTE, // : + Calendar.SECOND, // S + Calendar.SECOND, // S + Calendar.SECOND // end + }; + + /** + * Create a DateField with the specified date. + * + * @param date + * The {@link Date} to be used + */ + public JDateField(Date date) { + super(20); + this.addKeyListener(new KeyFocus()); + this.addFocusListener(new FocusClass()); + String myString = dateFormat.format(date); + setText(myString); + } + + // Dummy constructor to allo JUnit tests to work + public JDateField() { + this(new Date()); + } + + /** + * Set the date to the Date mask control. + * + * @param date + * The {@link Date} to be set + */ + public void setDate(Date date) { + setText(dateFormat.format(date)); + } + + /** + * Get the date from the Date mask control. + * + * @return The currently set date + */ + public Date getDate() { + try { + return dateFormat.parse(getText()); + } catch (ParseException e) { + return new Date(); + } catch (Exception e) { + // DateFormat.parse has some bugs (up to JDK 1.4.2) by which it + // throws unchecked exceptions. E.g. see: + // http://developer.java.sun.com/developer/bugParade/bugs/4699765.html + // + // To avoid problems with such situations, we'll catch all + // exceptions here and act just as for ParseException above: + return new Date(); + } + } + + /* + * Convert position in buffer to Calendar type Assumes that pos >=0 (which + * is true for getCaretPosition()) + */ + private static int posToField(int pos) { + if (pos >= fieldPositions.length) { // if beyond the end + pos = fieldPositions.length - 1; // then set to the end + } + return fieldPositions[pos]; + } + + /** + * Converts a date/time to a calendar using the defined format + */ + private Calendar parseDate(String datetime) { + Calendar c = Calendar.getInstance(); + try { + Date dat = dateFormat.parse(datetime); + c.setTime(dat); + } catch (ParseException e) { + // Do nothing; the current time will be returned + } + return c; + } + + /* + * Update the current field. The addend is only expected to be +1/-1, but + * other values will work. N.B. the roll() method only supports changes by a + * single unit - up or down + */ + private void update(int addend, boolean shifted) { + Calendar c = parseDate(getText()); + int pos = getCaretPosition(); + int field = posToField(pos); + if (shifted) { + c.roll(field, true); + } else { + c.add(field, addend); + } + String newDate = dateFormat.format(c.getTime()); + setText(newDate); + if (pos > newDate.length()) { + pos = newDate.length(); + } + setCaretPosition(pos);// Restore position + + } + + class KeyFocus extends KeyAdapter { + KeyFocus() { + } + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_UP) { + update(1, e.isShiftDown()); + } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { + update(-1, e.isShiftDown()); + } + } + } + + class FocusClass implements FocusListener { + FocusClass() { + } + + @Override + public void focusGained(FocusEvent e) { + } + + @Override + public void focusLost(FocusEvent e) { + try { + dateFormat.parse(getText()); + } catch (ParseException e1) { + requestFocusInWindow(); + } + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/JLabeledRadioI18N.java b/src/core/org/apache/jmeter/gui/util/JLabeledRadioI18N.java new file mode 100644 index 00000000000..0f3edfab4fa --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/JLabeledRadioI18N.java @@ -0,0 +1,226 @@ +/* + * 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.util; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.AbstractButton; +import javax.swing.ButtonGroup; +import javax.swing.ButtonModel; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledField; + +/** + * JLabeledRadioI18N creates a set of Radio buttons with a label. + * This is a version of the original JLabelledRadio class (now removed), but modified + * to accept resource names rather than language strings. + * + */ +public class JLabeledRadioI18N extends JPanel implements JLabeledField, ActionListener { + + private static final long serialVersionUID = 240L; + + private final JLabel mLabel = new JLabel(); + + private final ButtonGroup bGroup = new ButtonGroup(); + + private final ArrayList mChangeListeners = new ArrayList(3); + + /** + * + * @param label_resouce text resource name for group label + * @param item_resources list of resource names for individual buttons + * @param selectedItem button to be selected (if not null) + */ + public JLabeledRadioI18N(String label_resouce, String[] item_resources, String selectedItem) { + setLabel(label_resouce); + init(item_resources, selectedItem); + } + + /** + * @deprecated - only for use in testing + */ + @Deprecated + public JLabeledRadioI18N() { + super(); + } + + /** + * Method is responsible for creating the JRadioButtons and adding them to + * the ButtonGroup. + * + * The resource name is used as the action command for the button model, + * and the resource value is used to set the button label. + * + * @param resouces list of resource names + * @param selected initially selected resource (if not null) + * + */ + private void init(String[] resouces, String selected) { + this.add(mLabel); + initButtonGroup(resouces, selected); + } + + /** + * Method is responsible for creating the JRadioButtons and adding them to + * the ButtonGroup. + * + * The resource name is used as the action command for the button model, + * and the resource value is used to set the button label. + * + * @param resouces list of resource names + * @param selected initially selected resource (if not null) + * + */ + private void initButtonGroup(String[] resouces, String selected) { + for (int idx = 0; idx < resouces.length; idx++) { + JRadioButton btn = new JRadioButton(JMeterUtils.getResString(resouces[idx])); + btn.setActionCommand(resouces[idx]); + btn.addActionListener(this); + // add the button to the button group + this.bGroup.add(btn); + // add the button + this.add(btn); + if (selected != null && selected.equals(resouces[idx])) { + btn.setSelected(true); + } + } + } + + /** + * Method is responsible for removing current JRadioButtons of ButtonGroup and + * add creating the JRadioButtons and adding them to + * the ButtonGroup. + * + * The resource name is used as the action command for the button model, + * and the resource value is used to set the button label. + * + * @param resouces list of resource names + * @param selected initially selected resource (if not null) + * + */ + public void resetButtons(String[] resouces, String selected) { + Enumeration buttons = bGroup.getElements(); + List buttonsToRemove = new ArrayList(this.bGroup.getButtonCount()); + while (buttons.hasMoreElements()) { + AbstractButton abstractButton = buttons + .nextElement(); + buttonsToRemove.add(abstractButton); + } + for (AbstractButton abstractButton : buttonsToRemove) { + abstractButton.removeActionListener(this); + bGroup.remove(abstractButton); + } + for (AbstractButton abstractButton : buttonsToRemove) { + this.remove(abstractButton); + } + initButtonGroup(resouces, selected); + } + + /** + * The implementation will get the resource name from the selected radio button + * in the JButtonGroup. + */ + @Override + public String getText() { + return this.bGroup.getSelection().getActionCommand(); + } + + /** + * The implementation will iterate through the radio buttons and find the + * match. It then sets it to selected and sets all other radio buttons as + * not selected. + * @param resourcename name of resource whose button is to be selected + */ + @Override + public void setText(String resourcename) { + Enumeration en = this.bGroup.getElements(); + while (en.hasMoreElements()) { + ButtonModel model = en.nextElement().getModel(); + if (model.getActionCommand().equals(resourcename)) { + this.bGroup.setSelected(model, true); + } else { + this.bGroup.setSelected(model, false); + } + } + } + + /** + * Set the group label from the resource name. + * + * @param label_resource The text to be looked up and set + */ + @Override + public final void setLabel(String label_resource) { + this.mLabel.setText(JMeterUtils.getResString(label_resource)); + } + + /** {@inheritDoc} */ + @Override + public void addChangeListener(ChangeListener pChangeListener) { + this.mChangeListeners.add(pChangeListener); + } + + /** + * Notify all registered change listeners that the text in the text field + * has changed. + */ + private void notifyChangeListeners() { + ChangeEvent ce = new ChangeEvent(this); + for (int index = 0; index < mChangeListeners.size(); index++) { + mChangeListeners.get(index).stateChanged(ce); + } + } + + /** + * Method will return all the label and JRadioButtons. ButtonGroup is + * excluded from the list. + */ + @Override + public List getComponentList() { + List comps = new LinkedList(); + comps.add(mLabel); + Enumeration en = this.bGroup.getElements(); + while (en.hasMoreElements()) { + comps.add(en.nextElement()); + } + return comps; + } + + /** + * When a radio button is clicked, an ActionEvent is triggered. + */ + @Override + public void actionPerformed(ActionEvent e) { + this.notifyChangeListeners(); + } + +} diff --git a/src/core/org/apache/jmeter/gui/util/JMeterColor.java b/src/core/org/apache/jmeter/gui/util/JMeterColor.java new file mode 100644 index 00000000000..13e0d8a4a4e --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/JMeterColor.java @@ -0,0 +1,39 @@ +/* + * 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.util; + +import java.awt.Color; + +public class JMeterColor extends Color { + private static final long serialVersionUID = 240L; + + public static final Color dark_green = new JMeterColor(0F, .5F, 0F); + + public static final Color LAVENDER = new JMeterColor(206F / 255F, 207F / 255F, 1F); + + public static final Color purple = new JMeterColor(150 / 255F, 0, 150 / 255F); + + public JMeterColor(float r, float g, float b) { + super(r, g, b); + } + + public JMeterColor() { + super(0, 0, 0); + } +} diff --git a/src/core/org/apache/jmeter/gui/util/JMeterMenuBar.java b/src/core/org/apache/jmeter/gui/util/JMeterMenuBar.java new file mode 100644 index 00000000000..6f06969d34d --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/JMeterMenuBar.java @@ -0,0 +1,849 @@ +/* + * 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.util; + +import java.awt.Component; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.MenuElement; +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.gui.action.LoadRecentProject; +import org.apache.jmeter.gui.plugin.MenuCreator; +import org.apache.jmeter.gui.plugin.MenuCreator.MENU_LOCATION; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class JMeterMenuBar extends JMenuBar implements LocaleChangeListener { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private JMenu fileMenu; + + private JMenuItem file_save_as; + + private JMenuItem file_selection_as; + + private JMenuItem file_selection_as_test_fragment; + + private JMenuItem file_revert; + + private JMenuItem file_load; + + private JMenuItem templates; + + private List file_load_recent_files; + + private JMenuItem file_merge; + + private JMenuItem file_exit; + + private JMenuItem file_close; + + private JMenu editMenu; + + private JMenu edit_add; + + private JMenu runMenu; + + private JMenuItem run_start; + + private JMenuItem run_start_no_timers; + + private JMenu remote_start; + + private JMenuItem remote_start_all; + + private Collection remote_engine_start; + + private JMenuItem run_stop; + + private JMenuItem run_shut; + + private JMenu remote_stop; + + private JMenu remote_shut; + + private JMenuItem remote_stop_all; + + private JMenuItem remote_shut_all; + + private Collection remote_engine_stop; + + private Collection remote_engine_shut; + + private JMenuItem run_clear; + + private JMenuItem run_clearAll; + + // JMenu reportMenu; + // JMenuItem analyze; + private JMenu optionsMenu; + + private JMenu lafMenu; + + private JMenuItem sslManager; + + private JMenu helpMenu; + + private JMenuItem help_about; + + private String[] remoteHosts; + + private JMenu remote_exit; + + private JMenuItem remote_exit_all; + + private Collection remote_engine_exit; + + private JMenu searchMenu; + + private ArrayList menuCreators; + + public static final String SYSTEM_LAF = "System"; // $NON-NLS-1$ + + public static final String CROSS_PLATFORM_LAF = "CrossPlatform"; // $NON-NLS-1$ + + public JMeterMenuBar() { + // List for recent files menu items + file_load_recent_files = new LinkedList(); + // Lists for remote engines menu items + remote_engine_start = new LinkedList(); + remote_engine_stop = new LinkedList(); + remote_engine_shut = new LinkedList(); + remote_engine_exit = new LinkedList(); + remoteHosts = JOrphanUtils.split(JMeterUtils.getPropDefault("remote_hosts", ""), ","); //$NON-NLS-1$ + if (remoteHosts.length == 1 && remoteHosts[0].equals("")) { + remoteHosts = new String[0]; + } + this.getRemoteItems(); + createMenuBar(); + JMeterUtils.addLocaleChangeListener(this); + } + + public void setFileSaveEnabled(boolean enabled) { + if(file_save_as != null) { + file_save_as.setEnabled(enabled); + } + } + + public void setFileLoadEnabled(boolean enabled) { + if (file_load != null) { + file_load.setEnabled(enabled); + } + if (file_merge != null) { + file_merge.setEnabled(enabled); + } + } + + public void setFileRevertEnabled(boolean enabled) { + if(file_revert != null) { + file_revert.setEnabled(enabled); + } + } + + public void setProjectFileLoaded(String file) { + if(file_load_recent_files != null && file != null) { + LoadRecentProject.updateRecentFileMenuItems(file_load_recent_files, file); + } + } + + public void setEditEnabled(boolean enabled) { + if (editMenu != null) { + editMenu.setEnabled(enabled); + } + } + + // Does not appear to be used; called by MainFrame#setEditAddMenu() but that is not called + public void setEditAddMenu(JMenu menu) { + // If the Add menu already exists, remove it. + if (edit_add != null) { + editMenu.remove(edit_add); + } + // Insert the Add menu as the first menu item in the Edit menu. + edit_add = menu; + editMenu.insert(edit_add, 0); + } + + // Called by MainFrame#setEditMenu() which is called by EditCommand#doAction and GuiPackage#localeChanged + public void setEditMenu(JPopupMenu menu) { + if (menu != null) { + editMenu.removeAll(); + Component[] comps = menu.getComponents(); + for (int i = 0; i < comps.length; i++) { + editMenu.add(comps[i]); + } + editMenu.setEnabled(true); + } else { + editMenu.setEnabled(false); + } + } + + public void setEditAddEnabled(boolean enabled) { + // There was a NPE being thrown without the null check here.. JKB + if (edit_add != null) { + edit_add.setEnabled(enabled); + } + // If we are enabling the Edit-->Add menu item, then we also need to + // enable the Edit menu. The Edit menu may already be enabled, but + // there's no harm it trying to enable it again. + setEditEnabled(enabled); + } + + /** + * Creates the MenuBar for this application. I believe in my heart that this + * should be defined in a file somewhere, but that is for later. + */ + public void createMenuBar() { + this.menuCreators = new ArrayList(); + try { + List listClasses = ClassFinder.findClassesThatExtend( + JMeterUtils.getSearchPaths(), + new Class[] {MenuCreator.class }); + for (String strClassName : listClasses) { + try { + if(log.isDebugEnabled()) { + log.debug("Loading menu creator class: "+ strClassName); + } + Class commandClass = Class.forName(strClassName); + if (!Modifier.isAbstract(commandClass.getModifiers())) { + if(log.isDebugEnabled()) { + log.debug("Instantiating: "+ commandClass.getName()); + } + MenuCreator creator = (MenuCreator) commandClass.newInstance(); + menuCreators.add(creator); + } + } catch (Exception e) { + log.error("Exception registering "+MenuCreator.class.getName() + " with implementation:"+strClassName, e); + } + } + } catch (IOException e) { + log.error("Exception finding implementations of "+MenuCreator.class, e); + } + + makeFileMenu(); + makeEditMenu(); + makeRunMenu(); + makeOptionsMenu(); + makeHelpMenu(); + makeSearchMenu(); + this.add(fileMenu); + this.add(editMenu); + this.add(searchMenu); + this.add(runMenu); + this.add(optionsMenu); + for (Iterator iterator = menuCreators.iterator(); iterator.hasNext();) { + MenuCreator menuCreator = iterator.next(); + JMenu[] topLevelMenus = menuCreator.getTopLevelMenus(); + for (JMenu topLevelMenu : topLevelMenus) { + this.add(topLevelMenu); + } + } + this.add(helpMenu); + } + + private void makeHelpMenu() { + // HELP MENU + helpMenu = makeMenuRes("help",'H'); //$NON-NLS-1$ + + JMenuItem contextHelp = makeMenuItemRes("help", 'H', ActionNames.HELP, KeyStrokes.HELP); //$NON-NLS-1$ + + JMenuItem whatClass = makeMenuItemRes("help_node", 'W', ActionNames.WHAT_CLASS, KeyStrokes.WHAT_CLASS);//$NON-NLS-1$ + + JMenuItem setDebug = makeMenuItemRes("debug_on", ActionNames.DEBUG_ON, KeyStrokes.DEBUG_ON);//$NON-NLS-1$ + + JMenuItem resetDebug = makeMenuItemRes("debug_off", ActionNames.DEBUG_OFF, KeyStrokes.DEBUG_OFF);//$NON-NLS-1$ + + JMenuItem heapDump = makeMenuItemRes("heap_dump", ActionNames.HEAP_DUMP);//$NON-NLS-1$ + + help_about = makeMenuItemRes("about", 'A', ActionNames.ABOUT); //$NON-NLS-1$ + + helpMenu.add(contextHelp); + helpMenu.addSeparator(); + helpMenu.add(whatClass); + helpMenu.add(setDebug); + helpMenu.add(resetDebug); + helpMenu.add(heapDump); + + addPluginsMenuItems(helpMenu, menuCreators, MENU_LOCATION.HELP); + + helpMenu.addSeparator(); + helpMenu.add(help_about); + } + + private void makeOptionsMenu() { + // OPTIONS MENU + optionsMenu = makeMenuRes("option",'O'); //$NON-NLS-1$ + JMenuItem functionHelper = makeMenuItemRes("function_dialog_menu_item", 'F', ActionNames.FUNCTIONS, KeyStrokes.FUNCTIONS); //$NON-NLS-1$ + + lafMenu = makeMenuRes("appearance",'L'); //$NON-NLS-1$ + UIManager.LookAndFeelInfo lafs[] = getAllLAFs(); + for (int i = 0; i < lafs.length; ++i) { + JMenuItem laf = new JMenuItem(lafs[i].getName()); + laf.addActionListener(ActionRouter.getInstance()); + laf.setActionCommand(ActionNames.LAF_PREFIX + lafs[i].getClassName()); + laf.setToolTipText(lafs[i].getClassName()); // show the classname to the user + lafMenu.add(laf); + } + optionsMenu.add(functionHelper); + optionsMenu.add(lafMenu); + + JCheckBoxMenuItem menuToolBar = makeCheckBoxMenuItemRes("menu_toolbar", ActionNames.TOOLBAR); //$NON-NLS-1$ + JCheckBoxMenuItem menuLoggerPanel = makeCheckBoxMenuItemRes("menu_logger_panel", ActionNames.LOGGER_PANEL_ENABLE_DISABLE); //$NON-NLS-1$ + GuiPackage guiInstance = GuiPackage.getInstance(); + if (guiInstance != null) { //avoid error in ant task tests (good way?) + guiInstance.setMenuItemToolbar(menuToolBar); + guiInstance.setMenuItemLoggerPanel(menuLoggerPanel); + } + optionsMenu.add(menuToolBar); + optionsMenu.add(menuLoggerPanel); + + if (SSLManager.isSSLSupported()) { + sslManager = makeMenuItemRes("sslmanager", 'S', ActionNames.SSL_MANAGER, KeyStrokes.SSL_MANAGER); //$NON-NLS-1$ + optionsMenu.add(sslManager); + } + optionsMenu.add(makeLanguageMenu()); + + JMenuItem collapse = makeMenuItemRes("menu_collapse_all", ActionNames.COLLAPSE_ALL, KeyStrokes.COLLAPSE_ALL); //$NON-NLS-1$ + optionsMenu.add(collapse); + + JMenuItem expand = makeMenuItemRes("menu_expand_all", ActionNames.EXPAND_ALL, KeyStrokes.EXPAND_ALL); //$NON-NLS-1$ + optionsMenu.add(expand); + + addPluginsMenuItems(optionsMenu, menuCreators, MENU_LOCATION.OPTIONS); + } + + private static class LangMenuHelper{ + final ActionRouter actionRouter = ActionRouter.getInstance(); + final JMenu languageMenu; + + LangMenuHelper(JMenu _languageMenu){ + languageMenu = _languageMenu; + } + + /** + * Create a language entry from the locale name. + * + * @param locale - must also be a valid resource name + */ + void addLang(String locale){ + String localeString = JMeterUtils.getLocaleString(locale); + JMenuItem language = new JMenuItem(localeString); + language.addActionListener(actionRouter); + language.setActionCommand(ActionNames.CHANGE_LANGUAGE); + language.setName(locale); // This is used by the ChangeLanguage class to define the Locale + languageMenu.add(language); + } + + } + + /** + * Generate the list of supported languages. + * + * @return list of languages + */ + // Also used by org.apache.jmeter.resources.PackageTest + public static String[] getLanguages(){ + List lang = new ArrayList(20); + lang.add(Locale.ENGLISH.toString()); // en + lang.add(Locale.FRENCH.toString()); // fr + lang.add(Locale.GERMAN.toString()); // de + lang.add("no"); // $NON-NLS-1$ + lang.add("pl"); // $NON-NLS-1$ + lang.add("pt_BR"); // $NON-NLS-1$ + lang.add("es"); // $NON-NLS-1$ + lang.add("tr"); // $NON-NLS-1$ + lang.add(Locale.JAPANESE.toString()); // ja + lang.add(Locale.SIMPLIFIED_CHINESE.toString()); // zh_CN + lang.add(Locale.TRADITIONAL_CHINESE.toString()); // zh_TW + final String addedLocales = JMeterUtils.getProperty("locales.add"); + if (addedLocales != null){ + String [] addLanguages =addedLocales.split(","); // $NON-NLS-1$ + for(String newLang : addLanguages){ + log.info("Adding locale "+newLang); + lang.add(newLang); + } + } + return lang.toArray(new String[lang.size()]); + } + + static JMenu makeLanguageMenu() { + final JMenu languageMenu = makeMenuRes("choose_language",'C'); //$NON-NLS-1$ + + LangMenuHelper langMenu = new LangMenuHelper(languageMenu); + + /* + * Note: the item name is used by ChangeLanguage to create a Locale for + * that language, so need to ensure that the language strings are valid + * If they exist, use the Locale language constants. + * Also, need to ensure that the names are valid resource entries too. + */ + + for(String lang : getLanguages()){ + langMenu.addLang(lang); + } + return languageMenu; + } + + private void makeRunMenu() { + // RUN MENU + runMenu = makeMenuRes("run",'R'); //$NON-NLS-1$ + + run_start = makeMenuItemRes("start", 'S', ActionNames.ACTION_START, KeyStrokes.ACTION_START); //$NON-NLS-1$ + + run_start_no_timers = makeMenuItemRes("start_no_timers", ActionNames.ACTION_START_NO_TIMERS); //$NON-NLS-1$ + + run_stop = makeMenuItemRes("stop", 'T', ActionNames.ACTION_STOP, KeyStrokes.ACTION_STOP); //$NON-NLS-1$ + run_stop.setEnabled(false); + + run_shut = makeMenuItemRes("shutdown", 'Y', ActionNames.ACTION_SHUTDOWN, KeyStrokes.ACTION_SHUTDOWN); //$NON-NLS-1$ + run_shut.setEnabled(false); + + run_clear = makeMenuItemRes("clear", 'C', ActionNames.CLEAR, KeyStrokes.CLEAR); //$NON-NLS-1$ + + run_clearAll = makeMenuItemRes("clear_all", 'a', ActionNames.CLEAR_ALL, KeyStrokes.CLEAR_ALL); //$NON-NLS-1$ + + runMenu.add(run_start); + runMenu.add(run_start_no_timers); + if (remote_start != null) { + runMenu.add(remote_start); + } + remote_start_all = makeMenuItemRes("remote_start_all", ActionNames.REMOTE_START_ALL, KeyStrokes.REMOTE_START_ALL); //$NON-NLS-1$ + + runMenu.add(remote_start_all); + runMenu.add(run_stop); + runMenu.add(run_shut); + if (remote_stop != null) { + runMenu.add(remote_stop); + } + remote_stop_all = makeMenuItemRes("remote_stop_all", 'X', ActionNames.REMOTE_STOP_ALL, KeyStrokes.REMOTE_STOP_ALL); //$NON-NLS-1$ + runMenu.add(remote_stop_all); + + if (remote_shut != null) { + runMenu.add(remote_shut); + } + remote_shut_all = makeMenuItemRes("remote_shut_all", 'X', ActionNames.REMOTE_SHUT_ALL, KeyStrokes.REMOTE_SHUT_ALL); //$NON-NLS-1$ + runMenu.add(remote_shut_all); + + if (remote_exit != null) { + runMenu.add(remote_exit); + } + remote_exit_all = makeMenuItemRes("remote_exit_all", ActionNames.REMOTE_EXIT_ALL); //$NON-NLS-1$ + runMenu.add(remote_exit_all); + + runMenu.addSeparator(); + runMenu.add(run_clear); + runMenu.add(run_clearAll); + + addPluginsMenuItems(runMenu, menuCreators, MENU_LOCATION.RUN); + } + + private void makeEditMenu() { + // EDIT MENU + editMenu = makeMenuRes("edit",'E'); //$NON-NLS-1$ + + // From the Java Look and Feel Guidelines: If all items in a menu + // are disabled, then disable the menu. Makes sense. + editMenu.setEnabled(false); + + addPluginsMenuItems(editMenu, menuCreators, MENU_LOCATION.EDIT); + } + + private void makeFileMenu() { + // FILE MENU + fileMenu = makeMenuRes("file",'F'); //$NON-NLS-1$ + + JMenuItem file_save = makeMenuItemRes("save", 'S', ActionNames.SAVE, KeyStrokes.SAVE); //$NON-NLS-1$ + file_save.setEnabled(true); + + file_save_as = makeMenuItemRes("save_all_as", 'A', ActionNames.SAVE_ALL_AS, KeyStrokes.SAVE_ALL_AS); //$NON-NLS-1$ + file_save_as.setEnabled(true); + + file_selection_as = makeMenuItemRes("save_as", ActionNames.SAVE_AS); //$NON-NLS-1$ + file_selection_as.setEnabled(true); + + file_selection_as_test_fragment = makeMenuItemRes("save_as_test_fragment", ActionNames.SAVE_AS_TEST_FRAGMENT); //$NON-NLS-1$ + file_selection_as_test_fragment.setEnabled(true); + + file_revert = makeMenuItemRes("revert_project", 'R', ActionNames.REVERT_PROJECT); //$NON-NLS-1$ + file_revert.setEnabled(false); + + file_load = makeMenuItemRes("menu_open", 'O', ActionNames.OPEN, KeyStrokes.OPEN); //$NON-NLS-1$ + // Set default SAVE menu item to disabled since the default node that + // is selected is ROOT, which does not allow items to be inserted. + file_load.setEnabled(false); + + templates = makeMenuItemRes("template_menu", 'T', ActionNames.TEMPLATES); //$NON-NLS-1$ + templates.setEnabled(true); + + file_close = makeMenuItemRes("menu_close", 'C', ActionNames.CLOSE, KeyStrokes.CLOSE); //$NON-NLS-1$ + + file_exit = makeMenuItemRes("exit", 'X', ActionNames.EXIT, KeyStrokes.EXIT); //$NON-NLS-1$ + + file_merge = makeMenuItemRes("menu_merge", 'M', ActionNames.MERGE); //$NON-NLS-1$ + // file_merge.setAccelerator( + // KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK)); + // Set default SAVE menu item to disabled since the default node that + // is selected is ROOT, which does not allow items to be inserted. + file_merge.setEnabled(false); + + fileMenu.add(file_close); + fileMenu.add(file_load); + fileMenu.add(templates); + fileMenu.add(file_merge); + fileMenu.addSeparator(); + fileMenu.add(file_save); + fileMenu.add(file_save_as); + fileMenu.add(file_selection_as); + fileMenu.add(file_selection_as_test_fragment); + fileMenu.add(file_revert); + fileMenu.addSeparator(); + // Add the recent files, which will also add a separator that is + // visible when needed + file_load_recent_files = LoadRecentProject.getRecentFileMenuItems(); + for(JComponent jc : file_load_recent_files){ + fileMenu.add(jc); + } + + addPluginsMenuItems(fileMenu, menuCreators, MENU_LOCATION.FILE); + + fileMenu.add(file_exit); + } + + private void makeSearchMenu() { + // Search MENU + searchMenu = makeMenuRes("menu_search"); //$NON-NLS-1$ + + JMenuItem search = makeMenuItemRes("menu_search", 'F', ActionNames.SEARCH_TREE, KeyStrokes.SEARCH_TREE); //$NON-NLS-1$ + searchMenu.add(search); + search.setEnabled(true); + + JMenuItem searchReset = makeMenuItemRes("menu_search_reset", ActionNames.SEARCH_RESET); //$NON-NLS-1$ + searchMenu.add(searchReset); + searchReset.setEnabled(true); + + addPluginsMenuItems(searchMenu, menuCreators, MENU_LOCATION.SEARCH); + } + + /** + * @param menu + * @param menuCreators + * @param location + */ + private void addPluginsMenuItems(JMenu menu, List menuCreators, MENU_LOCATION location) { + boolean addedSeparator = false; + for (MenuCreator menuCreator : menuCreators) { + JMenuItem[] menuItems = menuCreator.getMenuItemsAtLocation(location); + for (JMenuItem jMenuItem : menuItems) { + if(!addedSeparator) { + menu.addSeparator(); + addedSeparator = true; + } + menu.add(jMenuItem); + } + } + } + + public void setRunning(boolean running, String host) { + log.info("setRunning(" + running + "," + host + ")"); + if(org.apache.jmeter.gui.MainFrame.LOCAL.equals(host)) { + return; + } + Iterator iter = remote_engine_start.iterator(); + Iterator iter2 = remote_engine_stop.iterator(); + Iterator iter3 = remote_engine_exit.iterator(); + Iterator iter4 = remote_engine_shut.iterator(); + while (iter.hasNext() && iter2.hasNext() && iter3.hasNext() &&iter4.hasNext()) { + JMenuItem start = iter.next(); + JMenuItem stop = iter2.next(); + JMenuItem exit = iter3.next(); + JMenuItem shut = iter4.next(); + if (start.getText().equals(host)) { + log.debug("Found start host: " + start.getText()); + start.setEnabled(!running); + } + if (stop.getText().equals(host)) { + log.debug("Found stop host: " + stop.getText()); + stop.setEnabled(running); + } + if (exit.getText().equals(host)) { + log.debug("Found exit host: " + exit.getText()); + exit.setEnabled(true); + } + if (shut.getText().equals(host)) { + log.debug("Found exit host: " + exit.getText()); + shut.setEnabled(running); + } + } + } + + /** {@inheritDoc} */ + @Override + public void setEnabled(boolean enable) { + run_start.setEnabled(!enable); + run_start_no_timers.setEnabled(!enable); + run_stop.setEnabled(enable); + run_shut.setEnabled(enable); + } + + private void getRemoteItems() { + if (remoteHosts.length > 0) { + remote_start = makeMenuRes("remote_start"); //$NON-NLS-1$ + remote_stop = makeMenuRes("remote_stop"); //$NON-NLS-1$ + remote_shut = makeMenuRes("remote_shut"); //$NON-NLS-1$ + remote_exit = makeMenuRes("remote_exit"); //$NON-NLS-1$ + + for (int i = 0; i < remoteHosts.length; i++) { + remoteHosts[i] = remoteHosts[i].trim(); + + JMenuItem item = makeMenuItemNoRes(remoteHosts[i], ActionNames.REMOTE_START); + remote_engine_start.add(item); + remote_start.add(item); + + item = makeMenuItemNoRes(remoteHosts[i], ActionNames.REMOTE_STOP); + item.setEnabled(false); + remote_engine_stop.add(item); + remote_stop.add(item); + + item = makeMenuItemNoRes(remoteHosts[i], ActionNames.REMOTE_SHUT); + item.setEnabled(false); + remote_engine_shut.add(item); + remote_shut.add(item); + + item = makeMenuItemNoRes(remoteHosts[i],ActionNames.REMOTE_EXIT); + item.setEnabled(false); + remote_engine_exit.add(item); + remote_exit.add(item); + } + } + } + + /** {@inheritDoc} */ + @Override + public void localeChanged(LocaleChangeEvent event) { + updateMenuElement(fileMenu); + updateMenuElement(editMenu); + updateMenuElement(searchMenu); + updateMenuElement(runMenu); + updateMenuElement(optionsMenu); + updateMenuElement(helpMenu); + for (MenuCreator creator : menuCreators) { + creator.localeChanged(); + } + } + + /** + * Get a list of all installed LAFs plus CrossPlatform and System. + * + * @return The list of available {@link LookAndFeelInfo}s + */ + // This is also used by LookAndFeelCommand + public static LookAndFeelInfo[] getAllLAFs() { + UIManager.LookAndFeelInfo lafs[] = UIManager.getInstalledLookAndFeels(); + int i = lafs.length; + UIManager.LookAndFeelInfo lafsAll[] = new UIManager.LookAndFeelInfo[i+2]; + System.arraycopy(lafs, 0, lafsAll, 0, i); + lafsAll[i++]=new UIManager.LookAndFeelInfo(CROSS_PLATFORM_LAF,UIManager.getCrossPlatformLookAndFeelClassName()); + lafsAll[i++]=new UIManager.LookAndFeelInfo(SYSTEM_LAF,UIManager.getSystemLookAndFeelClassName()); + return lafsAll; + } + /** + *

Refreshes all texts in the menu and all submenus to a new locale.

+ * + *

Assumes that the item name is set to the resource key, so the resource can be retrieved. + * Certain action types do not follow this rule, @see JMeterMenuBar#isNotResource(String)

+ * + * The Language Change event assumes that the name is the same as the locale name, + * so this additionally means that all supported locales must be defined as resources. + * + */ + private void updateMenuElement(MenuElement menu) { + Component component = menu.getComponent(); + final String compName = component.getName(); + if (compName != null) { + for (MenuCreator menuCreator : menuCreators) { + if(menuCreator.localeChanged(menu)) { + return; + } + } + if (component instanceof JMenu) { + final JMenu jMenu = (JMenu) component; + if (isResource(jMenu.getActionCommand())){ + jMenu.setText(JMeterUtils.getResString(compName)); + } + } else { + final JMenuItem jMenuItem = (JMenuItem) component; + if (isResource(jMenuItem.getActionCommand())){ + jMenuItem.setText(JMeterUtils.getResString(compName)); + } else if (ActionNames.CHANGE_LANGUAGE.equals(jMenuItem.getActionCommand())){ + jMenuItem.setText(JMeterUtils.getLocaleString(compName)); + } + } + } + + MenuElement[] subelements = menu.getSubElements(); + + for (int i = 0; i < subelements.length; i++) { + updateMenuElement(subelements[i]); + } + } + + /** + * Return true if component name is a resource.
+ * i.e it is not a hostname:
+ * + * ActionNames.REMOTE_START
+ * ActionNames.REMOTE_STOP
+ * ActionNames.REMOTE_EXIT
+ * + * nor a filename:
+ * ActionNames.OPEN_RECENT + * + * nor a look and feel prefix:
+ * ActionNames.LAF_PREFIX + */ + private static boolean isResource(String actionCommand) { + if (ActionNames.CHANGE_LANGUAGE.equals(actionCommand)){// + return false; + } + if (ActionNames.ADD.equals(actionCommand)){// + return false; + } + if (ActionNames.REMOTE_START.equals(actionCommand)){// + return false; + } + if (ActionNames.REMOTE_STOP.equals(actionCommand)){// + return false; + } + if (ActionNames.REMOTE_SHUT.equals(actionCommand)){// + return false; + } + if (ActionNames.REMOTE_EXIT.equals(actionCommand)){// + return false; + } + if (ActionNames.OPEN_RECENT.equals(actionCommand)){// + return false; + } + if (actionCommand != null && actionCommand.startsWith(ActionNames.LAF_PREFIX)){ + return false; + } + return true; + } + + /** + * Make a menu from a resource string. + * @param resource used to name menu and set text. + * @return the menu + */ + private static JMenu makeMenuRes(String resource) { + JMenu menu = new JMenu(JMeterUtils.getResString(resource)); + menu.setName(resource); + return menu; + } + + /** + * Make a menu from a resource string and set its mnemonic. + * + * @param resource + * @param mnemonic + * @return the menu + */ + private static JMenu makeMenuRes(String resource, int mnemonic){ + JMenu menu = makeMenuRes(resource); + menu.setMnemonic(mnemonic); + return menu; + } + + /** + * Make a menuItem using a fixed label which is also used as the item name. + * This is used for items such as recent files and hostnames which are not resources + * @param label (this is not used as a resource key) + * @param actionCommand + * @return the menu item + */ + private static JMenuItem makeMenuItemNoRes(String label, String actionCommand) { + JMenuItem menuItem = new JMenuItem(label); + menuItem.setName(label); + menuItem.setActionCommand(actionCommand); + menuItem.addActionListener(ActionRouter.getInstance()); + return menuItem; + } + + private static JMenuItem makeMenuItemRes(String resource, String actionCommand) { + return makeMenuItemRes(resource, KeyEvent.VK_UNDEFINED, actionCommand, null); + } + + private static JMenuItem makeMenuItemRes(String resource, String actionCommand, KeyStroke keyStroke) { + return makeMenuItemRes(resource, KeyEvent.VK_UNDEFINED, actionCommand, keyStroke); + } + + private static JMenuItem makeMenuItemRes(String resource, int mnemonic, String actionCommand) { + return makeMenuItemRes(resource, mnemonic, actionCommand, null); + } + + private static JMenuItem makeMenuItemRes(String resource, int mnemonic, String actionCommand, KeyStroke keyStroke){ + JMenuItem menuItem = new JMenuItem(JMeterUtils.getResString(resource), mnemonic); + menuItem.setName(resource); + menuItem.setActionCommand(actionCommand); + menuItem.setAccelerator(keyStroke); + menuItem.addActionListener(ActionRouter.getInstance()); + return menuItem; + } + + private static JCheckBoxMenuItem makeCheckBoxMenuItemRes(String resource, String actionCommand) { + return makeCheckBoxMenuItemRes(resource, actionCommand, null); + } + + private static JCheckBoxMenuItem makeCheckBoxMenuItemRes(String resource, + String actionCommand, KeyStroke keyStroke){ + JCheckBoxMenuItem cbkMenuItem = new JCheckBoxMenuItem(JMeterUtils.getResString(resource)); + cbkMenuItem.setName(resource); + cbkMenuItem.setActionCommand(actionCommand); + cbkMenuItem.setAccelerator(keyStroke); + cbkMenuItem.addActionListener(ActionRouter.getInstance()); + return cbkMenuItem; + } +} diff --git a/src/core/org/apache/jmeter/gui/util/JMeterToolBar.java b/src/core/org/apache/jmeter/gui/util/JMeterToolBar.java new file mode 100644 index 00000000000..d493d5e8921 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/JMeterToolBar.java @@ -0,0 +1,300 @@ +/* + * 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.util; + +import java.awt.Component; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JOptionPane; +import javax.swing.JToolBar; + +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The JMeter main toolbar class + * + */ +public class JMeterToolBar extends JToolBar implements LocaleChangeListener { + + /** + * + */ + private static final long serialVersionUID = -4591210341986068907L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String TOOLBAR_ENTRY_SEP = ","; //$NON-NLS-1$ + + private static final String TOOLBAR_PROP_NAME = "toolbar"; //$NON-NLS-1$ + + // protected fields: JMeterToolBar class can be use to create another toolbar (plugin, etc.) + protected static final String DEFAULT_TOOLBAR_PROPERTY_FILE = "org/apache/jmeter/images/toolbar/icons-toolbar.properties"; //$NON-NLS-1$ + + protected static final String USER_DEFINED_TOOLBAR_PROPERTY_FILE = "jmeter.toolbar.icons"; //$NON-NLS-1$ + + protected static final String TOOLBAR_ICON_SIZE = "jmeter.toolbar.icons.size"; //$NON-NLS-1$ + + protected static final String DEFAULT_TOOLBAR_ICON_SIZE = "22x22"; //$NON-NLS-1$ + + private static final String TOOLBAR_LIST = "jmeter.toolbar"; + + /** + * Create the default JMeter toolbar + * + * @param visible + * Flag whether toolbar should be visible + * @return the newly created {@link JMeterToolBar} + */ + public static JMeterToolBar createToolbar(boolean visible) { + JMeterToolBar toolBar = new JMeterToolBar(); + toolBar.setFloatable(false); + toolBar.setVisible(visible); + + setupToolbarContent(toolBar); + JMeterUtils.addLocaleChangeListener(toolBar); + // implicit return empty toolbar if icons == null + return toolBar; + } + + /** + * Setup toolbar content + * @param toolBar {@link JMeterToolBar} + */ + private static void setupToolbarContent(JMeterToolBar toolBar) { + List icons = getIconMappings(); + if (icons != null) { + for (IconToolbarBean iconToolbarBean : icons) { + if (iconToolbarBean == null) { + toolBar.addSeparator(); + } else { + try { + toolBar.add(makeButtonItemRes(iconToolbarBean)); + } catch (Exception e) { + log.warn(e.getMessage()); + } + } + } + toolBar.initButtonsState(); + } + } + + /** + * Generate a button component from icon bean + * @param iconBean contains I18N key, ActionNames, icon path, optional icon path pressed + * @return a button for toolbar + */ + private static JButton makeButtonItemRes(IconToolbarBean iconBean) throws Exception { + final URL imageURL = JMeterUtils.class.getClassLoader().getResource(iconBean.getIconPath()); + if (imageURL == null) { + throw new Exception("No icon for: " + iconBean.getActionName()); + } + JButton button = new JButton(new ImageIcon(imageURL)); + button.setToolTipText(JMeterUtils.getResString(iconBean.getI18nKey())); + final URL imageURLPressed = JMeterUtils.class.getClassLoader().getResource(iconBean.getIconPathPressed()); + button.setPressedIcon(new ImageIcon(imageURLPressed)); + button.addActionListener(ActionRouter.getInstance()); + button.setActionCommand(iconBean.getActionNameResolve()); + return button; + } + + /** + * Parse icon set file. + * @return List of icons/action definition + */ + private static List getIconMappings() { + // Get the standard toolbar properties + Properties defaultProps = JMeterUtils.loadProperties(DEFAULT_TOOLBAR_PROPERTY_FILE); + if (defaultProps == null) { + JOptionPane.showMessageDialog(null, + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + return null; + } + Properties p; + String userProp = JMeterUtils.getProperty(USER_DEFINED_TOOLBAR_PROPERTY_FILE); + if (userProp != null){ + p = JMeterUtils.loadProperties(userProp, defaultProps); + } else { + p = defaultProps; + } + + String order = JMeterUtils.getPropDefault(TOOLBAR_LIST, p.getProperty(TOOLBAR_PROP_NAME)); + + if (order == null) { + log.warn("Could not find toolbar definition list"); + JOptionPane.showMessageDialog(null, + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + return null; + } + + String[] oList = order.split(TOOLBAR_ENTRY_SEP); + + String iconSize = JMeterUtils.getPropDefault(TOOLBAR_ICON_SIZE, DEFAULT_TOOLBAR_ICON_SIZE); + + List listIcons = new ArrayList(); + for (String key : oList) { + log.debug("Toolbar icon key: " + key); //$NON-NLS-1$ + String trimmed = key.trim(); + if (trimmed.equals("|")) { //$NON-NLS-1$ + listIcons.add(null); + } else { + String property = p.getProperty(trimmed); + if (property == null) { + log.warn("No definition for toolbar entry: " + key); + } else { + try { + IconToolbarBean itb = new IconToolbarBean(property, iconSize); + listIcons.add(itb); + } catch (IllegalArgumentException e) { + // already reported by IconToolbarBean + } + } + } + } + return listIcons; + } + + /** + * {@inheritDoc} + */ + @Override + public void localeChanged(LocaleChangeEvent event) { + Map currentButtonStates = getCurrentButtonsStates(); + this.removeAll(); + setupToolbarContent(this); + updateButtons(currentButtonStates); + } + + /** + * + * @return Current state (enabled/disabled) of Toolbar button + */ + private Map getCurrentButtonsStates() { + Component[] components = getComponents(); + Map buttonStates = new HashMap(components.length); + for (int i = 0; i < components.length; i++) { + if (components[i] instanceof JButton) { + JButton button = (JButton) components[i]; + buttonStates.put(button.getActionCommand(), Boolean.valueOf(button.isEnabled())); + } + } + return buttonStates; + } + + /** + * Init the state of buttons + */ + public void initButtonsState() { + final boolean started = false; + Map buttonStates = new HashMap(); + buttonStates.put(ActionNames.ACTION_START, Boolean.valueOf(!started)); + buttonStates.put(ActionNames.ACTION_START_NO_TIMERS, Boolean.valueOf(!started)); + buttonStates.put(ActionNames.ACTION_STOP, Boolean.valueOf(started)); + buttonStates.put(ActionNames.ACTION_SHUTDOWN, Boolean.valueOf(started)); + buttonStates.put(ActionNames.UNDO, Boolean.FALSE); + buttonStates.put(ActionNames.REDO, Boolean.FALSE); + buttonStates.put(ActionNames.REMOTE_START_ALL, Boolean.valueOf(!started)); + buttonStates.put(ActionNames.REMOTE_STOP_ALL, Boolean.valueOf(started)); + buttonStates.put(ActionNames.REMOTE_SHUT_ALL, Boolean.valueOf(started)); + updateButtons(buttonStates); + } + + + /** + * Change state of buttons on local test + * + * @param started + * Flag whether local test is started + */ + public void setLocalTestStarted(boolean started) { + Map buttonStates = new HashMap(3); + buttonStates.put(ActionNames.ACTION_START, Boolean.valueOf(!started)); + buttonStates.put(ActionNames.ACTION_START_NO_TIMERS, Boolean.valueOf(!started)); + buttonStates.put(ActionNames.ACTION_STOP, Boolean.valueOf(started)); + buttonStates.put(ActionNames.ACTION_SHUTDOWN, Boolean.valueOf(started)); + updateButtons(buttonStates); + } + + /** + * Change state of buttons on remote test + * + * @param started + * Flag whether the test is started + */ + public void setRemoteTestStarted(boolean started) { + Map buttonStates = new HashMap(3); + buttonStates.put(ActionNames.REMOTE_START_ALL, Boolean.valueOf(!started)); + buttonStates.put(ActionNames.REMOTE_STOP_ALL, Boolean.valueOf(started)); + buttonStates.put(ActionNames.REMOTE_SHUT_ALL, Boolean.valueOf(started)); + updateButtons(buttonStates); + } + + /** + * Change state of buttons after undo or redo + * + * @param canUndo + * Flag whether the button corresponding to + * {@link ActionNames#UNDO} should be enabled + * @param canRedo + * Flag whether the button corresponding to + * {@link ActionNames#REDO} should be enabled + */ + public void updateUndoRedoIcons(boolean canUndo, boolean canRedo) { + Map buttonStates = new HashMap(2); + buttonStates.put(ActionNames.UNDO, Boolean.valueOf(canUndo)); + buttonStates.put(ActionNames.REDO, Boolean.valueOf(canRedo)); + updateButtons(buttonStates); + } + + /** + * Set buttons to a given state + * + * @param buttonStates + * {@link Map} of button names and their states + */ + private void updateButtons(Map buttonStates) { + Component[] components = getComponents(); + for (int i = 0; i < components.length; i++) { + if (components[i] instanceof JButton) { + JButton button = (JButton) components[i]; + Boolean enabled = buttonStates.get(button.getActionCommand()); + if (enabled != null) { + button.setEnabled(enabled.booleanValue()); + } + } + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/JSyntaxTextArea.java b/src/core/org/apache/jmeter/gui/util/JSyntaxTextArea.java new file mode 100644 index 00000000000..43658ae0616 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/JSyntaxTextArea.java @@ -0,0 +1,147 @@ +/* + * 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.util; + +import java.util.Properties; + +import org.apache.jmeter.util.JMeterUtils; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rtextarea.RUndoManager; + +/** + * Utility class to handle RSyntaxTextArea code + */ +public class JSyntaxTextArea extends RSyntaxTextArea { + + private static final long serialVersionUID = 210L; + + private final Properties languageProperties = JMeterUtils.loadProperties("org/apache/jmeter/gui/util/textarea.properties"); //$NON-NLS-1$; + + private final boolean disableUndo; + private static final boolean WRAP_STYLE_WORD = JMeterUtils.getPropDefault("jsyntaxtextarea.wrapstyleword", true); + private static final boolean LINE_WRAP = JMeterUtils.getPropDefault("jsyntaxtextarea.linewrap", true); + private static final boolean CODE_FOLDING = JMeterUtils.getPropDefault("jsyntaxtextarea.codefolding", true); + private static final int MAX_UNDOS = JMeterUtils.getPropDefault("jsyntaxtextarea.maxundos", 50); + + @Deprecated + public JSyntaxTextArea() { + // For use by test code only + this(30, 50, false); + } + + /** + * Creates the default syntax highlighting text area. The following are set: + *
    + *
  • setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA)
  • + *
  • setCodeFoldingEnabled(true)
  • + *
  • setAntiAliasingEnabled(true)
  • + *
  • setLineWrap(true)
  • + *
  • setWrapStyleWord(true)
  • + *
+ * + * @param rows + * The number of rows for the text area + * @param cols + * The number of columns for the text area + */ + public JSyntaxTextArea(int rows, int cols) { + this(rows, cols, false); + } + + /** + * Creates the default syntax highlighting text area. The following are set: + *
    + *
  • setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA)
  • + *
  • setCodeFoldingEnabled(true)
  • + *
  • setAntiAliasingEnabled(true)
  • + *
  • setLineWrap(true)
  • + *
  • setWrapStyleWord(true)
  • + *
+ * + * @param rows + * The number of rows for the text area + * @param cols + * The number of columns for the text area + * @param disableUndo + * true to disable undo manager, defaults to false + */ + public JSyntaxTextArea(int rows, int cols, boolean disableUndo) { + super(rows, cols); + super.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + super.setCodeFoldingEnabled(CODE_FOLDING); + super.setAntiAliasingEnabled(true); + super.setLineWrap(LINE_WRAP); + super.setWrapStyleWord(WRAP_STYLE_WORD); + this.disableUndo = disableUndo; + if(disableUndo) { + // We need to do this to force recreation of undoManager which + // will use the disableUndo otherwise it would always be false + // See BUG 57440 + discardAllEdits(); + } + } + + /** + * Sets the language of the text area. + * + * @param language + * The language to be set + */ + public void setLanguage(String language) { + if(language == null) { + // TODO: Log a message? + // But how to find the name of the offending GUI element in the case of a TestBean? + super.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE); + } else { + final String style = languageProperties.getProperty(language); + if (style == null) { + super.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE); + } else { + super.setSyntaxEditingStyle(style); + } + } + } + + /** + * Override UndoManager to allow disabling if feature causes issues + * See https://github.com/bobbylight/RSyntaxTextArea/issues/19 + */ + @Override + protected RUndoManager createUndoManager() { + RUndoManager undoManager = super.createUndoManager(); + if(disableUndo) { + undoManager.setLimit(0); + } else { + undoManager.setLimit(MAX_UNDOS); + } + return undoManager; + } + + /** + * Sets initial text resetting undo history + * + * @param string + * The initial text to be set + */ + public void setInitialText(String string) { + setText(string); + discardAllEdits(); + } +} diff --git a/src/core/org/apache/jmeter/gui/util/JTextScrollPane.java b/src/core/org/apache/jmeter/gui/util/JTextScrollPane.java new file mode 100644 index 00000000000..55e4a8e4e69 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/JTextScrollPane.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import org.fife.ui.rtextarea.RTextScrollPane; + + +/** + * Utility class to handle RSyntaxTextArea code + */ +public class JTextScrollPane extends RTextScrollPane { + + private static final long serialVersionUID = 210L; + + @Deprecated + public JTextScrollPane() { + // for use by test code only + } + + public JTextScrollPane(JSyntaxTextArea scriptField) { + super(scriptField); + } + + public JTextScrollPane(JSyntaxTextArea scriptField, boolean foldIndicatorEnabled) { + super(scriptField); + super.setFoldIndicatorEnabled(foldIndicatorEnabled); + } + +} diff --git a/src/core/org/apache/jmeter/gui/util/MenuFactory.java b/src/core/org/apache/jmeter/gui/util/MenuFactory.java new file mode 100644 index 00000000000..feecbd00724 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/MenuFactory.java @@ -0,0 +1,743 @@ +/* + * 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.util; + +import java.awt.Component; +import java.awt.HeadlessException; +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.MenuElement; +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TestBeanGUI; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Printable; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public final class MenuFactory { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * Predefined strings for makeMenu(). + * These are used as menu categories in the menuMap Hashmap, + * and also for resource lookup in messages.properties + */ + public static final String THREADS = "menu_threads"; //$NON-NLS-1$ + + public static final String FRAGMENTS = "menu_fragments"; //$NON-NLS-1$ + + public static final String TIMERS = "menu_timer"; //$NON-NLS-1$ + + public static final String CONTROLLERS = "menu_logic_controller"; //$NON-NLS-1$ + + public static final String SAMPLERS = "menu_generative_controller"; //$NON-NLS-1$ + + public static final String CONFIG_ELEMENTS = "menu_config_element"; //$NON-NLS-1$ + + public static final String POST_PROCESSORS = "menu_post_processors"; //$NON-NLS-1$ + + public static final String PRE_PROCESSORS = "menu_pre_processors"; //$NON-NLS-1$ + + public static final String ASSERTIONS = "menu_assertions"; //$NON-NLS-1$ + + public static final String NON_TEST_ELEMENTS = "menu_non_test_elements"; //$NON-NLS-1$ + + public static final String LISTENERS = "menu_listener"; //$NON-NLS-1$ + + private static final Map> menuMap = + new HashMap>(); + + private static final Set elementsToSkip = new HashSet(); + + // MENU_ADD_xxx - controls which items are in the ADD menu + // MENU_PARENT_xxx - controls which items are in the Insert Parent menu + private static final String[] MENU_ADD_CONTROLLER = new String[] { + MenuFactory.CONTROLLERS, + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.TIMERS, + MenuFactory.PRE_PROCESSORS, + MenuFactory.SAMPLERS, + MenuFactory.POST_PROCESSORS, + MenuFactory.ASSERTIONS, + MenuFactory.LISTENERS, + }; + + private static final String[] MENU_PARENT_CONTROLLER = new String[] { + MenuFactory.CONTROLLERS }; + + private static final String[] MENU_ADD_SAMPLER = new String[] { + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.TIMERS, + MenuFactory.PRE_PROCESSORS, + MenuFactory.POST_PROCESSORS, + MenuFactory.ASSERTIONS, + MenuFactory.LISTENERS, + }; + + private static final String[] MENU_PARENT_SAMPLER = new String[] { + MenuFactory.CONTROLLERS }; + + private static final List timers, controllers, samplers, threads, + fragments,configElements, assertions, listeners, nonTestElements, + postProcessors, preProcessors; + + static { + threads = new LinkedList(); + fragments = new LinkedList(); + timers = new LinkedList(); + controllers = new LinkedList(); + samplers = new LinkedList(); + configElements = new LinkedList(); + assertions = new LinkedList(); + listeners = new LinkedList(); + postProcessors = new LinkedList(); + preProcessors = new LinkedList(); + nonTestElements = new LinkedList(); + menuMap.put(THREADS, threads); + menuMap.put(FRAGMENTS, fragments); + menuMap.put(TIMERS, timers); + menuMap.put(ASSERTIONS, assertions); + menuMap.put(CONFIG_ELEMENTS, configElements); + menuMap.put(CONTROLLERS, controllers); + menuMap.put(LISTENERS, listeners); + menuMap.put(NON_TEST_ELEMENTS, nonTestElements); + menuMap.put(SAMPLERS, samplers); + menuMap.put(POST_PROCESSORS, postProcessors); + menuMap.put(PRE_PROCESSORS, preProcessors); + try { + String[] classesToSkip = + JOrphanUtils.split(JMeterUtils.getPropDefault("not_in_menu", ""), ","); //$NON-NLS-1$ + for (int i = 0; i < classesToSkip.length; i++) { + elementsToSkip.add(classesToSkip[i].trim()); + } + + initializeMenus(); + sortPluginMenus(); + } catch (Throwable e) { + log.error("", e); + if (e instanceof Error){ + throw (Error) e; + } + if (e instanceof RuntimeException){ + throw (RuntimeException) e; + } + } + } + + /** + * Private constructor to prevent instantiation. + */ + private MenuFactory() { + } + + public static void addEditMenu(JPopupMenu menu, boolean removable) { + addSeparator(menu); + if (removable) { + menu.add(makeMenuItemRes("cut", ActionNames.CUT, KeyStrokes.CUT)); //$NON-NLS-1$ + } + menu.add(makeMenuItemRes("copy", ActionNames.COPY, KeyStrokes.COPY)); //$NON-NLS-1$ + menu.add(makeMenuItemRes("paste", ActionNames.PASTE, KeyStrokes.PASTE)); //$NON-NLS-1$ + menu.add(makeMenuItemRes("duplicate", ActionNames.DUPLICATE, KeyStrokes.DUPLICATE)); //$NON-NLS-1$ + menu.add(makeMenuItemRes("reset_gui", ActionNames.RESET_GUI )); //$NON-NLS-1$ + if (removable) { + menu.add(makeMenuItemRes("remove", ActionNames.REMOVE, KeyStrokes.REMOVE)); //$NON-NLS-1$ + } + } + + public static void addPasteResetMenu(JPopupMenu menu) { + addSeparator(menu); + menu.add(makeMenuItemRes("paste", ActionNames.PASTE, KeyStrokes.PASTE)); //$NON-NLS-1$ + menu.add(makeMenuItemRes("reset_gui", ActionNames.RESET_GUI )); //$NON-NLS-1$ + } + + public static void addFileMenu(JPopupMenu pop) { + addFileMenu(pop, true); + } + + /** + * @param menu JPopupMenu + * @param addSaveTestFragmentMenu Add Save as Test Fragment menu if true + */ + public static void addFileMenu(JPopupMenu menu, boolean addSaveTestFragmentMenu) { + // the undo/redo as a standard goes first in Edit menus + // maybe there's better place for them in JMeter? + addUndoItems(menu); + + addSeparator(menu); + menu.add(makeMenuItemRes("open", ActionNames.OPEN));// $NON-NLS-1$ + menu.add(makeMenuItemRes("menu_merge", ActionNames.MERGE));// $NON-NLS-1$ + menu.add(makeMenuItemRes("save_as", ActionNames.SAVE_AS));// $NON-NLS-1$ + if(addSaveTestFragmentMenu) { + menu.add(makeMenuItemRes("save_as_test_fragment", ActionNames.SAVE_AS_TEST_FRAGMENT));// $NON-NLS-1$ + } + addSeparator(menu); + JMenuItem savePicture = makeMenuItemRes("save_as_image",// $NON-NLS-1$ + ActionNames.SAVE_GRAPHICS, + KeyStrokes.SAVE_GRAPHICS); + menu.add(savePicture); + if (!(GuiPackage.getInstance().getCurrentGui() instanceof Printable)) { + savePicture.setEnabled(false); + } + + JMenuItem savePictureAll = makeMenuItemRes("save_as_image_all",// $NON-NLS-1$ + ActionNames.SAVE_GRAPHICS_ALL, + KeyStrokes.SAVE_GRAPHICS_ALL); + menu.add(savePictureAll); + + addSeparator(menu); + + JMenuItem disabled = makeMenuItemRes("disable", ActionNames.DISABLE);// $NON-NLS-1$ + JMenuItem enabled = makeMenuItemRes("enable", ActionNames.ENABLE);// $NON-NLS-1$ + boolean isEnabled = GuiPackage.getInstance().getTreeListener().getCurrentNode().isEnabled(); + if (isEnabled) { + disabled.setEnabled(true); + enabled.setEnabled(false); + } else { + disabled.setEnabled(false); + enabled.setEnabled(true); + } + menu.add(enabled); + menu.add(disabled); + JMenuItem toggle = makeMenuItemRes("toggle", ActionNames.TOGGLE, KeyStrokes.TOGGLE);// $NON-NLS-1$ + menu.add(toggle); + addSeparator(menu); + menu.add(makeMenuItemRes("help", ActionNames.HELP));// $NON-NLS-1$ + } + + /** + * Add undo / redo + * @param menu JPopupMenu + */ + private static void addUndoItems(JPopupMenu menu) { + addSeparator(menu); + + JMenuItem undo = makeMenuItemRes("undo", ActionNames.UNDO); //$NON-NLS-1$ + undo.setEnabled(GuiPackage.getInstance().canUndo()); + menu.add(undo); + + JMenuItem redo = makeMenuItemRes("redo", ActionNames.REDO); //$NON-NLS-1$ + // TODO: we could even show some hints on action being undone here if this will be required (by passing those hints into history records) + redo.setEnabled(GuiPackage.getInstance().canRedo()); + menu.add(redo); + } + + + public static JMenu makeMenus(String[] categories, String label, String actionCommand) { + JMenu addMenu = new JMenu(label); + for (int i = 0; i < categories.length; i++) { + addMenu.add(makeMenu(categories[i], actionCommand)); + } + GuiUtils.makeScrollableMenu(addMenu); + return addMenu; + } + + public static JPopupMenu getDefaultControllerMenu() { + JPopupMenu pop = new JPopupMenu(); + pop.add(MenuFactory.makeMenus(MENU_ADD_CONTROLLER, + JMeterUtils.getResString("add"),// $NON-NLS-1$ + ActionNames.ADD)); + pop.add(makeMenus(MENU_PARENT_CONTROLLER, + JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$ + ActionNames.ADD_PARENT)); + pop.add(makeMenus(MENU_PARENT_CONTROLLER, + JMeterUtils.getResString("change_parent"),// $NON-NLS-1$ + ActionNames.CHANGE_PARENT)); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultSamplerMenu() { + JPopupMenu pop = new JPopupMenu(); + pop.add(MenuFactory.makeMenus(MENU_ADD_SAMPLER, + JMeterUtils.getResString("add"),// $NON-NLS-1$ + ActionNames.ADD)); + pop.add(makeMenus(MENU_PARENT_SAMPLER, + JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$ + ActionNames.ADD_PARENT)); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultConfigElementMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultVisualizerMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultTimerMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultAssertionMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultExtractorMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultMenu() { // if type is unknown + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + /** + * Create a menu from a menu category. + * + * @param category - predefined string (used as key for menuMap HashMap and messages.properties lookup) + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @return the menu + */ + public static JMenu makeMenu(String category, String actionCommand) { + return makeMenu(menuMap.get(category), actionCommand, JMeterUtils.getResString(category)); + } + + /** + * Create a menu from a collection of items. + * + * @param menuInfo - collection of MenuInfo items + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @param menuName The name of the newly created menu + * @return the menu + */ + public static JMenu makeMenu(Collection menuInfo, String actionCommand, String menuName) { + JMenu menu = new JMenu(menuName); + for (MenuInfo info : menuInfo) { + menu.add(makeMenuItem(info, actionCommand)); + } + GuiUtils.makeScrollableMenu(menu); + return menu; + } + + public static void setEnabled(JMenu menu) { + if (menu.getSubElements().length == 0) { + menu.setEnabled(false); + } + } + + /** + * Create a single menu item + * + * @param label for the MenuItem + * @param name for the MenuItem + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @return the menu item + */ + public static JMenuItem makeMenuItem(String label, String name, String actionCommand) { + JMenuItem newMenuChoice = new JMenuItem(label); + newMenuChoice.setName(name); + newMenuChoice.addActionListener(ActionRouter.getInstance()); + if (actionCommand != null) { + newMenuChoice.setActionCommand(actionCommand); + } + + return newMenuChoice; + } + + /** + * Create a single menu item from the resource name. + * + * @param resource for the MenuItem + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @return the menu item + */ + public static JMenuItem makeMenuItemRes(String resource, String actionCommand) { + JMenuItem newMenuChoice = new JMenuItem(JMeterUtils.getResString(resource)); + newMenuChoice.setName(resource); + newMenuChoice.addActionListener(ActionRouter.getInstance()); + if (actionCommand != null) { + newMenuChoice.setActionCommand(actionCommand); + } + + return newMenuChoice; + } + + /** + * Create a single menu item from a MenuInfo object + * + * @param info the MenuInfo object + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @return the menu item + */ + public static Component makeMenuItem(MenuInfo info, String actionCommand) { + JMenuItem newMenuChoice = new JMenuItem(info.getLabel()); + newMenuChoice.setName(info.getClassName()); + newMenuChoice.addActionListener(ActionRouter.getInstance()); + if (actionCommand != null) { + newMenuChoice.setActionCommand(actionCommand); + } + + return newMenuChoice; + } + + public static JMenuItem makeMenuItemRes(String resource, String actionCommand, KeyStroke accel) { + JMenuItem item = makeMenuItemRes(resource, actionCommand); + item.setAccelerator(accel); + return item; + } + + public static JMenuItem makeMenuItem(String label, String name, String actionCommand, KeyStroke accel) { + JMenuItem item = makeMenuItem(label, name, actionCommand); + item.setAccelerator(accel); + return item; + } + + private static void initializeMenus() { + try { + List guiClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { + JMeterGUIComponent.class, TestBean.class }); + Collections.sort(guiClasses); + for (String name : guiClasses) { + + /* + * JMeterTreeNode and TestBeanGUI are special GUI classes, and + * aren't intended to be added to menus + * + * TODO: find a better way of checking this + */ + if (name.endsWith("JMeterTreeNode") // $NON-NLS-1$ + || name.endsWith("TestBeanGUI")) {// $NON-NLS-1$ + continue;// Don't try to instantiate these + } + + if (elementsToSkip.contains(name)) { // No point instantiating class + log.info("Skipping " + name); + continue; + } + + boolean hideBean = false; // Should the TestBean be hidden? + + JMeterGUIComponent item; + try { + Class c = Class.forName(name); + if (TestBean.class.isAssignableFrom(c)) { + TestBeanGUI tbgui = new TestBeanGUI(c); + hideBean = tbgui.isHidden() || (tbgui.isExpert() && !JMeterUtils.isExpertMode()); + item = tbgui; + } else { + item = (JMeterGUIComponent) c.newInstance(); + } + } catch (NoClassDefFoundError e) { + log.warn("Missing jar? Could not create " + name + ". " + e); + continue; + } catch (Throwable e) { + log.warn("Could not instantiate " + name, e); + if (e instanceof Error){ + throw (Error) e; + } + if (e instanceof RuntimeException){ + if (!(e instanceof HeadlessException)) { // Allow headless testing + throw (RuntimeException) e; + } + } + continue; + } + if (hideBean || elementsToSkip.contains(item.getStaticLabel())) { + log.info("Skipping " + name); + continue; + } else { + elementsToSkip.add(name); // Don't add it again + } + Collection categories = item.getMenuCategories(); + if (categories == null) { + log.debug(name + " participates in no menus."); + continue; + } + if (categories.contains(THREADS)) { + threads.add(new MenuInfo(item, name)); + } + if (categories.contains(FRAGMENTS)) { + fragments.add(new MenuInfo(item, name)); + } + if (categories.contains(TIMERS)) { + timers.add(new MenuInfo(item, name)); + } + + if (categories.contains(POST_PROCESSORS)) { + postProcessors.add(new MenuInfo(item, name)); + } + + if (categories.contains(PRE_PROCESSORS)) { + preProcessors.add(new MenuInfo(item, name)); + } + + if (categories.contains(CONTROLLERS)) { + controllers.add(new MenuInfo(item, name)); + } + + if (categories.contains(SAMPLERS)) { + samplers.add(new MenuInfo(item, name)); + } + + if (categories.contains(NON_TEST_ELEMENTS)) { + nonTestElements.add(new MenuInfo(item, name)); + } + + if (categories.contains(LISTENERS)) { + listeners.add(new MenuInfo(item, name)); + } + + if (categories.contains(CONFIG_ELEMENTS)) { + configElements.add(new MenuInfo(item, name)); + } + if (categories.contains(ASSERTIONS)) { + assertions.add(new MenuInfo(item, name)); + } + + } + } catch (IOException e) { + log.error("", e); + } + } + + private static void addSeparator(JPopupMenu menu) { + MenuElement[] elements = menu.getSubElements(); + if ((elements.length > 0) && !(elements[elements.length - 1] instanceof JPopupMenu.Separator)) { + menu.addSeparator(); + } + } + + /** + * Determine whether or not nodes can be added to this parent. + * + * Used by Merge + * + * @param parentNode + * The {@link JMeterTreeNode} to test, if a new element can be + * added to it + * @param element + * - top-level test element to be added + * @return whether it is OK to add the element to this parent + */ + public static boolean canAddTo(JMeterTreeNode parentNode, TestElement element) { + JMeterTreeNode node = new JMeterTreeNode(element, null); + return canAddTo(parentNode, new JMeterTreeNode[]{node}); + } + + /** + * Determine whether or not nodes can be added to this parent. + * + * Used by DragNDrop and Paste. + * + * @param parentNode + * The {@link JMeterTreeNode} to test, if nodes[] + * can be added to it + * @param nodes + * - array of nodes that are to be added + * @return whether it is OK to add the dragged nodes to this parent + */ + public static boolean canAddTo(JMeterTreeNode parentNode, JMeterTreeNode nodes[]) { + if (null == parentNode) { + return false; + } + if (foundClass(nodes, new Class[]{WorkBench.class})){// Can't add a Workbench anywhere + return false; + } + if (foundClass(nodes, new Class[]{TestPlan.class})){// Can't add a TestPlan anywhere + return false; + } + TestElement parent = parentNode.getTestElement(); + + // Force TestFragment to only be pastable under a Test Plan + if (foundClass(nodes, new Class[]{org.apache.jmeter.control.TestFragmentController.class})){ + if (parent instanceof TestPlan) { + return true; + } + return false; + } + + if (parent instanceof WorkBench) {// allow everything else + return true; + } + if (parent instanceof TestPlan) { + if (foundClass(nodes, + new Class[]{Sampler.class, Controller.class}, // Samplers and Controllers need not apply ... + org.apache.jmeter.threads.AbstractThreadGroup.class) // but AbstractThreadGroup (Controller) is OK + ){ + return false; + } + return true; + } + // AbstractThreadGroup is only allowed under a TestPlan + if (foundClass(nodes, new Class[]{org.apache.jmeter.threads.AbstractThreadGroup.class})){ + return false; + } + if (parent instanceof Controller) {// Includes thread group; anything goes + return true; + } + if (parent instanceof Sampler) {// Samplers and Controllers need not apply ... + if (foundClass(nodes, new Class[]{Sampler.class, Controller.class})){ + return false; + } + return true; + } + // All other + return false; + } + + // Is any node an instance of one of the classes? + private static boolean foundClass(JMeterTreeNode nodes[],Class classes[]){ + for (int i = 0; i < nodes.length; i++) { + JMeterTreeNode node = nodes[i]; + for (int j=0; j < classes.length; j++) { + if (classes[j].isInstance(node.getUserObject())){ + return true; + } + } + } + return false; + } + + // Is any node an instance of one of the classes, but not an exception? + private static boolean foundClass(JMeterTreeNode nodes[],Class classes[], Class except){ + for (int i = 0; i < nodes.length; i++) { + JMeterTreeNode node = nodes[i]; + Object userObject = node.getUserObject(); + if (!except.isInstance(userObject)) { + for (int j=0; j < classes.length; j++) { + if (classes[j].isInstance(userObject)){ + return true; + } + } + } + } + return false; + } + + // Methods used for Test cases + static int menuMap_size() { + return menuMap.size(); + } + static int assertions_size() { + return assertions.size(); + } + static int configElements_size() { + return configElements.size(); + } + static int controllers_size() { + return controllers.size(); + } + static int listeners_size() { + return listeners.size(); + } + static int nonTestElements_size() { + return nonTestElements.size(); + } + static int postProcessors_size() { + return postProcessors.size(); + } + static int preProcessors_size() { + return preProcessors.size(); + } + static int samplers_size() { + return samplers.size(); + } + static int timers_size() { + return timers.size(); + } + static int elementsToSkip_size() { + return elementsToSkip.size(); + } + + /** + * Menu sort helper class + */ + private static class MenuInfoComparator implements Comparator, Serializable { + private static final long serialVersionUID = 1L; + private final boolean caseBlind; + MenuInfoComparator(boolean caseBlind){ + this.caseBlind = caseBlind; + } + @Override + public int compare(MenuInfo o1, MenuInfo o2) { + String lab1 = o1.getLabel(); + String lab2 = o2.getLabel(); + if (caseBlind) { + return lab1.toLowerCase(Locale.ENGLISH).compareTo(lab2.toLowerCase(Locale.ENGLISH)); + } + return lab1.compareTo(lab2); + } + } + + /** + * Sort loaded menus; all but THREADS are sorted case-blind. + * [This is so Thread Group appears before setUp and tearDown] + */ + private static void sortPluginMenus() { + for(Entry> me : menuMap.entrySet()){ + Collections.sort(me.getValue(), new MenuInfoComparator(!me.getKey().equals(THREADS))); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/MenuInfo.java b/src/core/org/apache/jmeter/gui/util/MenuInfo.java new file mode 100644 index 00000000000..c9004be3041 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/MenuInfo.java @@ -0,0 +1,56 @@ +/* + * 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.util; + +import org.apache.jmeter.gui.JMeterGUIComponent; + +/** + * Class to hold additional information needed when building the GUI lists + */ +public class MenuInfo { + + private final String label; + + private final String className; + + private final JMeterGUIComponent guiComp; + + public MenuInfo(String displayLabel, String classFullName) { + label = displayLabel; + className = classFullName; + guiComp = null; + } + + public MenuInfo(JMeterGUIComponent item, String classFullName) { + label = item.getStaticLabel(); + className = classFullName; + guiComp = item; + } + + public String getLabel(){ + if (guiComp != null) { + return guiComp.getStaticLabel(); + } + return label; + } + + public String getClassName(){ + return className; + } +} diff --git a/src/core/org/apache/jmeter/gui/util/NumberFieldErrorListener.java b/src/core/org/apache/jmeter/gui/util/NumberFieldErrorListener.java new file mode 100644 index 00000000000..a1ac99c04a4 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/NumberFieldErrorListener.java @@ -0,0 +1,58 @@ +/* + * 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.util; + +import java.awt.Component; +import java.awt.TextComponent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; + +import javax.swing.JOptionPane; +import javax.swing.text.JTextComponent; + +import org.apache.jmeter.util.JMeterUtils; + +public class NumberFieldErrorListener extends FocusAdapter { + + private static final NumberFieldErrorListener listener = new NumberFieldErrorListener(); + + public static NumberFieldErrorListener getNumberFieldErrorListener() { + return listener; + } + + @Override + public void focusLost(FocusEvent e) { + Component source = (Component) e.getSource(); + String text = ""; + if (source instanceof JTextComponent) { + text = ((JTextComponent) source).getText(); + } else if (source instanceof TextComponent) { + text = ((TextComponent) source).getText(); + } + try { + Integer.parseInt(text); + } catch (NumberFormatException nfe) { + JOptionPane.showMessageDialog(source, + JMeterUtils.getResString("you_must_enter_a_valid_number"), //$NON-NLS-1$ + JMeterUtils.getResString("invalid_data"), //$NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + FocusRequester.requestFocus(source); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/PowerTableModel.java b/src/core/org/apache/jmeter/gui/util/PowerTableModel.java new file mode 100644 index 00000000000..8d1c9d85b00 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/PowerTableModel.java @@ -0,0 +1,282 @@ +/* + * 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.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +import javax.swing.table.DefaultTableModel; + +import org.apache.jorphan.collections.Data; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class PowerTableModel extends DefaultTableModel { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + private Data model = new Data(); + + private Class[] columnClasses; + + public PowerTableModel(String[] headers, Class[] classes) { + if (headers.length != classes.length){ + throw new IllegalArgumentException("Header and column array sizes differ"); + } + model.setHeaders(headers); + columnClasses = classes; + } + + public PowerTableModel() { + } + + public void setRowValues(int row, Object[] values) { + if (values.length != model.getHeaderCount()){ + throw new IllegalArgumentException("Incorrect number of data items"); + } + model.setCurrentPos(row); + for (int i = 0; i < values.length; i++) { + model.addColumnValue(model.getHeaders()[i], values[i]); + } + } + + public Data getData() { + return model; + } + + public void addNewColumn(String colName, Class colClass) { + model.addHeader(colName); + Class[] newClasses = new Class[columnClasses.length + 1]; + System.arraycopy(columnClasses, 0, newClasses, 0, columnClasses.length); + newClasses[newClasses.length - 1] = colClass; + columnClasses = newClasses; + Object defaultValue = createDefaultValue(columnClasses.length - 1); + model.setColumnData(colName, defaultValue); + this.fireTableStructureChanged(); + } + + @Override + public void removeRow(int row) { + log.debug("remove row: " + row); + if (model.size() > row) { + log.debug("Calling remove row on Data"); + model.removeRow(row); + } + } + + public void removeColumn(int col) { + model.removeColumn(col); + this.fireTableStructureChanged(); + } + + public void setColumnData(int col, List data) { + model.setColumnData(col, data); + } + + public List getColumnData(String colName) { + return model.getColumnAsObjectArray(colName); + } + + public void clearData() { + String[] headers = model.getHeaders(); + model = new Data(); + model.setHeaders(headers); + this.fireTableDataChanged(); + } + + @Override + public void addRow(Object data[]) { + if (data.length != model.getHeaderCount()){ + throw new IllegalArgumentException("Incorrect number of data items"); + } + model.setCurrentPos(model.size()); + for (int i = 0; i < data.length; i++) { + model.addColumnValue(model.getHeaders()[i], data[i]); + } + } + + public void addNewRow() { + addRow(createDefaultRow()); + } + + private Object[] createDefaultRow() { + Object[] rowData = new Object[getColumnCount()]; + for (int i = 0; i < rowData.length; i++) { + rowData[i] = createDefaultValue(i); + } + return rowData; + } + + public Object[] getRowData(int row) { + Object[] rowData = new Object[getColumnCount()]; + for (int i = 0; i < rowData.length; i++) { + rowData[i] = model.getColumnValue(i, row); + } + return rowData; + } + + private Object createDefaultValue(int i) { + Class colClass = getColumnClass(i); + try { + return colClass.newInstance(); + } catch (Exception e) { + try { + Constructor constr = colClass.getConstructor(new Class[] { String.class }); + return constr.newInstance(new Object[] { "" }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Integer.TYPE }); + return constr.newInstance(new Object[] { Integer.valueOf(0) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Long.TYPE }); + return constr.newInstance(new Object[] { Long.valueOf(0L) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Boolean.TYPE }); + return constr.newInstance(new Object[] { Boolean.FALSE }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Float.TYPE }); + return constr.newInstance(new Object[] { Float.valueOf(0F) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Double.TYPE }); + return constr.newInstance(new Object[] { Double.valueOf(0D) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Character.TYPE }); + return constr.newInstance(new Object[] { Character.valueOf(' ') }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Byte.TYPE }); + return constr.newInstance(new Object[] { Byte.valueOf(Byte.MIN_VALUE) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Short.TYPE }); + return constr.newInstance(new Object[] { Short.valueOf(Short.MIN_VALUE) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + } + return ""; + } + + /** + * Required by table model interface. + * + * @return the RowCount value + */ + @Override + public int getRowCount() { + if (model == null) { + return 0; + } + return model.size(); + } + + /** + * Required by table model interface. + * + * @return the ColumnCount value + */ + @Override + public int getColumnCount() { + return model.getHeaders().length; + } + + /** + * Required by table model interface. + * + * @return the ColumnName value + */ + @Override + public String getColumnName(int column) { + return model.getHeaders()[column]; + } + + @Override + public boolean isCellEditable(int row, int column) { + // all table cells are editable + return true; + } + + @Override + public Class getColumnClass(int column) { + return columnClasses[column]; + } + + /** + * Required by table model interface. return the ValueAt value + */ + @Override + public Object getValueAt(int row, int column) { + return model.getColumnValue(column, row); + } + + /** + * Sets the ValueAt attribute of the Arguments object. + * + * @param value + * the new ValueAt value + */ + @Override + public void setValueAt(Object value, int row, int column) { + if (row < model.size()) { + model.setCurrentPos(row); + model.addColumnValue(model.getHeaders()[column], value); + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/TextAreaCellRenderer.java b/src/core/org/apache/jmeter/gui/util/TextAreaCellRenderer.java new file mode 100644 index 00000000000..f0e87367f76 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/TextAreaCellRenderer.java @@ -0,0 +1,56 @@ +/* + * 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.util; + +import java.awt.Component; + +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.table.TableCellRenderer; + +public class TextAreaCellRenderer implements TableCellRenderer { + + private JTextArea rend = new JTextArea(""); + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, + int row, int column) { + if(value != null) { + rend = new JTextArea(value.toString()); + } else { + rend = new JTextArea(); + } + // Use two rows, so that we have room for horisontal scrollbar, if the text is one long line. Fix for 40371 + // This is not an optimal solution, but makes it possible to see the line if it is long + rend.setRows(2); + rend.revalidate(); + if (!hasFocus && !isSelected) { + rend.setBackground(JMeterColor.LAVENDER); + } + if (table.getRowHeight(row) < getPreferredHeight()) { + table.setRowHeight(row, getPreferredHeight()); + } + return rend; + } + + public int getPreferredHeight() { + return rend.getPreferredSize().height + 5; + } +} diff --git a/src/core/org/apache/jmeter/gui/util/TextAreaTableCellEditor.java b/src/core/org/apache/jmeter/gui/util/TextAreaTableCellEditor.java new file mode 100644 index 00000000000..7b5e04e7234 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/TextAreaTableCellEditor.java @@ -0,0 +1,328 @@ +/* + * 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.util; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.ItemEvent; +import java.awt.event.MouseEvent; +import java.io.Serializable; +import java.util.EventObject; + +import javax.swing.AbstractCellEditor; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.JTree; +import javax.swing.table.TableCellEditor; +import javax.swing.tree.TreeCellEditor; + +public class TextAreaTableCellEditor extends AbstractCellEditor implements TableCellEditor, TreeCellEditor { + private static final long serialVersionUID = 240L; + + // + // Instance Variables + // + + /** The Swing component being edited. */ + protected JTextArea editorComponent; + + /** + * The delegate class which handles all methods sent from the + * CellEditor. + */ + protected EditorDelegate delegate; + + /** + * An integer specifying the number of clicks needed to start editing. Even + * if clickCountToStart is defined as zero, it will not + * initiate until a click occurs. + */ + protected int clickCountToStart = 1; + + // + // Constructors + // + + /** + * Constructs a TableCellEditor that uses a text field. + */ + public TextAreaTableCellEditor() { + editorComponent = new JTextArea(); + editorComponent.setRows(3); + this.clickCountToStart = 2; + delegate = new EditorDelegate() { + private static final long serialVersionUID = 240L; + + @Override + public void setValue(Object value) { + editorComponent.setText((value != null) ? value.toString() : ""); + } + + @Override + public Object getCellEditorValue() { + return editorComponent.getText(); + } + }; + editorComponent.addFocusListener(delegate); + } + + /** + * Returns a reference to the editor component. + * + * @return the editor Component + */ + public Component getComponent() { + return editorComponent; + } + + // + // Modifying + // + + /** + * Specifies the number of clicks needed to start editing. + * + * @param count + * an int specifying the number of clicks needed to start editing + * @see #getClickCountToStart + */ + public void setClickCountToStart(int count) { + clickCountToStart = count; + } + + /** + * Returns the number of clicks needed to start editing. + * + * @return the number of clicks needed to start editing + */ + public int getClickCountToStart() { + return clickCountToStart; + } + + // + // Override the implementations of the superclass, forwarding all methods + // from the CellEditor interface to our delegate. + // + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#getCellEditorValue + */ + @Override + public Object getCellEditorValue() { + return delegate.getCellEditorValue(); + } + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#isCellEditable(EventObject) + */ + @Override + public boolean isCellEditable(EventObject anEvent) { + return delegate.isCellEditable(anEvent); + } + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#shouldSelectCell(EventObject) + */ + @Override + public boolean shouldSelectCell(EventObject anEvent) { + return delegate.shouldSelectCell(anEvent); + } + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#stopCellEditing + */ + @Override + public boolean stopCellEditing() { + return delegate.stopCellEditing(); + } + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#cancelCellEditing + */ + @Override + public void cancelCellEditing() { + delegate.cancelCellEditing(); + } + + // + // Implementing the TreeCellEditor Interface + // + + /** Implements the TreeCellEditor interface. */ + @Override + public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, + boolean leaf, int row) { + String stringValue = tree.convertValueToText(value, isSelected, expanded, leaf, row, false); + + delegate.setValue(stringValue); + return new JScrollPane(editorComponent); + } + + // + // Implementing the CellEditor Interface + // + /** Implements the TableCellEditor interface. */ + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + delegate.setValue(value); + return new JScrollPane(editorComponent); + } + + // + // Protected EditorDelegate class + // + + /** + * The protected EditorDelegate class. + */ + protected class EditorDelegate implements FocusListener, Serializable { + private static final long serialVersionUID = 240L; + + /** The value of this cell. */ + protected Object value; + + /** + * Returns the value of this cell. + * + * @return the value of this cell + */ + public Object getCellEditorValue() { + return value; + } + + /** + * Sets the value of this cell. + * + * @param value + * the new value of this cell + */ + public void setValue(Object value) { + this.value = value; + } + + /** + * Returns true if anEvent is not a + * MouseEvent. Otherwise, it returns true if the + * necessary number of clicks have occurred, and returns false + * otherwise. + * + * @param anEvent + * the event + * @return true if cell is ready for editing, false otherwise + * @see #setClickCountToStart(int) + * @see #shouldSelectCell + */ + public boolean isCellEditable(EventObject anEvent) { + if (anEvent instanceof MouseEvent) { + return ((MouseEvent) anEvent).getClickCount() >= clickCountToStart; + } + return true; + } + + /** + * Returns true to indicate that the editing cell may be selected. + * + * @param anEvent + * the event + * @return true + * @see #isCellEditable + */ + public boolean shouldSelectCell(EventObject anEvent) { + return true; + } + + /** + * Returns true to indicate that editing has begun. + * + * @param anEvent + * the event + * @return always true + */ + public boolean startCellEditing(EventObject anEvent) { + return true; + } + + /** + * Stops editing and returns true to indicate that editing has stopped. + * This method calls fireEditingStopped. + * + * @return true + */ + public boolean stopCellEditing() { + fireEditingStopped(); + return true; + } + + /** + * Cancels editing. This method calls fireEditingCanceled. + */ + public void cancelCellEditing() { + fireEditingCanceled(); + } + + /** + * When an action is performed, editing is ended. + * + * @param e + * the action event + * @see #stopCellEditing + */ + public void actionPerformed(ActionEvent e) { + TextAreaTableCellEditor.this.stopCellEditing(); + } + + /** + * When an item's state changes, editing is ended. + * + * @param e + * the action event + * @see #stopCellEditing + */ + public void itemStateChanged(ItemEvent e) { + TextAreaTableCellEditor.this.stopCellEditing(); + } + + @Override + public void focusLost(FocusEvent ev) { + TextAreaTableCellEditor.this.stopCellEditing(); + } + + @Override + public void focusGained(FocusEvent ev) { + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/TextBoxDialoger.java b/src/core/org/apache/jmeter/gui/util/TextBoxDialoger.java new file mode 100644 index 00000000000..7dbc56d53ff --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/TextBoxDialoger.java @@ -0,0 +1,232 @@ +/* + * 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.util; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.TableModel; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; + +/** + * Dialog text box to display some text in a box + * + */ +public class TextBoxDialoger implements ActionListener { + + private static final String CANCEL_COMMAND = "cancel_dialog"; // $NON-NLS-1$ + + private static final String SAVE_CLOSE_COMMAND = "save_close_dialog"; // $NON-NLS-1$ + + private static final String CLOSE_COMMAND = "close_dialog"; // $NON-NLS-1$ + + private static JDialog dialog; + + private JEditorPane textBox; + + private String originalText; + + private boolean editable = false; + + /** + * Dialog text box + */ + public TextBoxDialoger() { + // Empty box + init(""); //$NON-NLS-1$ + } + + /** + * Dialog text box + * @param text - text to display in a box + */ + public TextBoxDialoger(String text) { + init(text); + } + + /** + * Dialog text box + * @param text - text to display in a box + * @param editable - allow to modify text + */ + public TextBoxDialoger(String text, boolean editable) { + this.editable = editable; + init(text); + } + + private void init(String text) { + createDialogBox(); + setTextBox(text); + dialog.setVisible(true); + } + + private void createDialogBox() { + JFrame mainFrame = GuiPackage.getInstance().getMainFrame(); + String title = editable ? JMeterUtils.getResString("textbox_title_edit") //$NON-NLS-1$ + : JMeterUtils.getResString("textbox_title_view"); //$NON-NLS-1$ + dialog = new JDialog(mainFrame, title, true); // modal dialog box + + // Close action dialog box when tapping Escape key + JPanel content = (JPanel) dialog.getContentPane(); + content.registerKeyboardAction(this, KeyStrokes.ESC, + JComponent.WHEN_IN_FOCUSED_WINDOW); + + textBox = new JEditorPane(); + textBox.setEditable(editable); + + JScrollPane textBoxScrollPane = GuiUtils.makeScrollPane(textBox); + + JPanel btnBar = new JPanel(); + btnBar.setLayout(new FlowLayout(FlowLayout.RIGHT)); + if (editable) { + JButton cancelBtn = new JButton(JMeterUtils.getResString("textbox_cancel")); //$NON-NLS-1$ + cancelBtn.setActionCommand(CANCEL_COMMAND); + cancelBtn.addActionListener(this); + JButton saveBtn = new JButton(JMeterUtils.getResString("textbox_save_close")); //$NON-NLS-1$ + saveBtn.setActionCommand(SAVE_CLOSE_COMMAND); + saveBtn.addActionListener(this); + + btnBar.add(cancelBtn); + btnBar.add(saveBtn); + } else { + JButton closeBtn = new JButton(JMeterUtils.getResString("textbox_close")); //$NON-NLS-1$ + closeBtn.setActionCommand(CLOSE_COMMAND); + closeBtn.addActionListener(this); + + btnBar.add(closeBtn); + } + + // Prepare dialog box + Container panel = dialog.getContentPane(); + dialog.setMinimumSize(new Dimension(400, 250)); + panel.add(textBoxScrollPane, BorderLayout.CENTER); + panel.add(btnBar, BorderLayout.SOUTH); + + // determine location on screen + Point p = mainFrame.getLocationOnScreen(); + Dimension d1 = mainFrame.getSize(); + Dimension d2 = dialog.getSize(); + dialog.setLocation(p.x + (d1.width - d2.width) / 2, p.y + (d1.height - d2.height) / 2); + dialog.pack(); + } + + private void closeDialog() { + dialog.setVisible(false); + } + + @Override + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + + if (CANCEL_COMMAND.equals(command)) { + closeDialog(); + setTextBox(originalText); + } else { + // must be CLOSE or SAVE_CLOSE COMMANDS + closeDialog(); + } + + } + + public void setTextBox(String text) { + originalText = text; // text backup + textBox.setText(text); + } + + public String getTextBox() { + return textBox.getText(); + } + + /** + * Class to display a dialog box and cell's content + * when double click on a table's cell + * + */ + public static class TextBoxDoubleClick extends MouseAdapter { + + private JTable table = null; + + public TextBoxDoubleClick(JTable table) { + super(); + this.table = table; + } + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { // double click + TableModel tm = table.getModel(); + Object value = tm.getValueAt(table.getSelectedRow(), table.getSelectedColumn()); + new TextBoxDialoger(value.toString(), false); // view only + } + } + } + + /** + * Class to edit in a dialog box the cell's content + * when double (pressed) click on a table's cell which is editable + * + */ + public static class TextBoxDoubleClickPressed extends MouseAdapter { + + private JTable table = null; + + public TextBoxDoubleClickPressed(JTable table) { + super(); + this.table = table; + } + + @Override + public void mousePressed(MouseEvent e) { + if (e.getClickCount() == 2) { // double (pressed) click + TableModel tm = table.getModel(); + Object value = tm.getValueAt(table.getSelectedRow(), table.getSelectedColumn()); + if (value instanceof String) { + if (table.getCellEditor() != null) { + table.getCellEditor().cancelCellEditing(); // in main table (evt mousePressed because cell is editable) + } + TextBoxDialoger tbd = new TextBoxDialoger(value.toString(), true); + tm.setValueAt(tbd.getTextBox(), table.getSelectedRow(), table.getSelectedColumn()); + } // else do nothing (cell isn't a string to edit) + } + } + + } + + +} diff --git a/src/core/org/apache/jmeter/gui/util/TristateCheckBox.java b/src/core/org/apache/jmeter/gui/util/TristateCheckBox.java new file mode 100644 index 00000000000..1d6dddebb25 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/TristateCheckBox.java @@ -0,0 +1,377 @@ +/* + * 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.util; + +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.EventQueue; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.ItemEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.Serializable; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.ButtonModel; +import javax.swing.Icon; +import javax.swing.JCheckBox; +import javax.swing.SwingUtilities; +import javax.swing.UIDefaults; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.UIResource; +import javax.swing.plaf.metal.MetalLookAndFeel; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.NullProperty; + +// derived from: http://www.javaspecialists.eu/archive/Issue145.html + +public final class TristateCheckBox extends JCheckBox { + private static final long serialVersionUID = 1L; + // Listener on model changes to maintain correct focusability + private final class TSCBChangeListener implements ChangeListener, Serializable { + /** + * + */ + private static final long serialVersionUID = -3718373200229708535L; + + @Override + public void stateChanged(ChangeEvent e) { + TristateCheckBox.this.setFocusable( + getModel().isEnabled()); + } + } + private final ChangeListener enableListener = new TSCBChangeListener(); + + public TristateCheckBox() { + this(null, null, TristateState.DESELECTED); + } + + public TristateCheckBox(String text) { + this(text, null, TristateState.DESELECTED); + } + + public TristateCheckBox(String text, boolean selected) { + this(text, null, selected ? TristateState.SELECTED : TristateState.DESELECTED); + } + + public TristateCheckBox(String text, Icon icon, TristateState initial) { + this(text, icon, initial, false); + } + + // For testing only at present + TristateCheckBox(String text, Icon icon, TristateState initial, boolean original) { + super(text, icon); + + //Set default single model + setModel(new TristateButtonModel(initial, this, original)); + + // override action behaviour + super.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + TristateCheckBox.this.iterateState(); + } + }); + ActionMap actions = new ActionMapUIResource(); + actions.put("pressed", new AbstractAction() { + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent e) { + TristateCheckBox.this.iterateState(); + } + }); + actions.put("released", null); + SwingUtilities.replaceUIActionMap(this, actions); + } + + /** + * Set state depending on property + * @param element TestElement + * @param propName String property name + */ + public void setTristateFromProperty(TestElement element,String propName) { + JMeterProperty jmp = element.getProperty(propName); + if (jmp instanceof NullProperty) { + this.setIndeterminate(); + } else { + this.setSelected(jmp.getBooleanValue()); + } + } + + /** + * Sets a boolean property from a tristate checkbox. + * + * @param element the test element + * @param propName the property name + */ + public void setPropertyFromTristate(TestElement element, String propName) { + if (isIndeterminate()) { + element.removeProperty(propName); + } else { + element.setProperty(propName, isSelected()); + } + } + + // Next two methods implement new API by delegation to model + public void setIndeterminate() { + getTristateModel().setIndeterminate(); + } + + public boolean isIndeterminate() { + return getTristateModel().isIndeterminate(); + } + + public TristateState getState() { + return getTristateModel().getState(); + } + + //Overrides superclass method + @Override + public void setModel(ButtonModel newModel) { + super.setModel(newModel); + //Listen for enable changes + if (model instanceof TristateButtonModel) + model.addChangeListener(enableListener); + } + + //Empty override of superclass method + @Override + public synchronized void addMouseListener(MouseListener l) { + } + + // Mostly delegates to model + private void iterateState() { + //Maybe do nothing at all? + if (!getModel().isEnabled()) return; + + grabFocus(); + + // Iterate state + getTristateModel().iterateState(); + + // Fire ActionEvent + int modifiers = 0; + AWTEvent currentEvent = EventQueue.getCurrentEvent(); + if (currentEvent instanceof InputEvent) { + modifiers = ((InputEvent) currentEvent).getModifiers(); + } else if (currentEvent instanceof ActionEvent) { + modifiers = ((ActionEvent) currentEvent).getModifiers(); + } + fireActionPerformed(new ActionEvent(this, + ActionEvent.ACTION_PERFORMED, getText(), + System.currentTimeMillis(), modifiers)); + } + + //Convenience cast + public TristateButtonModel getTristateModel() { + return (TristateButtonModel) super.getModel(); + } + + private static class TristateButtonModel extends ToggleButtonModel { + + private static final long serialVersionUID = 1L; + private TristateState state = TristateState.DESELECTED; + private final TristateCheckBox tristateCheckBox; + private final Icon icon; + private final boolean original; + + public TristateButtonModel(TristateState initial, + TristateCheckBox tristateCheckBox, boolean original) { + setState(TristateState.DESELECTED); + this.tristateCheckBox = tristateCheckBox; + icon = new TristateCheckBoxIcon(); + this.original = original; + } + + public void setIndeterminate() { + setState(TristateState.INDETERMINATE); + } + + public boolean isIndeterminate() { + return state == TristateState.INDETERMINATE; + } + + // Overrides of superclass methods + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + // Restore state display + displayState(); + } + + @Override + public void setSelected(boolean selected) { + setState(selected ? + TristateState.SELECTED : TristateState.DESELECTED); + } + + // Empty overrides of superclass methods + @Override + public void setArmed(boolean b) { + } + + @Override + public void setPressed(boolean b) { + } + + void iterateState() { + setState(state.next()); + } + + private void setState(TristateState state) { + //Set internal state + this.state = state; + displayState(); + if (state == TristateState.INDETERMINATE && isEnabled()) { + // force the events to fire + + // Send ChangeEvent + fireStateChanged(); + + // Send ItemEvent + int indeterminate = 3; + fireItemStateChanged(new ItemEvent( + this, ItemEvent.ITEM_STATE_CHANGED, this, + indeterminate)); + } + } + + private void displayState() { + super.setSelected(state != TristateState.DESELECTED); + if (original) { + super.setArmed(state == TristateState.INDETERMINATE); + } else { + if (state == TristateState.INDETERMINATE) { + tristateCheckBox.setIcon(icon); // Needed for all but Nimbus + tristateCheckBox.setSelectedIcon(icon); // Nimbus works - after a fashion - with this + tristateCheckBox.setDisabledSelectedIcon(icon); // Nimbus works - after a fashion - with this + } else { // reset + if (tristateCheckBox!= null){ + tristateCheckBox.setIcon(null); + tristateCheckBox.setSelectedIcon(null); + tristateCheckBox.setDisabledSelectedIcon(null); // Nimbus works - after a fashion - with this + } + } + } + super.setPressed(state == TristateState.INDETERMINATE); + + } + + public TristateState getState() { + return state; + } + } + + // derived from: http://www.coderanch.com/t/342563/GUI/java/TriState-CheckBox + + private static class TristateCheckBoxIcon implements Icon, UIResource, Serializable { + + private static final long serialVersionUID = 290L; + + private final int iconHeight; + private final int iconWidth; + + public TristateCheckBoxIcon() { + // Assume that the UI has not changed since the checkbos was created + UIDefaults defaults = UIManager.getLookAndFeelDefaults(); + final Icon icon = (Icon) defaults.get("CheckBox.icon"); + iconHeight = icon.getIconHeight(); + iconWidth = icon.getIconWidth(); + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + JCheckBox cb = (JCheckBox) c; + ButtonModel model = cb.getModel(); + + // TODO fix up for Nimbus LAF + if (model.isEnabled()) { + if (model.isPressed() && model.isArmed()) { + g.setColor(MetalLookAndFeel.getControlShadow()); + g.fillRect(x, y, iconWidth - 1, iconHeight - 1); + drawPressed3DBorder(g, x, y, iconWidth, iconHeight); + } else { + drawFlush3DBorder(g, x, y, iconWidth, iconHeight); + } + g.setColor(MetalLookAndFeel.getControlInfo()); + } else { + g.setColor(MetalLookAndFeel.getControlShadow()); + g.drawRect(x, y, iconWidth - 1, iconHeight - 1); + } + + drawLine(g, x, y); +// drawCross(g, x, y); + + }// paintIcon + +// private void drawCross(Graphics g, int x, int y) { +// g.drawLine(x + (iconWidth - 4), y + 2, x + 3, y + (iconHeight - 5)); +// g.drawLine(x + (iconWidth - 4), y + 3, x + 3, y + (iconHeight - 4)); +// g.drawLine(x + 3, y + 2, x + (iconWidth - 4), y + (iconHeight - 5)); +// g.drawLine(x + 3, y + 3, x + (iconWidth - 4), y + (iconHeight - 4)); +// } + + private void drawLine(Graphics g, int x, int y) { + final int left = x + 2, right = x + (iconWidth - 4); + int height = y + iconHeight/2; + g.drawLine(left, height, right, height); + g.drawLine(left, height - 1, right, height - 1); + } + + private void drawFlush3DBorder(Graphics g, int x, int y, int w, int h) { + g.translate(x, y); + g.setColor(MetalLookAndFeel.getControlDarkShadow()); + g.drawRect(0, 0, w - 2, h - 2); + g.setColor(MetalLookAndFeel.getControlHighlight()); + g.drawRect(1, 1, w - 2, h - 2); + g.setColor(MetalLookAndFeel.getControl()); + g.drawLine(0, h - 1, 1, h - 2); + g.drawLine(w - 1, 0, w - 2, 1); + g.translate(-x, -y); + } + + private void drawPressed3DBorder(Graphics g, int x, int y, int w, int h) { + g.translate(x, y); + drawFlush3DBorder(g, 0, 0, w, h); + g.setColor(MetalLookAndFeel.getControlShadow()); + g.drawLine(1, 1, 1, h - 2); + g.drawLine(1, 1, w - 2, 1); + g.translate(-x, -y); + } + + @Override + public int getIconWidth() { + return iconWidth; + } + + @Override + public int getIconHeight() { + return iconHeight; + } + } +} diff --git a/src/core/org/apache/jmeter/gui/util/TristateState.java b/src/core/org/apache/jmeter/gui/util/TristateState.java new file mode 100644 index 00000000000..8f677c86d1d --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/TristateState.java @@ -0,0 +1,44 @@ +/* + * 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.util; + +//derived from: http://www.javaspecialists.eu/archive/Issue145.html + +public enum TristateState { + SELECTED { + @Override + public TristateState next() { + return INDETERMINATE; + } + }, + INDETERMINATE { + @Override + public TristateState next() { + return DESELECTED; + } + }, + DESELECTED { + @Override + public TristateState next() { + return SELECTED; + } + }; + + public abstract TristateState next(); +} diff --git a/src/core/org/apache/jmeter/gui/util/VerticalPanel.java b/src/core/org/apache/jmeter/gui/util/VerticalPanel.java new file mode 100644 index 00000000000..08217642d0d --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/VerticalPanel.java @@ -0,0 +1,72 @@ +/* + * 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.util; + +import java.awt.Color; +import java.awt.BorderLayout; +import java.awt.Component; + +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JPanel; + +public class VerticalPanel extends JPanel { + private static final long serialVersionUID = 240L; + + private final Box subPanel = Box.createVerticalBox(); + + private final float horizontalAlign; + + private final int vgap; + + public VerticalPanel() { + this(5, LEFT_ALIGNMENT); + } + + public VerticalPanel(Color bkg) { + this(); + subPanel.setBackground(bkg); + this.setBackground(bkg); + } + + public VerticalPanel(int vgap, float horizontalAlign) { + super(new BorderLayout()); + add(subPanel, BorderLayout.NORTH); + this.vgap = vgap; + this.horizontalAlign = horizontalAlign; + } + + /** + * {@inheritDoc} + */ + @Override + public Component add(Component c) { + // This won't work right if we remove components. But we don't, so I'm + // not going to worry about it right now. + if (vgap > 0 && subPanel.getComponentCount() > 0) { + subPanel.add(Box.createVerticalStrut(vgap)); + } + + if (c instanceof JComponent) { + ((JComponent) c).setAlignmentX(horizontalAlign); + } + + return subPanel.add(c); + } +} diff --git a/src/core/org/apache/jmeter/gui/util/textarea.properties b/src/core/org/apache/jmeter/gui/util/textarea.properties new file mode 100644 index 00000000000..be7b02c9a55 --- /dev/null +++ b/src/core/org/apache/jmeter/gui/util/textarea.properties @@ -0,0 +1,58 @@ +# +# 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. +# + +# Helper file for use with RSyntaxTextArea +# Converts language name to the string needed by RSyntaxTextArea#setSyntaxEditingStyle(String) +# N.B. values must agree with org.fife.ui.rsyntaxtextarea.SyntaxConstants + +applescript = text/plain +beanbasic = text/java +beanshell = text/java +bml = text/xml +# bsh=beanshell +bsh = text/java +ecmascript = text/actionscript +groovy = text/groovy +jacl = text/tcl +java = text/java +javaclass = text/java +javascript = text/javascript +jexl = text/java +jexl2 = text/java +jpython = text/python +js = text/javascript +jscript = text/javascript +judoscript = text/plain +jython = text/python +lisp = text/lisp +lotusscript = text/vb +lua = text/lua +netrexx = text/plain +objectscript = text/javascript +perl = text/perl +perlscript = text/perl +php = text/php +pnuts = text/java +prolog = text/plain +rexx = text/plain +rhino = text/javascript +ruby = text/ruby +sql = text/sql +text = text/plain +vbscript = text/vb +xml = text/xml +xslt = text/xml diff --git a/src/core/org/apache/jmeter/help.txt b/src/core/org/apache/jmeter/help.txt new file mode 100644 index 00000000000..cb5c751316e --- /dev/null +++ b/src/core/org/apache/jmeter/help.txt @@ -0,0 +1,37 @@ + +To run Apache JMeter in GUI mode: +Double-click on the ApacheJMeter.jar file. +If this doesn't work, open a command prompt and type: + +java -jar ApacheJMeter.jar [-p property-file] + +-------------------------------------------------- + +To run Apache JMeter in NON_GUI mode: +Open a command prompt (or Unix shell) and type: + +java -jar ApacheJMeter.jar -n -t test-file [-p property-file] [-l log-file] + +-------------------------------------------------- + +To tell Apache JMeter to use a proxy server: +Open a command prompt and type: + +java -jar ApacheJMeter.jar -H [your.proxy.server] -P [your proxy server port] + +--------------------------------------------------- + +To run Apache JMeter in server mode: +Open a command prompty and type + +java -jar ApacheJMeter.jar -s + +Or, use the provided script file: jmeter-server.bat(Windows)/jmeter-server(Linux) + +--------------------------------------------------- + +Please note that a script file is provided: +jmeter.bat(Windows)/jmeter(Linux) that can be +used in place of "java -jar ApacheJMeter.jar". Example: + +jmeter -p jmeter.properties -H my.proxy.com -P 9999 diff --git a/src/core/org/apache/jmeter/images/about.down.gif b/src/core/org/apache/jmeter/images/about.down.gif new file mode 100644 index 00000000000..5476fee9e55 Binary files /dev/null and b/src/core/org/apache/jmeter/images/about.down.gif differ diff --git a/src/core/org/apache/jmeter/images/about.off.gif b/src/core/org/apache/jmeter/images/about.off.gif new file mode 100644 index 00000000000..d49b882c113 Binary files /dev/null and b/src/core/org/apache/jmeter/images/about.off.gif differ diff --git a/src/core/org/apache/jmeter/images/about.on.gif b/src/core/org/apache/jmeter/images/about.on.gif new file mode 100644 index 00000000000..8bdefc56957 Binary files /dev/null and b/src/core/org/apache/jmeter/images/about.on.gif differ diff --git a/src/core/org/apache/jmeter/images/about.over.gif b/src/core/org/apache/jmeter/images/about.over.gif new file mode 100644 index 00000000000..84122ebb585 Binary files /dev/null and b/src/core/org/apache/jmeter/images/about.over.gif differ diff --git a/src/core/org/apache/jmeter/images/beaker.gif b/src/core/org/apache/jmeter/images/beaker.gif new file mode 100644 index 00000000000..8dc5e0c0bb2 Binary files /dev/null and b/src/core/org/apache/jmeter/images/beaker.gif differ diff --git a/src/core/org/apache/jmeter/images/clear.down.gif b/src/core/org/apache/jmeter/images/clear.down.gif new file mode 100644 index 00000000000..627f8402486 Binary files /dev/null and b/src/core/org/apache/jmeter/images/clear.down.gif differ diff --git a/src/core/org/apache/jmeter/images/clear.off.gif b/src/core/org/apache/jmeter/images/clear.off.gif new file mode 100644 index 00000000000..5fbbf8a6b69 Binary files /dev/null and b/src/core/org/apache/jmeter/images/clear.off.gif differ diff --git a/src/core/org/apache/jmeter/images/clear.on.gif b/src/core/org/apache/jmeter/images/clear.on.gif new file mode 100644 index 00000000000..674c8c73e97 Binary files /dev/null and b/src/core/org/apache/jmeter/images/clear.on.gif differ diff --git a/src/core/org/apache/jmeter/images/clear.over.gif b/src/core/org/apache/jmeter/images/clear.over.gif new file mode 100644 index 00000000000..ee3cd27f371 Binary files /dev/null and b/src/core/org/apache/jmeter/images/clear.over.gif differ diff --git a/src/core/org/apache/jmeter/images/clipboard.gif b/src/core/org/apache/jmeter/images/clipboard.gif new file mode 100644 index 00000000000..68b1f190e25 Binary files /dev/null and b/src/core/org/apache/jmeter/images/clipboard.gif differ diff --git a/src/core/org/apache/jmeter/images/ear.gif b/src/core/org/apache/jmeter/images/ear.gif new file mode 100644 index 00000000000..9194c92f420 Binary files /dev/null and b/src/core/org/apache/jmeter/images/ear.gif differ diff --git a/src/core/org/apache/jmeter/images/feather.gif b/src/core/org/apache/jmeter/images/feather.gif new file mode 100644 index 00000000000..2eef4417e4e Binary files /dev/null and b/src/core/org/apache/jmeter/images/feather.gif differ diff --git a/src/core/org/apache/jmeter/images/icon-apache.png b/src/core/org/apache/jmeter/images/icon-apache.png new file mode 100644 index 00000000000..8935a7a3e21 Binary files /dev/null and b/src/core/org/apache/jmeter/images/icon-apache.png differ diff --git a/src/core/org/apache/jmeter/images/icon.properties b/src/core/org/apache/jmeter/images/icon.properties new file mode 100644 index 00000000000..79816d891b4 --- /dev/null +++ b/src/core/org/apache/jmeter/images/icon.properties @@ -0,0 +1,29 @@ +# 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. + +# Icon definition file. +# Key: (super) class names +# Value: icon, optionally followed by space and then the disabled icon name +org.apache.jmeter.control.gui.TestPlanGui=org/apache/jmeter/images/beaker.gif +org.apache.jmeter.timers.gui.AbstractTimerGui=org/apache/jmeter/images/timer.gif +org.apache.jmeter.threads.gui.AbstractThreadGroupGui=org/apache/jmeter/images/thread.gif +org.apache.jmeter.visualizers.gui.AbstractListenerGui=org/apache/jmeter/images/meter.png +org.apache.jmeter.config.gui.AbstractConfigGui=org/apache/jmeter/images/testtubes.png +org.apache.jmeter.processor.gui.AbstractPreProcessorGui=org/apache/jmeter/images/leafnode.gif +org.apache.jmeter.processor.gui.AbstractPostProcessorGui=org/apache/jmeter/images/leafnodeflip.gif +org.apache.jmeter.control.gui.AbstractControllerGui=org/apache/jmeter/images/knob.gif +org.apache.jmeter.samplers.gui.AbstractSamplerGui=org/apache/jmeter/images/pipet.png +org.apache.jmeter.assertions.gui.AbstractAssertionGui=org/apache/jmeter/images/question.gif +org.apache.jmeter.control.gui.WorkBenchGui=org/apache/jmeter/images/clipboard.gif \ No newline at end of file diff --git a/src/core/org/apache/jmeter/images/icon_1.properties b/src/core/org/apache/jmeter/images/icon_1.properties new file mode 100644 index 00000000000..3d94f9472ff --- /dev/null +++ b/src/core/org/apache/jmeter/images/icon_1.properties @@ -0,0 +1,35 @@ +# 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. + +# Icon definition file. +# Key: (super) class names +# Value: icon, optionally followed by space and then the disabled icon name +org.apache.jmeter.control.gui.TestPlanGui=org/apache/jmeter/images/new/book.png org/apache/jmeter/images/new/book-grey.png +org.apache.jmeter.timers.gui.AbstractTimerGui=org/apache/jmeter/images/new/clock.png org/apache/jmeter/images/new/clock-grey.png +org.apache.jmeter.threads.gui.ThreadGroupGui=org/apache/jmeter/images/new/thread.png org/apache/jmeter/images/new/thread-grey.png +org.apache.jmeter.visualizers.gui.AbstractListenerGui=org/apache/jmeter/images/new/pencil.png org/apache/jmeter/images/new/pencil-grey.png +org.apache.jmeter.config.gui.AbstractConfigGui=org/apache/jmeter/images/new/puzzle.png org/apache/jmeter/images/new/puzzle-grey.png +org.apache.jmeter.processor.gui.AbstractPreProcessorGui=org/apache/jmeter/images/new/funnel.png org/apache/jmeter/images/new/funnel-grey.png +org.apache.jmeter.processor.gui.AbstractPostProcessorGui=org/apache/jmeter/images/new/mglass.png org/apache/jmeter/images/new/mglass-grey.png +org.apache.jmeter.control.gui.AbstractControllerGui=org/apache/jmeter/images/new/remote.png org/apache/jmeter/images/new/remote-grey.png +org.apache.jmeter.samplers.gui.AbstractSamplerGui=org/apache/jmeter/images/new/glasses.png org/apache/jmeter/images/new/glasses-grey.png +org.apache.jmeter.assertions.gui.AbstractAssertionGui=org/apache/jmeter/images/new/pin.png org/apache/jmeter/images/new/pin-grey.png +org.apache.jmeter.report.writers.gui.AbstractReportWriterGui=org/apache/jmeter/images/new/pin.png org/apache/jmeter/images/new/pin-grey.png +org.apache.jmeter.report.gui.ReportPageGui=org/apache/jmeter/images/new/scroll.png org/apache/jmeter/images/new/scroll-grey.png +org.apache.jmeter.report.gui.TableGui=org/apache/jmeter/images/new/table.png org/apache/jmeter/images/new/table-grey.png +org.apache.jmeter.report.gui.BarChartGui=org/apache/jmeter/images/new/barchart.png org/apache/jmeter/images/new/barchart-grey.png +org.apache.jmeter.report.gui.LineGraphGui=org/apache/jmeter/images/new/chart.png org/apache/jmeter/images/new/chart-grey.png +org.apache.jmeter.report.writers.gui.HTMLReportWriterGui=org/apache/jmeter/images/new/typewriter.png org/apache/jmeter/images/new/typewriter-grey.png +org.apache.jmeter.control.gui.ReportGui=org/apache/jmeter/images/new/blue-quill.png \ No newline at end of file diff --git a/src/core/org/apache/jmeter/images/icon_error_sml.gif b/src/core/org/apache/jmeter/images/icon_error_sml.gif new file mode 100644 index 00000000000..61132ef2b01 Binary files /dev/null and b/src/core/org/apache/jmeter/images/icon_error_sml.gif differ diff --git a/src/core/org/apache/jmeter/images/icon_success_sml.gif b/src/core/org/apache/jmeter/images/icon_success_sml.gif new file mode 100644 index 00000000000..52e85a430af Binary files /dev/null and b/src/core/org/apache/jmeter/images/icon_success_sml.gif differ diff --git a/src/core/org/apache/jmeter/images/icon_warning_sml.gif b/src/core/org/apache/jmeter/images/icon_warning_sml.gif new file mode 100644 index 00000000000..873bbb52cb9 Binary files /dev/null and b/src/core/org/apache/jmeter/images/icon_warning_sml.gif differ diff --git a/src/core/org/apache/jmeter/images/jmeter.jpg b/src/core/org/apache/jmeter/images/jmeter.jpg new file mode 100644 index 00000000000..fe9c4de3992 Binary files /dev/null and b/src/core/org/apache/jmeter/images/jmeter.jpg differ diff --git a/src/core/org/apache/jmeter/images/knob.gif b/src/core/org/apache/jmeter/images/knob.gif new file mode 100644 index 00000000000..62ef0080a7e Binary files /dev/null and b/src/core/org/apache/jmeter/images/knob.gif differ diff --git a/src/core/org/apache/jmeter/images/leafnode.gif b/src/core/org/apache/jmeter/images/leafnode.gif new file mode 100644 index 00000000000..33b22211f19 Binary files /dev/null and b/src/core/org/apache/jmeter/images/leafnode.gif differ diff --git a/src/core/org/apache/jmeter/images/leafnodeflip.gif b/src/core/org/apache/jmeter/images/leafnodeflip.gif new file mode 100644 index 00000000000..8fe04634e17 Binary files /dev/null and b/src/core/org/apache/jmeter/images/leafnodeflip.gif differ diff --git a/src/core/org/apache/jmeter/images/meter.png b/src/core/org/apache/jmeter/images/meter.png new file mode 100644 index 00000000000..6a86d2d53be Binary files /dev/null and b/src/core/org/apache/jmeter/images/meter.png differ diff --git a/src/core/org/apache/jmeter/images/monitor-active.gif b/src/core/org/apache/jmeter/images/monitor-active.gif new file mode 100644 index 00000000000..1a355f281a9 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-active.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-blue-legend.gif b/src/core/org/apache/jmeter/images/monitor-blue-legend.gif new file mode 100644 index 00000000000..f5f3efdee9c Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-blue-legend.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-dead.gif b/src/core/org/apache/jmeter/images/monitor-dead.gif new file mode 100644 index 00000000000..de6915ed5d0 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-dead.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-green-legend.gif b/src/core/org/apache/jmeter/images/monitor-green-legend.gif new file mode 100644 index 00000000000..e401e5b4a34 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-green-legend.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-healthy.gif b/src/core/org/apache/jmeter/images/monitor-healthy.gif new file mode 100644 index 00000000000..c2e527a9a1a Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-healthy.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-legend.gif b/src/core/org/apache/jmeter/images/monitor-legend.gif new file mode 100644 index 00000000000..57ff83bd029 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-legend.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-0.gif b/src/core/org/apache/jmeter/images/monitor-load-0.gif new file mode 100644 index 00000000000..21b5985e632 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-0.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-1.gif b/src/core/org/apache/jmeter/images/monitor-load-1.gif new file mode 100644 index 00000000000..358b6a3564d Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-1.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-10.gif b/src/core/org/apache/jmeter/images/monitor-load-10.gif new file mode 100644 index 00000000000..c62928da8fc Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-10.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-2.gif b/src/core/org/apache/jmeter/images/monitor-load-2.gif new file mode 100644 index 00000000000..8422b767351 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-2.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-3.gif b/src/core/org/apache/jmeter/images/monitor-load-3.gif new file mode 100644 index 00000000000..c193e9b4514 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-3.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-4.gif b/src/core/org/apache/jmeter/images/monitor-load-4.gif new file mode 100644 index 00000000000..3df4bc86e3f Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-4.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-5.gif b/src/core/org/apache/jmeter/images/monitor-load-5.gif new file mode 100644 index 00000000000..5005f31587c Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-5.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-6.gif b/src/core/org/apache/jmeter/images/monitor-load-6.gif new file mode 100644 index 00000000000..242290c0387 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-6.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-7.gif b/src/core/org/apache/jmeter/images/monitor-load-7.gif new file mode 100644 index 00000000000..40037e71280 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-7.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-8.gif b/src/core/org/apache/jmeter/images/monitor-load-8.gif new file mode 100644 index 00000000000..49698cc5143 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-8.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-load-9.gif b/src/core/org/apache/jmeter/images/monitor-load-9.gif new file mode 100644 index 00000000000..3ef80d1f247 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-load-9.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-orange-legend.gif b/src/core/org/apache/jmeter/images/monitor-orange-legend.gif new file mode 100644 index 00000000000..8a45f218247 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-orange-legend.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-red-legend.gif b/src/core/org/apache/jmeter/images/monitor-red-legend.gif new file mode 100644 index 00000000000..6bfc21f7ee4 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-red-legend.gif differ diff --git a/src/core/org/apache/jmeter/images/monitor-warning.gif b/src/core/org/apache/jmeter/images/monitor-warning.gif new file mode 100644 index 00000000000..0912de12791 Binary files /dev/null and b/src/core/org/apache/jmeter/images/monitor-warning.gif differ diff --git a/src/core/org/apache/jmeter/images/new/barchart.png b/src/core/org/apache/jmeter/images/new/barchart.png new file mode 100644 index 00000000000..1fc6626c443 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/barchart.png differ diff --git a/src/core/org/apache/jmeter/images/new/barchart.png-grey.png b/src/core/org/apache/jmeter/images/new/barchart.png-grey.png new file mode 100644 index 00000000000..70958fc5795 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/barchart.png-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/blue-quill.png b/src/core/org/apache/jmeter/images/new/blue-quill.png new file mode 100644 index 00000000000..3f9a8e9aef6 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/blue-quill.png differ diff --git a/src/core/org/apache/jmeter/images/new/book-grey.png b/src/core/org/apache/jmeter/images/new/book-grey.png new file mode 100644 index 00000000000..67dc4c7a13b Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/book-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/book.png b/src/core/org/apache/jmeter/images/new/book.png new file mode 100644 index 00000000000..68c30f973b8 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/book.png differ diff --git a/src/core/org/apache/jmeter/images/new/chart-grey.png b/src/core/org/apache/jmeter/images/new/chart-grey.png new file mode 100644 index 00000000000..d1524ff8e8b Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/chart-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/chart.png b/src/core/org/apache/jmeter/images/new/chart.png new file mode 100644 index 00000000000..dab75190953 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/chart.png differ diff --git a/src/core/org/apache/jmeter/images/new/clock-grey.png b/src/core/org/apache/jmeter/images/new/clock-grey.png new file mode 100644 index 00000000000..78b7b3494d5 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/clock-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/clock.png b/src/core/org/apache/jmeter/images/new/clock.png new file mode 100644 index 00000000000..3000aff12d9 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/clock.png differ diff --git a/src/core/org/apache/jmeter/images/new/funnel-grey.png b/src/core/org/apache/jmeter/images/new/funnel-grey.png new file mode 100644 index 00000000000..005b8a257b7 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/funnel-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/funnel.png b/src/core/org/apache/jmeter/images/new/funnel.png new file mode 100644 index 00000000000..5b3a5c75620 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/funnel.png differ diff --git a/src/core/org/apache/jmeter/images/new/glasses-grey.png b/src/core/org/apache/jmeter/images/new/glasses-grey.png new file mode 100644 index 00000000000..ae4fc37c0af Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/glasses-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/glasses.png b/src/core/org/apache/jmeter/images/new/glasses.png new file mode 100644 index 00000000000..16be5647ed4 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/glasses.png differ diff --git a/src/core/org/apache/jmeter/images/new/mglass-grey.png b/src/core/org/apache/jmeter/images/new/mglass-grey.png new file mode 100644 index 00000000000..b76eeaac7bb Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/mglass-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/mglass.png b/src/core/org/apache/jmeter/images/new/mglass.png new file mode 100644 index 00000000000..15f26b6208c Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/mglass.png differ diff --git a/src/core/org/apache/jmeter/images/new/pencil-grey.png b/src/core/org/apache/jmeter/images/new/pencil-grey.png new file mode 100644 index 00000000000..97130c6ec08 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/pencil-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/pencil.png b/src/core/org/apache/jmeter/images/new/pencil.png new file mode 100644 index 00000000000..9d3f4085983 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/pencil.png differ diff --git a/src/core/org/apache/jmeter/images/new/pin-grey.png b/src/core/org/apache/jmeter/images/new/pin-grey.png new file mode 100644 index 00000000000..d3559d42aa1 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/pin-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/pin.png b/src/core/org/apache/jmeter/images/new/pin.png new file mode 100644 index 00000000000..48ae352a65f Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/pin.png differ diff --git a/src/core/org/apache/jmeter/images/new/puzzle-grey.png b/src/core/org/apache/jmeter/images/new/puzzle-grey.png new file mode 100644 index 00000000000..95ae9b49bff Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/puzzle-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/puzzle.png b/src/core/org/apache/jmeter/images/new/puzzle.png new file mode 100644 index 00000000000..9ac41c09f5b Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/puzzle.png differ diff --git a/src/core/org/apache/jmeter/images/new/remote-grey.png b/src/core/org/apache/jmeter/images/new/remote-grey.png new file mode 100644 index 00000000000..2a0b93b2e57 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/remote-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/remote.png b/src/core/org/apache/jmeter/images/new/remote.png new file mode 100644 index 00000000000..dbee5dfc2bf Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/remote.png differ diff --git a/src/core/org/apache/jmeter/images/new/scroll-grey.png b/src/core/org/apache/jmeter/images/new/scroll-grey.png new file mode 100644 index 00000000000..212866db3dc Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/scroll-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/scroll.png b/src/core/org/apache/jmeter/images/new/scroll.png new file mode 100644 index 00000000000..07cfc50cbde Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/scroll.png differ diff --git a/src/core/org/apache/jmeter/images/new/table-grey.png b/src/core/org/apache/jmeter/images/new/table-grey.png new file mode 100644 index 00000000000..e0b68ebf777 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/table-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/table.png b/src/core/org/apache/jmeter/images/new/table.png new file mode 100644 index 00000000000..5b402921775 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/table.png differ diff --git a/src/core/org/apache/jmeter/images/new/thread-grey.png b/src/core/org/apache/jmeter/images/new/thread-grey.png new file mode 100644 index 00000000000..04b6e4d323f Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/thread-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/thread.png b/src/core/org/apache/jmeter/images/new/thread.png new file mode 100644 index 00000000000..db24b596084 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/thread.png differ diff --git a/src/core/org/apache/jmeter/images/new/typewriter-grey.png b/src/core/org/apache/jmeter/images/new/typewriter-grey.png new file mode 100644 index 00000000000..87c2edf6860 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/typewriter-grey.png differ diff --git a/src/core/org/apache/jmeter/images/new/typewriter.png b/src/core/org/apache/jmeter/images/new/typewriter.png new file mode 100644 index 00000000000..16b294c2b20 Binary files /dev/null and b/src/core/org/apache/jmeter/images/new/typewriter.png differ diff --git a/src/core/org/apache/jmeter/images/pipet.png b/src/core/org/apache/jmeter/images/pipet.png new file mode 100644 index 00000000000..65555c9fdef Binary files /dev/null and b/src/core/org/apache/jmeter/images/pipet.png differ diff --git a/src/core/org/apache/jmeter/images/question.gif b/src/core/org/apache/jmeter/images/question.gif new file mode 100644 index 00000000000..6bc15b99b66 Binary files /dev/null and b/src/core/org/apache/jmeter/images/question.gif differ diff --git a/src/core/org/apache/jmeter/images/smallthread.disabled.gif b/src/core/org/apache/jmeter/images/smallthread.disabled.gif new file mode 100644 index 00000000000..3da09c37a01 Binary files /dev/null and b/src/core/org/apache/jmeter/images/smallthread.disabled.gif differ diff --git a/src/core/org/apache/jmeter/images/smallthread.enabled.gif b/src/core/org/apache/jmeter/images/smallthread.enabled.gif new file mode 100644 index 00000000000..f99aa8702d9 Binary files /dev/null and b/src/core/org/apache/jmeter/images/smallthread.enabled.gif differ diff --git a/src/core/org/apache/jmeter/images/smallthread.idle.gif b/src/core/org/apache/jmeter/images/smallthread.idle.gif new file mode 100644 index 00000000000..bea1c27811c Binary files /dev/null and b/src/core/org/apache/jmeter/images/smallthread.idle.gif differ diff --git a/src/core/org/apache/jmeter/images/start.down.gif b/src/core/org/apache/jmeter/images/start.down.gif new file mode 100644 index 00000000000..5cf8d4bf41a Binary files /dev/null and b/src/core/org/apache/jmeter/images/start.down.gif differ diff --git a/src/core/org/apache/jmeter/images/start.off.gif b/src/core/org/apache/jmeter/images/start.off.gif new file mode 100644 index 00000000000..c2f4b759ebd Binary files /dev/null and b/src/core/org/apache/jmeter/images/start.off.gif differ diff --git a/src/core/org/apache/jmeter/images/start.on.gif b/src/core/org/apache/jmeter/images/start.on.gif new file mode 100644 index 00000000000..42895f237ee Binary files /dev/null and b/src/core/org/apache/jmeter/images/start.on.gif differ diff --git a/src/core/org/apache/jmeter/images/start.over.gif b/src/core/org/apache/jmeter/images/start.over.gif new file mode 100644 index 00000000000..41ab7498609 Binary files /dev/null and b/src/core/org/apache/jmeter/images/start.over.gif differ diff --git a/src/core/org/apache/jmeter/images/stop.down.gif b/src/core/org/apache/jmeter/images/stop.down.gif new file mode 100644 index 00000000000..29970291df4 Binary files /dev/null and b/src/core/org/apache/jmeter/images/stop.down.gif differ diff --git a/src/core/org/apache/jmeter/images/stop.off.gif b/src/core/org/apache/jmeter/images/stop.off.gif new file mode 100644 index 00000000000..7f14ddf34af Binary files /dev/null and b/src/core/org/apache/jmeter/images/stop.off.gif differ diff --git a/src/core/org/apache/jmeter/images/stop.on.gif b/src/core/org/apache/jmeter/images/stop.on.gif new file mode 100644 index 00000000000..0b38a7a8335 Binary files /dev/null and b/src/core/org/apache/jmeter/images/stop.on.gif differ diff --git a/src/core/org/apache/jmeter/images/stop.over.gif b/src/core/org/apache/jmeter/images/stop.over.gif new file mode 100644 index 00000000000..b75f5469b8a Binary files /dev/null and b/src/core/org/apache/jmeter/images/stop.over.gif differ diff --git a/src/core/org/apache/jmeter/images/testtubes.png b/src/core/org/apache/jmeter/images/testtubes.png new file mode 100644 index 00000000000..ffa5caf68a7 Binary files /dev/null and b/src/core/org/apache/jmeter/images/testtubes.png differ diff --git a/src/core/org/apache/jmeter/images/thread.disabled.gif b/src/core/org/apache/jmeter/images/thread.disabled.gif new file mode 100644 index 00000000000..89f265d36c0 Binary files /dev/null and b/src/core/org/apache/jmeter/images/thread.disabled.gif differ diff --git a/src/core/org/apache/jmeter/images/thread.enabled.gif b/src/core/org/apache/jmeter/images/thread.enabled.gif new file mode 100644 index 00000000000..2962c0fdd9c Binary files /dev/null and b/src/core/org/apache/jmeter/images/thread.enabled.gif differ diff --git a/src/core/org/apache/jmeter/images/thread.gif b/src/core/org/apache/jmeter/images/thread.gif new file mode 100644 index 00000000000..6b7296c7cdc Binary files /dev/null and b/src/core/org/apache/jmeter/images/thread.gif differ diff --git a/src/core/org/apache/jmeter/images/thread.idle.gif b/src/core/org/apache/jmeter/images/thread.idle.gif new file mode 100644 index 00000000000..1f207268210 Binary files /dev/null and b/src/core/org/apache/jmeter/images/thread.idle.gif differ diff --git a/src/core/org/apache/jmeter/images/timer.gif b/src/core/org/apache/jmeter/images/timer.gif new file mode 100644 index 00000000000..f3f0f56d6b2 Binary files /dev/null and b/src/core/org/apache/jmeter/images/timer.gif differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/applications-office.png b/src/core/org/apache/jmeter/images/toolbar/22x22/applications-office.png new file mode 100644 index 00000000000..e742314a6c6 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/applications-office.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/arrow-right-3-notimer.png b/src/core/org/apache/jmeter/images/toolbar/22x22/arrow-right-3-notimer.png new file mode 100644 index 00000000000..02ef2009590 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/arrow-right-3-notimer.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/arrow-right-3-startremoteall.png b/src/core/org/apache/jmeter/images/toolbar/22x22/arrow-right-3-startremoteall.png new file mode 100644 index 00000000000..a98065f4346 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/arrow-right-3-startremoteall.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/arrow-right-3.png b/src/core/org/apache/jmeter/images/toolbar/22x22/arrow-right-3.png new file mode 100644 index 00000000000..57e24e88bd7 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/arrow-right-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/color-picker-toggle.png b/src/core/org/apache/jmeter/images/toolbar/22x22/color-picker-toggle.png new file mode 100644 index 00000000000..4c2f701d583 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/color-picker-toggle.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/document-close-4.png b/src/core/org/apache/jmeter/images/toolbar/22x22/document-close-4.png new file mode 100644 index 00000000000..759d5fa5212 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/document-close-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/document-new-4.png b/src/core/org/apache/jmeter/images/toolbar/22x22/document-new-4.png new file mode 100644 index 00000000000..960d29e5c01 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/document-new-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/document-open-2.png b/src/core/org/apache/jmeter/images/toolbar/22x22/document-open-2.png new file mode 100644 index 00000000000..624806aea3b Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/document-open-2.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/document-save-5.png b/src/core/org/apache/jmeter/images/toolbar/22x22/document-save-5.png new file mode 100644 index 00000000000..ae0a14a09a1 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/document-save-5.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/document-save-as-5.png b/src/core/org/apache/jmeter/images/toolbar/22x22/document-save-as-5.png new file mode 100644 index 00000000000..f64833df80c Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/document-save-as-5.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/documentation.png b/src/core/org/apache/jmeter/images/toolbar/22x22/documentation.png new file mode 100644 index 00000000000..000adcf2ee6 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/documentation.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/edit-clear-3.png b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-clear-3.png new file mode 100644 index 00000000000..2c466bd77de Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-clear-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/edit-copy-4.png b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-copy-4.png new file mode 100644 index 00000000000..046e780eca8 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-copy-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/edit-cut-4.png b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-cut-4.png new file mode 100644 index 00000000000..e21b5245440 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-cut-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/edit-find-7.png b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-find-7.png new file mode 100644 index 00000000000..3c858706517 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-find-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/edit-paste-4.png b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-paste-4.png new file mode 100644 index 00000000000..c1d4c4d4964 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-paste-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/edit-redo-7.png b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-redo-7.png new file mode 100644 index 00000000000..55ced28489e Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-redo-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/edit-undo-7.png b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-undo-7.png new file mode 100644 index 00000000000..c45b8a673de Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/edit-undo-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/help-contents-5.png b/src/core/org/apache/jmeter/images/toolbar/22x22/help-contents-5.png new file mode 100644 index 00000000000..e264177fabc Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/help-contents-5.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/list-add-3.png b/src/core/org/apache/jmeter/images/toolbar/22x22/list-add-3.png new file mode 100644 index 00000000000..9e5bf87b0b3 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/list-add-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/list-remove-3.png b/src/core/org/apache/jmeter/images/toolbar/22x22/list-remove-3.png new file mode 100644 index 00000000000..15e4c0e861b Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/list-remove-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/process-stop-4.png b/src/core/org/apache/jmeter/images/toolbar/22x22/process-stop-4.png new file mode 100644 index 00000000000..30e4ef541bf Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/process-stop-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/process-stop-7-shutdownremoteall.png b/src/core/org/apache/jmeter/images/toolbar/22x22/process-stop-7-shutdownremoteall.png new file mode 100644 index 00000000000..d1453141144 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/process-stop-7-shutdownremoteall.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/process-stop-7.png b/src/core/org/apache/jmeter/images/toolbar/22x22/process-stop-7.png new file mode 100644 index 00000000000..c40a2a47736 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/process-stop-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/road-sign-us-stop-stopremoteall.png b/src/core/org/apache/jmeter/images/toolbar/22x22/road-sign-us-stop-stopremoteall.png new file mode 100644 index 00000000000..618db30ada7 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/road-sign-us-stop-stopremoteall.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/road-sign-us-stop.png b/src/core/org/apache/jmeter/images/toolbar/22x22/road-sign-us-stop.png new file mode 100644 index 00000000000..32bef0ad437 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/road-sign-us-stop.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/run-build-clean.png b/src/core/org/apache/jmeter/images/toolbar/22x22/run-build-clean.png new file mode 100644 index 00000000000..8862eef66af Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/run-build-clean.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/22x22/run-build-prune.png b/src/core/org/apache/jmeter/images/toolbar/22x22/run-build-prune.png new file mode 100644 index 00000000000..5a74fbdb628 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/22x22/run-build-prune.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/applications-office.png b/src/core/org/apache/jmeter/images/toolbar/32x32/applications-office.png new file mode 100644 index 00000000000..6a9e36079a2 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/applications-office.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/arrow-right-3-notimer.png b/src/core/org/apache/jmeter/images/toolbar/32x32/arrow-right-3-notimer.png new file mode 100644 index 00000000000..98390a4c2f5 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/arrow-right-3-notimer.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/arrow-right-3-startremoteall.png b/src/core/org/apache/jmeter/images/toolbar/32x32/arrow-right-3-startremoteall.png new file mode 100644 index 00000000000..0856b4f7484 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/arrow-right-3-startremoteall.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/arrow-right-3.png b/src/core/org/apache/jmeter/images/toolbar/32x32/arrow-right-3.png new file mode 100644 index 00000000000..d11e0c071b4 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/arrow-right-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/color-picker-toggle.png b/src/core/org/apache/jmeter/images/toolbar/32x32/color-picker-toggle.png new file mode 100644 index 00000000000..4db4f86d710 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/color-picker-toggle.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/document-close-4.png b/src/core/org/apache/jmeter/images/toolbar/32x32/document-close-4.png new file mode 100644 index 00000000000..6f720b5d372 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/document-close-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/document-new-4.png b/src/core/org/apache/jmeter/images/toolbar/32x32/document-new-4.png new file mode 100644 index 00000000000..0fddee8a802 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/document-new-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/document-open-2.png b/src/core/org/apache/jmeter/images/toolbar/32x32/document-open-2.png new file mode 100644 index 00000000000..84d52dc0b34 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/document-open-2.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/document-save-5.png b/src/core/org/apache/jmeter/images/toolbar/32x32/document-save-5.png new file mode 100644 index 00000000000..05a00227c06 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/document-save-5.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/document-save-as-5.png b/src/core/org/apache/jmeter/images/toolbar/32x32/document-save-as-5.png new file mode 100644 index 00000000000..9e049961348 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/document-save-as-5.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/documentation.png b/src/core/org/apache/jmeter/images/toolbar/32x32/documentation.png new file mode 100644 index 00000000000..ffe6ea0f951 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/documentation.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/edit-clear-3.png b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-clear-3.png new file mode 100644 index 00000000000..59255ad5402 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-clear-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/edit-copy-4.png b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-copy-4.png new file mode 100644 index 00000000000..155a91f701d Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-copy-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/edit-cut-4.png b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-cut-4.png new file mode 100644 index 00000000000..3212882c3a6 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-cut-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/edit-find-7.png b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-find-7.png new file mode 100644 index 00000000000..d749c619f0f Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-find-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/edit-paste-4.png b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-paste-4.png new file mode 100644 index 00000000000..84ee24194f3 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-paste-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/edit-redo-7.png b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-redo-7.png new file mode 100644 index 00000000000..df5745cceea Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-redo-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/edit-undo-7.png b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-undo-7.png new file mode 100644 index 00000000000..c1396ab50ac Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/edit-undo-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/help-contents-5.png b/src/core/org/apache/jmeter/images/toolbar/32x32/help-contents-5.png new file mode 100644 index 00000000000..f5f3ffc98e8 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/help-contents-5.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/list-add-3.png b/src/core/org/apache/jmeter/images/toolbar/32x32/list-add-3.png new file mode 100644 index 00000000000..fa6812a38ea Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/list-add-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/list-remove-3.png b/src/core/org/apache/jmeter/images/toolbar/32x32/list-remove-3.png new file mode 100644 index 00000000000..79314e86d94 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/list-remove-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/process-stop-4.png b/src/core/org/apache/jmeter/images/toolbar/32x32/process-stop-4.png new file mode 100644 index 00000000000..1a712b2dd8e Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/process-stop-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/process-stop-7-shutdownremoteall.png b/src/core/org/apache/jmeter/images/toolbar/32x32/process-stop-7-shutdownremoteall.png new file mode 100644 index 00000000000..78bd583310b Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/process-stop-7-shutdownremoteall.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/process-stop-7.png b/src/core/org/apache/jmeter/images/toolbar/32x32/process-stop-7.png new file mode 100644 index 00000000000..827fb31781b Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/process-stop-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/road-sign-us-stop-stopremoteall.png b/src/core/org/apache/jmeter/images/toolbar/32x32/road-sign-us-stop-stopremoteall.png new file mode 100644 index 00000000000..c869d6d2b0e Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/road-sign-us-stop-stopremoteall.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/road-sign-us-stop.png b/src/core/org/apache/jmeter/images/toolbar/32x32/road-sign-us-stop.png new file mode 100644 index 00000000000..22e0c059ba2 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/road-sign-us-stop.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/run-build-clean.png b/src/core/org/apache/jmeter/images/toolbar/32x32/run-build-clean.png new file mode 100644 index 00000000000..3f7ac555048 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/run-build-clean.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/32x32/run-build-prune.png b/src/core/org/apache/jmeter/images/toolbar/32x32/run-build-prune.png new file mode 100644 index 00000000000..e406d654e60 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/32x32/run-build-prune.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/applications-office.png b/src/core/org/apache/jmeter/images/toolbar/48x48/applications-office.png new file mode 100644 index 00000000000..c195ca45b2d Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/applications-office.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/arrow-right-3-notimer.png b/src/core/org/apache/jmeter/images/toolbar/48x48/arrow-right-3-notimer.png new file mode 100644 index 00000000000..1e51c2cca2a Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/arrow-right-3-notimer.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/arrow-right-3-startremoteall.png b/src/core/org/apache/jmeter/images/toolbar/48x48/arrow-right-3-startremoteall.png new file mode 100644 index 00000000000..7d231841d52 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/arrow-right-3-startremoteall.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/arrow-right-3.png b/src/core/org/apache/jmeter/images/toolbar/48x48/arrow-right-3.png new file mode 100644 index 00000000000..2e1d5ba16e5 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/arrow-right-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/color-picker-toggle.png b/src/core/org/apache/jmeter/images/toolbar/48x48/color-picker-toggle.png new file mode 100644 index 00000000000..24f6930a773 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/color-picker-toggle.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/document-close-4.png b/src/core/org/apache/jmeter/images/toolbar/48x48/document-close-4.png new file mode 100644 index 00000000000..542f0d951b9 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/document-close-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/document-new-4.png b/src/core/org/apache/jmeter/images/toolbar/48x48/document-new-4.png new file mode 100644 index 00000000000..d51d6975949 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/document-new-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/document-open-2.png b/src/core/org/apache/jmeter/images/toolbar/48x48/document-open-2.png new file mode 100644 index 00000000000..139ac9aa115 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/document-open-2.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/document-save-5.png b/src/core/org/apache/jmeter/images/toolbar/48x48/document-save-5.png new file mode 100644 index 00000000000..c48fe6dc177 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/document-save-5.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/document-save-as-5.png b/src/core/org/apache/jmeter/images/toolbar/48x48/document-save-as-5.png new file mode 100644 index 00000000000..fc71f5c7eb2 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/document-save-as-5.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/documentation.png b/src/core/org/apache/jmeter/images/toolbar/48x48/documentation.png new file mode 100644 index 00000000000..77fb5cf1c38 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/documentation.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/edit-clear-3.png b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-clear-3.png new file mode 100644 index 00000000000..ea97b64354b Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-clear-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/edit-copy-4.png b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-copy-4.png new file mode 100644 index 00000000000..3c8cefa141c Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-copy-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/edit-cut-4.png b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-cut-4.png new file mode 100644 index 00000000000..5130910293c Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-cut-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/edit-find-7.png b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-find-7.png new file mode 100644 index 00000000000..4e3bffa52b3 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-find-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/edit-paste-4.png b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-paste-4.png new file mode 100644 index 00000000000..30cf962949b Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-paste-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/edit-redo-7.png b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-redo-7.png new file mode 100644 index 00000000000..4fa12657ec3 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-redo-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/edit-undo-7.png b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-undo-7.png new file mode 100644 index 00000000000..76a308e6f45 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/edit-undo-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/help-contents-5.png b/src/core/org/apache/jmeter/images/toolbar/48x48/help-contents-5.png new file mode 100644 index 00000000000..19ba9a13285 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/help-contents-5.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/list-add-3.png b/src/core/org/apache/jmeter/images/toolbar/48x48/list-add-3.png new file mode 100644 index 00000000000..fd20a91074d Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/list-add-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/list-remove-3.png b/src/core/org/apache/jmeter/images/toolbar/48x48/list-remove-3.png new file mode 100644 index 00000000000..4b626514a11 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/list-remove-3.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/process-stop-4.png b/src/core/org/apache/jmeter/images/toolbar/48x48/process-stop-4.png new file mode 100644 index 00000000000..41eaff667b8 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/process-stop-4.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/process-stop-7-shutdownremoteall.png b/src/core/org/apache/jmeter/images/toolbar/48x48/process-stop-7-shutdownremoteall.png new file mode 100644 index 00000000000..bb83df92a57 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/process-stop-7-shutdownremoteall.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/process-stop-7.png b/src/core/org/apache/jmeter/images/toolbar/48x48/process-stop-7.png new file mode 100644 index 00000000000..f62c741d36e Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/process-stop-7.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/road-sign-us-stop-stopremoteall.png b/src/core/org/apache/jmeter/images/toolbar/48x48/road-sign-us-stop-stopremoteall.png new file mode 100644 index 00000000000..778577ea491 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/road-sign-us-stop-stopremoteall.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/road-sign-us-stop.png b/src/core/org/apache/jmeter/images/toolbar/48x48/road-sign-us-stop.png new file mode 100644 index 00000000000..0a7d278f8d2 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/road-sign-us-stop.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/run-build-clean.png b/src/core/org/apache/jmeter/images/toolbar/48x48/run-build-clean.png new file mode 100644 index 00000000000..f10e62827b5 Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/run-build-clean.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/48x48/run-build-prune.png b/src/core/org/apache/jmeter/images/toolbar/48x48/run-build-prune.png new file mode 100644 index 00000000000..33f0c16ae7d Binary files /dev/null and b/src/core/org/apache/jmeter/images/toolbar/48x48/run-build-prune.png differ diff --git a/src/core/org/apache/jmeter/images/toolbar/icons-custom/arrow-right-3-notimer.svg b/src/core/org/apache/jmeter/images/toolbar/icons-custom/arrow-right-3-notimer.svg new file mode 100644 index 00000000000..69c86f8df8a --- /dev/null +++ b/src/core/org/apache/jmeter/images/toolbar/icons-custom/arrow-right-3-notimer.svg @@ -0,0 +1,1343 @@ + + + + + + + + + + + + unsorted + + + + + Open Clip Art Library, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons + + + + + + + + + + + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core/org/apache/jmeter/images/toolbar/icons-custom/arrow-right-3-startremoteall.svg b/src/core/org/apache/jmeter/images/toolbar/icons-custom/arrow-right-3-startremoteall.svg new file mode 100644 index 00000000000..000d0963d2d --- /dev/null +++ b/src/core/org/apache/jmeter/images/toolbar/icons-custom/arrow-right-3-startremoteall.svg @@ -0,0 +1,1641 @@ + + + + + + + + + + + + unsorted + + + + + Open Clip Art Library, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons + + + + + + + + + + + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core/org/apache/jmeter/images/toolbar/icons-custom/color-picker-toggle.svg b/src/core/org/apache/jmeter/images/toolbar/icons-custom/color-picker-toggle.svg new file mode 100644 index 00000000000..9f333a0c734 --- /dev/null +++ b/src/core/org/apache/jmeter/images/toolbar/icons-custom/color-picker-toggle.svg @@ -0,0 +1,1554 @@ + + + + + + + + + + + + unsorted + + + + + Open Clip Art Library, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons + + + + + + + + + + + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core/org/apache/jmeter/images/toolbar/icons-custom/process-stop-7-shutdownremoteall.svg b/src/core/org/apache/jmeter/images/toolbar/icons-custom/process-stop-7-shutdownremoteall.svg new file mode 100644 index 00000000000..28a1509eb3d --- /dev/null +++ b/src/core/org/apache/jmeter/images/toolbar/icons-custom/process-stop-7-shutdownremoteall.svg @@ -0,0 +1,1326 @@ + + + + + + + + + + + + unsorted + + + + + Open Clip Art Library, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons + + + + + + + + + + + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core/org/apache/jmeter/images/toolbar/icons-custom/road-sign-us-stop-stopremoteall.svg b/src/core/org/apache/jmeter/images/toolbar/icons-custom/road-sign-us-stop-stopremoteall.svg new file mode 100644 index 00000000000..5d2626caf39 --- /dev/null +++ b/src/core/org/apache/jmeter/images/toolbar/icons-custom/road-sign-us-stop-stopremoteall.svg @@ -0,0 +1,328 @@ + + + + + + + + + + + + unsorted + + + + + Open Clip Art Library, Source: Wikimedia Commons, Source: Wikimedia Commons, Source: Wikimedia Commons + + + + + + + + + + + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core/org/apache/jmeter/images/toolbar/icons-toolbar.properties b/src/core/org/apache/jmeter/images/toolbar/icons-toolbar.properties new file mode 100644 index 00000000000..246a638e45b --- /dev/null +++ b/src/core/org/apache/jmeter/images/toolbar/icons-toolbar.properties @@ -0,0 +1,49 @@ +# 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. + +# Icons order. Keys separate by comma. Use a pipe | to have a space between two icons. +toolbar=new,templates,open,close,save,save_as_testplan,|,undo,redo,cut,copy,paste,|,expand,collapse,toggle,|,test_start,test_start_notimers,test_stop,test_shutdown,|,test_start_remote_all,test_stop_remote_all,test_shutdown_remote_all,|,test_clear,test_clear_all,|,search,search_reset,|,function_helper,help + +# Icon / action definition file. +# Key: button names +# Value: I18N key in messages.properties, ActionNames key field, icon path, optionally followed by comma and then the pressed icon name +# Special keyword "" for the different size of the same icon (Available sizes are: 22x22, 32x32, 48x48) +new=new,CLOSE,org/apache/jmeter/images/toolbar//document-new-4.png +templates=template_menu,TEMPLATES,org/apache/jmeter/images/toolbar//applications-office.png +open=menu_open,OPEN,org/apache/jmeter/images/toolbar//document-open-2.png +close=menu_close,CLOSE,org/apache/jmeter/images/toolbar//document-close-4.png +save=save,SAVE,org/apache/jmeter/images/toolbar//document-save-5.png +save_as_testplan=save_as,SAVE_AS,org/apache/jmeter/images/toolbar//document-save-as-5.png +cut=cut,CUT,org/apache/jmeter/images/toolbar//edit-cut-4.png +copy=copy,COPY,org/apache/jmeter/images/toolbar//edit-copy-4.png +paste=paste,PASTE,org/apache/jmeter/images/toolbar//edit-paste-4.png +test_start=start,ACTION_START,org/apache/jmeter/images/toolbar//arrow-right-3.png +test_start_notimers=start_no_timers,ACTION_START_NO_TIMERS,org/apache/jmeter/images/toolbar//arrow-right-3-notimer.png +test_stop=stop,ACTION_STOP,org/apache/jmeter/images/toolbar//road-sign-us-stop.png +test_shutdown=shutdown,ACTION_SHUTDOWN,org/apache/jmeter/images/toolbar//process-stop-7.png +test_start_remote_all=remote_start_all,REMOTE_START_ALL,org/apache/jmeter/images/toolbar//arrow-right-3-startremoteall.png +test_stop_remote_all=remote_stop_all,REMOTE_STOP_ALL,org/apache/jmeter/images/toolbar//road-sign-us-stop-stopremoteall.png +test_shutdown_remote_all=remote_shut_all,REMOTE_SHUT_ALL,org/apache/jmeter/images/toolbar//process-stop-7-shutdownremoteall.png +test_clear=clear,CLEAR,org/apache/jmeter/images/toolbar//run-build-clean.png +test_clear_all=clear_all,CLEAR_ALL,org/apache/jmeter/images/toolbar//run-build-prune.png +toggle=toggle,TOGGLE,org/apache/jmeter/images/toolbar//color-picker-toggle.png +expand=menu_expand_all,EXPAND_ALL,org/apache/jmeter/images/toolbar//list-add-3.png +collapse=menu_collapse_all,COLLAPSE_ALL,org/apache/jmeter/images/toolbar//list-remove-3.png +search=menu_search,SEARCH_TREE,org/apache/jmeter/images/toolbar//edit-find-7.png +search_reset=menu_search_reset,SEARCH_RESET,org/apache/jmeter/images/toolbar//edit-clear-3.png +function_helper=function_dialog_menu_item,FUNCTIONS,org/apache/jmeter/images/toolbar//documentation.png +help=help,HELP,org/apache/jmeter/images/toolbar//help-contents-5.png +undo=undo,UNDO,org/apache/jmeter/images/toolbar//edit-undo-7.png +redo=redo,REDO,org/apache/jmeter/images/toolbar//edit-redo-7.png \ No newline at end of file diff --git a/src/core/org/apache/jmeter/images/warning.png b/src/core/org/apache/jmeter/images/warning.png new file mode 100644 index 00000000000..e6d163b1f1e Binary files /dev/null and b/src/core/org/apache/jmeter/images/warning.png differ diff --git a/src/core/org/apache/jmeter/plugin/JMeterPlugin.java b/src/core/org/apache/jmeter/plugin/JMeterPlugin.java new file mode 100644 index 00000000000..46b2b397fe5 --- /dev/null +++ b/src/core/org/apache/jmeter/plugin/JMeterPlugin.java @@ -0,0 +1,25 @@ +/* + * 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.plugin; + +public interface JMeterPlugin { + String[][] getIconMappings(); + + String[][] getResourceBundles(); +} diff --git a/src/core/org/apache/jmeter/plugin/PluginManager.java b/src/core/org/apache/jmeter/plugin/PluginManager.java new file mode 100644 index 00000000000..868ca3cbcba --- /dev/null +++ b/src/core/org/apache/jmeter/plugin/PluginManager.java @@ -0,0 +1,73 @@ +/* + * 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.plugin; + +import java.net.URL; + +import javax.swing.ImageIcon; + +import org.apache.jmeter.gui.GUIFactory; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public final class PluginManager { + private static final PluginManager instance = new PluginManager(); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private PluginManager() { + } + + /** + * Installs a plugin. + * + * @param plugin + * the plugin to install + * @param useGui + * indication of whether or not the gui will be used + */ + public static void install(JMeterPlugin plugin, boolean useGui) { + if (useGui) { + instance.installPlugin(plugin); + } + } + + private void installPlugin(JMeterPlugin plugin) { + String[][] icons = plugin.getIconMappings(); + ClassLoader classloader = plugin.getClass().getClassLoader(); + + for (int i = 0; i < icons.length; i++) { + URL resource = classloader.getResource(icons[i][1].trim()); + + if (resource == null) { + log.warn("Can't find icon for " + icons[i][0] + " - " + icons[i][1]); + } else { + GUIFactory.registerIcon(icons[i][0], new ImageIcon(resource)); + if (icons[i].length > 2 && icons[i][2] != null) { + URL resource2 = classloader.getResource(icons[i][2].trim()); + if (resource2 == null) { + log.info("Can't find disabled icon for " + icons[i][0] + " - " + icons[i][2]); + } else { + GUIFactory.registerDisabledIcon(icons[i][0], new ImageIcon(resource2)); + } + } + } + } + } +} diff --git a/src/core/org/apache/jmeter/processor/PostProcessor.java b/src/core/org/apache/jmeter/processor/PostProcessor.java new file mode 100644 index 00000000000..a30112835d1 --- /dev/null +++ b/src/core/org/apache/jmeter/processor/PostProcessor.java @@ -0,0 +1,32 @@ +/* + * 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.processor; + +/** + * The PostProcessor is activated after a sample result has been generated. + * + * @version $Revision$ + */ +public interface PostProcessor { + /** + * Provides the PostProcessor with a SampleResult object from which to + * extract values for use in future Queries. + */ + void process(); +} diff --git a/src/core/org/apache/jmeter/processor/PreProcessor.java b/src/core/org/apache/jmeter/processor/PreProcessor.java new file mode 100644 index 00000000000..6863f57dbb7 --- /dev/null +++ b/src/core/org/apache/jmeter/processor/PreProcessor.java @@ -0,0 +1,28 @@ +/* + * 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.processor; + +/** + * PreProcessors are executed just prior to a sample being run. + * + * @version $Revision$ + */ +public interface PreProcessor { + void process(); +} diff --git a/src/core/org/apache/jmeter/processor/gui/AbstractPostProcessorGui.java b/src/core/org/apache/jmeter/processor/gui/AbstractPostProcessorGui.java new file mode 100644 index 00000000000..2a9bcd36d82 --- /dev/null +++ b/src/core/org/apache/jmeter/processor/gui/AbstractPostProcessorGui.java @@ -0,0 +1,45 @@ +/* + * 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.processor.gui; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.jmeter.gui.AbstractScopedJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage PostProcessors. + * + * PostProcessors which can be applied to different scopes (parent, children or both) + * need to use the createScopePanel() to add the panel to the GUI, and they also + * need to use saveScopeSettings() and showScopeSettings() to keep the test element + * and GUI in synch. + * + */ +public abstract class AbstractPostProcessorGui extends AbstractScopedJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.POST_PROCESSORS }); + } + +} diff --git a/src/core/org/apache/jmeter/processor/gui/AbstractPreProcessorGui.java b/src/core/org/apache/jmeter/processor/gui/AbstractPreProcessorGui.java new file mode 100644 index 00000000000..332ea1f50ca --- /dev/null +++ b/src/core/org/apache/jmeter/processor/gui/AbstractPreProcessorGui.java @@ -0,0 +1,43 @@ +/* + * 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.processor.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +public abstract class AbstractPreProcessorGui extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + @Override + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultExtractorMenu(); + } + + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.PRE_PROCESSORS }); + } + +} diff --git a/src/core/org/apache/jmeter/reporters/AbstractListenerElement.java b/src/core/org/apache/jmeter/reporters/AbstractListenerElement.java new file mode 100644 index 00000000000..50cf2ae8500 --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/AbstractListenerElement.java @@ -0,0 +1,57 @@ +/* + * 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.reporters; + +import java.lang.ref.WeakReference; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.visualizers.Visualizer; + +/** + * Base class for Listeners + */ + +public abstract class AbstractListenerElement extends AbstractTestElement { + private static final long serialVersionUID = 240L; + + // TODO should class implement SampleListener? + private transient WeakReference listener; + + public AbstractListenerElement() { + } + + protected final Visualizer getVisualizer() { + if (listener == null){ // e.g. in non-GUI mode + return null; + } + return listener.get(); + } + + public void setListener(Visualizer vis) { + listener = new WeakReference(vis); + } + + @Override + public Object clone() { + AbstractListenerElement clone = (AbstractListenerElement) super.clone(); + + clone.listener=this.listener; + return clone; + } +} diff --git a/src/core/org/apache/jmeter/reporters/FileReporter.java b/src/core/org/apache/jmeter/reporters/FileReporter.java new file mode 100644 index 00000000000..1ae15360d14 --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/FileReporter.java @@ -0,0 +1,403 @@ +/* + * 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.reporters; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * This class loads data from a saved file and displays statistics about it. + * + * + * @version $Revision$ + */ +public class FileReporter extends JPanel { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Map> data = new ConcurrentHashMap>(); + + /** + * Initialize a file reporter from a file + * + * @param file + * The name of the file to read from + * @throws IOException + * when the corresponding file can not be opened or be read from + */ + public void init(String file) throws IOException { + File datafile = new File(file); + BufferedReader reader = null; + + try { + if (datafile.canRead()) { + reader = new BufferedReader(new FileReader(datafile)); + } else { + JOptionPane.showMessageDialog(null, "The file you specified cannot be read.", "Information", + JOptionPane.INFORMATION_MESSAGE); + return; + } + String line; + + while ((line = reader.readLine()) != null) { + try { + line = line.trim(); + if (line.startsWith("#") || line.length() == 0) { + continue; + } + int splitter = line.lastIndexOf(' '); + String key = line.substring(0, splitter); + int len = line.length() - 1; + Integer value = null; + + if (line.charAt(len) == ',') { + value = Integer.valueOf(line.substring(splitter + 1, len)); + } else { + value = Integer.valueOf(line.substring(splitter + 1)); + } + List v = getData(key); + + if (v == null) { + v = Collections.synchronizedList(new ArrayList()); + this.data.put(key, v); + } + v.add(value); + } catch (NumberFormatException nfe) { + log.error("This line could not be parsed: " + line, nfe); + } catch (Exception e) { + log.error("This line caused a problem: " + line, e); + } + } + } finally { + JOrphanUtils.closeQuietly(reader); + } + showPanel(); + } + + /** + * Get the data currently assigned to a given key + * + * @param key + * The key for which the data should be given + * @return list of data for the given key + */ + public List getData(String key) { + return data.get(key); + } + + /** + * Show main panel with length, graph, and stats. + */ + public void showPanel() { + JFrame f = new JFrame("Data File Report"); + + setLayout(new BorderLayout()); + GraphPanel gp = new GraphPanel(data); + + add(gp, "Center"); + add(gp.getStats(), BorderLayout.EAST); + add(gp.getLegend(), BorderLayout.NORTH); + f.setSize(500, 300); + f.getContentPane().add(this); + f.setVisible(true); + } + +/** + * Graph panel generates all the panels for this reporter. Data is organized + * based on thread name in a hashtable. The data itself is a List of Integer + * objects + */ +private static class GraphPanel extends JPanel { + private static final long serialVersionUID = 240L; + + // boolean autoScale = true; + private final Map> data; + + private final List keys = Collections.synchronizedList(new ArrayList()); + + private final List colorList = Collections.synchronizedList(new ArrayList()); + + public GraphPanel(Map> data) { + this.data = data; + for (String key : data.keySet()) { + keys.add(key); + } + for (int a = 0x33; a < 0xFF; a += 0x66) { + for (int b = 0x33; b < 0xFF; b += 0x66) { + for (int c = 0x33; c < 0xFF; c += 0x66) { + colorList.add(new Color(a, b, c)); + } + } + } + } + + /** + * Get the maximum for all the data. + * + * @return the maximum of all data for all keys, or 0 if no + * data is present + */ + public float getMax() { + float maxValue = 0; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + List temp = data.get(key); + + for (int j = 0; j < temp.size(); j++) { + float f = temp.get(j).intValue(); + + maxValue = Math.max(f, maxValue); + } + } + return (float) (maxValue + maxValue * 0.1); + } + + /** + * Get the minimum for all the data. + * @return the maximum of all data for all keys, or 9999999 if no + * data is present + */ + public float getMin() { + float minValue = 9999999; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + List temp = data.get(key); + + for (int j = 0; j < temp.size(); j++) { + float f = temp.get(j).intValue(); + + minValue = Math.min(f, minValue); + } + } + return (float) (minValue - minValue * 0.1); + } + + /** + * Get the legend panel. + * + * @return the newly created legend panel + */ + public JPanel getLegend() { + JPanel main = new JPanel(); + GridBagLayout g = new GridBagLayout(); + + main.setLayout(g); + GridBagConstraints c = new GridBagConstraints(); + + c.insets = new Insets(3, 3, 3, 3); + c.fill = GridBagConstraints.BOTH; + c.gridwidth = 1; + c.gridheight = 1; + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + JLabel colorSwatch = new JLabel(" "); + + colorSwatch.setBackground(colorList.get(t % colorList.size())); + colorSwatch.setOpaque(true); + c.gridx = 1; + c.gridy = t; + g.setConstraints(colorSwatch, c); + main.add(colorSwatch); + JLabel name = new JLabel(key); + + c.gridx = 2; + c.gridy = t; + g.setConstraints(name, c); + main.add(name); + } + return main; + } + + /** + * Get the stats panel. + * + * @return a newly created stats panel + */ + public JPanel getStats() { + int total = 0; + float totalValue = 0; + float maxValue = 0; + float minValue = 999999; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + List temp = data.get(key); + + for (int j = 0; j < temp.size(); j++) { + float f = temp.get(j).intValue(); + + minValue = Math.min(f, minValue); + maxValue = Math.max(f, maxValue); + totalValue += f; + total++; + } + } + float averageValue = totalValue / total; + JPanel main = new JPanel(); + GridBagLayout g = new GridBagLayout(); + + main.setLayout(g); + DecimalFormat df = new DecimalFormat("#0.0"); + GridBagConstraints c = new GridBagConstraints(); + + c.insets = new Insets(3, 6, 3, 6); + c.fill = GridBagConstraints.BOTH; + c.gridwidth = 1; + c.gridheight = 1; + JLabel count = new JLabel("Count: " + total); + + c.gridx = 1; + c.gridy = 1; + g.setConstraints(count, c); + JLabel min = new JLabel("Min: " + df.format(Float.valueOf(minValue))); + + c.gridx = 1; + c.gridy = 2; + g.setConstraints(min, c); + JLabel max = new JLabel("Max: " + df.format(Float.valueOf(maxValue))); + + c.gridx = 1; + c.gridy = 3; + g.setConstraints(max, c); + JLabel average = new JLabel("Average: " + df.format(Float.valueOf(averageValue))); + + c.gridx = 1; + c.gridy = 4; + g.setConstraints(average, c); + main.add(count); + main.add(min); + main.add(max); + main.add(average); + return main; + } + + /** + * Gets the size of the biggest List. + * + * @return the max size of all lists for all key + */ + public int getDataWidth() { + int size = 0; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + size = Math.max(size, data.get(key).size()); + } + return size; + } + + /** + * Draws the graph. + */ + @Override + public void update(Graphics g) { + // setup drawing area + int base = 10; + + g.setColor(Color.white); + g.fillRect(0, 0, getSize().width, getSize().height); + int width = getSize().width; + int height = getSize().height; + float maxValue = getMax(); + float minValue = getMin(); + + // draw grid + g.setColor(Color.gray); + int dataWidth = getDataWidth(); + int increment = Math.round((float)(width - 1) / (dataWidth - 1)); + + /* + * for (int t = 0; t < dataWidth; t++) { g.drawLine(t * increment, 0, t * + * increment, height); } + */ + int yIncrement = Math.round(((float) height - (1 + base)) / (10 - 1)); + + /* + * for (int t = 0; t < 10; t++) { g.drawLine(0, height - t * yIncrement, + * width, height - t * yIncrement); } + */ + // draw axis + for (int t = 1; t < dataWidth; t += (dataWidth / 25 + 1)) { + g.drawString((Integer.valueOf(t)).toString(), t * increment + 2, height - 2); + } + float incrementValue = (maxValue - minValue) / (10 - 1); + + for (int t = 0; t < 10; t++) { + g.drawString(Integer.valueOf(Math.round(minValue + (t * incrementValue))).toString(), 2, height - t + * yIncrement - 2 - base); + } + // draw data lines + int start = 0; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + List v = data.get(key); + + start = 0; + g.setColor(colorList.get(t % colorList.size())); + for (int i = 0; i < v.size() - 1; i++) { + float y1 = v.get(i).intValue(); + float y2 = v.get(i + 1).intValue(); + + y1 = y1 - minValue; + y2 = y2 - minValue; + int Y1 = Math.round((height * y1) / (maxValue - minValue)); + int Y2 = Math.round((height * y2) / (maxValue - minValue)); + + Y1 = height - Y1 - base; + Y2 = height - Y2 - base; + g.drawLine(start, Y1, start + increment, Y2); + + start += increment; + } + } + } + + @Override + public void paint(Graphics g) { + update(g); + } + } +} diff --git a/src/core/org/apache/jmeter/reporters/ResultAction.java b/src/core/org/apache/jmeter/reporters/ResultAction.java new file mode 100644 index 00000000000..059401e8dde --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/ResultAction.java @@ -0,0 +1,94 @@ +/* + * 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.reporters; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.OnErrorTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * ResultAction - take action based on the status of the last Result + * + */ +public class ResultAction extends OnErrorTestElement implements Serializable, SampleListener { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * Constructor is initially called once for each occurrence in the test plan + * For GUI, several more instances are created Then clear is called at start + * of test Called several times during test startup The name will not + * necessarily have been set at this point. + */ + public ResultAction() { + super(); + // log.debug(Thread.currentThread().getName()); + // System.out.println(">> "+me+" "+this.getName()+" + // "+Thread.currentThread().getName()); + } + + /** + * Examine the sample(s) and take appropriate action + * + * @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) + */ + @Override + public void sampleOccurred(SampleEvent e) { + SampleResult s = e.getResult(); + log.debug(s.getSampleLabel() + " OK? " + s.isSuccessful()); + if (!s.isSuccessful()) { + if (isStopTestNow()) { + s.setStopTestNow(true); + } + if (isStopTest()) { + s.setStopTest(true); + } + if (isStopThread()) { + s.setStopThread(true); + } + if (isStartNextThreadLoop()) { + s.setStartNextThreadLoop(true); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void sampleStarted(SampleEvent e) { + // not used + } + + /** + * {@inheritDoc} + */ + @Override + public void sampleStopped(SampleEvent e) { + // not used + } + +} diff --git a/src/core/org/apache/jmeter/reporters/ResultCollector.java b/src/core/org/apache/jmeter/reporters/ResultCollector.java new file mode 100644 index 00000000000..f473f02bb49 --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/ResultCollector.java @@ -0,0 +1,699 @@ +/* + * 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.reporters; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.save.OldSaveService; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.ObjectProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.xml.sax.SAXException; + +import com.thoughtworks.xstream.converters.ConversionException; + +/** + * This class handles all saving of samples. + * The class must be thread-safe because it is shared between threads (NoThreadClone). + */ +public class ResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable, + TestStateListener, Remoteable, NoThreadClone { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + // This string is used to identify local test runs, so must not be a valid host name + private static final String TEST_IS_LOCAL = "*local*"; // $NON-NLS-1$ + + private static final String TESTRESULTS_START = ""; // $NON-NLS-1$ + + private static final String TESTRESULTS_START_V1_1_PREVER = ""; // $NON-NLS-1$ + + private static final String TESTRESULTS_END = ""; // $NON-NLS-1$ + + private static final String XML_HEADER = ""; // $NON-NLS-1$ + + private static final int MIN_XML_FILE_LEN = XML_HEADER.length() + TESTRESULTS_START.length() + + TESTRESULTS_END.length(); + + public static final String FILENAME = "filename"; // $NON-NLS-1$ + + private static final String SAVE_CONFIG = "saveConfig"; // $NON-NLS-1$ + + private static final String ERROR_LOGGING = "ResultCollector.error_logging"; // $NON-NLS-1$ + + private static final String SUCCESS_ONLY_LOGGING = "ResultCollector.success_only_logging"; // $NON-NLS-1$ + + /** AutoFlush on each line */ + private static final boolean SAVING_AUTOFLUSH = JMeterUtils.getPropDefault("jmeter.save.saveservice.autoflush", false); //$NON-NLS-1$ + + // Static variables + + // Lock used to guard static mutable variables + private static final Object LOCK = new Object(); + + //@GuardedBy("LOCK") + private static final Map files = new HashMap(); + + /** + * Shutdown Hook that ensures PrintWriter is flushed is CTRL+C or kill is called during a test + */ + //@GuardedBy("LOCK") + private static Thread shutdownHook; + + /* + * Keep track of the file writer and the configuration, + * as the instance used to close them is not the same as the instance that creates + * them. This means one cannot use the saved PrintWriter or use getSaveConfig() + */ + private static class FileEntry{ + final PrintWriter pw; + final SampleSaveConfiguration config; + FileEntry(PrintWriter _pw, SampleSaveConfiguration _config){ + pw =_pw; + config = _config; + } + } + + /** + * The instance count is used to keep track of whether any tests are currently running. + * It's not possible to use the constructor or threadStarted etc as tests may overlap + * e.g. a remote test may be started, + * and then a local test started whilst the remote test is still running. + */ + //@GuardedBy("LOCK") + private static int instanceCount; // Keep track of how many instances are active + + // Instance variables (guarded by volatile) + + private transient volatile PrintWriter out; + + private volatile boolean inTest = false; + + private volatile boolean isStats = false; + + /** the summarizer to which this result collector will forward the samples */ + private volatile Summariser summariser; + + private static final class ShutdownHook implements Runnable { + + @Override + public void run() { + log.info("Shutdown hook started"); + synchronized (LOCK) { + flushFileOutput(); + } + log.info("Shutdown hook ended"); + } + } + + /** + * No-arg constructor. + */ + public ResultCollector() { + this(null); + } + + /** + * Constructor which sets the used {@link Summariser} + * @param summer The {@link Summariser} to use + */ + public ResultCollector(Summariser summer) { + setErrorLogging(false); + setSuccessOnlyLogging(false); + setProperty(new ObjectProperty(SAVE_CONFIG, new SampleSaveConfiguration())); + summariser = summer; + } + + // Ensure that the sample save config is not shared between copied nodes + // N.B. clone only seems to be used for client-server tests + @Override + public Object clone(){ + ResultCollector clone = (ResultCollector) super.clone(); + clone.setSaveConfig((SampleSaveConfiguration)clone.getSaveConfig().clone()); + // Unfortunately AbstractTestElement does not call super.clone() + clone.summariser = this.summariser; + return clone; + } + + private void setFilenameProperty(String f) { + setProperty(FILENAME, f); + } + + /** + * Get the filename of the file this collector uses + * + * @return The name of the file + */ + public String getFilename() { + return getPropertyAsString(FILENAME); + } + + /** + * Get the state of error logging + * + * @return Flag whether errors should be logged + */ + public boolean isErrorLogging() { + return getPropertyAsBoolean(ERROR_LOGGING); + } + + /** + * Sets error logging flag + * + * @param errorLogging + * The flag whether errors should be logged + */ + public final void setErrorLogging(boolean errorLogging) { + setProperty(new BooleanProperty(ERROR_LOGGING, errorLogging)); + } + + /** + * Sets the flag whether only successful samples should be logged + * + * @param value + * The flag whether only successful samples should be logged + */ + public final void setSuccessOnlyLogging(boolean value) { + if (value) { + setProperty(new BooleanProperty(SUCCESS_ONLY_LOGGING, true)); + } else { + removeProperty(SUCCESS_ONLY_LOGGING); + } + } + + /** + * Get the state of successful only logging + * + * @return Flag whether only successful samples should be logged + */ + public boolean isSuccessOnlyLogging() { + return getPropertyAsBoolean(SUCCESS_ONLY_LOGGING,false); + } + + /** + * Decides whether or not to a sample is wanted based on: + *
    + *
  • errorOnly
  • + *
  • successOnly
  • + *
  • sample success
  • + *
+ * Should only be called for single samples. + * + * @param success is sample successful + * @return whether to log/display the sample + */ + public boolean isSampleWanted(boolean success){ + boolean errorOnly = isErrorLogging(); + boolean successOnly = isSuccessOnlyLogging(); + return isSampleWanted(success, errorOnly, successOnly); + } + + /** + * Decides whether or not to a sample is wanted based on: + *
    + *
  • errorOnly
  • + *
  • successOnly
  • + *
  • sample success
  • + *
+ * This version is intended to be called by code that loops over many samples; + * it is cheaper than fetching the settings each time. + * @param success status of sample + * @param errorOnly if errors only wanted + * @param successOnly if success only wanted + * @return whether to log/display the sample + */ + public static boolean isSampleWanted(boolean success, boolean errorOnly, + boolean successOnly) { + return (!errorOnly && !successOnly) || + (success && successOnly) || + (!success && errorOnly); + // successOnly and errorOnly cannot both be set + } + /** + * Sets the filename attribute of the ResultCollector object. + * + * @param f + * the new filename value + */ + public void setFilename(String f) { + if (inTest) { + return; + } + setFilenameProperty(f); + } + + @Override + public void testEnded(String host) { + synchronized(LOCK){ + instanceCount--; + if (instanceCount <= 0) { + // No need for the hook now + // Bug 57088 - prevent (im?)possible NPE + if (shutdownHook != null) { + Runtime.getRuntime().removeShutdownHook(shutdownHook); + } else { + log.warn("Should not happen: shutdownHook==null, instanceCount=" + instanceCount); + } + finalizeFileOutput(); + inTest = false; + } + } + + if(summariser != null) { + summariser.testEnded(host); + } + } + + @Override + public void testStarted(String host) { + synchronized(LOCK){ + if (instanceCount == 0) { // Only add the hook once + shutdownHook = new Thread(new ShutdownHook()); + Runtime.getRuntime().addShutdownHook(shutdownHook); + } + instanceCount++; + try { + initializeFileOutput(); + if (getVisualizer() != null) { + this.isStats = getVisualizer().isStats(); + } + } catch (Exception e) { + log.error("", e); + } + } + inTest = true; + + if(summariser != null) { + summariser.testStarted(host); + } + } + + @Override + public void testEnded() { + testEnded(TEST_IS_LOCAL); + } + + @Override + public void testStarted() { + testStarted(TEST_IS_LOCAL); + } + + /** + * Loads an existing sample data (JTL) file. + * This can be one of: + *
    + *
  • XStream format
  • + *
  • Avalon format
  • + *
  • CSV format
  • + *
+ * + */ + public void loadExistingFile() { + final Visualizer visualizer = getVisualizer(); + if (visualizer == null) { + return; // No point reading the file if there's no visualiser + } + boolean parsedOK = false; + String filename = getFilename(); + File file = new File(filename); + if (file.exists()) { + BufferedReader dataReader = null; + BufferedInputStream bufferedInputStream = null; + try { + dataReader = new BufferedReader(new FileReader(file)); // TODO Charset ? + // Get the first line, and see if it is XML + String line = dataReader.readLine(); + dataReader.close(); + dataReader = null; + if (line == null) { + log.warn(filename+" is empty"); + } else { + if (!line.startsWith(" 0) { + writer.println(pi); + } + // Can't do it as a static initialisation, because SaveService + // is being constructed when this is called + writer.print(TESTRESULTS_START_V1_1_PREVER); + writer.print(SaveService.getVERSION()); + writer.print(TESTRESULTS_START_V1_1_POSTVER); + // Write the EOL separately so we generate LF line ends on Unix and Windows + writer.print("\n"); // $NON-NLS-1$ + } else if (saveConfig.saveFieldNames()) { + writer.println(CSVSaveService.printableFieldNamesToString(saveConfig)); + } + } + + private static void writeFileEnd(PrintWriter pw, SampleSaveConfiguration saveConfig) { + if (saveConfig.saveAsXml()) { + pw.print("\n"); // $NON-NLS-1$ + pw.print(TESTRESULTS_END); + pw.print("\n");// Added in version 1.1 // $NON-NLS-1$ + } + } + + private static PrintWriter getFileWriter(String filename, SampleSaveConfiguration saveConfig) + throws IOException { + if (filename == null || filename.length() == 0) { + return null; + } + filename = FileServer.resolveBaseRelativeName(filename); + FileEntry fe = files.get(filename); + PrintWriter writer = null; + boolean trimmed = true; + + if (fe == null) { + if (saveConfig.saveAsXml()) { + trimmed = trimLastLine(filename); + } else { + trimmed = new File(filename).exists(); + } + // Find the name of the directory containing the file + // and create it - if there is one + File pdir = new File(filename).getParentFile(); + if (pdir != null) { + // returns false if directory already exists, so need to check again + if(pdir.mkdirs()){ + log.info("Folder "+pdir.getAbsolutePath()+" was created"); + } // else if might have been created by another process so not a problem + if (!pdir.exists()){ + log.warn("Error creating directories for "+pdir.toString()); + } + } + writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(filename, + trimmed)), SaveService.getFileEncoding("UTF-8")), SAVING_AUTOFLUSH); // $NON-NLS-1$ + log.debug("Opened file: "+filename); + files.put(filename, new FileEntry(writer, saveConfig)); + } else { + writer = fe.pw; + } + if (!trimmed) { + writeFileStart(writer, saveConfig); + } + return writer; + } + + // returns false if the file did not contain the terminator + private static boolean trimLastLine(String filename) { + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(filename, "rw"); // $NON-NLS-1$ + long len = raf.length(); + if (len < MIN_XML_FILE_LEN) { + return false; + } + raf.seek(len - TESTRESULTS_END.length() - 10);// TODO: may not work on all OSes? + String line; + long pos = raf.getFilePointer(); + int end = 0; + while ((line = raf.readLine()) != null)// reads to end of line OR end of file + { + end = line.indexOf(TESTRESULTS_END); + if (end >= 0) // found the string + { + break; + } + pos = raf.getFilePointer(); + } + if (line == null) { + log.warn("Unexpected EOF trying to find XML end marker in " + filename); + raf.close(); + return false; + } + raf.setLength(pos + end);// Truncate the file + raf.close(); + raf = null; + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + log.warn("Error trying to find XML terminator " + e.toString()); + return false; + } finally { + try { + if (raf != null) { + raf.close(); + } + } catch (IOException e1) { + log.info("Could not close " + filename + " " + e1.getLocalizedMessage()); + } + } + return true; + } + + @Override + public void sampleStarted(SampleEvent e) { + } + + @Override + public void sampleStopped(SampleEvent e) { + } + + /** + * When a test result is received, display it and save it. + * + * @param event + * the sample event that was received + */ + @Override + public void sampleOccurred(SampleEvent event) { + SampleResult result = event.getResult(); + + if (isSampleWanted(result.isSuccessful())) { + sendToVisualizer(result); + if (out != null && !isResultMarked(result) && !this.isStats) { + SampleSaveConfiguration config = getSaveConfig(); + result.setSaveConfig(config); + try { + if (config.saveAsXml()) { + SaveService.saveSampleResult(event, out); + } else { // !saveAsXml + String savee = CSVSaveService.resultToDelimitedString(event); + out.println(savee); + } + } catch (Exception err) { + log.error("Error trying to record a sample", err); // should throw exception back to caller + } + } + } + + if(summariser != null) { + summariser.sampleOccurred(event); + } + } + + protected final void sendToVisualizer(SampleResult r) { + if (getVisualizer() != null) { + getVisualizer().add(r); + } + } + + /** + * recordStats is used to save statistics generated by visualizers + * + * @param e The data to save + * @throws IOException when data writing fails + */ + // Used by: MonitorHealthVisualizer.add(SampleResult res) + public void recordStats(TestElement e) throws IOException { + if (out != null) { + SaveService.saveTestElement(e, out); + } + } + + /** + * Checks if the sample result is marked or not, and marks it + * @param res - the sample result to check + * @return true if the result was marked + */ + private boolean isResultMarked(SampleResult res) { + String filename = getFilename(); + return res.markFile(filename); + } + + private void initializeFileOutput() throws IOException { + + String filename = getFilename(); + if (filename != null) { + if (out == null) { + try { + out = getFileWriter(filename, getSaveConfig()); + } catch (FileNotFoundException e) { + out = null; + } + } + } + } + + /** + * Flush PrintWriter to synchronize file contents + */ + protected void flushFile() { + if (out != null) { + log.info("forced flush through ResultCollecto#flushFile"); + out.flush(); + } + } + + /** + * Flush PrintWriter, called by Shutdown Hook to ensure no data is lost + */ + private static void flushFileOutput() { + for(Map.Entry me : files.entrySet()){ + log.debug("Flushing: "+me.getKey()); + FileEntry fe = me.getValue(); + fe.pw.flush(); + if (fe.pw.checkError()){ + log.warn("Problem detected during use of "+me.getKey()); + } + } + } + + private void finalizeFileOutput() { + for(Map.Entry me : files.entrySet()){ + log.debug("Closing: "+me.getKey()); + FileEntry fe = me.getValue(); + writeFileEnd(fe.pw, fe.config); + fe.pw.close(); + if (fe.pw.checkError()){ + log.warn("Problem detected during use of "+me.getKey()); + } + } + files.clear(); + } + + /** + * @return Returns the saveConfig. + */ + public SampleSaveConfiguration getSaveConfig() { + try { + return (SampleSaveConfiguration) getProperty(SAVE_CONFIG).getObjectValue(); + } catch (ClassCastException e) { + setSaveConfig(new SampleSaveConfiguration()); + return getSaveConfig(); + } + } + + /** + * @param saveConfig + * The saveConfig to set. + */ + public void setSaveConfig(SampleSaveConfiguration saveConfig) { + getProperty(SAVE_CONFIG).setObjectValue(saveConfig); + } + + // This is required so that + // @see org.apache.jmeter.gui.tree.JMeterTreeModel.getNodesOfType() + // can find the Clearable nodes - the userObject has to implement the interface. + @Override + public void clearData() { + } +} diff --git a/src/core/org/apache/jmeter/reporters/ResultCollectorHelper.java b/src/core/org/apache/jmeter/reporters/ResultCollectorHelper.java new file mode 100644 index 00000000000..f6ce914539a --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/ResultCollectorHelper.java @@ -0,0 +1,46 @@ +/* + * 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.reporters; + +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.visualizers.Visualizer; + +/** + * Helper class to allow TestResultWrapperConverter to send samples + * directly to the visualiser if required. + */ +public class ResultCollectorHelper { + + private final Visualizer visualizer; + private final boolean errorsOnly; + private final boolean successOnly; + + public ResultCollectorHelper(ResultCollector resultCollector, Visualizer visualizer) { + this.visualizer = visualizer; + this.errorsOnly = resultCollector.isErrorLogging(); + this.successOnly = resultCollector.isSuccessOnlyLogging(); + } + + public void add(SampleResult sample){ + if (ResultCollector.isSampleWanted(sample.isSuccessful(), errorsOnly, successOnly)){ + visualizer.add(sample); + } + } +} diff --git a/src/core/org/apache/jmeter/reporters/ResultSaver.java b/src/core/org/apache/jmeter/reporters/ResultSaver.java new file mode 100644 index 00000000000..52107cbbaac --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/ResultSaver.java @@ -0,0 +1,287 @@ +/* + * 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.reporters; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Save Result responseData to a set of files + * + * + * This is mainly intended for validation tests + * + */ +// TODO - perhaps save other items such as headers? +public class ResultSaver extends AbstractTestElement implements Serializable, SampleListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Object LOCK = new Object(); + + // File name sequence number + //@GuardedBy("LOCK") + private static long sequenceNumber = 0; + + //@GuardedBy("LOCK") + private static String timeStamp; + + private static final String TIMESTAMP_FORMAT = "yyyyMMdd-HHmm_"; // $NON-NLS-1$ + + //@GuardedBy("LOCK") + private static int numberPadLength; + + //+ JMX property names; do not change + + public static final String FILENAME = "FileSaver.filename"; // $NON-NLS-1$ + + public static final String VARIABLE_NAME = "FileSaver.variablename"; // $NON-NLS-1$ + + public static final String ERRORS_ONLY = "FileSaver.errorsonly"; // $NON-NLS-1$ + + public static final String SUCCESS_ONLY = "FileSaver.successonly"; // $NON-NLS-1$ + + public static final String SKIP_AUTO_NUMBER = "FileSaver.skipautonumber"; // $NON-NLS-1$ + + public static final String SKIP_SUFFIX = "FileSaver.skipsuffix"; // $NON-NLS-1$ + + public static final String ADD_TIMESTAMP = "FileSaver.addTimstamp"; // $NON-NLS-1$ + + public static final String NUMBER_PAD_LENGTH = "FileSaver.numberPadLen"; // $NON-NLS-1$ + + //- JMX property names + + private synchronized long nextNumber() { + return ++sequenceNumber; + } + + /* + * Constructor is initially called once for each occurrence in the test plan + * For GUI, several more instances are created Then clear is called at start + * of test Called several times during test startup The name will not + * necessarily have been set at this point. + */ + public ResultSaver() { + super(); + // log.debug(Thread.currentThread().getName()); + // System.out.println(">> "+me+" "+this.getName()+" + // "+Thread.currentThread().getName()); + } + + /* + * Constructor for use during startup (intended for non-GUI use) @param name + * of summariser + */ + public ResultSaver(String name) { + this(); + setName(name); + } + + /* + * This is called once for each occurrence in the test plan, before the + * start of the test. The super.clear() method clears the name (and all + * other properties), so it is called last. + */ + @Override + public void clear() { + synchronized(LOCK){ + sequenceNumber = 0; // TODO is this the right thing to do? + if (getAddTimeStamp()) { + DateFormat format = new SimpleDateFormat(TIMESTAMP_FORMAT); + timeStamp = format.format(new Date()); + } else { + timeStamp = ""; + } + numberPadLength=getNumberPadLen(); + } + super.clear(); + } + + /** + * Saves the sample result (and any sub results) in files + * + * @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) + */ + @Override + public void sampleOccurred(SampleEvent e) { + processSample(e.getResult(), new Counter()); + } + + /** + * Recurse the whole (sub)result hierarchy. + * + * @param s Sample result + * @param c sample counter + */ + private void processSample(SampleResult s, Counter c) { + saveSample(s, c.num++); + SampleResult[] sr = s.getSubResults(); + for (int i = 0; i < sr.length; i++) { + processSample(sr[i], c); + } + } + + /** + * @param s SampleResult to save + * @param num number to append to variable (if >0) + */ + private void saveSample(SampleResult s, int num) { + // Should we save the sample? + if (s.isSuccessful()){ + if (getErrorsOnly()){ + return; + } + } else { + if (getSuccessOnly()){ + return; + } + } + + String fileName = makeFileName(s.getContentType(), getSkipAutoNumber(), getSkipSuffix()); + log.debug("Saving " + s.getSampleLabel() + " in " + fileName); + s.setResultFileName(fileName);// Associate sample with file name + String variable = getVariableName(); + if (variable.length()>0){ + if (num > 0) { + StringBuilder sb = new StringBuilder(variable); + sb.append(num); + variable=sb.toString(); + } + JMeterContextService.getContext().getVariables().put(variable, fileName); + } + File out = new File(fileName); + FileOutputStream pw = null; + try { + pw = new FileOutputStream(out); + JOrphanUtils.write(s.getResponseData(), pw); // chunk the output if necessary + } catch (FileNotFoundException e1) { + log.error("Error creating sample file for " + s.getSampleLabel(), e1); + } catch (IOException e1) { + log.error("Error saving sample " + s.getSampleLabel(), e1); + } finally { + JOrphanUtils.closeQuietly(pw); + } + } + + /** + * @return fileName composed of fixed prefix, a number, and a suffix derived + * from the contentType e.g. Content-Type: + * text/html;charset=ISO-8859-1 + */ + private String makeFileName(String contentType, boolean skipAutoNumber, boolean skipSuffix) { + StringBuilder sb = new StringBuilder(FileServer.resolveBaseRelativeName(getFilename())); + sb.append(timeStamp); // may be the empty string + if (!skipAutoNumber){ + String number = Long.toString(nextNumber()); + for(int i=number.length(); i < numberPadLength; i++) { + sb.append('0'); + } + sb.append(number); + } + if (!skipSuffix){ + sb.append('.'); + if (contentType != null) { + int i = contentType.indexOf('/'); // $NON-NLS-1$ + if (i != -1) { + int j = contentType.indexOf(';'); // $NON-NLS-1$ + if (j != -1) { + sb.append(contentType.substring(i + 1, j)); + } else { + sb.append(contentType.substring(i + 1)); + } + } else { + sb.append("unknown"); + } + } else { + sb.append("unknown"); + } + } + return sb.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public void sampleStarted(SampleEvent e) { + // not used + } + + /** + * {@inheritDoc} + */ + @Override + public void sampleStopped(SampleEvent e) { + // not used + } + + private String getFilename() { + return getPropertyAsString(FILENAME); + } + + private String getVariableName() { + return getPropertyAsString(VARIABLE_NAME,""); // $NON-NLS-1$ + } + + private boolean getErrorsOnly() { + return getPropertyAsBoolean(ERRORS_ONLY); + } + + private boolean getSkipAutoNumber() { + return getPropertyAsBoolean(SKIP_AUTO_NUMBER); + } + + private boolean getSkipSuffix() { + return getPropertyAsBoolean(SKIP_SUFFIX); + } + + private boolean getSuccessOnly() { + return getPropertyAsBoolean(SUCCESS_ONLY); + } + + private boolean getAddTimeStamp() { + return getPropertyAsBoolean(ADD_TIMESTAMP); + } + + private int getNumberPadLen() { + return getPropertyAsInt(NUMBER_PAD_LENGTH, 0); + } + + // Mutable int to keep track of sample count + private static class Counter{ + int num; + } +} diff --git a/src/core/org/apache/jmeter/reporters/Summariser.java b/src/core/org/apache/jmeter/reporters/Summariser.java new file mode 100644 index 00000000000..63c0ee81b15 --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/Summariser.java @@ -0,0 +1,396 @@ +/* + * 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.reporters; + +import java.io.Serializable; +import java.text.DecimalFormat; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterContextService.ThreadCounts; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Generate a summary of the test run so far to the log file and/or standard + * output. Both running and differential totals are shown. Output is generated + * every n seconds (default 30 seconds (property summariser.interval)) on the appropriate time boundary, so that + * multiple test runs on the same time will be synchronised. + * + * This is mainly intended for batch (non-GUI) runs + * FIXME : Docs below are outdated, need fixing + * Note that the {@link SummariserRunningSample} start and end times relate to the samples, + * not the reporting interval. + * + * Since the first sample in a delta is likely to have started in the previous reporting interval, + * this means that the delta interval is likely to be longer than the reporting interval. + * + * Also, the sum of the delta intervals will be larger than the overall elapsed time. + * + * Data is accumulated according to the test element name. + * + */ +public class Summariser extends AbstractTestElement + implements Serializable, SampleListener, TestStateListener, NoThreadClone, Remoteable { + + /* + * N.B. NoThreadClone is used to ensure that the testStarted() methods will share the same + * instance as the sampleOccured() methods, so the testStarted() method can fetch the + * Totals accumulator object for the samples to be stored in. + */ + + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** interval between summaries (in seconds) default 30 seconds */ + private static final long INTERVAL = JMeterUtils.getPropDefault("summariser.interval", 30); //$NON-NLS-1$ + + /** Write messages to log file ? */ + private static final boolean TOLOG = JMeterUtils.getPropDefault("summariser.log", true); //$NON-NLS-1$ + + /** Write messages to System.out ? */ + private static final boolean TOOUT = JMeterUtils.getPropDefault("summariser.out", true); //$NON-NLS-1$ + + /* + * Ensure that a report is not skipped if we are slightly late in checking + * the time. + */ + private static final int INTERVAL_WINDOW = 5; // in seconds + + /** + * Lock used to protect ACCUMULATORS update + INSTANCE_COUNT update + */ + private static final Object LOCK = new Object(); + + /* + * This map allows summarisers with the same name to contribute to the same totals. + */ + //@GuardedBy("LOCK") - needed to ensure consistency between this and INSTANCE_COUNT + private static final Map ACCUMULATORS = new ConcurrentHashMap(); + + //@GuardedBy("LOCK") + private static int INSTANCE_COUNT; // number of active tests + + /* + * Cached copy of Totals for this instance. + * The variables do not need to be synchronised, + * as they are not shared between threads + * However the contents do need to be synchronized. + */ + //@GuardedBy("myTotals") + private transient Totals myTotals = null; + + // Name of the accumulator. Set up by testStarted(). + private transient String myName; + + /* + * Constructor is initially called once for each occurrence in the test plan. + * For GUI, several more instances are created. + * Then clear is called at start of test. + * Called several times during test startup. + * The name will not necessarily have been set at this point. + */ + public Summariser() { + super(); + synchronized (LOCK) { + ACCUMULATORS.clear(); + INSTANCE_COUNT=0; + } + } + + /** + * Constructor for use during startup (intended for non-GUI use) + * + * @param name of summariser + */ + public Summariser(String name) { + this(); + setName(name); + } + + /* + * Contains the items needed to collect stats for a summariser + * + */ + private static class Totals { + + /** Time of last summary (to prevent double reporting) */ + private long last = 0; + + private final SummariserRunningSample delta = new SummariserRunningSample("DELTA"); + + private final SummariserRunningSample total = new SummariserRunningSample("TOTAL"); + + /** + * Add the delta values to the total values and clear the delta + */ + private void moveDelta() { + total.addSample(delta); + delta.clear(); + } + } + + /** + * Accumulates the sample in two SampleResult objects - one for running + * totals, and the other for deltas. + * + * @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) + */ + @Override + public void sampleOccurred(SampleEvent e) { + SampleResult s = e.getResult(); + + long now = System.currentTimeMillis() / 1000;// in seconds + + SummariserRunningSample myDelta = null; + SummariserRunningSample myTotal = null; + boolean reportNow = false; + + /* + * Have we reached the reporting boundary? + * Need to allow for a margin of error, otherwise can miss the slot. + * Also need to check we've not hit the window already + */ + synchronized (myTotals) { + if (s != null) { + myTotals.delta.addSample(s); + } + + if ((now > myTotals.last + INTERVAL_WINDOW) && (now % INTERVAL <= INTERVAL_WINDOW)) { + reportNow = true; + + // copy the data to minimise the synch time + myDelta = new SummariserRunningSample(myTotals.delta); + myTotals.moveDelta(); + myTotal = new SummariserRunningSample(myTotals.total); + + myTotals.last = now; // stop double-reporting + } + } + if (reportNow) { + String str; + str = format(myName, myDelta, "+"); + if (TOLOG) { + log.info(str); + } + if (TOOUT) { + System.out.println(str); + } + + // Only if we have updated them + if (myTotal != null && myDelta != null &&myTotal.getNumSamples() != myDelta.getNumSamples()) { + str = format(myName, myTotal, "="); + if (TOLOG) { + log.info(str); + } + if (TOOUT) { + System.out.println(str); + } + } + } + } + + private static StringBuilder longToSb(StringBuilder sb, long l, int len) { + sb.setLength(0); + sb.append(l); + return JOrphanUtils.rightAlign(sb, len); + } + + private static StringBuilder doubleToSb(DecimalFormat dfDouble, StringBuilder sb, double d, int len, int frac) { + sb.setLength(0); + dfDouble.setMinimumFractionDigits(frac); + dfDouble.setMaximumFractionDigits(frac); + sb.append(dfDouble.format(d)); + return JOrphanUtils.rightAlign(sb, len); + } + + /** + * Formats summariserRunningSample + * @param name Summariser name + * @param summariserRunningSample {@link SummariserRunningSample} + * @param type Type of summariser (difference or total) + * @return the summary information + */ + private static String format(String name, SummariserRunningSample summariserRunningSample, String type) { + DecimalFormat dfDouble = new DecimalFormat("#0.0"); // $NON-NLS-1$ + StringBuilder tmp = new StringBuilder(20); // for intermediate use + StringBuilder sb = new StringBuilder(100); // output line buffer + sb.append(name); + sb.append(" "); + sb.append(type); + sb.append(" "); + sb.append(longToSb(tmp, summariserRunningSample.getNumSamples(), 6)); + sb.append(" in "); + long elapsed = summariserRunningSample.getElapsed(); + long elapsedSec = (elapsed + 500) / 1000; // rounded seconds + if (elapsedSec > 100 // No point displaying decimals (less than 1% error) + || (elapsed - elapsedSec * 1000) < 50 // decimal would be zero + ) { + sb.append(longToSb(tmp, elapsedSec, 5)); + } else { + double elapsedSecf = elapsed / 1000.0d; // fractional seconds + sb.append(doubleToSb(dfDouble, tmp, elapsedSecf, 5, 1)); // This will round + } + sb.append("s = "); + if (elapsed > 0) { + sb.append(doubleToSb(dfDouble, tmp, summariserRunningSample.getRate(), 6, 1)); + } else { + sb.append("******");// Rate is effectively infinite + } + sb.append("/s Avg: "); + sb.append(longToSb(tmp, summariserRunningSample.getAverage(), 5)); + sb.append(" Min: "); + sb.append(longToSb(tmp, summariserRunningSample.getMin(), 5)); + sb.append(" Max: "); + sb.append(longToSb(tmp, summariserRunningSample.getMax(), 5)); + sb.append(" Err: "); + sb.append(longToSb(tmp, summariserRunningSample.getErrorCount(), 5)); + sb.append(" ("); + sb.append(summariserRunningSample.getErrorPercentageString()); + sb.append(")"); + if ("+".equals(type)) { + ThreadCounts tc = JMeterContextService.getThreadCounts(); + sb.append(" Active: "); + sb.append(tc.activeThreads); + sb.append(" Started: "); + sb.append(tc.startedThreads); + sb.append(" Finished: "); + sb.append(tc.finishedThreads); + } + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void sampleStarted(SampleEvent e) { + // not used + } + + /** {@inheritDoc} */ + @Override + public void sampleStopped(SampleEvent e) { + // not used + } + + /* + * The testStarted/testEnded methods are called at the start and end of a test. + * + * However, when a test is run on multiple nodes, there is no guarantee that all the + * testStarted() methods will be called before all the threadStart() or sampleOccurred() + * methods for other threads - nor that testEnded() will only be called after all + * sampleOccurred() calls. The ordering is only guaranteed within a single test. + * + */ + + + /** {@inheritDoc} */ + @Override + public void testStarted() { + testStarted("local"); + } + + /** {@inheritDoc} */ + @Override + public void testEnded() { + testEnded("local"); + } + + /** + * Called once for each Summariser in the test plan. + * There may be more than one summariser with the same name, + * however they will all be called before the test proper starts. + *

+ * However, note that this applies to a single test only. + * When running in client-server mode, testStarted() may be + * invoked after sampleOccurred(). + *

+ * {@inheritDoc} + */ + @Override + public void testStarted(String host) { + synchronized (LOCK) { + myName = getName(); + myTotals = ACCUMULATORS.get(myName); + if (myTotals == null){ + myTotals = new Totals(); + ACCUMULATORS.put(myName, myTotals); + } + INSTANCE_COUNT++; + } + } + + /** + * Called from a different thread as testStarted() but using the same instance. + * So synch is needed to fetch the accumulator, and the myName field will already be set up. + *

+ * {@inheritDoc} + */ + @Override + public void testEnded(String host) { + Set> totals = null; + synchronized (LOCK) { + INSTANCE_COUNT--; + if (INSTANCE_COUNT <= 0){ + totals = ACCUMULATORS.entrySet(); + } + } + if (totals == null) {// We're not done yet + return; + } + for(Map.Entry entry : totals){ + String str; + String name = entry.getKey(); + Totals total = entry.getValue(); + total.delta.setEndTime(); // ensure delta has correct end time + // Only print final delta if there were some samples in the delta + // and there has been at least one sample reported previously + if (total.delta.getNumSamples() > 0 && total.total.getNumSamples() > 0) { + str = format(name, total.delta, "+"); + if (TOLOG) { + log.info(str); + } + if (TOOUT) { + System.out.println(str); + } + } + total.moveDelta(); // This will update the total endTime + str = format(name, total.total, "="); + if (TOLOG) { + log.info(str); + } + if (TOOUT) { + System.out.println(str); + } + } + } + +} diff --git a/src/core/org/apache/jmeter/reporters/SummariserRunningSample.java b/src/core/org/apache/jmeter/reporters/SummariserRunningSample.java new file mode 100644 index 00000000000..a450c0ef0be --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/SummariserRunningSample.java @@ -0,0 +1,248 @@ +/* + * 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.reporters; + +import java.text.DecimalFormat; + +import org.apache.jmeter.samplers.SampleResult; + +/** + *

+ * Running sample data container. Just instantiate a new instance of this + * class, and then call {@link #addSample(SampleResult)} a few times, and pull + * the stats out with whatever methods you prefer. + *

+ *

+ * Please note that this class is not thread-safe. + * The calling class is responsible for ensuring thread safety if required. + * The caller needs to synchronize access in order to ensure that variables are consistent. + *

+ * @since 2.13 + */ +class SummariserRunningSample { + + private final DecimalFormat errorFormatter = new DecimalFormat("#0.00%"); // $NON-NLS-1$ + + private long counter; + + private long runningSum; + + private long max; + + private long min; + + private long errorCount; + + private long startTime; + + private long endTime; + + private final String label; + + /** + * @param label the label of this component + */ + public SummariserRunningSample(String label) { + this.label = label; + init(); + } + + /** + * Copy constructor + * @param src the instance to copy + */ + public SummariserRunningSample(SummariserRunningSample src) { + label = src.label; + counter = src.counter; + errorCount = src.errorCount; + startTime = src.startTime; + endTime = src.endTime; + max = src.max; + min = src.min; + runningSum = src.runningSum; + } + + private void init() { + counter = 0L; + runningSum = 0L; + max = Long.MIN_VALUE; + min = Long.MAX_VALUE; + errorCount = 0L; + startTime = System.currentTimeMillis(); + endTime = startTime; + } + + /** + * Clear at every interval + */ + public void clear() { + init(); + } + + /** + * Used for delta + * @param rs {@link SummariserRunningSample} + */ + public void addSample(SummariserRunningSample rs) { + counter += rs.counter; + errorCount += rs.errorCount; + runningSum += rs.runningSum; + if (max < rs.max) { + max = rs.max; + } + if (min > rs.min) { + min = rs.min; + } + // We want end time to be current time so sample rates reflect real time + endTime = System.currentTimeMillis(); + } + + /** + * Used for each SampleResult + * @param res {@link SampleResult} + */ + public void addSample(SampleResult res) { + counter += res.getSampleCount(); + errorCount += res.getErrorCount(); + long aTimeInMillis = res.getTime(); + runningSum += aTimeInMillis; + if (aTimeInMillis > max) { + max = aTimeInMillis; + } + if (aTimeInMillis < min) { + min = aTimeInMillis; + } + // We want end time to be current time so sample rates reflect real time + endTime = System.currentTimeMillis(); + } + + /** + * Returns the number of samples that have been recorded by this instance of + * the {@link SummariserRunningSample} class. + * + * @return the number of samples that have been recorded by this instance of + * the {@link SummariserRunningSample} class. + */ + public long getNumSamples() { + return counter; + } + + /** + * Get the elapsed time for the samples + * + * @return how long the samples took + */ + public long getElapsed() { + if (counter == 0) { + return 0;// No samples collected ... + } + return endTime - startTime; + } + + /** + * Returns the throughput associated to this sampler in requests per second. + * + * @return throughput associated to this sampler + */ + public double getRate() { + if (counter == 0) { + return 0.0;// No samples collected ... + } + + long howLongRunning = endTime - startTime; + + if (howLongRunning == 0) { + return Double.MAX_VALUE; + } + + return (double) counter / howLongRunning * 1000.0; + } + + /** + * Returns the average time in milliseconds that samples ran in. + * + * @return the average time in milliseconds that samples ran in. + */ + public long getAverage() { + if (counter == 0) { + return 0; + } + return runningSum / counter; + } + + /** + * @return errorCount + */ + public long getErrorCount() { + return errorCount; + } + + /** + * Returns a String which represents the percentage of sample errors that + * have occurred. ("0.00%" through "100.00%") + * + * @return a String which represents the percentage of sample errors that + * have occurred. + */ + public String getErrorPercentageString() { + return errorFormatter.format(getErrorPercentage()); + } + + /** + * Returns the raw double value of the percentage of samples with errors + * that were recorded. (Between 0.0 and 1.0) If you want a nicer return + * format, see {@link #getErrorPercentageString()}. + * + * @return the raw double value of the percentage of samples with errors + * that were recorded. Returns 0.0 if there are no samples + */ + public double getErrorPercentage() { + if (counter == 0) { + return 0.0; + } + double rval = (double) errorCount / (double) counter; + return rval; + } + + /** + * Returns the time in milliseconds of the slowest sample. + * + * @return the time in milliseconds of the slowest sample. + */ + public long getMax() { + return max; + } + + /** + * Returns the time in milliseconds of the quickest sample. + * + * @return the time in milliseconds of the quickest sample. + */ + public long getMin() { + return min; + } + + /** + * Set end time + */ + public void setEndTime() { + endTime = System.currentTimeMillis(); + } + +} diff --git a/src/core/org/apache/jmeter/reporters/gui/ResultActionGui.java b/src/core/org/apache/jmeter/reporters/gui/ResultActionGui.java new file mode 100644 index 00000000000..f804f9ff936 --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/gui/ResultActionGui.java @@ -0,0 +1,103 @@ +/* + * 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.reporters.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; + +import org.apache.jmeter.reporters.ResultAction; +import org.apache.jmeter.gui.OnErrorPanel; +import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; +import org.apache.jmeter.testelement.OnErrorTestElement; +import org.apache.jmeter.testelement.TestElement; + +/** + * Create a Result Action Test Element + * + */ +public class ResultActionGui extends AbstractPostProcessorGui { + + private static final long serialVersionUID = 240L; + + private OnErrorPanel errorPanel; + + public ResultActionGui() { + super(); + init(); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#getStaticLabel() + */ + @Override + public String getLabelResource() { + return "resultaction_title"; //$NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + errorPanel.configure(((OnErrorTestElement) el).getErrorAction()); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + ResultAction resultAction = new ResultAction(); + modifyTestElement(resultAction); + return resultAction; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement te) { + super.configureTestElement(te); + ((OnErrorTestElement) te).setErrorAction(errorPanel.getOnErrorSetting()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + errorPanel.configure(OnErrorTestElement.ON_ERROR_CONTINUE); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + errorPanel = new OnErrorPanel(); + box.add(errorPanel); + add(box, BorderLayout.NORTH); + } +} diff --git a/src/core/org/apache/jmeter/reporters/gui/ResultSaverGui.java b/src/core/org/apache/jmeter/reporters/gui/ResultSaverGui.java new file mode 100644 index 00000000000..1d2a6d8ada7 --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/gui/ResultSaverGui.java @@ -0,0 +1,193 @@ +/* + * 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.reporters.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.reporters.ResultSaver; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractListenerGui; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * Create a ResultSaver test element, which saves the sample information in set + * of files + * + */ +public class ResultSaverGui extends AbstractListenerGui implements Clearable { + + private static final long serialVersionUID = 240L; + + private JTextField filename; + + private JTextField variableName; + + private JCheckBox errorsOnly; + + private JCheckBox successOnly; + + private JCheckBox skipAutoNumber; + + private JCheckBox skipSuffix; + + private JCheckBox addTimestamp; + + private JLabeledTextField numberPadLength; + + public ResultSaverGui() { + super(); + init(); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#getStaticLabel() + */ + @Override + public String getLabelResource() { + return "resultsaver_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + filename.setText(el.getPropertyAsString(ResultSaver.FILENAME)); + errorsOnly.setSelected(el.getPropertyAsBoolean(ResultSaver.ERRORS_ONLY)); + successOnly.setSelected(el.getPropertyAsBoolean(ResultSaver.SUCCESS_ONLY)); + skipAutoNumber.setSelected(el.getPropertyAsBoolean(ResultSaver.SKIP_AUTO_NUMBER)); + skipSuffix.setSelected(el.getPropertyAsBoolean(ResultSaver.SKIP_SUFFIX)); + variableName.setText(el.getPropertyAsString(ResultSaver.VARIABLE_NAME,"")); + addTimestamp.setSelected(el.getPropertyAsBoolean(ResultSaver.ADD_TIMESTAMP)); + numberPadLength.setText(el.getPropertyAsString(ResultSaver.NUMBER_PAD_LENGTH,"")); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + ResultSaver resultSaver = new ResultSaver(); + modifyTestElement(resultSaver); + return resultSaver; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement te) { + super.configureTestElement(te); + te.setProperty(ResultSaver.FILENAME, filename.getText()); + te.setProperty(ResultSaver.ERRORS_ONLY, errorsOnly.isSelected()); + te.setProperty(ResultSaver.SKIP_AUTO_NUMBER, skipAutoNumber.isSelected()); + te.setProperty(ResultSaver.SKIP_SUFFIX, skipSuffix.isSelected()); + te.setProperty(ResultSaver.SUCCESS_ONLY, successOnly.isSelected()); + te.setProperty(ResultSaver.ADD_TIMESTAMP, addTimestamp.isSelected(), false); + AbstractTestElement at = (AbstractTestElement) te; + at.setProperty(ResultSaver.VARIABLE_NAME, variableName.getText(),""); //$NON-NLS-1$ + at.setProperty(ResultSaver.NUMBER_PAD_LENGTH, numberPadLength.getText(),""); //$NON-NLS-1$ + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + skipAutoNumber.setSelected(false); + skipSuffix.setSelected(false); + filename.setText(""); //$NON-NLS-1$ + errorsOnly.setSelected(false); + successOnly.setSelected(false); + addTimestamp.setSelected(false); + variableName.setText(""); //$NON-NLS-1$ + numberPadLength.setText(""); //$NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createFilenamePrefixPanel()); + box.add(createVariableNamePanel()); + errorsOnly = new JCheckBox(JMeterUtils.getResString("resultsaver_errors")); // $NON-NLS-1$ + box.add(errorsOnly); + successOnly = new JCheckBox(JMeterUtils.getResString("resultsaver_success")); // $NON-NLS-1$ + box.add(successOnly); + skipAutoNumber = new JCheckBox(JMeterUtils.getResString("resultsaver_skipautonumber")); // $NON-NLS-1$ + box.add(skipAutoNumber); + skipSuffix = new JCheckBox(JMeterUtils.getResString("resultsaver_skipsuffix")); // $NON-NLS-1$ + box.add(skipSuffix); + addTimestamp = new JCheckBox(JMeterUtils.getResString("resultsaver_addtimestamp")); // $NON-NLS-1$ + box.add(addTimestamp); + numberPadLength = new JLabeledTextField(JMeterUtils.getResString("resultsaver_numberpadlen"));// $NON-NLS-1$ + box.add(numberPadLength); + add(box, BorderLayout.NORTH); + } + + private JPanel createFilenamePrefixPanel() + { + JLabel label = new JLabel(JMeterUtils.getResString("resultsaver_prefix")); // $NON-NLS-1$ + + filename = new JTextField(10); + filename.setName(ResultSaver.FILENAME); + label.setLabelFor(filename); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(filename, BorderLayout.CENTER); + return filenamePanel; + } + + + private JPanel createVariableNamePanel() + { + JLabel label = new JLabel(JMeterUtils.getResString("resultsaver_variable")); // $NON-NLS-1$ + + variableName = new JTextField(10); + variableName.setName(ResultSaver.VARIABLE_NAME); + label.setLabelFor(variableName); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(variableName, BorderLayout.CENTER); + return filenamePanel; + } + + // Needed to avoid Class cast error in Clear.java + @Override + public void clearData() { + } + +} diff --git a/src/core/org/apache/jmeter/reporters/gui/SummariserGui.java b/src/core/org/apache/jmeter/reporters/gui/SummariserGui.java new file mode 100644 index 00000000000..bce5e3d7a64 --- /dev/null +++ b/src/core/org/apache/jmeter/reporters/gui/SummariserGui.java @@ -0,0 +1,76 @@ +/* + * 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.reporters.gui; + +import java.awt.BorderLayout; + +import org.apache.jmeter.reporters.Summariser; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.visualizers.gui.AbstractListenerGui; + +/** + * Create a summariser test element GUI. + * + */ +public class SummariserGui extends AbstractListenerGui { + + private static final long serialVersionUID = 240L; + + public SummariserGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "summariser_title"; //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + Summariser summariser = new Summariser(); + modifyTestElement(summariser); + return summariser; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement summariser) { + super.configureTestElement(summariser); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + } +} diff --git a/src/core/org/apache/jmeter/resources/messages.properties b/src/core/org/apache/jmeter/resources/messages.properties new file mode 100644 index 00000000000..01bbba238d6 --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages.properties @@ -0,0 +1,1332 @@ +# 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. + +# Warning: JMeterUtils.getResString() replaces space with '_' +# and converts keys to lowercase before lookup +# => All keys in this file must also be lower case or they won't match +# + +# Please add new entries in alphabetical order + +about=About Apache JMeter +active_threads_tooltip=Running threads +add=Add +add_as_child=Add as Child +add_from_clipboard=Add from Clipboard +add_from_suggested_excludes=Add suggested Excludes +add_parameter=Add Variable +add_pattern=Add Pattern\: +add_test=Add Test +add_user=Add User +add_value=Add Value +addtest=Add test +aggregate_graph=Statistical Graphs +aggregate_graph_choose_color=Choose color +aggregate_graph_choose_foreground_color=Foreground color +aggregate_graph_color_bar=Color\: +aggregate_graph_column=Column\: +aggregate_graph_column_selection=Column label selection\: +aggregate_graph_column_settings=Column settings +aggregate_graph_columns_to_display=Columns to display\: +aggregate_graph_dimension=Graph size +aggregate_graph_display=Display Graph +aggregate_graph_draw_outlines=Draw outlines bar? +aggregate_graph_dynamic_size=Dynamic graph size +aggregate_graph_font=Font\: +aggregate_graph_height=Height\: +aggregate_graph_increment_scale=Increment scale\: +aggregate_graph_legend=Legend +aggregate_graph_legend.placement.bottom=Bottom +aggregate_graph_legend.placement.left=Left +aggregate_graph_legend.placement.right=Right +aggregate_graph_legend.placement.top=Top +aggregate_graph_legend_placement=Placement\: +aggregate_graph_max_length_xaxis_label=Max length of x-axis label\: +aggregate_graph_ms=Milliseconds +aggregate_graph_no_values_to_graph=No values to graph +aggregate_graph_number_grouping=Show number grouping? +aggregate_graph_response_time=Response Time +aggregate_graph_save=Save Graph +aggregate_graph_save_table=Save Table Data +aggregate_graph_save_table_header=Save Table Header +aggregate_graph_size=Size\: +aggregate_graph_style=Style\: +aggregate_graph_sync_with_name=Synchronize with name +aggregate_graph_tab_graph=Graph +aggregate_graph_tab_settings=Settings +aggregate_graph_title=Aggregate Graph +aggregate_graph_title_group=Title +aggregate_graph_use_group_name=Include group name in label? +aggregate_graph_user_title=Graph title\: +aggregate_graph_value_font=Value font\: +aggregate_graph_value_labels_vertical=Value labels vertical? +aggregate_graph_width=Width\: +aggregate_graph_xaxis_group=X Axis +aggregate_graph_yaxis_group=Y Axis (milli-seconds) +aggregate_graph_yaxis_max_value=Scale maximum value\: +aggregate_report=Aggregate Report +aggregate_report_xx_pct1_line={0}% Line +aggregate_report_xx_pct2_line={0}% Line +aggregate_report_xx_pct3_line={0}% Line +aggregate_report_90=90% +aggregate_report_bandwidth=KB/sec +aggregate_report_count=# Samples +aggregate_report_error=Error +aggregate_report_error%=Error % +aggregate_report_max=Max +aggregate_report_median=Median +aggregate_report_min=Min +aggregate_report_rate=Throughput +aggregate_report_stddev=Std. Dev. +aggregate_report_total_label=TOTAL +ajp_sampler_title=AJP/1.3 Sampler +als_message=Note\: The Access Log Parser is generic in design and allows you to plugin +als_message2=your own parser. To do so, implement the LogParser, add the jar to the +als_message3=/lib directory and enter the class in the sampler. +analyze=Analyze Data File... +anchor_modifier_title=HTML Link Parser +appearance=Look and Feel +argument_must_not_be_negative=The Argument must not be negative\! +arguments_panel_title=Command parameters +assertion_assume_success=Ignore Status +assertion_body_resp=Response Body +assertion_code_resp=Response Code +assertion_contains=Contains +assertion_equals=Equals +assertion_headers=Response Headers +assertion_matches=Matches +assertion_message_resp=Response Message +assertion_network_size=Full Response +assertion_not=Not +assertion_pattern_match_rules=Pattern Matching Rules +assertion_patterns_to_test=Patterns to Test +assertion_resp_field=Response Field to Test +assertion_resp_size_field=Response Size Field to Test +assertion_substring=Substring +assertion_text_document=Document (text) +assertion_text_resp=Text Response +assertion_textarea_label=Assertions\: +assertion_title=Response Assertion +assertion_url_samp=URL Sampled +assertion_visualizer_title=Assertion Results +attribute=Attribute +attribute_field=Attribute\: +attrs=Attributes +auth_base_url=Base URL +auth_manager_clear_per_iter=Clear auth on each iteration? +auth_manager_options=Options +auth_manager_title=HTTP Authorization Manager +auths_stored=Authorizations Stored in the Authorization Manager +average=Average +average_bytes=Avg. Bytes +backend_listener=Backend Listener +backend_listener_classname=Backend Listener implementation +backend_listener_paramtable=Parameters +backend_listener_queue_size=Async Queue size +bind=Thread Bind +bouncy_castle_unavailable_message=The jars for bouncy castle are unavailable, please add them to your classpath. +browse=Browse... +bsf_sampler_title=BSF Sampler +bsf_script=Script to run (variables: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsf_script_file=Script file to run +bsf_script_language=Scripting language\: +bsf_script_parameters=Parameters to pass to script/file\: +bsh_assertion_script=Script (see below for variables that are defined) +bsh_assertion_script_variables=The following variables are defined for the script:\nRead/Write: Failure, FailureMessage, SampleResult, vars, props, log.\nReadOnly: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData, ctx +bsh_assertion_title=BeanShell Assertion +bsh_function_expression=Expression to evaluate +bsh_sampler_title=BeanShell Sampler +bsh_script=Script (see below for variables that are defined) +bsh_script_file=Script file +bsh_script_parameters=Parameters (-> String Parameters and String []bsh.args) +bsh_script_reset_interpreter=Reset bsh.Interpreter before each call +bsh_script_variables=The following variables are defined for the script\:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=I'm busy testing, please stop the test before changing settings +cache_manager_size=Max Number of elements in cache +cache_manager_title=HTTP Cache Manager +cache_session_id=Cache Session Id? +cancel=Cancel +cancel_exit_to_save=There are test items that have not been saved. Do you wish to save before exiting? +cancel_new_from_template=There are test items that have not been saved. Do you wish to save before creating a test plan from selected template? +cancel_new_to_save=There are test items that have not been saved. Do you wish to save before clearing the test plan? +cancel_revert_project=There are test items that have not been saved. Do you wish to revert to the previously saved test plan? +change_parent=Change Controller +char_value=Unicode character number (decimal or 0xhex) +check_return_code_title=Check Return Code +choose_function=Choose a function +choose_language=Choose Language +clear=Clear +clear_all=Clear All +clear_cache_each_iteration=Clear cache each iteration +clear_cache_per_iter=Clear cache each iteration? +clear_cookies_per_iter=Clear cookies each iteration? +clipboard_node_read_error=An error occurred while copying node +close=Close +closeconnection=Close connection +column_delete_disallowed=Deleting this column is not permitted +column_number=Column number of CSV file | next | *alias +command_config_box_title=Command to Execute +command_config_std_streams_title=Standard streams (files) +command_field_title=Command: +compare=Compare +comparefilt=Compare filter +comparison_differ_content=Responses differ in content +comparison_differ_time=Responses differ in response time by more than +comparison_invalid_node=Invalid Node +comparison_regex_string=Regex String +comparison_regex_substitution=Substitution +comparison_response_time=Response Time: +comparison_unit=\ ms +comparison_visualizer_title=Comparison Assertion Visualizer +config_element=Config Element +config_save_settings=Configure +configure_wsdl=Configure +confirm=Confirm +constant_throughput_timer_memo=Add a delay between sampling to attain constant throughput +constant_timer_delay=Thread Delay (in milliseconds)\: +constant_timer_memo=Add a constant delay between sampling +constant_timer_title=Constant Timer +content_encoding=Content encoding\: +controller=Controller +cookie_implementation_choose=Implementation: +cookie_manager_policy=Cookie Policy: +cookie_manager_title=HTTP Cookie Manager +cookie_options=Options +cookies_stored=User-Defined Cookies +copy=Copy +counter_config_title=Counter +counter_per_user=Track counter independently for each user +counter_reset_per_tg_iteration=Reset counter on each Thread Group Iteration +countlim=Size limit +critical_section_controller_label=Lock name +critical_section_controller_title=Critical Section Controller +cssjquery_attribute=Attribute\: +cssjquery_tester_error=An error occured evaluating expression:{0}, error:{1} +cssjquery_impl=CSS/JQuery implementation\: +cssjquery_render_no_text=Data response result isn't text. +cssjquery_tester_button_test=Test +cssjquery_tester_field=Selector\: +cssjquery_tester_title=CSS/JQuery Tester +csvread_file_file_name=CSV file to get values from | *alias +cut=Cut +cut_paste_function=Copy and paste function string +database_conn_pool_max_usage=Max Usage For Each Connection\: +database_conn_pool_props=Database Connection Pool +database_conn_pool_size=Number of Connections in Pool\: +database_conn_pool_title=JDBC Database Connection Pool Defaults +database_driver_class=Driver Class\: +database_login_title=JDBC Database Login Defaults +database_sql_query_string=SQL Query String\: +database_sql_query_title=JDBC SQL Query Defaults +database_testing_title=JDBC Request +database_url=JDBC URL\: +database_url_jdbc_props=Database URL and JDBC Driver +ddn=DN +de=German +debug_off=Disable debug +debug_on=Enable debug +default_parameters=Default Parameters +default_value_field=Default Value\: +delay=Startup delay (seconds) +delayed_start=Delay Thread creation until needed +delete=Delete +delete_parameter=Delete Variable +delete_test=Delete Test +delete_user=Delete User +deltest=Deletion test +deref=Dereference aliases +description=Description +detail=Detail +directory_field_title=Working directory: +disable=Disable +distribution_graph_title=Distribution Graph (alpha) +distribution_note1=The graph will update every 10 samples +dn=DN +dns_cache_manager_title=DNS Cache Manager +dns_hostname_or_ip=Hostname or IP address +dns_servers=DNS Servers +domain=Domain +done=Done +down=Down +duplicate=Duplicate +duration=Duration (seconds) +duration_assertion_duration_test=Duration to Assert +duration_assertion_failure=The operation lasted too long\: It took {0} milliseconds, but should not have lasted longer than {1} milliseconds. +duration_assertion_input_error=Please enter a valid positive integer. +duration_assertion_label=Duration in milliseconds\: +duration_assertion_title=Duration Assertion +edit=Edit +email_results_title=Email Results +en=English +enable=Enable +encode=URL Encode +encode?=Encode? +encoded_value=URL Encoded Value +endtime=End Time +entry_dn=Entry DN +entrydn=Entry DN +environment_panel_title=Environment Variables +eolbyte=End of line(EOL) byte value: +error_indicator_tooltip=Show the number of errors in log, click to open Log Viewer panel +error_loading_help=Error loading help page +error_occurred=Error Occurred +error_title=Error +es=Spanish +escape_html_string=String to escape +eval_name_param=Text containing variable and function references +evalvar_name_param=Name of variable +example_data=Sample Data +example_title=Example Sampler +exit=Exit +expand=Expand +expected_return_code_title=Expected Return Code: +expiration=Expiration +expression_field=CSS/JQuery expression\: +field_name=Field name +file=File +file_already_in_use=That file is already in use +file_visualizer_append=Append to Existing Data File +file_visualizer_auto_flush=Automatically Flush After Each Data Sample +file_visualizer_browse=Browse... +file_visualizer_close=Close +file_visualizer_file_options=File Options +file_visualizer_filename=Filename +file_visualizer_flush=Flush +file_visualizer_missing_filename=No output filename specified. +file_visualizer_open=Open +file_visualizer_output_file=Write results to file / Read from file +file_visualizer_submit_data=Include Submitted Data +file_visualizer_title=File Reporter +file_visualizer_verbose=Verbose Output +filename=File Name +follow_redirects=Follow Redirects +follow_redirects_auto=Redirect Automatically +font.sansserif=Sans Serif +font.serif=Serif +fontstyle.bold=Bold +fontstyle.italic=Italic +fontstyle.normal=Normal +foreach_controller_title=ForEach Controller +foreach_end_index=End index for loop (inclusive) +foreach_input=Input variable prefix +foreach_output=Output variable name +foreach_start_index=Start index for loop (exclusive) +foreach_use_separator=Add "_" before number ? +format=Number format +fr=French +ftp_binary_mode=Use Binary mode ? +ftp_get=get(RETR) +ftp_local_file=Local File: +ftp_local_file_contents=Local File Contents: +ftp_put=put(STOR) +ftp_remote_file=Remote File: +ftp_sample_title=FTP Request Defaults +ftp_save_response_data=Save File in Response ? +ftp_testing_title=FTP Request +function_dialog_menu_item=Function Helper Dialog +function_helper_title=Function Helper +function_name_param=Name of variable in which to store the result (required) +function_name_paropt=Name of variable in which to store the result (optional) +function_params=Function Parameters +functional_mode=Functional Test Mode (i.e. save Response Data and Sampler Data) +functional_mode_explanation=Selecting Functional Test Mode may adversely affect performance. +gaussian_timer_delay=Constant Delay Offset (in milliseconds)\: +gaussian_timer_memo=Adds a random delay with a gaussian distribution +gaussian_timer_range=Deviation (in milliseconds)\: +gaussian_timer_title=Gaussian Random Timer +generate=Generate +generator=Name of Generator class +generator_cnf_msg=Could not find the generator class. Please make sure you place your jar file in the /lib directory. +generator_illegal_msg=Could not access the generator class due to IllegalAccessException. +generator_instantiate_msg=Could not create an instance of the generator parser. Please make sure the generator implements Generator interface. +get_xml_from_file=File with SOAP XML Data (overrides above text) +get_xml_from_random=Message(s) Folder +graph_apply_filter=Apply filter +graph_choose_graphs=Graphs to Display +graph_full_results_title=Graph Full Results +graph_pointshape_circle=Circle +graph_pointshape_diamond=Diamond +graph_pointshape_none=None +graph_pointshape_square=Square +graph_pointshape_triangle=Triangle +graph_resp_time_interval_label=Interval (ms): +graph_resp_time_interval_reload=Apply interval +graph_resp_time_not_enough_data=Unable to graph, not enough data +graph_resp_time_series_selection=Sampler label selection: +graph_resp_time_settings_line=Line settings +graph_resp_time_settings_pane=Graph settings +graph_resp_time_shape_label=Shape point: +graph_resp_time_stroke_width=Stroke width: +graph_resp_time_title=Response Time Graph +graph_resp_time_title_label=Graph title: +graph_resp_time_xaxis_time_format=Time format (SimpleDateFormat): +graph_results_average=Average +graph_results_data=Data +graph_results_deviation=Deviation +graph_results_latest_sample=Latest Sample +graph_results_median=Median +graph_results_ms=ms +graph_results_no_samples=No of Samples +graph_results_throughput=Throughput +graph_results_title=Graph Results +grouping_add_separators=Add separators between groups +grouping_in_controllers=Put each group in a new controller +grouping_in_transaction_controllers=Put each group in a new transaction controller +grouping_mode=Grouping\: +grouping_no_groups=Do not group samplers +grouping_store_first_only=Store 1st sampler of each group only +header_manager_title=HTTP Header Manager +headers_stored=Headers Stored in the Header Manager +heap_dump=Create a heap dump +help=Help +help_node=What's this node? +html_assertion_file=Write JTidy report to file +html_assertion_label=HTML Assertion +html_assertion_title=HTML Assertion +html_extractor_title=CSS/JQuery Extractor +html_extractor_type=CSS/JQuery Extractor Implementation +html_parameter_mask=HTML Parameter Mask (DEPRECATED) +http_implementation=Implementation: +http_response_code=HTTP response code +http_url_rewriting_modifier_title=HTTP URL Re-writing Modifier +http_user_parameter_modifier=HTTP User Parameter Modifier +httpmirror_max_pool_size=Max number of Threads: +httpmirror_max_queue_size=Max queue size: +httpmirror_settings=Settings +httpmirror_title=HTTP Mirror Server +id_prefix=ID Prefix +id_suffix=ID Suffix +if_controller_evaluate_all=Evaluate for all children? +if_controller_expression=Interpret Condition as Variable Expression? +if_controller_label=Condition (default Javascript) +if_controller_title=If Controller +ignore_subcontrollers=Ignore sub-controller blocks +include_controller=Include Controller +include_equals=Include Equals? +include_path=Include Test Plan +increment=Increment +infinite=Forever +initial_context_factory=Initial Context Factory +insert_after=Insert After +insert_before=Insert Before +insert_parent=Insert Parent +interleave_control_title=Interleave Controller +intsum_param_1=First int to add. +intsum_param_2=Second int to add - further ints can be summed by adding further arguments. +invalid_data=Invalid data +invalid_mail=Error occurred sending the e-mail +invalid_mail_address=One or more invalid e-mail addresses detected +invalid_mail_server=Problem contacting the e-mail server (see JMeter log file) +invalid_variables=Invalid variables +iteration_counter_arg_1=TRUE, for each user to have own counter, FALSE for a global counter +iterator_num=Loop Count\: +ja=Japanese +jar_file=Jar Files +java_request=Java Request +java_request_defaults=Java Request Defaults +javascript_expression=JavaScript expression to evaluate +jexl_expression=JEXL expression to evaluate +jms_auth_required=Required +jms_bytes_message=Bytes Message +jms_client_caption=Receiver client uses MessageConsumer.receive() to listen for message. +jms_client_caption2=MessageListener uses onMessage(Message) interface to listen for new messages. +jms_client_id=Client ID +jms_client_type=Client +jms_communication_style=Communication style +jms_concrete_connection_factory=Concrete Connection Factory +jms_config=Message source +jms_config_title=JMS Configuration +jms_connection_factory=Connection Factory +jms_correlation_title=Use alternate fields for message correlation +jms_dest_setup=Setup +jms_dest_setup_dynamic=Each sample +jms_dest_setup_static=At startup +jms_durable_subscription_id=Durable Subscription ID +jms_expiration=Expiration (ms) +jms_file=File +jms_initial_context_factory=Initial Context Factory +jms_itertions=Number of samples to aggregate +jms_jndi_defaults_title=JNDI Default Configuration +jms_jndi_props=JNDI Properties +jms_map_message=Map Message +jms_message_title=Message properties +jms_message_type=Message Type +jms_msg_content=Content +jms_object_message=Object Message +jms_point_to_point=JMS Point-to-Point +jms_priority=Priority (0-9) +jms_properties=JMS Properties +jms_properties_name=Name +jms_properties_title=JMS Properties +jms_properties_type=Class of value +jms_properties_value=Value +jms_props=JMS Properties +jms_provider_url=Provider URL +jms_publisher=JMS Publisher +jms_pwd=Password +jms_queue=Queue +jms_queue_connection_factory=QueueConnection Factory +jms_queueing=JMS Resources +jms_random_file=Random folder containing files ending with .dat for bytes messages, .txt or .obj for text and Object messages +jms_read_response=Read Response +jms_receive_queue=JNDI name Receive queue +jms_request=Request Only +jms_requestreply=Request Response +jms_sample_title=JMS Default Request +jms_selector=JMS Selector +jms_send_queue=JNDI name Request queue +jms_separator=Separator +jms_stop_between_samples=Stop between samples? +jms_subscriber_on_message=Use MessageListener.onMessage() +jms_subscriber_receive=Use MessageConsumer.receive() +jms_subscriber_title=JMS Subscriber +jms_testing_title=Messaging Request +jms_text_area=Text Message or Object Message serialized to XML by XStream +jms_text_message=Text Message +jms_timeout=Timeout (ms) +jms_topic=Destination +jms_use_auth=Use Authorization? +jms_use_file=From file +jms_use_non_persistent_delivery=Use non-persistent delivery mode? +jms_use_properties_file=Use jndi.properties file +jms_use_random_file=Random File from folder specified below +jms_use_req_msgid_as_correlid=Use Request Message Id +jms_use_res_msgid_as_correlid=Use Response Message Id +jms_use_text=Textarea +jms_user=User +jndi_config_title=JNDI Configuration +jndi_lookup_name=Remote Interface +jndi_lookup_title=JNDI Lookup Configuration +jndi_method_button_invoke=Invoke +jndi_method_button_reflect=Reflect +jndi_method_home_name=Home Method Name +jndi_method_home_parms=Home Method Parameters +jndi_method_name=Method Configuration +jndi_method_remote_interface_list=Remote Interfaces +jndi_method_remote_name=Remote Method Name +jndi_method_remote_parms=Remote Method Parameters +jndi_method_title=Remote Method Configuration +jndi_testing_title=JNDI Request +jndi_url_jndi_props=JNDI Properties +junit_append_error=Append assertion errors +junit_append_exception=Append runtime exceptions +junit_constructor_error=Unable to create an instance of the class +junit_constructor_string=Constructor String Label +junit_create_instance_per_sample=Create a new instance per sample +junit_do_setup_teardown=Do not call setUp and tearDown +junit_error_code=Error Code +junit_error_default_code=9999 +junit_error_default_msg=An unexpected error occured +junit_error_msg=Error Message +junit_failure_code=Failure Code +junit_failure_default_code=0001 +junit_failure_default_msg=Test failed +junit_failure_msg=Failure Message +junit_junit4=Search for JUnit 4 annotations (instead of JUnit 3) +junit_pkg_filter=Package Filter +junit_request=JUnit Request +junit_request_defaults=JUnit Request Defaults +junit_success_code=Success Code +junit_success_default_code=1000 +junit_success_default_msg=Test successful +junit_success_msg=Success Message +junit_test_config=JUnit Test Parameters +junit_test_method=Test Method +ldap_argument_list=LDAPArgument List +ldap_connto=Connection timeout (in milliseconds) +ldap_parse_results=Parse the search results ? +ldap_sample_title=LDAP Request Defaults +ldap_search_baseobject=Perform baseobject search +ldap_search_onelevel=Perform onelevel search +ldap_search_subtree=Perform subtree search +ldap_secure=Use Secure LDAP Protocol ? +ldap_testing_title=LDAP Request +ldapext_sample_title=LDAP Extended Request Defaults +ldapext_testing_title=LDAP Extended Request +library=Library +load=Load +load_wsdl=Load WSDL +log_errors_only=Errors +log_file=Location of log File +log_function_comment=Additional comment (optional) +log_function_level=Log level (default INFO) or OUT or ERR +log_function_string=String to be logged +log_function_string_ret=String to be logged (and returned) +log_function_throwable=Throwable text (optional) +log_only=Log/Display Only: +log_parser=Name of Log Parser class +log_parser_cnf_msg=Could not find the class. Please make sure you place your jar file in the /lib directory. +log_parser_illegal_msg=Could not access the class due to IllegalAccessException. +log_parser_instantiate_msg=Could not create an instance of the log parser. Please make sure the parser implements LogParser interface. +log_sampler=Tomcat Access Log Sampler +log_success_only=Successes +logic_controller_title=Simple Controller +login_config=Login Configuration +login_config_element=Login Config Element +longsum_param_1=First long to add +longsum_param_2=Second long to add - further longs can be summed by adding further arguments. +loop_controller_title=Loop Controller +looping_control=Looping Control +lower_bound=Lower Bound +mail_reader_account=Username: +mail_reader_all_messages=All +mail_reader_delete=Delete messages from the server +mail_reader_folder=Folder: +mail_reader_header_only=Fetch headers only +mail_reader_num_messages=Number of messages to retrieve: +mail_reader_password=Password: +mail_reader_port=Server Port (optional): +mail_reader_server=Server Host: +mail_reader_server_type=Protocol (e.g. pop3, imaps): +mail_reader_storemime=Store the message using MIME (raw) +mail_reader_title=Mail Reader Sampler +mail_sent=Mail sent successfully +mailer_addressees=Addressee(s): +mailer_attributes_panel=Mailing attributes +mailer_connection_security=Connection security: +mailer_error=Couldn't send mail. Please correct any misentries. +mailer_failure_limit=Failure Limit: +mailer_failure_subject=Failure Subject: +mailer_failures=Failures: +mailer_from=From: +mailer_host=Host: +mailer_login=Login: +mailer_msg_title_error=Error +mailer_msg_title_information=Information +mailer_password=Password: +mailer_port=Port: +mailer_string=E-Mail Notification +mailer_success_limit=Success Limit: +mailer_success_subject=Success Subject: +mailer_test_mail=Test Mail +mailer_title_message=Message +mailer_title_settings=Mailer settings +mailer_title_smtpserver=SMTP server +mailer_visualizer_title=Mailer Visualizer +match_num_field=Match No. (0 for Random)\: +max=Maximum +maximum_param=The maximum value allowed for a range of values +md5hex_assertion_failure=Error asserting MD5 sum : got {0} but should have been {1} +md5hex_assertion_label=MD5Hex +md5hex_assertion_md5hex_test=MD5Hex to Assert +md5hex_assertion_title=MD5Hex Assertion +mechanism=Mechanism +memory_cache=Memory Cache +menu_assertions=Assertions +menu_close=Close +menu_collapse_all=Collapse All +menu_config_element=Config Element +menu_edit=Edit +menu_expand_all=Expand All +menu_fragments=Test Fragment +menu_generative_controller=Sampler +menu_listener=Listener +menu_logger_panel=Log Viewer +menu_logic_controller=Logic Controller +menu_merge=Merge +menu_modifiers=Modifiers +menu_non_test_elements=Non-Test Elements +menu_open=Open +menu_post_processors=Post Processors +menu_pre_processors=Pre Processors +menu_response_based_modifiers=Response Based Modifiers +menu_search=Search +menu_search_reset=Reset Search +menu_tables=Table +menu_threads=Threads (Users) +menu_timer=Timer +menu_toolbar=Toolbar +metadata=MetaData +method=Method\: +mimetype=Mimetype +minimum_param=The minimum value allowed for a range of values +minute=minute +modddn=Old entry name +modification_controller_title=Modification Controller +modification_manager_title=Modification Manager +modify_test=Modify Test +modtest=Modification test +module_controller_module_to_run=Module To Run +module_controller_title=Module Controller +module_controller_warning=Could not find module: +monitor_equation_active=Active: (busy/max) > 25% +monitor_equation_dead=Dead: no response +monitor_equation_healthy=Healthy: (busy/max) < 25% +monitor_equation_load=Load: ( (busy / max) * 50) + ( (used memory / max memory) * 50) +monitor_equation_warning=Warning: (busy/max) > 67% +monitor_health_tab_title=Health +monitor_health_title=Monitor Results +monitor_is_title=Use as Monitor +monitor_label_left_bottom=0 % +monitor_label_left_middle=50 % +monitor_label_left_top=100 % +monitor_label_prefix=Connection Prefix +monitor_label_right_active=Active +monitor_label_right_dead=Dead +monitor_label_right_healthy=Healthy +monitor_label_right_warning=Warning +monitor_legend_health=Health +monitor_legend_load=Load +monitor_legend_memory_per=Memory % (used/total) +monitor_legend_thread_per=Thread % (busy/max) +monitor_load_factor_mem=50 +monitor_load_factor_thread=50 +monitor_performance_servers=Servers +monitor_performance_tab_title=Performance +monitor_performance_title=Performance Graph +name=Name\: +new=New +newdn=New distinguished name +next=Next +no=Norwegian +notify_child_listeners_fr=Notify Child Listeners of filtered samplers +number_of_threads=Number of Threads (users)\: +obsolete_test_element=This test element is obsolete +once_only_controller_title=Once Only Controller +opcode=opCode +open=Open... +option=Options +optional_tasks=Optional Tasks +paramtable=Send Parameters With the Request\: +password=Password +paste=Paste +paste_insert=Paste As Insert +path=Path\: +path_extension_choice=Path Extension (use ";" as separator) +path_extension_dont_use_equals=Do not use equals in path extension (Intershop Enfinity compatibility) +path_extension_dont_use_questionmark=Do not use questionmark in path extension (Intershop Enfinity compatibility) +patterns_to_exclude=URL Patterns to Exclude +patterns_to_include=URL Patterns to Include +pkcs12_desc=PKCS 12 Key (*.p12) +pl=Polish +poisson_timer_delay=Constant Delay Offset (in milliseconds)\: +poisson_timer_memo=Adds a random delay with a poisson distribution +poisson_timer_range=Lambda (in milliseconds)\: +poisson_timer_title=Poisson Random Timer +port=Port\: +post_as_parameters=Parameters +post_body=Body Data +post_body_raw=Body Data +post_thread_group_title=tearDown Thread Group +previous=Previous +property_as_field_label={0}\: +property_default_param=Default value +property_edit=Edit +property_editor.value_is_invalid_message=The text you just entered is not a valid value for this property.\nThe property will be reverted to its previous value. +property_editor.value_is_invalid_title=Invalid input +property_name_param=Name of property +property_returnvalue_param=Return Original Value of property (default false) ? +property_tool_tip={0}

{1} +property_undefined=Undefined +property_value_param=Value of property +property_visualiser_title=Property Display +protocol=Protocol [http]\: +protocol_java_border=Java class +protocol_java_classname=Classname\: +protocol_java_config_tile=Configure Java Sample +protocol_java_test_title=Java Testing +provider_url=Provider URL +proxy_assertions=Add Assertions +proxy_cl_error=If specifying a proxy server, host and port must be given +proxy_cl_wrong_target_cl=Target Controller is configured to "Use Recording Controller" but no such controller exists, \nensure you add a Recording Controller as child of Thread Group node to start recording correctly +proxy_content_type_exclude=Exclude\: +proxy_content_type_filter=Content-type filter +proxy_content_type_include=Include\: +proxy_daemon_bind_error=Could not create script recorder - port in use. Choose another port. +proxy_daemon_error=Could not create script recorder - see log for details +proxy_daemon_error_from_clipboard=from clipboard +proxy_daemon_error_not_retrieve=Could not add retrieve +proxy_daemon_error_read_args=Could not add read arguments from clipboard\: +proxy_daemon_msg_check_details=Please check the details below when installing the certificate in the browser +proxy_daemon_msg_created_in_bin=created in JMeter bin directory +proxy_daemon_msg_install_as_in_doc=You can install it following instructions in Component Reference documentation (see Installing the JMeter CA certificate for HTTPS recording paragraph) +proxy_daemon_msg_rootca_cert=Root CA certificate\: +proxy_domains=HTTPS Domains \: +proxy_domains_dynamic_mode_tooltip=List of domain names for HTTPS url, ex. jmeter.apache.org or wildcard domain like *.apache.org. Use comma as separator. +proxy_domains_dynamic_mode_tooltip_java6=To activate this field, use a Java 7+ runtime environment +proxy_general_settings=Global Settings +proxy_headers=Capture HTTP Headers +proxy_regex=Regex matching +proxy_sampler_settings=HTTP Sampler settings +proxy_sampler_type=Type\: +proxy_separators=Add Separators +proxy_settings_port_error_digits=Only digits allowed +proxy_settings_port_error_invalid_data=Invalid data +proxy_target=Target Controller\: +proxy_test_plan_content=Test plan content +proxy_title=HTTP(S) Test Script Recorder +pt_br=Portugese (Brazilian) +ramp_up=Ramp-Up Period (in seconds)\: +random_control_title=Random Controller +random_order_control_title=Random Order Controller +random_string_chars_to_use=Chars to use for random string generation +random_string_length=Random string length +read_response_message=Read response is not checked. To see the response, please check the box in the sampler. +read_response_note=If read response is unchecked, the sampler will not read the response +read_response_note2=or set the SampleResult. This improves performance, but it means +read_response_note3=the response content won't be logged. +read_soap_response=Read SOAP Response +realm=Realm +record_controller_title=Recording Controller +redo=Redo +ref_name_field=Reference Name\: +regex_extractor_title=Regular Expression Extractor +regex_field=Regular Expression\: +regex_params_names_field=Parameter names regexp group number +regex_params_ref_name_field=Regular Expression Reference Name +regex_params_title=RegEx User Parameters +regex_params_values_field=Parameter values regex group number +regex_source=Field to check +regex_src_body=Body +regex_src_body_as_document=Body as a Document +regex_src_body_unescaped=Body (unescaped) +regex_src_hdrs=Response Headers +regex_src_hdrs_req=Request Headers +regex_src_url=URL +regexfunc_param_1=Regular expression used to search previous sample - or variable. +regexfunc_param_2=Template for the replacement string, using groups from the regular expression. Format is $[group]$. Example $1$. +regexfunc_param_3=Which match to use. An integer 1 or greater, RAND to indicate JMeter should randomly choose, A float, or ALL indicating all matches should be used ([1]) +regexfunc_param_4=Between text. If ALL is selected, the between text will be used to generate the results ([""]) +regexfunc_param_5=Default text. Used instead of the template if the regular expression finds no matches ([""]) +regexfunc_param_7=Input variable name containing the text to be parsed ([previous sample]) +regexp_render_no_text=Data response result isn't text. +regexp_tester_button_test=Test +regexp_tester_field=Regular expression\: +regexp_tester_title=RegExp Tester +remote_error_init=Error initialising remote server +remote_error_starting=Error starting remote server +remote_exit=Remote Exit +remote_exit_all=Remote Exit All +remote_shut=Remote Shutdown +remote_shut_all=Remote Shutdown All +remote_start=Remote Start +remote_start_all=Remote Start All +remote_stop=Remote Stop +remote_stop_all=Remote Stop All +remove=Remove +remove_confirm_msg=Are you sure you want remove the selected element(s)? +remove_confirm_title=Confirm remove? +rename=Rename entry +report=Report +report_bar_chart=Bar Chart +report_bar_graph_url=URL +report_base_directory=Base Directory +report_chart_caption=Chart Caption +report_chart_x_axis=X Axis +report_chart_x_axis_label=Label for X Axis +report_chart_y_axis=Y Axis +report_chart_y_axis_label=Label for Y Axis +report_line_graph=Line Graph +report_line_graph_urls=Include URLs +report_output_directory=Output Directory for Report +report_page=Report Page +report_page_element=Page Element +report_page_footer=Page Footer +report_page_header=Page Header +report_page_index=Create Page Index +report_page_intro=Page Introduction +report_page_style_url=Stylesheet url +report_page_title=Page Title +report_pie_chart=Pie Chart +report_plan=Report Plan +report_select=Select +report_summary=Report Summary +report_table=Report Table +report_writer=Report Writer +report_writer_html=HTML Report Writer +request_data=Request Data +reset_gui=Reset Gui +response_save_as_md5=Save response as MD5 hash? +restart=Restart +resultaction_title=Result Status Action Handler +resultsaver_addtimestamp=Add timestamp +resultsaver_errors=Save Failed Responses only +resultsaver_numberpadlen=Minimum Length of sequence number +resultsaver_prefix=Filename prefix\: +resultsaver_skipautonumber=Don't add number to prefix +resultsaver_skipsuffix=Don't add suffix +resultsaver_success=Save Successful Responses only +resultsaver_title=Save Responses to a file +resultsaver_variable=Variable Name: +retobj=Return object +return_code_config_box_title=Return Code Configuration +reuseconnection=Re-use connection +revert_project=Revert +revert_project?=Revert project? +root=Root +root_title=Root +run=Run +running_test=Running test +runtime_controller_title=Runtime Controller +runtime_seconds=Runtime (seconds) +sample_result_save_configuration=Sample Result Save Configuration +sample_scope=Apply to: +sample_scope_all=Main sample and sub-samples +sample_scope_children=Sub-samples only +sample_scope_parent=Main sample only +sample_scope_variable=JMeter Variable +sampler_label=Label +sampler_on_error_action=Action to be taken after a Sampler error +sampler_on_error_continue=Continue +sampler_on_error_start_next_loop=Start Next Thread Loop +sampler_on_error_stop_test=Stop Test +sampler_on_error_stop_test_now=Stop Test Now +sampler_on_error_stop_thread=Stop Thread +save=Save +save?=Save? +save_all_as=Save Test Plan as +save_as=Save Selection As... +save_as_error=More than one item selected! +save_as_image=Save Node As Image +save_as_image_all=Save Screen As Image +save_as_test_fragment=Save as Test Fragment +save_as_test_fragment_error=One of the selected nodes cannot be put inside a Test Fragment +save_assertionresultsfailuremessage=Save Assertion Failure Message +save_assertions=Save Assertion Results (XML) +save_asxml=Save As XML +save_bytes=Save byte count +save_code=Save Response Code +save_datatype=Save Data Type +save_encoding=Save Encoding +save_fieldnames=Save Field Names (CSV) +save_filename=Save Response Filename +save_graphics=Save Graph +save_hostname=Save Hostname +save_idletime=Save Idle Time +save_label=Save Label +save_latency=Save Latency +save_connecttime=Save Connect Time +save_message=Save Response Message +save_overwrite_existing_file=The selected file already exists, do you want to overwrite it? +save_requestheaders=Save Request Headers (XML) +save_responsedata=Save Response Data (XML) +save_responseheaders=Save Response Headers (XML) +save_samplecount=Save Sample and Error Counts +save_samplerdata=Save Sampler Data (XML) +save_subresults=Save Sub Results (XML) +save_success=Save Success +save_threadcounts=Save Active Thread Counts +save_threadname=Save Thread Name +save_time=Save Elapsed Time +save_timestamp=Save Time Stamp +save_url=Save URL +save_workbench=Save WorkBench +sbind=Single bind/unbind +scheduler=Scheduler +scheduler_configuration=Scheduler Configuration +scope=Scope +search=Search +search_base=Search base +search_expand=Search & Expand +search_filter=Search Filter +search_test=Search Test +search_text_button_close=Close +search_text_button_find=Find +search_text_button_next=Find next +search_text_chkbox_case=Case sensitive +search_text_chkbox_regexp=Regular exp. +search_text_field=Search: +search_text_msg_not_found=Text not found +search_text_title_not_found=Not found +search_tree_title=Search Tree +searchbase=Search base +searchfilter=Search Filter +searchtest=Search test +second=second +secure=Secure +send_file=Send Files With the Request\: +send_file_browse=Browse... +send_file_filename_label=File Path\: +send_file_mime_label=MIME Type\: +send_file_param_name_label=Parameter Name\: +server=Server Name or IP\: +servername=Servername \: +session_argument_name=Session Argument Name +setup_thread_group_title=setUp Thread Group +should_save=You should save your test plan before running it. \nIf you are using supporting data files (ie, for CSV Data Set or _StringFromFile), \nthen it is particularly important to first save your test script. \nDo you want to save your test plan first? +shutdown=Shutdown +simple_config_element=Simple Config Element +simple_data_writer_title=Simple Data Writer +size_assertion_comparator_error_equal=been equal to +size_assertion_comparator_error_greater=been greater than +size_assertion_comparator_error_greaterequal=been greater or equal to +size_assertion_comparator_error_less=been less than +size_assertion_comparator_error_lessequal=been less than or equal to +size_assertion_comparator_error_notequal=not been equal to +size_assertion_comparator_label=Type of Comparison +size_assertion_failure=The result was the wrong size\: It was {0} bytes, but should have {1} {2} bytes. +size_assertion_input_error=Please enter a valid positive integer. +size_assertion_label=Size in bytes\: +size_assertion_size_test=Size to Assert +size_assertion_title=Size Assertion +smime_assertion_issuer_dn=Issuer distinguished name +smime_assertion_message_position=Execute assertion on message at position +smime_assertion_not_signed=Message not signed +smime_assertion_signature=Signature +smime_assertion_signer=Signer certificate +smime_assertion_signer_by_file=Certificate file +smime_assertion_signer_constraints=Check values +smime_assertion_signer_dn=Signer distinguished name +smime_assertion_signer_email=Signer email address +smime_assertion_signer_no_check=No check +smime_assertion_signer_serial=Serial Number +smime_assertion_title=SMIME Assertion +smime_assertion_verify_signature=Verify signature +smtp_additional_settings=Additional Settings +smtp_attach_file=Attach file(s): +smtp_attach_file_tooltip=Separate multiple files with ";" +smtp_auth_settings=Auth settings +smtp_bcc=Address To BCC: +smtp_cc=Address To CC: +smtp_default_port=(Defaults: SMTP:25, SSL:465, StartTLS:587) +smtp_eml=Send .eml: +smtp_enabledebug=Enable debug logging? +smtp_enforcestarttls=Enforce StartTLS +smtp_enforcestarttls_tooltip=Enforces the server to use StartTLS.
If not selected and the SMTP-Server doesn't support StartTLS,
a normal SMTP-Connection will be used as fallback instead.
Please note that this checkbox creates a file in "/tmp/",
so this will cause problems under windows. +smtp_from=Address From: +smtp_header_add=Add Header +smtp_header_name=Header Name +smtp_header_remove=Remove +smtp_header_value=Header Value +smtp_mail_settings=Mail settings +smtp_message=Message: +smtp_message_settings=Message settings +smtp_messagesize=Calculate message size +smtp_password=Password: +smtp_plainbody=Send plain body (i.e. not multipart/mixed) +smtp_replyto=Address Reply-To: +smtp_sampler_title=SMTP Sampler +smtp_security_settings=Security settings +smtp_server=Server: +smtp_server_connection_timeout=Connection timeout: +smtp_server_port=Port: +smtp_server_settings=Server settings +smtp_server_timeout=Read timeout: +smtp_server_timeouts_settings=Timeouts (milliseconds) +smtp_subject=Subject: +smtp_suppresssubj=Suppress Subject Header +smtp_timestamp=Include timestamp in subject +smtp_to=Address To: +smtp_trustall=Trust all certificates +smtp_trustall_tooltip=Enforces JMeter to trust all certificates, whatever CA it comes from. +smtp_truststore=Local truststore: +smtp_truststore_tooltip=The pathname of the truststore.
Relative paths are resolved against the current directory.
Failing that, against the directory containing the test script (JMX file) +smtp_useauth=Use Auth +smtp_usenone=Use no security features +smtp_username=Username: +smtp_usessl=Use SSL +smtp_usestarttls=Use StartTLS +smtp_usetruststore=Use local truststore +smtp_usetruststore_tooltip=Allows JMeter to use a local truststore. +soap_action=Soap Action +soap_data_title=Soap/XML-RPC Data +soap_sampler_file_invalid=Filename references a missing or unreadable file\: +soap_sampler_title=SOAP/XML-RPC Request +soap_send_action=Send SOAPAction: +solinger=SO_LINGER: +spline_visualizer_average=Average +spline_visualizer_incoming=Incoming +spline_visualizer_maximum=Maximum +spline_visualizer_minimum=Minimum +spline_visualizer_title=Spline Visualizer +spline_visualizer_waitingmessage=Waiting for samples +split_function_separator=String to split on. Default is , (comma). +split_function_string=String to split +ssl_alias_prompt=Please type your preferred alias +ssl_alias_select=Select your alias for the test +ssl_alias_title=Client Alias +ssl_error_title=Key Store Problem +ssl_pass_prompt=Please type your password +ssl_pass_title=KeyStore Password +ssl_port=SSL Port +sslmanager=SSL Manager +start=Start +start_no_timers=Start no pauses +starttime=Start Time +stop=Stop +stopping_test=Shutting down all test threads. You can see number of active threads in the upper right corner of GUI. Please be patient. +stopping_test_failed=One or more test threads won't exit; see log file. +stopping_test_host=Host +stopping_test_title=Stopping Test +string_from_file_encoding=File encoding if not the platform default (opt) +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) +summariser_title=Generate Summary Results +summary_report=Summary Report +switch_controller_label=Switch Value +switch_controller_title=Switch Controller +system_sampler_stderr=Standard error (stderr): +system_sampler_stdin=Standard input (stdin): +system_sampler_stdout=Standard output (stdout): +system_sampler_title=OS Process Sampler +table_visualizer_bytes=Bytes +table_visualizer_latency=Latency +table_visualizer_connect=Connect Time(ms) +table_visualizer_sample_num=Sample # +table_visualizer_sample_time=Sample Time(ms) +table_visualizer_start_time=Start Time +table_visualizer_status=Status +table_visualizer_success=Success +table_visualizer_thread_name=Thread Name +table_visualizer_warning=Warning +target_server=Target Server +tcp_classname=TCPClient classname\: +tcp_config_title=TCP Sampler Config +tcp_nodelay=Set NoDelay +tcp_port=Port Number\: +tcp_request_data=Text to send +tcp_sample_title=TCP Sampler +tcp_timeout=Timeout (milliseconds)\: +teardown_on_shutdown=Run tearDown Thread Groups after shutdown of main threads +template_choose=Select Template +template_create_from=Create +template_field=Template\: +template_load?=Load template ? +template_menu=Templates... +template_merge_from=Merge +template_reload=Reload templates +template_title=Templates +test=Test +test_action_action=Action +test_action_duration=Duration (milliseconds) +test_action_pause=Pause +test_action_restart_next_loop=Go to next loop iteration +test_action_stop=Stop +test_action_stop_now=Stop Now +test_action_target=Target +test_action_target_test=All Threads +test_action_target_thread=Current Thread +test_action_title=Test Action +test_configuration=Test Configuration +test_fragment_title=Test Fragment +test_plan=Test Plan +test_plan_classpath_browse=Add directory or jar to classpath +testconfiguration=Test Configuration +testplan.serialized=Run Thread Groups consecutively (i.e. run groups one at a time) +testplan_comments=Comments\: +testt=Test +textbox_cancel=Cancel +textbox_close=Close +textbox_save_close=Save & Close +textbox_title_edit=Edit text +textbox_title_view=View text +textbox_tooltip_cell=Double click to view/edit +thread_delay_properties=Thread Delay Properties +thread_group_title=Thread Group +thread_properties=Thread Properties +threadgroup=Thread Group +throughput_control_bynumber_label=Total Executions +throughput_control_bypercent_label=Percent Executions +throughput_control_perthread_label=Per User +throughput_control_title=Throughput Controller +throughput_control_tplabel=Throughput +time_format=Format string for SimpleDateFormat (optional) +timelim=Time limit +timeout_config_box_title=Timeout configuration +timeout_title=Timeout (ms) +toggle=Toggle +toolbar_icon_set_not_found=The file description of toolbar icon set is not found. See logs. +total_threads_tooltip=Total number of threads to run +tr=Turkish +transaction_controller_include_timers=Include duration of timer and pre-post processors in generated sample +transaction_controller_parent=Generate parent sample +transaction_controller_title=Transaction Controller +unbind=Thread Unbind +undo=Undo +unescape_html_string=String to unescape +unescape_string=String containing Java escapes +uniform_timer_delay=Constant Delay Offset (in milliseconds)\: +uniform_timer_memo=Adds a random delay with a uniform distribution +uniform_timer_range=Random Delay Maximum (in milliseconds)\: +uniform_timer_title=Uniform Random Timer +up=Up +update=Update +update_per_iter=Update Once Per Iteration +upload=File Upload +upper_bound=Upper Bound +url=URL +url_config_get=GET +url_config_http=HTTP +url_config_https=HTTPS +url_config_post=POST +url_config_protocol=Protocol\: +url_config_title=HTTP Request Defaults +url_full_config_title=UrlFull Sample +url_multipart_config_title=HTTP Multipart Request Defaults +urldecode_string=String with URL encoded chars to decode +urlencode_string=String to encode in URL encoded chars +use_custom_dns_resolver=Use custom DNS resolver +use_expires=Use Cache-Control/Expires header when processing GET requests +use_keepalive=Use KeepAlive +use_multipart_for_http_post=Use multipart/form-data for POST +use_multipart_mode_browser=Browser-compatible headers +use_recording_controller=Use Recording Controller +use_system_dns_resolver=Use system DNS resolver +user=User +user_defined_test=User Defined Test +user_defined_variables=User Defined Variables +user_param_mod_help_note=(Do not change this. Instead, modify the file of that name in JMeter's /bin directory) +user_parameters_table=Parameters +user_parameters_title=User Parameters +userdn=Username +username=Username +userpw=Password +value=Value +value_to_quote_meta=Value to escape from ORO Regexp meta chars +var_name=Reference Name +variable_name_param=Name of variable (may include variable and function references) +view_graph_tree_title=View Graph Tree +view_results_assertion_error=Assertion error: +view_results_assertion_failure=Assertion failure: +view_results_assertion_failure_message=Assertion failure message: +view_results_autoscroll=Scroll automatically? +view_results_childsamples=Child samples? +view_results_desc=Shows the text results of sampling in tree form +view_results_error_count=Error Count: +view_results_fields=fields: +view_results_in_table=View Results in Table +view_results_latency=Latency: +view_results_connect_time=Connect Time: +view_results_load_time=Load time: +view_results_render=Render: +view_results_render_document=Document +view_results_render_html=HTML +view_results_render_html_embedded=HTML (download resources) +view_results_render_json=JSON +view_results_render_text=Text +view_results_render_xml=XML +view_results_request_headers=Request Headers: +view_results_response_code=Response code: +view_results_response_headers=Response headers: +view_results_response_message=Response message: +view_results_response_missing_tika=Missing tika-app.jar in classpath. Unable to convert to plain text this kind of document.\nDownload the tika-app-x.x.jar file from http://tika.apache.org/download.html\nAnd put the file in /lib directory. +view_results_response_partial_message=Start of message: +view_results_response_too_large_message=Response too large to be displayed. Size: +view_results_sample_count=Sample Count: +view_results_sample_start=Sample Start: +view_results_search_pane=Search pane +view_results_size_body_in_bytes=Body size in bytes: +view_results_size_headers_in_bytes=Headers size in bytes: +view_results_size_in_bytes=Size in bytes: +view_results_tab_assertion=Assertion result +view_results_tab_request=Request +view_results_tab_response=Response data +view_results_tab_sampler=Sampler result +view_results_table_fields_key=Additional field +view_results_table_fields_value=Value +view_results_table_headers_key=Response header +view_results_table_headers_value=Value +view_results_table_request_headers_key=Request header +view_results_table_request_headers_value=Value +view_results_table_request_http_cookie=Cookie +view_results_table_request_http_host=Host +view_results_table_request_http_method=Method +view_results_table_request_http_nohttp=No HTTP Sample +view_results_table_request_http_path=Path +view_results_table_request_http_port=Port +view_results_table_request_http_protocol=Protocol +view_results_table_request_params_key=Parameter name +view_results_table_request_params_value=Value +view_results_table_request_raw_nodata=No data to display +view_results_table_request_tab_http=HTTP +view_results_table_request_tab_raw=Raw +view_results_table_result_tab_parsed=Parsed +view_results_table_result_tab_raw=Raw +view_results_thread_name=Thread Name: +view_results_title=View Results +view_results_tree_title=View Results Tree +warning=Warning! +web_cannot_convert_parameters_to_raw=Cannot convert parameters to Body Data \nbecause one of the parameters has a name +web_cannot_switch_tab=You cannot switch because data cannot be converted\n to target Tab data, empty data to switch +web_parameters_lost_message=Switching to Body Data will convert the parameters.\nParameter table will be cleared when you select\nanother node or save the test plan.\nOK to proceeed? +web_proxy_server_title=Proxy Server +web_request=HTTP Request +web_server=Web Server +web_server_client=Client implementation: +web_server_domain=Server Name or IP\: +web_server_port=Port Number\: +web_server_timeout_connect=Connect: +web_server_timeout_response=Response: +web_server_timeout_title=Timeouts (milliseconds) +web_testing2_title=HTTP Request HTTPClient +web_testing_concurrent_download=Use concurrent pool. Size: +web_testing_embedded_url_pattern=URLs must match\: +web_testing_retrieve_images=Retrieve All Embedded Resources +web_testing_retrieve_title=Embedded Resources from HTML Files +web_testing_source_ip=Source address +web_testing_source_ip_device=Device +web_testing_source_ip_device_ipv4=Device IPv4 +web_testing_source_ip_device_ipv6=Device IPv6 +web_testing_source_ip_hostname=IP/Hostname +web_testing_title=HTTP Request +webservice_configuration_wizard=WSDL helper +webservice_get_xml_from_random_title=Use random messages SOAP +webservice_maintain_session=Maintain HTTP Session +webservice_message_soap=WebService message +webservice_methods=Web Methods +webservice_proxy_host=Proxy Host +webservice_proxy_note=If Use HTTP Proxy is checked, but no host or port are provided, the sampler +webservice_proxy_note2=will look at command line options. If no proxy host or port are provided by +webservice_proxy_note3=either, it will fail silently. +webservice_proxy_port=Proxy Port +webservice_sampler_title=WebService(SOAP) Request (DEPRECATED) +webservice_soap_action=SOAPAction +webservice_timeout=Timeout: +webservice_use_proxy=Use HTTP Proxy +while_controller_label=Condition (function or variable) +while_controller_title=While Controller +workbench_title=WorkBench +wsdl_helper_error=The WSDL was not valid, please double check the url. +wsdl_url=WSDL URL +wsdl_url_error=The WSDL was emtpy. +xml_assertion_title=XML Assertion +xml_download_dtds=Fetch external DTDs +xml_namespace_button=Use Namespaces +xml_tolerant_button=Use Tidy (tolerant parser) +xml_validate_button=Validate XML +xml_whitespace_button=Ignore Whitespace +xmlschema_assertion_label=File Name: +xmlschema_assertion_title=XML Schema Assertion +xpath_assertion_button=Validate +xpath_assertion_check=Check XPath Expression +xpath_assertion_error=Error with XPath +xpath_assertion_failed=Invalid XPath Expression +xpath_assertion_label=XPath +xpath_assertion_negate=True if nothing matches +xpath_assertion_option=XML Parsing Options +xpath_assertion_test=XPath Assertion +xpath_assertion_tidy=Try and tidy up the input +xpath_assertion_title=XPath Assertion +xpath_assertion_valid=Valid XPath Expression +xpath_assertion_validation=Validate the XML against the DTD +xpath_assertion_whitespace=Ignore whitespace +xpath_expression=XPath expression to match against +xpath_extractor_fragment=Return entire XPath fragment instead of text content? +xpath_extractor_query=XPath query: +xpath_extractor_title=XPath Extractor +xpath_file_file_name=XML file to get values from +xpath_tester=XPath Tester +xpath_tester_button_test=Test +xpath_tester_field=XPath expression +xpath_tester_fragment=Return entire XPath fragment instead of text content? +xpath_tester_no_text=Data response result isn't text. +xpath_tester_title=XPath Tester +xpath_tidy_quiet=Quiet +xpath_tidy_report_errors=Report errors +xpath_tidy_show_warnings=Show warnings +you_must_enter_a_valid_number=You must enter a valid number +zh_cn=Chinese (Simplified) +zh_tw=Chinese (Traditional) diff --git a/src/core/org/apache/jmeter/resources/messages_de.properties b/src/core/org/apache/jmeter/resources/messages_de.properties new file mode 100644 index 00000000000..aa44676b084 --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_de.properties @@ -0,0 +1,573 @@ +# 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. + +about=\u00DCber Apache JMeter +add=Hinzuf\u00FCgen +add_as_child=Als ein Kind hinzuf\u00FCgen +add_parameter=Variable hinzuf\u00FCgen +add_pattern=Muster hinzuf\u00FCgen\: +add_test=Test hinzuf\u00FCgen +add_user=Benutzer hinzuf\u00FCgen +add_value=Wert hinzuf\u00FCgen +addtest=Test hinzuf\u00FCgen +aggregate_graph=Statistischer Graph +aggregate_graph_column=Spalte +aggregate_graph_display=Graphen anzeigen +aggregate_graph_height=H\u00F6he +aggregate_graph_max_length_xaxis_label=Maximale L\u00E4nge des x-Achsen Bezeichners +aggregate_graph_ms=Millisekunden +aggregate_graph_response_time=Reaktionszeit +aggregate_graph_save=Graphen speichern +aggregate_graph_save_table=Tabellen Daten speichern +aggregate_graph_save_table_header=Tabellen Kopf speichern +aggregate_graph_title=Graph +aggregate_graph_user_title=Titel f\u00FCr den Graphen +aggregate_graph_width=Breite +aggregate_report=Report +aggregate_report_bandwidth=KB/sek +aggregate_report_count=Anz. der Proben +aggregate_report_error=Fehler +aggregate_report_error%=% Fehler +aggregate_report_median=Mittel +aggregate_report_rate=Durchsatz +aggregate_report_total_label=Gesamt +als_message=Hinweis\: Der Zugriff-Log Parser ist allgmein gehalten. Es ist m\u00F6glich ein Plugin zu erstellen. +als_message2=Eigener Parser. Implementieren sie hierzu "LogParser" und f\u00FCgen sie es als .jar hinzu +analyze=Analysiere Daten Datei... +appearance=Aussehen (Look & Feel) +argument_must_not_be_negative=Der Wert darf nicht negativ sein\! +assertion_assume_success=Status ignorieren +assertion_code_resp=Antwort-Code (Response-Code) +assertion_contains=Enth\u00E4lt +assertion_equals=Gleicht +assertion_headers=Antwort-Header (Response-Header) +assertion_matches=Entsprechungen +assertion_message_resp=Antwort-Message +assertion_not=Nicht +assertion_pattern_match_rules=Regeln f\u00FCr passende Muster +assertion_patterns_to_test=Zu testende(s) Muster +assertion_resp_field=Zu testendes Antwort-Feld (Response-Feld) +assertion_substring=Teilzeichenkette (Substring) +assertion_text_resp=Text-Antwort (Text-Response) +assertion_textarea_label=Behauptungen\: +assertion_title=Versicherte Anwort +assertion_url_samp=URL gesampled +assertion_visualizer_title=Versicherungs Erebnis +attribute=Attribut +attrs=Attribute +auth_manager_title=HTTP Authorisierungs Manager +auths_stored=Im Authorization Manager gespeicherte Authorisierungen +average=Durchschnitt +average_bytes=Durchschnittliche Bytes +browse=Datei laden... +bsf_script=Auszuf\u00FChrendes Script (Variablen\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsf_script_file=Auszuf\u00FChrendes Script +bsf_script_language=Scriptsprache +bsf_script_parameters=An das Script bzw. die Script-Datei zu \u00FCbergebende Parameter +bsh_assertion_script=Script (untenstehende Variablen sind definiert) +bsh_assertion_title=BeanShell Behauptung +bsh_function_expression=Auszuwertender Ausdruck +bsh_script=Script (untenstehende Variablen sind definiert) +bsh_script_file=Script-Datei +bsh_script_variables=Folgende Variablen wurden f\u00FCr das Script definiert\:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=Ich bin mit dem Testen besch\u00E4ftigt, bitte stoppen sie den Test bevor sie die Einstellungen \u00E4ndern. +cache_session_id=Session ID zwischenspeichern? +cancel=Abbrechen +cancel_exit_to_save=Es gibt Tests die noch nicht gespeichert wurden. M\u00F6chten sie diese vor dem Beenden speichern? +cancel_new_to_save=Es gibt Tests die noch nicht gespeichert wurden. M\u00F6chten sie diese vor dem Bereinigen speichern? +cancel_revert_project=Es gibt Tests die noch nicht gespeichert wurden. M\u00F6chten sie zum zuletzt gespeicherten Testplan zur\u00FCck gehen? +choose_function=W\u00E4hlen Sie eine Funktion +choose_language=W\u00E4hlen sie eine Sprache +clear=L\u00F6schen +clear_all=Alle L\u00F6schen +clear_cookies_per_iter=Cookies bei jedem Durchgang l\u00F6schen? +column_delete_disallowed=Das l\u00F6schen dieser Spalte ist nicht erlaubt +column_number=Spaltennummer der CSV Datei file | next | *alias +compare=Vergleichen +comparefilt=Vergleichsfilter +config_element=Konfigurations Element +config_save_settings=Konfigurieren +configure_wsdl=Konfigurieren +constant_throughput_timer_memo=Geben sie eine Pause zwischen den Proben an um einen konstanten Durchsatz zu gew\u00E4hrleisten +constant_timer_delay=Thread-Pause (in Millisekunden) +constant_timer_memo=Geben sie eine Pause zwischen den Proben an +constant_timer_title=Konstanter Timer +content_encoding=Content Kodierung\: +cookie_manager_policy=Cookie Richtlinie +cookies_stored=Anzahl der gespeicherten Cookies im Cookie Manager +copy=Kopieren +counter_config_title=Z\u00E4hler (Counter) +counter_per_user=Z\u00E4hler (Counter) f\u00FCr jeden Benutzer einzeln f\u00FChren +countlim=Gr\u00F6\u00DFen-Beschr\u00E4nkung +csvread_file_file_name=CVS Datei aus der die Werte gelesen werden | *alias +cut=Ausschneiden +cut_paste_function=Kopieren und Einf\u00FCgen des Funktions Strings +database_conn_pool_max_usage=Maximale Auslastung jeder Verbindung\: +database_conn_pool_props=Datenbank Verbindungs-Pool\: +database_conn_pool_size=Anzahl der Verbindungen im Pool\: +database_conn_pool_title=Vorgaben zum JDBC Datenbank Verbindungs Pool +database_driver_class=Treiber-Klasse\: +database_login_title=JDBC Datenbank Login Vorgabe +database_sql_query_string=SQL Abfrage\: +database_sql_query_title=Vorgaben zur JDBC SQL Abfrage +database_testing_title=JDBC Anfrage +database_url_jdbc_props=Database URL und JDBC Treiber +de=Deutsch +debug_off=Debugging deaktivieren +debug_on=Debugging aktivieren +default_parameters=Standard Parameter +default_value_field=Vorgabe-Wert\: +delay=Pause zu Beginn (Sekunden) +delete=L\u00F6schen +delete_parameter=L\u00F6sche Variable +delete_test=Test L\u00F6schen +delete_user=Benutzer L\u00F6schen +deltest=L\u00F6sch-Test +deref=Dereferenzierungs Aliasse +disable=Deaktivieren +distribution_graph_title=Verteilungs-Graph (Alpha) +distribution_note1=Der Graph wird mit jeder 10. Probe aktualisiert +done=Fertig +duration=Dauer (Sekunden) +duration_assertion_duration_test=Dauer der Aufrechterhaltung +duration_assertion_failure=Die Operation dauerte zu lang\: es wurden {0} Millisekunden ben\u00F6tigt, h\u00E4tte aber maximal {1} Millisekunden dauern d\u00FCrfen. +duration_assertion_input_error=Geben Sie bitte einen g\u00FCltigen, positive Ganzzahl-Wert ein. +duration_assertion_label=Dauer in Millisekunden +duration_assertion_title=Aufrechterhaltungs-Dauer +edit=Bearbeiten +email_results_title=Ergebnisse per eMail verschicken +en=Englisch +enable=Aktivieren +encode?=Encodieren? +encoded_value=URL-Encodierter Wert +endtime=End-Zeitpunkt +entry_dn=Ausgangs DN +entrydn=Ausgangs DN +error_loading_help=Fehler beim laden der Hilfe-Seite +error_occurred=Es ist ein Fehler aufgetreten +error_title=Fehler +es=Spanisch +eval_name_param=Ein Ausdruck der eine Variable und Funktions-Referenz enth\u00E4lt +evalvar_name_param=Variablenname +example_data=Beispieldaten +example_title=Beispiel Proben +exit=Beenden +expiration=Verfall +field_name=Feldname +file=Datei +file_already_in_use=Die Datei ist bereits ge\u00F6ffnet +file_visualizer_append=An eine existierende Daten-Datei anh\u00E4ngen +file_visualizer_auto_flush=Nach jeder Daten-Probe bereinigen (Flush) +file_visualizer_browse=Datei laden... +file_visualizer_close=Schliessen +file_visualizer_file_options=Datei Optionen +file_visualizer_filename=Dateinamen eingeben, oder eine existierende Datei ausw\u00E4hlen. +file_visualizer_flush=Bereinigen +file_visualizer_missing_filename=Kein Ausgabe Dateiname angegeben. +file_visualizer_open=\u00D6ffnen +file_visualizer_output_file=Schreibe alle Daten in eine Datei +file_visualizer_submit_data=Einschliesslich \u00FCbermittelter Daten +file_visualizer_title=Datei Reporter +file_visualizer_verbose=Umfangreiche Ausgabe (Verbose) +filename=Dateiname +follow_redirects=Folge Redirects +follow_redirects_auto=Automatisch Redirects folgen +foreach_input=Prefix der Eingabe-Variable +foreach_output=Name der Ausgabe-Variable +foreach_use_separator=Trennzeichen "_" vor jeder Nummer einf\u00FCgen? +format=Zahlenformat +fr=Franz\u00F6sisch +ftp_binary_mode=Bin\u00E4r-Modus verwenden? +ftp_local_file=Lokale Datei\: +ftp_remote_file=Entfernte Datei +ftp_sample_title=Vorgaben zum FTP Request +ftp_save_response_data=Datei in Antwort speichern? +ftp_testing_title=FTP Anfrage +function_dialog_menu_item=Funktions Hilfe-Dialog +function_helper_title=Funktions Hilfe +function_name_param=Name der Variablen, in der die Ergebnisse abgelegt werden sollen (ben\u00F6tigt) +function_name_paropt=Name der Variablen, in der die Ergebnisse abgelegt werden sollen (optional) +function_params=Funktions Parameter +functional_mode=Funktionaler Test Mode +functional_mode_explanation=Diese Funktion f\u00FChrt zu betr\u00E4chtlichen Performanceverlusten. +gaussian_timer_delay=Konstante Pause (in Millisekunden) +gaussian_timer_memo=Zus\u00E4tzliche Pause zur Gauss'schen Verteilung +gaussian_timer_range=Abweichung (in Millisekunden) +gaussian_timer_title=Gauss'scher Zufalls-Zeitgeber +generate=Generiere +generator=Name der Erzeuger-Klasse +generator_cnf_msg=Kann die Erzeuger-Klasse (Generator) nicht finden. Vergewissern sie sich, dass die das .jar Archiv in das /lib Verzeichnis gelegt haben. +generator_illegal_msg=Konnte wegen einer "IllegalAcessException" nicht auf die Erzeuger-Klasse (Generator) zugreifen. +generator_instantiate_msg=Konnte keine Instanz der Erzeuger-Klasse ertsllen. Stellen sie sicher, dass der Erzeuger das "Generator"-Interface implementiert\! +get_xml_from_file=Datei mit SOAP XML Daten (Vorrang vor obenstehendem Text) +get_xml_from_random=Verzeichnis mit Meldungen +graph_choose_graphs=Anzuzeigende Graphen +graph_full_results_title=Vollst\u00E4ndige Ergebnisse +graph_results_average=Durchschnitt +graph_results_data=Daten +graph_results_deviation=Abweichung +graph_results_latest_sample=Letzte Probe +graph_results_median=Mittel +graph_results_ms=Millisekunden (ms) +graph_results_no_samples=Anzahl der Proben +graph_results_throughput=Durchsatz +graph_results_title=Ergebnisse +grouping_add_separators=Zwischen den Gruppen Trennzeichen einf\u00FCgen +grouping_in_controllers=Jede Gruppe in einen neuen Controller legen +grouping_mode=Gruppierung +grouping_no_groups=Sampler nicht gruppieren +grouping_store_first_only=Nur den ersten Sampler jeder Gruppe speichern +headers_stored=Gespeicherte Header im Header Manager +help=Hilfe +help_node=Wof\u00FCr ist das? +html_assertion_file=Schreibe JTidy Bericht in eine Datei +html_assertion_label=HTML Bericht +html_assertion_title=Titel des HTML Bericht +html_parameter_mask=HTML Parameter Maske +http_url_rewriting_modifier_title=HTTP URL Re-writing Bezeichner +http_user_parameter_modifier=HTTP User Parameter Bezeichner +httpmirror_title=HTTP Spiegel +if_controller_evaluate_all=F\u00FCr alle Unterelemente auswerten? +if_controller_label=Bedingung (Javascript) +if_controller_title=If-Controller +ignore_subcontrollers=Ignoriere Sub-Controller Bl\u00F6cke +include_controller=Controller einschlie\u00DFen +include_equals=Gleichheitszeichen mit einbeziehen? +include_path=Test-Plan mit einbeziehen? +increment=Zunahme +infinite=endlos Wiederholen +insert_after=Dahinter einf\u00FCgen +insert_before=Davor einf\u00FCgen +insert_parent=Dar\u00FCber Einf\u00FCgen +interleave_control_title=Controller \u00FCberlagern +intsum_param_1=Erster Ganzzahl Wert (int) +intsum_param_2=Zweiter Ganzzahl Wert (int). Weitere Werte k\u00F6nnen durch Angabe weiterer Argumente addiert werden. +invalid_data=Ung\u00FCltige Daten +invalid_mail=Fehler beim senden der E-Mail +invalid_mail_address=Eine oder mehrere fehlerhafte E-Mail Adressen gefunden +invalid_mail_server=Probleme beim Verbinden mit dem Mail-Server (siehe JMeter Log-Datei) +invalid_variables=Ung\u00FCltige Variablen +iteration_counter_arg_1="TRUE" damit jeder Benutzer einen eingenen Z\u00E4hler hat. "FALSE" f\u00FCr einen globalen Z\u00E4hler. +iterator_num=Anzahl der Wiederholungen\: +jar_file=.jar Dateien +java_request=Java Anfrage (Request) +java_request_defaults=Java Anfrage (Request) Vorgabe +javascript_expression=Zu evaluierender JavaScript Ausdruck +jexl_expression=Auszuwertender JEXL Ausdruck +jms_auth_required=Ben\u00F6tigt +jndi_config_title=JNDI Konfiguration +jndi_url_jndi_props=JNDI Eigenschaften +load=Laden +load_wsdl=Lade WSDL +log_errors_only=Fehler +log_file=Ort der Log-Datei +log_function_comment=Zus\u00E4tzliche Kommentare (optional) +log_function_level=Log-Level "INFO" (Vorgabe), "OUT" oder "ERR" +log_function_string=Zu loggende Zeichenkette +log_function_string_ret=Zu loggende (und zur\u00FCckzugebende) Zeichenkette +log_function_throwable="Throwable" Test (optional) +log_only=Nur Loggen/Anzeigen\: +log_parser=Name der Log-Parser Klasse +log_parser_cnf_msg=Kann die Klasse nicht finden. Vergewissern sie sich, dass die das .jar Archiv in das /lib Verzeichnis gelegt haben. +log_parser_illegal_msg=Konnte aufgrund einer "IllegalAcessException" nicht auf die Klasse zugreifen. +log_parser_instantiate_msg=Konnte keine Instanz der Log-Parsers erstellen. Stellen sie sicher, dass der Erzeuger das "LogParser"-Interface implementiert\! +log_sampler=Tomcat Zugriffs-Log Sampler +log_success_only=Erfolge +logic_controller_title=Einfacher Controller +login_config=Login Konfiguration +login_config_element=Login Konfigurations Element +longsum_param_1=Erster long-Wert +longsum_param_2=Zweiter long-Wert. Durch weitere Parameter k\u00F6nnen zus\u00E4tzliche long-Werte hinzugef\u00FCgt werden +loop_controller_title=Schleifen-Controller (Loop Controller) +looping_control=Wiederholungs-Control +lower_bound=Untere Grenze +mail_reader_account=Benutzername\: +mail_reader_all_messages=Alle +mail_reader_delete=Nachrichten vom Server l\u00F6schen +mail_reader_folder=Verzeichnis\: +mail_reader_num_messages=Anzahl der zu ladenen Nachrichten\: +mail_reader_password=Passwort\: +mail_reader_server_type=Server-Typ\: +mail_sent=Mail erfolgreich gesendet +mailer_attributes_panel=Mail Eigenschaften +mailer_error=Konnte die Mail nicht senden. Bitte korrigieren Sie jede fehlerhafte Eingabe. +mailer_visualizer_title=Mailer-Visualisierung +maximum_param=Der maximale Wert welcher f\u00FCr einen Wertebereich erlaubt ist +md5hex_assertion_failure=Fehler beim \u00FCberpr\u00FCfen der MD5 Summe\: {0} erhalten, sollte {1} sein +md5hex_assertion_md5hex_test=Zu pr\u00FCfender MD5 Hex String +md5hex_assertion_title=MD5 Hex \u00DCberpr\u00FCfung +mechanism=Mechanismus +menu_assertions=\u00DCberpr\u00FCfung +menu_close=Schlie\u00DFen +menu_collapse_all=Alle schlie\u00DFen +menu_config_element=Konfigurations Element +menu_edit=Editieren +menu_expand_all=Alle \u00F6ffnen +menu_logic_controller=Logik-Controller +menu_merge=Zusammenf\u00FCgen +menu_modifiers=Modifizierer +menu_non_test_elements=Nicht-Test Elemente +menu_open=\u00D6ffnen +menu_post_processors=Post-Processors +menu_pre_processors=Pre-Processors +menu_response_based_modifiers=Antwort-Basierter Modifizierer +menu_timer=Zeitgeber (Timer) +method=Methode\: +minimum_param=Der minimale Wert welcher f\u00FCr einen Wertebereich erlaubt ist +minute=Minute +modddn=Alter Name +modification_controller_title=Modifikations-Controller +modification_manager_title=Modifikations-Manager +modify_test=Test \u00E4ndern +modtest=\u00C4nderungs-Test +module_controller_module_to_run=Auszurufendes Modul +module_controller_title=Modul-Controller +module_controller_warning=Konnte Modul nicht finden\: +monitor_equation_active=Aktiv\: (aktiv/maximum) > 25% +monitor_equation_dead=Abgestoben\: keine Antwort +monitor_equation_healthy=Gut\: (aktiv/maximum) < 25% +monitor_equation_load=Last\: ((aktiv/mamimum)*50) + ((genutzer Speicher/maximaler Speicher)*50) +monitor_equation_warning=Warnung\: (aktiv/maximum) > 67% +monitor_health_tab_title=Gut +monitor_health_title=Ergebnisse \u00DCberwachen +monitor_is_title=Als \u00DCberwacher (Monitor) benutzen +monitor_label_right_active=Aktiv +monitor_label_right_dead=Abgestorben +monitor_label_right_healthy=Gut +monitor_label_right_warning=Warnung +monitor_legend_health=Gut +monitor_legend_load=Last +monitor_legend_memory_per=Speicher % (genutzt/gesamt) +monitor_legend_thread_per=Thread % (aktiv/maimum) +monitor_performance_servers=Server +monitor_performance_title=Performance-Graph +new=Neu +newdn=Neuer DN (distinguished name) +no=Norwegisch +number_of_threads=Anzahl von Threads\: +obsolete_test_element=Dieser Test-Abschnitt ist hinf\u00E4lig +once_only_controller_title=Einmal-Controller +opcode=OPcode +open=\u00D6ffnen +option=Optionen +optional_tasks=Optionale Aufgaben +paramtable=Parameter die mit dem Request gesendet werden\: +password=Passwort +paste=Einf\u00FCgen +paste_insert=Als Eintrag einf\u00FCgen +path=Pfad\: +path_extension_choice=Pfad-Erweiterung (benutze ";" als Trennzeichen) +path_extension_dont_use_equals=Keine Gleichheitszeichen in Pfad-Erweiterung benutzen (Intershop Enfinity compatibility) +path_extension_dont_use_questionmark=Keine Fragezeichen in Pfad-Erweiterung benutzen (Intershop Enfinity compatibility) +patterns_to_exclude=Auszuschlie\u00DFende URL-Muster +patterns_to_include=Einzuschlie\u00DFende URL-Muster +property_default_param=Vorgabe-Wert +property_edit=Bearbeiten +property_editor.value_is_invalid_message=Der eingegebene Text ist f\u00FCr diese Eigenschaft ung\u00FCltig.\nDer Wert wird auf seinen vorherigen Wert zur\u00FCck gesetzt. +property_editor.value_is_invalid_title=Ung\u00FCltige Eingabe +property_name_param=Name der Eigenschaft +property_returnvalue_param=Urspr\u00FCnglichen Wert der Eigenschaft zur\u00FCckgeben? Vorgabe\: false +property_undefined=undefiniert +property_value_param=Wert der Eigeschaft +property_visualiser_title=Eigenschaften +protocol=Protokoll [http]\: +protocol_java_border=Java-Klasse +protocol_java_classname=Klassenname (classname)\: +protocol_java_config_tile=Java Sample Konfigurieren +protocol_java_test_title=Java Tests +proxy_assertions=Versicherungen hinzuf\u00FCgen +proxy_cl_error=Wenn Sie einen Proxy Server spezifizieren, m\u00FCssen Sie den Host und Port angeben +proxy_content_type_exclude=Ausschlie\u00DFen\: +proxy_content_type_filter=Content-Type Filter +proxy_content_type_include=Einschlie\u00DFen\: +proxy_headers=HTTP-Header \u00FCberwachen +proxy_regex=RegEx Muster +proxy_sampler_settings=HTTP Sampler Einstellungen +proxy_sampler_type=Typ\: +proxy_separators=F\u00FCgen sie Trennzeichen hinzu +proxy_target=Ziel-Controller (Target-Controller)\: +proxy_test_plan_content=Test-Plan Inhalt\: +random_control_title=Zufalls-Controller +random_order_control_title=Zufalls-Reihenfolgen-Controller +read_response_message=Empfange Antwort wurde nicht gepr\u00FCft. Um die Antwort anzusehen aktivieren Sie die Checkbox im Sampler. +read_soap_response=SOAP-Antwort lesen +realm=Bereich +regexfunc_param_1=Regul\u00E4re Ausdr\u00FCcke zum Suchen in den Results der vorherigen Requests +regexfunc_param_2=Beispiel f\u00FCr Ersetzungs Strings, benuzte Gruppen von den regul\u00E4ren Ausdr\u00FCcken +regexfunc_param_3=Which match to use. Einen Integer 1 oder gr\u00F6sser, RAND damit JMeter eine zuf\u00E4llige Auswahl trifft, eine Fliesskommazahl, oder ALL wenn alle Treffer benutzt werden +regexfunc_param_4=Zwischen Text. Wenn ALL ausgew\u00E4hlt ist, wird der zwischen Test benutzt um das Ergebnis zu generieren +regexfunc_param_5=Standard Text. Wird benutzt anstatt der Vorlage, falls der Regul\u00E4re Ausdruck keine Treffer findet +remove=Entfernen +report_bar_chart=Balken-Diagramm +report_base_directory=Basis-Verzeichnis +report_chart_caption=Diagramm-Titel +report_chart_x_axis=X-Achse +report_chart_x_axis_label=Bezeichner f\u00FCr die X-Achse +report_chart_y_axis=Y-Achse +report_chart_y_axis_label=Bezeichner f\u00FCr die Y-Achse +report_line_graph=Linien-Diagramm +report_line_graph_urls=URLs einbeziehen +report_output_directory=Ausgabe-Verzeichnis f\u00FCr den Bericht +report_page=Bericht-Seite +report_page_element=Seitenelement +report_page_footer=Seitenfu\u00DF +report_page_header=Seitenkopf +report_page_index=Seiten-Index erstellen +report_page_intro=Seiteneinleitung +report_page_style_url=Stylesheet URL +report_page_title=Seitentitel +report_pie_chart=Torten-Diagramm +report_select=Ausw\u00E4hlen +report_summary=Bericht-Zusammenfassung +report_writer=Bericht-Schreiber +report_writer_html=HTML Bericht-Schreiber +request_data=Request Daten +reset_gui=GUI zur\u00FCcksetzen +restart=Neu starten +revert_project=Zur\u00FCcksetzen +revert_project?=Projekt zur\u00FCck setzten? +root=Wurzel +root_title=Wurzel +run=Start +running_test=Test starten +sampler_on_error_action=Aktion die bei einem Sampler-Fehler ausgef\u00FChrt werden soll +sampler_on_error_continue=Fortfahren +sampler_on_error_stop_test=Test Anhalten +sampler_on_error_stop_thread=Thread Anhalten +save=Speichern +save?=Speichern? +save_all_as=Test-Plan speichern unter +save_as=Speichern unter +save_as_error=Mehr als ein Element ausgew\u00E4hlt\! +save_as_image=Als Bild speichern +save_as_image_all=Bildschirm als Bild speichern +save_assertionresultsfailuremessage=Speichere Meldungen der Versicherungs-Fehler +save_assertions=Speichere Versicherungs-Ergebnisse (XML) +save_asxml=Speichere als XML +save_bytes=Speichere anzahl der Bytes +save_code=Speichere Response-Code +save_datatype=Speichere Daten-Typ +save_encoding=Speichere Kodierung +save_fieldnames=Speichere Feld-Namen (CSV) +save_filename=Speichere Response-Dateiname +save_graphics=Speichere Graphen +save_hostname=Speichere Hostenamen +save_label=Speichere Bezeichner +save_latency=Speichere Latenz +save_message=Speichere Response-Nachricht +save_overwrite_existing_file=Die ausgew\u00E4hlte Datei existiert bereits, m\u00F6chten sie sie \u00FCberschreiben? +save_requestheaders=Speichere Request-Header (XML) +save_responsedata=Speichere Response-Daten (XML) +save_responseheaders=Speichere Response-Header (XML) +save_samplecount=Speichere Proben und Fehler Anzahl +save_samplerdata=Speichere Sampler-Daten (XML) +save_subresults=Speichere Unter-Ergebnisse (XML) +save_success=Speichere Erfolge +save_threadcounts=Speichere aktive Thread Anzahl +save_threadname=Speichere Thread-Name +save_time=Speichere ben\u00F6tigte Zeit +save_timestamp=Speichere Zeitstempel +save_url=Speichere URL +scheduler_configuration=Scheduler Konfiguration +scope=G\u00FCltigkeitsbereich (Scope) +second=Sekunde +secure=Sicher +send_file=Datei mit dem Request senden\: +send_file_browse=Datei ausw\u00E4hlen... +send_file_filename_label=Dateiname\: +send_file_param_name_label=Wert des "name"-Attributes\: +server=Server Name oder IP\: +should_save=Wenn sie supportete Daten Dateien (z.B. CSV) benutzen ist es wichtig zuerst das Test-Script zu speichern.\nM\u00F6chten sie den Test-Plan speichern? +shutdown=Beenden +size_assertion_comparator_error_equal=gleich +size_assertion_comparator_error_greater=gr\u00F6\u00DFer als +size_assertion_comparator_error_greaterequal=gr\u00F6\u00DFer oder gleich +size_assertion_comparator_error_less=kleiner als +size_assertion_comparator_error_lessequal=kleiner oder gleich +size_assertion_comparator_error_notequal=nicht gleich +size_assertion_comparator_label=Art des Vergleichs +size_assertion_failure=Das Ergebnis hatte die falsche Gr\u00F6\u00DFe ({0} Byte). Es h\u00E4tte {1} {2} Byte sein m\u00FCssen. +size_assertion_input_error=Bitte geben sie einen g\u00FCltigen Ganzzahl-Wert ein. +size_assertion_label=Gr\u00F6\u00DFe in Byte\: +size_assertion_size_test=Gr\u00F6\u00DFe versichern +size_assertion_title=Gr\u00F6\u00DFen Versicherung +soap_action=SOAP Aktion +soap_data_title=SOAP/XML-RPC Daten +soap_sampler_title=SOAP/XML-RPC Anfrage +soap_send_action=Sende SOAP Aktion\: +spline_visualizer_average=Durchschnitt +spline_visualizer_incoming=Eingehend +spline_visualizer_maximum=Maximal +spline_visualizer_minimum=Minimal +spline_visualizer_title=Spline Darstellung +spline_visualizer_waitingmessage=Warte auf Proben +ssl_alias_prompt=Bitte geben Sie Ihren bevorzugten Alias ein +ssl_alias_select=W\u00E4hlen Sie Ihren Alias f\u00FCr den Test +ssl_error_title=Problem beim Schl\u00FCssel Speichern +ssl_pass_prompt=Bitte geben Sie Ihr Passwort ein +ssl_pass_title=Schl\u00FCssel Speicher Passwort +starttime=Startzeit +stopping_test=Stoppe alle Tests. Bitte warten ... +stopping_test_title=Stoppe den Test +string_from_file_file_name=Geben sie den vollst\u00E4ndingen Datei-Pfad ein +string_from_file_seq_final=Letzte Datei-Sequenznummer (optional) +string_from_file_seq_start=Erste Datei-Sequenznummer (optional) +table_visualizer_sample_num=Proben Anzahl +table_visualizer_sample_time=Proben-Zeit (ms) +table_visualizer_start_time=Startzeit +table_visualizer_success=Erfolgreich +table_visualizer_thread_name=Thread-Name +table_visualizer_warning=Warnung +tcp_config_title=TCP Sampler Konfiguration +tcp_nodelay=Setzte "NoDelay" +tcp_port=Port-Nummer\: +tcp_request_data=Text senden +tcp_timeout=Timeout (Millisekunden)\: +test_action_duration=Dauer (Millisekunden) +test_action_target=Ziel +test_action_target_test=Alle Threads +test_action_target_thread=Aktueller Thread +test_action_title=Test-Aktion +test_configuration=Test-Konfiguration +test_plan=Testplan +test_plan_classpath_browse=F\u00FCgen sie das Verzeichnis oder .jar zum classpath hinzu +testconfiguration=Konfiguration Testen +testplan.serialized=Thread-Gruppen nacheinander starten +testplan_comments=Kommentare\: +thread_delay_properties=Thread-Pause Eigenschaften +thread_group_title=Thread Gruppe +thread_properties=Thread-Eigenschaften +threadgroup=Thread-Gruppe +throughput_control_bynumber_label=Ausf\u00FChrungen (Gesamt) +throughput_control_bypercent_label=Ausf\u00FChrungen (Prozent) +throughput_control_perthread_label=pro Benutzer +throughput_control_title=Durchsatz-Controller +throughput_control_tplabel=Durchsatz +time_format=Format-Zeichenkette des "SimpleDateFormat" (optional) +timelim=Zeit-Limit +tr=T\u00FCrkisch +upload=Datei hochladen +upper_bound=obere Grenze +url_config_title=HTTP Request Default Einstellungen +use_keepalive=Benutze KeepAlive +user_defined_variables=Benutzer definierte Variablen +user_param_mod_help_note=(\u00C4ndern Sie dies nicht. Stattdessen, bitte die Datei mit dem Namen in JMeter's /bin Ordner \u00E4ndern.) +username=Benutzername +value=Wert +view_results_in_table=Zeige Ergebnisse in der Tabelle +warning=Warnung\! +web_server_domain=Server Name oder IP\: +web_testing_retrieve_images=Hole alle Bilder und Java Applets (nur HTML Dateien) +you_must_enter_a_valid_number=Sie m\u00FCssen ein g\u00FCltige Nummer eingeben diff --git a/src/core/org/apache/jmeter/resources/messages_es.properties b/src/core/org/apache/jmeter/resources/messages_es.properties new file mode 100644 index 00000000000..5a9889717eb --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_es.properties @@ -0,0 +1,1061 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=Acerca de Apache JMeter +add=A\u00F1adir +add_as_child=A\u00F1adir como hijo +add_parameter=A\u00F1adir Variable +add_pattern=A\u00F1adir Patr\u00F3n\: +add_test=A\u00F1adir Test +add_user=A\u00F1adir Usuario +add_value=A\u00F1adir Valor +addtest=A\u00F1adir test +aggregate_graph=Gr\u00E1ficos estad\u00EDsticos +aggregate_graph_column=Columna +aggregate_graph_display=Mostrar gr\u00E1fico +aggregate_graph_height=Altura +aggregate_graph_max_length_xaxis_label=Longitud m\u00E1xima de la etiqueta del eje x +aggregate_graph_ms=Milisegundos +aggregate_graph_response_time=Tiempo de respuesta +aggregate_graph_save=Guardar gr\u00E1fico +aggregate_graph_save_table=Guardar la tabla de datos +aggregate_graph_save_table_header=Guardar la cabecera de la tabla +aggregate_graph_title=Gr\u00E1fico +aggregate_graph_use_group_name=\u00BFIncluir el nombre del grupo en la etiqueta? +aggregate_graph_user_title=T\u00EDtulo del gr\u00E1fico +aggregate_graph_width=Anchura +aggregate_report=Informe Agregado +aggregate_report_bandwidth=Kb/sec +aggregate_report_count=\# Muestras +aggregate_report_error=Error +aggregate_report_error%=% Error +aggregate_report_max=M\u00E1x +aggregate_report_median=Mediana +aggregate_report_min=M\u00EDn +aggregate_report_rate=Rendimiento +aggregate_report_stddev=Desv. Est\u00E1ndar +aggregate_report_total_label=Total +ajp_sampler_title=AJP/1.3 Muestreador +als_message=Nota\: El Parser de Access Log tiene un dise\u00F1o gen\u00E9rico y le permite incorporar +als_message2=su propio parser. Para hacer esto, implemente "LogParser", y a\u00F1ada el jar al +als_message3=directorio /lib e introduzca la clases en el muestreador. +analyze=Analizar Archivo de Datos... +anchor_modifier_title=Parseador de Enlaces HTML +appearance=Apariencia +argument_must_not_be_negative=\u00A1El Argumento no puede ser negativo\! +assertion_assume_success=Ignorar el Estado +assertion_code_resp=C\u00F3digo de Respuesta +assertion_contains=Contiene +assertion_equals=igual +assertion_headers=Cabeceras de la respuesta +assertion_matches=Coincide +assertion_message_resp=Mensaje de Respuesta +assertion_not=No +assertion_pattern_match_rules=Reglas de Coincidencia de Patrones +assertion_patterns_to_test=Patr\u00F3n a Probar +assertion_resp_field=Campo de Respuesta a Probar +assertion_substring=Substring +assertion_text_resp=Respuesta Textual +assertion_textarea_label=Aserciones\: +assertion_title=Aserci\u00F3n de Respuesta +assertion_url_samp=URL Muestreada +assertion_visualizer_title=Resultados de la Aserci\u00F3n +attribute=Atributo +attrs=Atributos +auth_base_url=URL Base +auth_manager_title=Gestor de Autorizaci\u00F3n HTTP +auths_stored=Autorizaciones Almacenadas en el Gestor de Autorizaci\u00F3n +average=Media +average_bytes=Media de Bytes +bind=Enlace a Hilo +bouncy_castle_unavailable_message=Los jars para bouncy castle no est\u00E1n disponibles, por favor a\u00F1adalos a su classpath. +browse=Navegar... +bsf_sampler_title=Muestreador BSF +bsf_script=Script a lanzar (variables\: log, Label, FileName, Parameters, args[], SampleResult (aka prev), sampler, ctx, vars, props, OUT) +bsf_script_file=Archivo de Script a lanzar +bsf_script_language=Lenguaje de Script\: +bsf_script_parameters=Par\u00E1metros a pasar al script/archivo\: +bsh_assertion_script=Script (ver abajo para las variables que est\u00E1n definidas) +bsh_assertion_script_variables=Lectura/Escritura\: Failure, FailureMessage, SampleResult, log.\nS\u00F3lo Lectura\: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData +bsh_assertion_title=Aserci\u00F3n BeanShell +bsh_function_expression=Expresi\u00F3n a evaluar +bsh_sampler_title=Muestreador BeanShell +bsh_script=Script (ver abajo para las variables que est\u00E1n definidas) +bsh_script_file=Archivo de script +bsh_script_parameters=Par\u00E1metros (-> Par\u00E1metros String y String[]bsh.args) +bsh_script_reset_interpreter=Resetear el int\u00E9rprete bsh antes de cada llamada +bsh_script_variables=Las siguientes variables est\u00E1n definidas para el script\:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=Estoy ocupado probando, por favor pare el test antes de cambiar la configuraci\u00F3n +cache_manager_title=Gestionador de la Cach\u00E9 HTTP +cache_session_id=\u00BFIdentificador de la sesi\u00F3n de cach\u00E9? +cancel=Cancelar +cancel_exit_to_save=\u00BFHay elementos de prueba que no han sido salvados. \u00BFQuiere salvar antes de salir? +cancel_new_to_save=\u00BFHay elementos del test que no han sido salvados. \u00BFQuiere salvar antes de limpiar el plan de pruebas? +cancel_revert_project=Hay elementos del test que no han sido guardados. \u00BFDesea revertir a una versi\u00F3n guardada previamente del plan de test? +char_value=N\u00FAmero del car\u00E1cter Unicode (decimal or 0xhex) +choose_function=Elija una funci\u00F3n +choose_language=Elija lenguaje +clear=Limpiar +clear_all=Limpiar Todo +clear_cache_per_iter=\u00BFLimpiar la cach\u00E9 en cada iteraci\u00F3n? +clear_cookies_per_iter=\u00BFLimpiar las cookies en cada iteraci\u00F3n? +column_delete_disallowed=Borrar esta columna no est\u00E1 permitido +column_number=N\u00FAmero de columna del archivo CSV | siguiente | *alias +compare=Comparar +comparefilt=Filtro de Comparaci\u00F3n +comparison_differ_content=Las respuestas difieren en el contenido +comparison_differ_time=Las respuestas difieren en el tiempo de respuesta en m\u00E1s de +comparison_invalid_node=Nodo inv\u00E1lido +comparison_regex_string=Expresi\u00F3n regular +comparison_regex_substitution=Sustituci\u00F3n +comparison_response_time=Tiempo de respuesta\: +comparison_unit=ms +comparison_visualizer_title=Visualizador de la aserci\u00F3n de comparaci\u00F3n +config_element=Elemento de Configuraci\u00F3n +config_save_settings=Configurar +configure_wsdl=Configurar +constant_throughput_timer_memo=A\u00F1ade un retardo entre muestras para obtener un rendimiento constante +constant_timer_delay=Retardo de Hilo (en milisegundos)\: +constant_timer_memo=A\u00F1ade un retardo constante entre muestras +constant_timer_title=Temporizador Constante +content_encoding=Codificac\u00EDon del contenido\: +controller=Controlador +cookie_manager_policy=Pol\u00EDtica de Cookies +cookie_manager_title=Gestor de Cookies HTTP +cookies_stored=Cookies almacenadas en el Gestor de Cookies +copy=Copiar +counter_config_title=Contador +counter_per_user=Contador independiente para cada usuario +countlim=L\u00EDmite de tama\u00F1o +csvread_file_file_name=Archivo CSV del que obtener valores | *alias +cut=Cortar +cut_paste_function=Funci\u00F3n de cadena para copiar y pegar +database_conn_pool_max_usage=Uso m\u00E1ximo para cada Conexi\u00F3n +database_conn_pool_props=Pool de Conexiones a Base de Datos +database_conn_pool_size=N\u00FAmero de Conexiones en el Pool +database_conn_pool_title=Valores por defecto del Pool de Conexiones JDBC +database_driver_class=Clase del Driver\: +database_login_title=Valores por defecto para el Login a JDBC +database_sql_query_string=Query String de SQL\: +database_sql_query_title=Valores por defecto de Query SQL JDBC +database_testing_title=Petici\u00F3n JDBC +database_url=URL JDBC\: +database_url_jdbc_props=Driver JDBC y URL a Base de Datos +ddn=DN +de=Alem\u00E1n +debug_off=Deshabilitar depuraci\u00F3n +debug_on=Habilitar depuraci\u00F3n +default_parameters=Valores por defecto +default_value_field=Valor por defecto\: +delay=Retardo de arranque (segundos) +delete=Borrar +delete_parameter=Borrar Variable +delete_test=Borrar Test +delete_user=Borrar Usuario +deltest=Test de borrado +deref=Alias para desreferenciar +disable=Deshabilitar +distribution_graph_title=Gr\u00E1fico de Distribuci\u00F3n (alfa) +distribution_note1=El gr\u00E1fico se actualiza cada 10 muestras +dn=DN +domain=Dominio +done=Hecho +duration=Duraci\u00F3n (segundos) +duration_assertion_duration_test=Duraci\u00F3n a asegurar +duration_assertion_failure=La operaci\u00F3n dur\u00F3 demasiado\: tard\u00F3 {0} milisegundos, cuando no deber\u00EDa haber tardado m\u00E1s de {1} milisegundos. +duration_assertion_input_error=Por favor, introduzca un entero positivo v\u00E1lido. +duration_assertion_label=Duraci\u00F3n en milisegundos\: +duration_assertion_title=Aserci\u00F3n de Duraci\u00F3n +edit=Editar +email_results_title=Resultados del Email +en=Ingl\u00E9s +enable=Habilitar +encode?=\u00BFCodificar? +encoded_value=Valor de URL Codificada +endtime=Tiempo de Finalizaci\u00F3n +entry_dn=Introduzca DN +entrydn=Introduzca DN +error_loading_help=Error cargando p\u00E1gina de ayuda +error_occurred=Error +error_title=Error +es=Espa\u00F1ol +escape_html_string=Texto de escapado +eval_name_param=Texto que contien variables y referencias de funci\u00F3n +evalvar_name_param=Nombre de la variable +example_data=Dato de muestra +example_title=Muestreador de ejemplo +exit=Salir +expiration=Expiraci\u00F3n +field_name=Nombre de campo +file=Archivo +file_already_in_use=Ese archivo est\u00E1 en uso +file_visualizer_append=A\u00F1adir a archivo de datos existente +file_visualizer_auto_flush=Limpiar autom\u00E1ticamente despu\u00E9s de cada muestra de datos +file_visualizer_browse=Navegar... +file_visualizer_close=Cerrar +file_visualizer_file_options=Opciones de Archivo +file_visualizer_filename=Nombre de archivo +file_visualizer_flush=Limpiar +file_visualizer_missing_filename=No se ha especificado nombre de archivo de salida. +file_visualizer_open=Abrir +file_visualizer_output_file=Escribir todos los datos a Archivo +file_visualizer_submit_data=Incluir Datos Enviados +file_visualizer_title=Informe de Archivo +file_visualizer_verbose=Salida Verbosa +filename=Nombre de Archivo +follow_redirects=Seguir Redirecciones +follow_redirects_auto=Redirigir Autom\u00E1ticamente +foreach_controller_title=Controlador ForEach +foreach_input=Prefijo de variable de entrada +foreach_output=Nombre de variable de salida +foreach_use_separator=\u00BFA\u00F1adir "_" antes de n\u00FAmero? +format=Formato del n\u00FAmero +fr=Franc\u00E9s +ftp_binary_mode=\u00BFUsar modo binario? +ftp_get=get(RETR) +ftp_local_file=Fichero local\: +ftp_local_file_contents=Contenidos del fichero local\: +ftp_put=put(STOR) +ftp_remote_file=Fichero remoto\: +ftp_sample_title=Valores por defecto para petici\u00F3n FTP +ftp_save_response_data=\u00BFGuardar fichero en la respuesta? +ftp_testing_title=Petici\u00F3n FTP +function_dialog_menu_item=Di\u00E1logo de Ayuda de Funci\u00F3n +function_helper_title=Ayuda de Funci\u00F3n +function_name_param=Nombre de funci\u00F3n. Usado para almacenar valores a utilizar en cualquier sitio del plan de prueba. +function_name_paropt=Nombre de variable donde almacenar el resultado (opcional) +function_params=Par\u00E1metros de Funci\u00F3n +functional_mode=Modo de Prueba Funcional +functional_mode_explanation=Seleccione modo de prueba funcional solo si necesita archivar los datos recibidos del servidor para cada petici\u00F3n.\nSeleccionar esta opci\u00F3n impacta en el rendimiento considerablemente. +gaussian_timer_delay=Desplazamiento para Retardo Constante (en milisegundos)\: +gaussian_timer_memo=A\u00F1ade un retardo aleatorio con distribuci\u00F3n gaussiana. +gaussian_timer_range=Desviaci\u00F3n (en milisegundos)\: +gaussian_timer_title=Temporizador Aleatorio Gaussiano +generate=Generar +generator=Nombre de la clase Generadora +generator_cnf_msg=No pude encontrar la clase generadora. Por favor aseg\u00FArese de que puso el archivo jar en el directorio /lib +generator_illegal_msg=No pude acceder a la clase generadora debido a una "IllegalAcessException". +generator_instantiate_msg=No pude crear una instancia del parser generador. Por favor aseg\u00FArese de que el generador implementa la interfaz Generator. +get_xml_from_file=Archivo con datos SOAP XML (sobreescribe el texto anterior) +get_xml_from_random=Carpeta de Mensaje +graph_choose_graphs=Gr\u00E1ficos a Mostrar +graph_full_results_title=Resultados de Gr\u00E1fico Completo +graph_results_average=Media +graph_results_data=Datos +graph_results_deviation=Desviaci\u00F3n +graph_results_latest_sample=\u00DAltima Muestra +graph_results_median=Mediana +graph_results_ms=ms +graph_results_no_samples=No. de Muestras +graph_results_throughput=Rendimiento +graph_results_title=Gr\u00E1fico de Resultados +grouping_add_separators=A\u00F1adir separadores entre grupos +grouping_in_controllers=Poner cada grupo en un nuevo controlador +grouping_in_transaction_controllers=Poner cada grupo en un nuevo controlador de transacciones +grouping_mode=Agrupaci\u00F3n\: +grouping_no_groups=No agrupar muestreadores +grouping_store_first_only=Almacenar el primer muestreador de cada grupo solamente +header_manager_title=Gestor de Cabecera HTTP +headers_stored=Cabeceras Almacenadas en el Gestor de Cabeceras +help=Ayuda +help_node=\u00BFQu\u00E9 es este nodo? +html_assertion_file=Escribir el reporte JTidy en fichero +html_assertion_label=Aserci\u00F3n HTML +html_assertion_title=Aserci\u00F3n HTML +html_parameter_mask=M\u00E1scara de Par\u00E1metro HTML +http_implementation=Implementaci\u00F3n HTTP\: +http_response_code=c\u00F3digo de respuesta HTTP +http_url_rewriting_modifier_title=Modificador de re-escritura HTTP URL +http_user_parameter_modifier=Modificador de Par\u00E1metro de Usuario HTTP +httpmirror_title=Servidor espejo HTTP +id_prefix=Prefijo ID +id_suffix=Sufijo ID +if_controller_evaluate_all=\u00BFEvaluar para todos los hijos? +if_controller_expression=\u00BFInterpretar la condici\u00F3n como una variable de expresi\u00F3n? +if_controller_label=Condici\u00F3n +if_controller_title=Controlador If +ignore_subcontrollers=Ignorar bloques sub-controladores +include_controller=Incluir Controlador +include_equals=\u00BFIncluir Equals? +include_path=Incluir Plan de Pruebas +increment=Incrementar +infinite=Sin f\u00EDn +initial_context_factory=Factor\u00EDa Initial Context +insert_after=Insertar Despu\u00E9s +insert_before=Insertar Antes +insert_parent=Insertar Padre +interleave_control_title=Controlador Interleave +intsum_param_1=Primer int a a\u00F1adir. +intsum_param_2=Segundo int a a\u00F1adir - m\u00E1s ints pueden ser insertados a\u00F1adiendo m\u00E1s argumentos +invalid_data=Dato inv\u00E1lido +invalid_mail=Error al enviar el e-mail +invalid_mail_address=Una o m\u00E1s direcciones de e-mail inv\u00E1lidas +invalid_mail_server=Problema contactantdo el servidor de e-mail (mire los logs de JMeter) +invalid_variables=Variables inv\u00E1lidas +iteration_counter_arg_1=TRUE, para que cada usuario su propio contador, FALSE para tener un contador global +iterator_num=Contador del bucle\: +ja=Japon\u00E9s +jar_file=Ficheros .jar +java_request=Petici\u00F3n Java +java_request_defaults=Valores por defecto para Petici\u00F3n Java +javascript_expression=Expresi\u00F3n JavaScript a evaluar +jexl_expression=Expresi\u00F3n JEXL a evaluar +jms_auth_required=Requerido +jms_client_caption=El cliente Receive utiliza TopicSubscriber.receive() para escuchar un mensaje. +jms_client_caption2=MessageListener utiliza la interfaz onMessage(Message) para escuchar nuevos mensajes +jms_client_type=Cliente +jms_communication_style=Estilo de Comunicaci\u00F3n +jms_concrete_connection_factory=Factor\u00EDa de Connection Concreto +jms_config=Fuente del mensaje +jms_config_title=Configuraci\u00F3n JMS +jms_connection_factory=Factor\u00EDa de Connection +jms_correlation_title=Usar campos alternativos para la correlaci\u00F3n de mensajes +jms_dest_setup=Configuraci\u00F3n +jms_dest_setup_dynamic=En cada muestra +jms_dest_setup_static=Al arranque +jms_file=Archivo +jms_initial_context_factory=Factor\u00EDa de Initial Context +jms_itertions=N\u00FAmero de muestras a agregar +jms_jndi_defaults_title=Configuraci\u00F3n por defecto de JNDI +jms_jndi_props=Propiedades JNDI +jms_map_message=Mensaje Map +jms_message_title=Propiedades de Mensaje +jms_message_type=Tipo de Mensaje +jms_msg_content=Contenido +jms_object_message=Mensaje Object +jms_point_to_point=JMS Punto-a-Punto +jms_props=Propiedades JMS +jms_provider_url=URL Proveedor +jms_publisher=Publicador JMS +jms_pwd=Contrase\u00F1a +jms_queue=Cola +jms_queue_connection_factory=Factor\u00EDa de QueueConnection +jms_queueing=Recursos JMS +jms_random_file=Archivo Aleatorio +jms_read_response=Respuesta Le\u00EDda +jms_receive_queue=Nombre JNDI cola Recepci\u00F3n +jms_request=S\u00F3lo Petici\u00F3n +jms_requestreply=Respuesta a Petici\u00F3n +jms_sample_title=Petici\u00F3n JMS por defecto +jms_send_queue=Nombre JNDI Cola Petici\u00F3n +jms_stop_between_samples=\u00BFParar entre muestras? +jms_subscriber_on_message=Utilizar MessageListener.onMessage() +jms_subscriber_receive=Utilizar TopicSubscriber.receive() +jms_subscriber_title=Suscriptor JMS +jms_testing_title=Petici\u00F3n Mensajer\u00EDa +jms_text_message=Mensaje Texto +jms_timeout=Timeout (milisegundos) +jms_topic=T\u00F3pico +jms_use_auth=\u00BFUsar Autorizaci\u00F3n? +jms_use_file=Desde archivo +jms_use_non_persistent_delivery=\u00BFUsar modo de entrega no persistente? +jms_use_properties_file=Utilizar archivo jndi.properties +jms_use_random_file=Archivo Aleatorio +jms_use_req_msgid_as_correlid=Usar el identificador del mensaje Request +jms_use_res_msgid_as_correlid=Usar el identificador del mensaje Response +jms_use_text=\u00C1rea de Texto +jms_user=Usuario +jndi_config_title=Configuraci\u00F3n JNDI +jndi_lookup_name=Interfaz Remota +jndi_lookup_title=Configuraci\u00F3n del Lookup JNDI +jndi_method_button_invoke=Invocar +jndi_method_button_reflect=Reflejar +jndi_method_home_name=Nombre de M\u00E9todo Home +jndi_method_home_parms=Par\u00E1metros de M\u00E9todo Home +jndi_method_name=Configuraci\u00F3n de M\u00E9todo +jndi_method_remote_interface_list=Interfaces Remotas +jndi_method_remote_name=Nombre de M\u00E9todo Remoto +jndi_method_remote_parms=Par\u00E1metros de M\u00E9todo Remoto +jndi_method_title=Configuraci\u00F3n de M\u00E9todo Remoto +jndi_testing_title=Petici\u00F3n JNDI +jndi_url_jndi_props=Propiedades JNDI +junit_append_error=A\u00F1adir errores de aserci\u00F3n +junit_append_exception=A\u00F1adir excepciones de ejecuci\u00F3n +junit_constructor_error=Imposible crear una instancia de la clase +junit_constructor_string=Etiqueta del constructor de String +junit_do_setup_teardown=No llamar a setUp y tearDown +junit_error_code=C\u00F3digo de error +junit_error_default_code=9999 +junit_error_default_msg=Ocurri\u00F3 un error no esperado +junit_error_msg=Mensaje de error +junit_failure_code=Codigo de fallo +junit_failure_default_code=0001 +junit_failure_default_msg=Test fall\u00F3 +junit_failure_msg=Mensaje de fallo +junit_junit4=Buscar anotaciones JUnit 4 (en el caso de JUnit 3) +junit_pkg_filter=Filtro de paquetes +junit_request=Petici\u00F3n JUnit +junit_request_defaults=Valores por defecto de la petici\u00F3n JUnit +junit_success_code=C\u00F3digo de \u00E9xito +junit_success_default_code=1000 +junit_success_default_msg=Test satisfactorio +junit_success_msg=Mensaje de \u00E9xito +junit_test_config=Par\u00E1metros del test JUnit +junit_test_method=M\u00E9todo de Test +ldap_argument_list=Lista de LDAPArgument +ldap_connto=Timeout de conexi\u00F3n (en milisegundos) +ldap_parse_results=\u00BFParsear los resultados de la b\u00FAsqueda? +ldap_sample_title=Valores por defecto Petici\u00F3n LDAP +ldap_search_baseobject=Realizar la b\u00FAsqueda 'baseobject' +ldap_search_onelevel=Realizar la b\u00FAsqueda 'onelevel' +ldap_search_subtree=Realizar la b\u00FAsqueda 'subtree' +ldap_secure=\u00BFUsar el Protocolo LDAP Seguro? +ldap_testing_title=Petici\u00F3n LDAP +ldapext_sample_title=Valores por defecto Petici\u00F3n Extendidad LDAP +ldapext_testing_title=Petici\u00F3n Extendida LDAP +library=Librer\u00EDa +load=Cargar +load_wsdl=Cargar WSDL +log_errors_only=Escribir en Log S\u00F3lo Errores +log_file=Ubicaci\u00F3n del archivo de logs +log_function_comment=Comentario adicional (opcional) +log_function_level=Nivel de Log (por defecto INFO) o OUT o ERR +log_function_string=Texto a escribir en log +log_function_string_ret=Texto a ser escrito en log (y retornado) +log_function_throwable=Texto para 'Throwable' (Opcional) +log_only=Log/Mostrar s\u00F3lo\: +log_parser=Nombre de la clase Parser de Log +log_parser_cnf_msg=No pude encontrar la clase. Por favor, aseg\u00FArese de colocar el archivo jar en el directorio /lib. +log_parser_illegal_msg=No pude acceder a la clase debido a una "IllegalAcessException". +log_parser_instantiate_msg=No pude crear una instancia del parser de log. Por favor aseg\u00FArese de que el parser implementar la interfaz LogParser. +log_sampler=Muestreador de Log de Acceso de Tomcat +log_success_only=\u00C9xitos +logic_controller_title=Controlador Simple +login_config=Configuraci\u00F3n de Login +login_config_element=Elemento de Configuraci\u00F3n de Login +longsum_param_1=Primer 'long' a a\u00F1adir +longsum_param_2=Segundo 'long' a a\u00F1adir - m\u00E1s 'longs' pueden ser sumados a\u00F1adiendo m\u00E1s argumentos. +loop_controller_title=Controlador Bucle +looping_control=Control de Bucles +lower_bound=L\u00EDmite inferior +mail_reader_account=Usuario\: +mail_reader_all_messages=Todo +mail_reader_delete=Borrar archivos del servidor +mail_reader_folder=Carpeta\: +mail_reader_num_messages=N\u00FAmero de mensajes a recuperar\: +mail_reader_password=Contrase\u00F1a\: +mail_reader_port=Puerto del servidor (opcional)\: +mail_reader_server=Servidor\: +mail_reader_server_type=Tipo de Servidor\: +mail_reader_storemime=Almacenar el mensaje usando MIME(raw) +mail_reader_title=Muestreador Lector de Correo +mail_sent=Mail enviado con \u00E9xito +mailer_attributes_panel=Atributos de Mailing +mailer_error=No pude enviar mail. Por favor, corrija las entradas incorrectas. +mailer_visualizer_title=Visualizador de Mailer +match_num_field=Coincidencia No. (0 para Aleatorio)\: +max=M\u00E1ximo +maximum_param=El valor m\u00E1ximo permitido para un rango de valores +md5hex_assertion_failure=Error validando MD5\: obtuve {0} pero deber\u00EDa haber obtenido {1} +md5hex_assertion_label=MD5Hex +md5hex_assertion_md5hex_test=MD5Hex a Comprobar +md5hex_assertion_title=Aserci\u00F3n MD5Hex +memory_cache=Cach\u00E9 en Memoria +menu_assertions=Aserciones +menu_close=Cerrar +menu_collapse_all=Colapsar todo +menu_config_element=Elemento de Configuraci\u00F3n +menu_edit=Editar +menu_expand_all=Expandir todo +menu_fragments=Fragmento de Prueba +menu_generative_controller=Muestreador +menu_listener=Receptor +menu_logic_controller=Controlador L\u00F3gico +menu_merge=Mezclar +menu_modifiers=Modificadores +menu_non_test_elements=Elementos NoDePrueba +menu_open=Abrir +menu_post_processors=Post Procesadores +menu_pre_processors=Pre Procesadores +menu_response_based_modifiers=Modificadores Basados en Respuesta +menu_tables=Tabla +menu_threads=Hilos (Usuarios) +menu_timer=Temporizador +metadata=MetaDatos +method=M\u00E9todo\: +mimetype=Tipo MIME +minimum_param=El valor m\u00EDnimo admitido para un rango de valores +minute=minuto +modddn=Nombre de entrada antiguo +modification_controller_title=Controlador de Modificaci\u00F3n +modification_manager_title=Gestor de Modificaci\u00F3n +modify_test=Prueba de Modificaci\u00F3n +modtest=Prueba de Modificaci\u00F3n +module_controller_module_to_run=M\u00F3dulo a ejecutar +module_controller_title=Controlador de M\u00F3dulo +module_controller_warning=No pudo encontrar el m\u00F3dulo\: +monitor_equation_active=Activo\: (ocupado/m\u00E1x) > 25% +monitor_equation_dead=Muerto\: no hay respuesta +monitor_equation_healthy=Sano. (ocupado/m\u00E1x) < 25% +monitor_equation_load=Carga\: ((ocupado/m\u00E1x) * 50) + ((memoria usada/memoria m\u00E1x) * 50) +monitor_equation_warning=Aviso\: (ocupado/m\u00E1x) > 67% +monitor_health_tab_title=Salud +monitor_health_title=Resultados del Monitor +monitor_is_title=Utilizar como Monitor +monitor_label_left_bottom=0 % +monitor_label_left_middle=50 % +monitor_label_left_top=100 % +monitor_label_prefix=Prefijo de conexi\u00F3n +monitor_label_right_active=Activo +monitor_label_right_dead=Muerto +monitor_label_right_healthy=Sano +monitor_label_right_warning=Aviso +monitor_legend_health=Salud +monitor_legend_load=Carga +monitor_legend_memory_per=Memoria % (usada/total) +monitor_legend_thread_per=Hilo % (ocupado/m\u00E1x) +monitor_load_factor_mem=50 +monitor_load_factor_thread=50 +monitor_performance_servers=Servidores +monitor_performance_tab_title=Rendimiento +monitor_performance_title=Gr\u00E1fico de Rendimiento +name=Nombre\: +new=Nuevo +newdn=Nuevo distinghuised name +no=Noruego +number_of_threads=N\u00FAmero de Hilos +obsolete_test_element=Este elemento de test es obsoleto +once_only_controller_title=Controlador Only Once +opcode=opCode +open=Abrir... +option=Opciones +optional_tasks=Tareas Opcionales +paramtable=Enviar Par\u00E1metros Con la Petici\u00F3n\: +password=Contrase\u00F1a +paste=Pegar +paste_insert=Pegar como Inserci\u00F3n +path=Ruta\: +path_extension_choice=Extensi\u00F3n de Path (utilice ";" como separador) +path_extension_dont_use_equals=No utilice el signo igual en la extensi\u00F3n del path (compatibilidad con Intershop Enfinity) +path_extension_dont_use_questionmark=No utilice el signo interrogaci\u00F3n en la extensi\u00F3n del path (compatibilidad con Intershop Enfinity) +patterns_to_exclude=URL Patrones a Excluir +patterns_to_include=URL Patrones a Incluir +pkcs12_desc=Clave PKCS (*.p12) +pl=Polaco +port=Puerto\: +post_thread_group_title=Tirar abajo grupo de Hilos +property_as_field_label={0}\: +property_default_param=Valor por defecto +property_edit=Editar +property_editor.value_is_invalid_message=El texto que acaba de introducir no es un valor v\u00E1lido para esta propiedad. La propiedad ser\u00E1 devuelta a su valor anterior. +property_editor.value_is_invalid_title=Entrada inv\u00E1lida +property_name_param=Nombre de propiedad +property_returnvalue_param=\u00BFRetornar el valor original de la propiedad (falso, por defecto)? +property_tool_tip={0}\: {1} +property_undefined=No definido +property_value_param=Valor de propiedad +property_visualiser_title=Visualizador de propiedades +protocol=Protocolo\: +protocol_java_border=Clase java +protocol_java_classname=Nombre de clase\: +protocol_java_config_tile=Muestra de Configure Java +protocol_java_test_title=Test Java +provider_url=URL Proveedor +proxy_assertions=A\u00F1adir Aserciones +proxy_cl_error=Si est\u00E1 especificando un servidor proxy, el puerto y el host deben ser provistos. +proxy_content_type_exclude=Excluir\: +proxy_content_type_filter=Filtro de tipo de contenido +proxy_content_type_include=Incluir\: +proxy_daemon_bind_error=No pudo crear el proxy - puerto en uso. Escoger otro puerto. +proxy_daemon_error=No pudo crear el proxy - ver traza para m\u00E1s detalles +proxy_headers=Capturar Cabeceras HTTP +proxy_regex=Coincidencia Regex +proxy_sampler_settings=Par\u00E1metros muestra HTTP +proxy_sampler_type=Tipo\: +proxy_separators=A\u00F1adir Separadores +proxy_target=Controlador Objetivo\: +proxy_test_plan_content=Contenido del plan de pruebas +proxy_title=Servidor Proxy HTTP +pt_br=Portugu\u00E9s (Brasile\u00F1o) +ramp_up=Periodo de Subida (en segundos)\: +random_control_title=Controlador Aleatorio +random_order_control_title=Controlador Orden Aleatorio +read_response_message=La lectura de respuesta no est\u00E1 activada. Para ver la respuesta, por favor marque la caja en el sampler. +read_response_note=Si "leer respuesta" est\u00E1 desactivado, el muestreador no leer\u00E1 la respuesta +read_response_note2=ni establecer\u00E1 el "SampleResult". Esto mejora el rendimiento, pero significa +read_response_note3=que el contenido de respuesta no ser\u00E1 logado. +read_soap_response=Leer Respuesta SOAP +realm=Dominio (realm) +record_controller_title=Controlador Grabaci\u00F3n +ref_name_field=Nombre de Referencia\: +regex_extractor_title=Extractor de Expresiones Regulares +regex_field=Expresi\u00F3n Regular\: +regex_source=Campo de Respuesta a comprobar +regex_src_body=Cuerpo +regex_src_body_unescaped=Cuerpo (No escapado) +regex_src_hdrs=Cabeceras +regex_src_url=URL +regexfunc_param_1=Expresi\u00F3n regular usada para buscar resultados en la peticiones previas +regexfunc_param_2=Plantilla para la cadena de sustituci\u00F3n, utilizando grupos de la expresi\u00F3n regular. El formato es $[grupo]$.\nEjemplo $1$. +regexfunc_param_3=Qu\u00E9 coincidencia utilizar. Un entero 1 o mayor, RAND para indicar a JMeter que utilice un n\u00FAmero aleatorio, un floar o ALL para indicar que todas las coincidencias deber\u00EDan ser utilizadas +regexfunc_param_4=Texto intermedio. Si se selecciona ALL, the texto intermedio ser\u00E1 utilizado para generar los resultados +regexfunc_param_5=Texto por Defecto. Utilizado en lugar de la plantilla si la expresi\u00F3n regular no encuentra coincidencias. +regexfunc_param_7=Nombre de la variable de entrada que contiene el texto a ser parseado ([muestra anterior]) +regexp_render_no_text=El dato de respuesta del resultado no es texto. +regexp_tester_button_test=Test +regexp_tester_field=Expresi\u00F3n regular\: +regexp_tester_title=Testeador de RegExp +remote_error_init=Error inicializando el servidor remoto +remote_error_starting=Error arrancando el servidor remoto +remote_exit=Salir Remoto +remote_exit_all=Salir de Todo Remoto +remote_shut=Apagar remoto +remote_shut_all=Apagar todo remoto +remote_start=Arrancar Remoto +remote_start_all=Arrancar Todo Remoto +remote_stop=Parar Remoto +remote_stop_all=Parar Todo Remoto +remove=Borrar +rename=Renombrar entrada +report=Informe +report_bar_chart=Gr\u00E1fico de barras +report_bar_graph_url=URL +report_base_directory=Directorio base +report_chart_caption=Leyenda del gr\u00E1fico +report_chart_x_axis=Eje X +report_chart_x_axis_label=Etiqueta para el eje X +report_chart_y_axis=Eje Y +report_chart_y_axis_label=Etiqueta para el eje Y +report_line_graph=Gr\u00E1fico de l\u00EDneas +report_line_graph_urls=Incluir URL +report_output_directory=Directorio de salida para el informe +report_page=P\u00E1gina del informe +report_page_element=Elemento de p\u00E1gina +report_page_footer=Pie de p\u00E1gina +report_page_header=Cabecera de p\u00E1gina +report_page_index=Crear \u00EDndice de p\u00E1gina +report_page_intro=P\u00E1gina de introducci\u00F3n +report_page_style_url=URL de la hoja de estilos +report_page_title=T\u00EDtulo de p\u00E1gina +report_pie_chart=Gr\u00E1fico de tarta +report_plan=Esquema del reporte +report_select=Seleccionar +report_summary=Resumen de informe +report_table=Tabla de informe +report_writer=Escritor del reporte +report_writer_html=Escritor HTML del reporte +request_data=Pedir Datos +reset_gui=Resetear GUI +response_save_as_md5=\u00BFGuardar la respuesta como MD5 hash? +restart=Rearranque +resultaction_title=Manejador de Acci\u00F3n para Status de Resultados +resultsaver_errors=Guardar Respuestas Fallidas Solamente +resultsaver_prefix=Prefijo de nombre de archivo\: +resultsaver_skipautonumber=No a\u00F1adir n\u00FAmero al prefijo +resultsaver_skipsuffix=No a\u00F1adir sufijo +resultsaver_success=Guardar s\u00F3lo respuestas satisfactorias +resultsaver_title=Guardar respuestas en archivo +resultsaver_variable=Nombre de variable\: +retobj=Devolver objeto +reuseconnection=Reusar conexi\u00F3n +revert_project=Revertir +revert_project?=\u00BFRevertir proyecto? +root=Ra\u00EDz +root_title=Ra\u00EDz +run=Lanzar +running_test=Test lanzado +runtime_controller_title=Controlador Tiempo de Ejecuci\u00F3n +runtime_seconds=Tiempo de ejecuci\u00F3n (segundos) +sample_result_save_configuration=Guardar Configuraci\u00F3n de Resultado de Muestra +sample_scope=Aplicar a\: +sample_scope_all=Muestra principal y submuestras +sample_scope_children=S\u00F3lo submuestras +sample_scope_parent=S\u00F3lo muestra principal +sample_scope_variable=Variable JMeter +sampler_label=Etiqueta +sampler_on_error_action=Acci\u00F3n a tomar despu\u00E9s de un error de Muestreador +sampler_on_error_continue=Continuar +sampler_on_error_start_next_loop=Comenzar siguiente iteraci\u00F3n +sampler_on_error_stop_test=Parar Test +sampler_on_error_stop_test_now=Parar test ahora +sampler_on_error_stop_thread=Parar Hilo +save=Guardar +save?=\u00BFGuardar? +save_all_as=Guardar Plan de Pruebas como +save_as=Guardar selecci\u00F3n como... +save_as_error=\u00A1M\u00E1s de un item seleccionado\! +save_as_image=Guardar como imagen +save_as_image_all=Guardar la pantalla como imagen +save_assertionresultsfailuremessage=Guardar Mensaje de Fallo de Resultados de Aserci\u00F3n +save_assertions=Guardar Resultados de Aserci\u00F3n +save_asxml=Guardar Como XML +save_bytes=Guardar conteo de bytes +save_code=Guardar C\u00F3digo de Respuesta +save_datatype=Guardar Tipo de Datos +save_encoding=Guardar Codificaci\u00F3n +save_fieldnames=Guardar Nombre de Campo +save_filename=Guardar el nombre del fichero de respuesta +save_graphics=Guardar Gr\u00E1ficos +save_hostname=Guardar el nombre de host +save_idletime=Guardar tiempo inactivo +save_label=Guardar Etiqueta +save_latency=Guardar Latencia +save_message=Guardar Mensaje de Respuesta +save_overwrite_existing_file=El fichero seleccionado ya existe, \u00BFquiere sobreescribirlo? +save_requestheaders=Guardar Cabeceras de Petici\u00F3n +save_responsedata=Guardar Datos de Respuesta +save_responseheaders=Guardar Cabeceras de Respuesta +save_samplecount=Guardar muestra y conteo de error +save_samplerdata=Guardar Datos de Muestreador +save_subresults=Guardar Sub Resultados +save_success=Guardado Correctamente +save_threadcounts=Guardar conteos hilos activos +save_threadname=Guardar Nombre de Hilo +save_time=Guardar Tiempo +save_timestamp=Guardar Etiqueta de Tiempo +save_url=Guardar URL +sbind=Conexi\u00F3n/Desconexi\u00F3n Simple +scheduler=Planificador +scheduler_configuration=Configuraci\u00F3n del Planificador +scope=\u00C1mbito +search_base=Base de B\u00FAsqueda +search_filter=Filtro de B\u00FAsqueda +search_test=Prueba de B\u00FAsqueda +search_text_button_close=Cerrar +search_text_button_find=Encontrar +search_text_button_next=Encontrar siguiente +search_text_chkbox_case=Sensible a may\u00FAsculas +search_text_chkbox_regexp=Expresi\u00F3n regular +search_text_field=Buscar\: +search_text_msg_not_found=Texto no encontrado +search_text_title_not_found=No encontrado +searchbase=Base de B\u00FAsqueda +searchfilter=Filtro de B\u00FAsqueda +searchtest=Prueba de B\u00FAsqueda +second=segundo +secure=Seguro +send_file=Enviar un archivo Con la Petici\u00F3n +send_file_browse=Navegar... +send_file_filename_label=Nombre de Archivo\: +send_file_mime_label=Tipo MIME\: +send_file_param_name_label=Nombre de Par\u00E1metro\: +server=Nombre de Servidor o IP\: +servername=Nombre de Servidor\: +session_argument_name=Nombre de Argumento de Sesi\u00F3n +setup_thread_group_title=Montar grupo de Hilos +should_save=Deber\u00EDa guardar el plan de pruebas antes de lanzarlo. Si est\u00E1 utilizando archivos de datos (ie, para DCV o _StringFromFile), entonces es especialmente importante que primero guarde su script de prueba. +shutdown=Interrumpir +simple_config_element=Elemento de Configuraci\u00F3n Simple +simple_data_writer_title=Escritor de Datos Simple +size_assertion_comparator_error_equal=siendo igual a +size_assertion_comparator_error_greater=siendo mayor que +size_assertion_comparator_error_greaterequal=siendo mayor o igual a +size_assertion_comparator_error_less=siendo menor que +size_assertion_comparator_error_lessequal=siendo menor o igual que +size_assertion_comparator_error_notequal=no siendo igual a +size_assertion_comparator_label=Tipo de Comparaci\u00F3n +size_assertion_failure=El resultado tuvo el tama\u00F1o incorrecto\: fu\u00E9 {0} bytes, pero deber\u00EDa haber sido {1} {2} bytes. +size_assertion_input_error=Por favor, introduzca un entero positivo v\u00E1lido. +size_assertion_label=Tama\u00F1o en bytes\: +size_assertion_size_test=Tama\u00F1o a Comprobar +size_assertion_title=Aserci\u00F3n de Tama\u00F1o +smime_assertion_issuer_dn=Nombre \u00FAnico del emisor\: +smime_assertion_message_position=Ejecutar aserci\u00F3n sobre el mensaje a partir de la posici\u00F3n +smime_assertion_not_signed=Mensaje no firmado +smime_assertion_signature=Firma +smime_assertion_signer=Cerficado del firmante +smime_assertion_signer_by_file=Certificado +smime_assertion_signer_constraints=Chequear valores +smime_assertion_signer_dn=Nombre \u00FAnico del firmante +smime_assertion_signer_email=Direcci\u00F3n de correo del firmante +smime_assertion_signer_no_check=No chequear +smime_assertion_signer_serial=N\u00FAmero de serie +smime_assertion_title=Aserci\u00F3n SMIME +smime_assertion_verify_signature=Verificar firma +smtp_additional_settings=Par\u00E1metros adicionales +smtp_attach_file=Adjuntar fichero(s)\: +smtp_attach_file_tooltip=Separar ficheros con ";" +smtp_auth_settings=Par\u00E1metros de autentificaci\u00F3n +smtp_bcc=Direcciones en copia oculta (BCC)\: +smtp_cc=Direcciones en copia(CC)\: +smtp_default_port=(Por defecto\: SMTP\:25, SSL\:465, StartTLS\:587) +smtp_eml=Enviar .eml\: +smtp_enabledebug=\u00BFActivar las trazas de depuraci\u00F3n? +smtp_enforcestarttls=Imponer StartTLS +smtp_enforcestarttls_tooltip=Forza al servidor a usar StartTLS.
Si no es seleccionado el servidor SMTP no soporta StartTLS,
una conexi\u00F3n normal SMTP ser\u00E1 usada como reserva.
Por favor advierta que este objeto crea un fichero en "/tmp/",
so Esto causar\u00E1 problemas bajo Windows. +smtp_from=Direcci\u00F3n Desde\: +smtp_header_add=A\u00F1adir cabecera +smtp_header_name=Nombre de cabecera +smtp_header_remove=Suprimir +smtp_header_value=Valor de cabecera +smtp_mail_settings=Par\u00E1metros del correo +smtp_message=Mensaje\: +smtp_message_settings=Par\u00E1metros del mensaje\: +smtp_messagesize=Calcular tama\u00F1o del mensaje +smtp_password=Contrase\u00F1a\: +smtp_plainbody=Enviar texto plano(i.e. no multipart/mixed) +smtp_replyto=Direcci\u00F3n Responder-a\: +smtp_sampler_title=Muestra SMTP +smtp_security_settings=Par\u00E1metros de seguridad +smtp_server=Servidor\: +smtp_server_port=Puerto\: +smtp_server_settings=Par\u00E1metros del servidor +smtp_subject=Asunto\: +smtp_suppresssubj=Suprimir la cabecera del asunto +smtp_timestamp=Incluir timestamp en el asunto +smtp_to=Direcci\u00F3n A\: +smtp_trustall=Verificar todos los certificados +smtp_trustall_tooltip=Fuerza a JMeter a verificar todos los certificados, que vienen del CA. +smtp_truststore=Almacenamiento local de confianza\: +smtp_truststore_tooltip=Nombre de la ruta del almacenamiento local de confianza.
Rutas relativas son resueltas contra el directorio actual.
Si esto falla, contra el directorio que contiene el script de test (JMX file) +smtp_useauth=Usar autentificaci\u00F3n +smtp_usenone=No usar funcionalidades de seguridad +smtp_username=Nombre de usuario\: +smtp_usessl=Usar SSL +smtp_usestarttls=Usar StartTLS +smtp_usetruststore=Usar almacenamiento local de confianza +smtp_usetruststore_tooltip=Permite a JMeter usar un almacenamiento de confianza local. +soap_action=Acci\u00F3n Soap +soap_data_title=Datos Soap/XML-RPC +soap_sampler_title=Petici\u00F3n Soap/XML-RPC +soap_send_action=Enviar SOAPAction\: +spline_visualizer_average=Media +spline_visualizer_incoming=Entrando +spline_visualizer_maximum=M\u00E1ximo +spline_visualizer_minimum=M\u00EDnimo +spline_visualizer_title=Visualizador Spline +spline_visualizer_waitingmessage=Esperando muestras +split_function_separator=Texto para separar. Por defecto es , (coma) +split_function_string=Texto a separar +ssl_alias_prompt=Por favor, introduzca su alias favorito +ssl_alias_select=Seleccione su alias para la prueba +ssl_alias_title=Alias de Cliente +ssl_error_title=Problema con el KeyStore +ssl_pass_prompt=Por favor, introduzca su contrase\u00F1a +ssl_pass_title=Contrase\u00F1a de KeyStore +ssl_port=Puerto SSL +sslmanager=Gestor SSL +start=Arrancar +start_no_timers=Inicio no se detiene +starttime=Tiempo de Arranque +stop=Parar +stopping_test=Parando todos los hilos. Por favor, sea paciente. +stopping_test_failed=Uno o m\u00E1s hilos de test no saldr\u00E1n; ver fichero de log. +stopping_test_title=Parando la Prueba +string_from_file_encoding=Codificaci\u00F3n, si no el por defecto de la plataforma (opcional) +string_from_file_file_name=Introduzca ruta completa al archivo +string_from_file_seq_final=N\u00FAmero final de secuencia de archivo +string_from_file_seq_start=N\u00FAmero inicial de secuencia de archivo +summariser_title=Generar Resumen de Resultados +summary_report=Reporte resumen +switch_controller_label=Conmutar Valor +switch_controller_title=Conmutar Controlador +table_visualizer_bytes=Bytes +table_visualizer_sample_num=Muestra \# +table_visualizer_sample_time=Tiempo de Muestra (ms) +table_visualizer_start_time=Tiempo de comienzo +table_visualizer_status=Estado +table_visualizer_success=\u00C9xito +table_visualizer_thread_name=Nombre del hilo +table_visualizer_warning=Alerta +tcp_classname=Nombre de clase TCPClient\: +tcp_config_title=Configuraci\u00F3n de Muestreador TCP +tcp_nodelay=Establecer SinRetardo +tcp_port=Puerto\: +tcp_request_data=Texto a enviar +tcp_sample_title=Muestreador TCP +tcp_timeout=Timeout (milisegundos) +template_field=Plantilla\: +test=Prueba +test_action_action=Acci\u00F3n +test_action_duration=Duraci\u00F3n +test_action_pause=Pausa +test_action_stop=Parar +test_action_stop_now=Parar ahora +test_action_target=Objetivo +test_action_target_test=Todos los Hilos +test_action_target_thread=Hilo Actual +test_action_title=Acci\u00F3n de Prueba +test_configuration=Configuraci\u00F3n de Pruebas +test_fragment_title=Fragmento de Prueba +test_plan=Plan de Pruebas +test_plan_classpath_browse=A\u00F1adir directorio o jar al classpath +testconfiguration=Configuraci\u00F3n de Pruebas +testplan.serialized=Lanza cada Grupo de Hilos separadamente (i.e. lanza un grupo antes de lanzar el siguiente) +testplan_comments=Comentarios +testt=Prueba +textbox_cancel=Cancelar +textbox_close=Cerrar +textbox_save_close=Guardar y cerrar +textbox_title_edit=Editar texto +textbox_title_view=Ver texto +textbox_tooltip_cell=Doble click para ver/editar +thread_delay_properties=Propiedades de Retardo de Hilos +thread_group_title=Grupo de Hilos +thread_properties=Propiedades de Hilo +threadgroup=Grupo de Hilos +throughput_control_bynumber_label=Ejecuciones Totales +throughput_control_bypercent_label=Porcentaje de Ejecuciones +throughput_control_perthread_label=Por Usuario +throughput_control_title=Controlador Throughput +throughput_control_tplabel=Rendimiento +time_format=Cadena de formateo para SimpleDateFormat(opcional) +timelim=L\u00EDmite de Tiempo +tr=Turco +transaction_controller_include_timers=Incluir la duraci\u00F3n de temporizador y pre-post procesadores en la muestra generada +transaction_controller_parent=Generar muestra padre +transaction_controller_title=Controlador Transaction +unbind=Desligar Hilo +unescape_html_string=Cadena de texto para quitar caracteres de escapado +unescape_string=Cadena de texto contiene caracteres Java de escapado +uniform_timer_delay=Desplazamiento de Retraso Constante (en milisegundos)\: +uniform_timer_memo=A\u00F1ade un retardo aleatorio con una distribuci\u00F3n uniforme +uniform_timer_range=M\u00E1ximo retardo Aleatorio (en milisegundos) +uniform_timer_title=Temporizador Aleatorio Uniforme +update_per_iter=Actualizar Una Vez Por Iteraci\u00F3n +upload=Subida de Archivo +upper_bound=L\u00EDmite Superior +url=URL +url_config_get=GET +url_config_http=HTTP +url_config_https=HTTPS +url_config_post=POST +url_config_protocol=Protocolo\: +url_config_title=Valores por Defecto para Petici\u00F3n HTTP +url_full_config_title=Muestra UrlFull +url_multipart_config_title=Valores por Defecto para Petici\u00F3n HTTP Multipart +use_expires=Usar cabecera 'Cache-Control/Expires' cuando se procesan peticiones GET +use_keepalive=Utilizar KeepAlive +use_multipart_for_http_post=Usar 'multipart/form-data' para HTTP POST +use_multipart_mode_browser=Cabeceras compatibles con navegadores +use_recording_controller=Utilizar Controlador Recording +user=Usuario +user_defined_test=Prueba Definida por el Usuario +user_defined_variables=Variables definidas por el Usuario +user_param_mod_help_note=(No cambie esto. En su lugar, modifique el archivo con ese nombre en el directorio /bin de JMeter) +user_parameters_table=Par\u00E1metros +user_parameters_title=Par\u00E1metros de Usuario +userdn=Nombre de Usuario +username=Nombre de Usuario +userpw=Contrase\u00F1a +value=Valor +var_name=Nombre de Referencia +variable_name_param=Nombre de variable(puede incluir variables y referencias a funci\u00F3n) +view_graph_tree_title=Ver \u00C1rbol Gr\u00E1fico +view_results_assertion_error=Error de aserci\u00F3n\: +view_results_assertion_failure=Fallo de aserci\u00F3n\: +view_results_assertion_failure_message=Mensaje de fallo de aserci\u00F3n\: +view_results_desc=Muestra los resultados de texto del muestreo en forma de \u00E1rbol +view_results_error_count=Conteo de error\: +view_results_fields=campos\: +view_results_in_table=Ver Resultados en \u00C1rbol +view_results_latency=Latencia\: +view_results_load_time=Tiempo de carga\: +view_results_render=Renderizador\: +view_results_render_html=HTML +view_results_render_html_embedded=HTML(descargar elementos embebidos) +view_results_render_json=JSON +view_results_render_text=Texto +view_results_render_xml=XML +view_results_request_headers=Cabeceras de petici\u00F3n\: +view_results_response_code=C\u00F3digo de respuesta\: +view_results_response_headers=Cabeceras de respuesta\: +view_results_response_message=Mensaje de respuesta\: +view_results_response_too_large_message=Respuesta muy larga a ser mostrada. Tama\u00F1o\: +view_results_response_partial_message=Principio del mensaje: +view_results_sample_count=Conteo de muestra\: +view_results_sample_start=Comienzo de muestra\: +view_results_search_pane=Panel de b\u00FAsqueda +view_results_size_in_bytes=Tama\u00F1o en bytes\: +view_results_tab_assertion=Resultado de la aserci\u00F3n +view_results_tab_request=Petici\u00F3n +view_results_tab_response=Datos de Respuesta +view_results_tab_sampler=Resultado del Muestreador +view_results_table_fields_key=Campo adicional +view_results_table_fields_value=Valor +view_results_table_headers_key=Cabecera de respuesta +view_results_table_headers_value=Valor +view_results_table_request_headers_key=Cabecera de petici\u00F3n +view_results_table_request_headers_value=Valor +view_results_table_request_http_cookie=Cookie +view_results_table_request_http_host=M\u00E1quina +view_results_table_request_http_method=M\u00E9todo +view_results_table_request_http_nohttp=No muestra HTTP +view_results_table_request_http_path=Ruta +view_results_table_request_http_port=Puerto +view_results_table_request_http_protocol=Protocolo +view_results_table_request_params_key=Nombre de par\u00E1metro +view_results_table_request_params_value=Valor +view_results_table_request_raw_nodata=No mostrar datos +view_results_table_request_tab_http=HTTP +view_results_table_request_tab_raw=En bruto +view_results_table_result_tab_parsed=Parseado +view_results_table_result_tab_raw=En bruto +view_results_thread_name=Nombre del hilo\: +view_results_title=Ver Resultados +view_results_tree_title=Ver \u00C1rbol de Resultados +warning=\u00A1Atenci\u00F3n\! +web_proxy_server_title=Servidor Proxy +web_request=Petici\u00F3n HTTP +web_server=Servidor Web +web_server_client=Implementaci\u00F3n del Cliente\: +web_server_domain=Nombre de Servidor o IP\: +web_server_port=Puerto\: +web_server_timeout_connect=Conexi\u00F3n\: +web_server_timeout_response=Respuesta\: +web_server_timeout_title=Timeout (milisegundos) +web_testing2_title=Petici\u00F3n HTTP HttpClient +web_testing_embedded_url_pattern=Las URLs embebidas deben coincidir a\: +web_testing_retrieve_images=Recuperar Todos los Recursos Empotrados de Archivos HTML +web_testing_source_ip=Direcci\u00F3n IP fuente\: +web_testing_title=Petici\u00F3n HTTP +webservice_proxy_host=Host Proxy +webservice_proxy_note=Si est\u00E1 seleccionado "Utilizar Proxy HTTP", pero no se proporciona host o puerto, el muestreador +webservice_proxy_note2=buscar\u00E1 opciones en la l\u00EDnea de comandos. Si no se proporcionan host o puerto +webservice_proxy_note3=all\u00ED, finalmente fallar\u00E1 silenciosamente. +webservice_proxy_port=Puerto Proxy +webservice_sampler_title=Petici\u00F3n WebService(SOAP) +webservice_soap_action=Acci\u00F3n SOAP +webservice_timeout=Timeout\: +webservice_use_proxy=Utilizar Proxy HTTP +while_controller_label=Condici\u00F3n (funci\u00F3n o variable) +while_controller_title=Controlador While +workbench_title=Banco de Trabajo +wsdl_helper_error=El WSDL no es v\u00E1lido, por favor compruebe la url. +wsdl_url=URL del WSDL +wsdl_url_error=El WSDL est\u00E1 vacio. +xml_assertion_title=Aserci\u00F3n XML +xml_download_dtds=Recuperar DTDs externos +xml_namespace_button=Utilizar NameSpaces +xml_tolerant_button=Parser XML/HTML Tolerante +xml_validate_button=Validar XML +xml_whitespace_button=Ignorar Espacios +xmlschema_assertion_label=Nombre de Archivo\: +xmlschema_assertion_title=Aserci\u00F3n de Esquema XML +xpath_assertion_button=Validar +xpath_assertion_check=Comprobar Expresi\u00F3n XPath +xpath_assertion_error=Error en XPath +xpath_assertion_failed=Expresi\u00F3n XPath Inv\u00E1lida +xpath_assertion_label=XPath +xpath_assertion_negate=True si nada coincide +xpath_assertion_option=Opciones para parsear XML +xpath_assertion_test=Aserci\u00F3n XPath +xpath_assertion_tidy=Prueba y ordena la entrada +xpath_assertion_title=Aserci\u00F3n XPath +xpath_assertion_valid=Expresi\u00F3n XPath V\u00E1lida +xpath_assertion_validation=Validar el XML contra el DTD +xpath_assertion_whitespace=Ignorar espacios +xpath_expression=Expresi\u00F3n XPath contra la que comparar +xpath_extractor_fragment=\u00BFRetornar el fragmento XPATH en el caso de contenido de texto? +xpath_extractor_query=Consulta XPath\: +xpath_extractor_title=Extractor XPath +xpath_file_file_name=Archivo XML del que obtener valores +xpath_tidy_quiet=Silencioso +xpath_tidy_report_errors=Reportar los errores +xpath_tidy_show_warnings=Mostrar advertencias +you_must_enter_a_valid_number=Debe introducir un n\u00FAmero v\u00E1lido +zh_cn=Chino (Simplificado) +zh_tw=Chino (Tradicional) diff --git a/src/core/org/apache/jmeter/resources/messages_fr.properties b/src/core/org/apache/jmeter/resources/messages_fr.properties new file mode 100644 index 00000000000..851611600ed --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_fr.properties @@ -0,0 +1,1317 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=A propos de JMeter +active_threads_tooltip=Unit\u00E9s actives +add=Ajouter +add_as_child=Ajouter en tant qu'enfant +add_from_clipboard=Ajouter depuis Presse-papier +add_from_suggested_excludes=Ajouter exclusions propos\u00E9es +add_parameter=Ajouter un param\u00E8tre +add_pattern=Ajouter un motif \: +add_test=Ajout +add_user=Ajouter un utilisateur +add_value=Ajouter valeur +addtest=Ajout +aggregate_graph=Graphique des statistiques +aggregate_graph_choose_color=Choisir couleur +aggregate_graph_choose_foreground_color=Couleur valeur +aggregate_graph_color_bar=Couleur \: +aggregate_graph_column=Colonne +aggregate_graph_column_selection=S\u00E9lection de colonnes par libell\u00E9 \: +aggregate_graph_column_settings=Param\u00E8tres colonne +aggregate_graph_columns_to_display=Colonnes \u00E0 afficher \: +aggregate_graph_dimension=Taille graphique +aggregate_graph_display=G\u00E9n\u00E9rer le graphique +aggregate_graph_draw_outlines=Bordure de barre ? +aggregate_graph_dynamic_size=Taille de graphique dynamique +aggregate_graph_font=Police \: +aggregate_graph_height=Hauteur \: +aggregate_graph_increment_scale=Intervalle \u00E9chelle \: +aggregate_graph_legend=L\u00E9gende +aggregate_graph_legend.placement.bottom=Bas +aggregate_graph_legend.placement.left=Gauche +aggregate_graph_legend.placement.right=Droite +aggregate_graph_legend.placement.top=Haut +aggregate_graph_legend_placement=Position \: +aggregate_graph_max_length_xaxis_label=Longueur maximum du libell\u00E9 de l'axe des abscisses \: +aggregate_graph_ms=Millisecondes +aggregate_graph_no_values_to_graph=Pas de valeurs pour le graphique +aggregate_graph_number_grouping=S\u00E9parateur de milliers ? +aggregate_graph_response_time=Temps de r\u00E9ponse +aggregate_graph_save=Enregistrer le graphique +aggregate_graph_save_table=Enregistrer le tableau de donn\u00E9es +aggregate_graph_save_table_header=Inclure l'ent\u00EAte du tableau +aggregate_graph_size=Taille \: +aggregate_graph_style=Style \: +aggregate_graph_sync_with_name=Synchroniser avec nom +aggregate_graph_tab_graph=Graphique +aggregate_graph_tab_settings=Param\u00E8tres +aggregate_graph_title=Graphique agr\u00E9g\u00E9 +aggregate_graph_title_group=Titre +aggregate_graph_use_group_name=Ajouter le nom du groupe aux libell\u00E9s +aggregate_graph_user_title=Titre du graphique \: +aggregate_graph_value_font=Police de la valeur \: +aggregate_graph_value_labels_vertical=Libell\u00E9 de valeurs vertical ? +aggregate_graph_width=Largeur \: +aggregate_graph_xaxis_group=Abscisses +aggregate_graph_yaxis_group=Ordonn\u00E9es (milli-secondes) +aggregate_graph_yaxis_max_value=Echelle maximum \: +aggregate_report=Rapport agr\u00E9g\u00E9 +aggregate_report_bandwidth=Ko/sec +aggregate_report_count=\# Echantillons +aggregate_report_error=Erreur +aggregate_report_error%=% Erreur +aggregate_report_max=Max +aggregate_report_median=M\u00E9diane +aggregate_report_min=Min +aggregate_report_rate=D\u00E9bit +aggregate_report_stddev=Ecart type +aggregate_report_total_label=TOTAL +aggregate_report_xx_pct1_line={0}% centile +aggregate_report_xx_pct2_line={0}% centile +aggregate_report_xx_pct3_line={0}% centile +ajp_sampler_title=Requ\u00EAte AJP/1.3 +als_message=Note \: Le parseur de log d'acc\u00E8s est g\u00E9n\u00E9rique et vous permet de se brancher \u00E0 +als_message2=votre propre parseur. Pour se faire, impl\u00E9menter le LogParser, ajouter le jar au +als_message3=r\u00E9pertoire /lib et entrer la classe (fichier .class) dans l'\u00E9chantillon (sampler). +analyze=En train d'analyser le fichier de donn\u00E9es +anchor_modifier_title=Analyseur de lien HTML +appearance=Apparence +argument_must_not_be_negative=L'argument ne peut pas \u00EAtre n\u00E9gatif \! +arguments_panel_title=Param\u00E8tres de commande +assertion_assume_success=Ignorer le statut +assertion_body_resp=Corps de r\u00E9ponse +assertion_code_resp=Code de r\u00E9ponse +assertion_contains=Contient (exp. r\u00E9guli\u00E8re) +assertion_equals=Est \u00E9gale \u00E0 (texte brut) +assertion_headers=Ent\u00EAtes de r\u00E9ponse +assertion_matches=Correspond \u00E0 (exp. r\u00E9guli\u00E8re) +assertion_message_resp=Message de r\u00E9ponse +assertion_network_size=R\u00E9ponse compl\u00E8te +assertion_not=Inverser +assertion_pattern_match_rules=Type de correspondance du motif +assertion_patterns_to_test=Motifs \u00E0 tester +assertion_resp_field=Section de r\u00E9ponse \u00E0 tester +assertion_resp_size_field=Taille \u00E0 v\u00E9rifier sur +assertion_substring=Contient (texte brut) +assertion_text_document=Document (texte) +assertion_text_resp=Texte de r\u00E9ponse +assertion_textarea_label=Assertions \: +assertion_title=Assertion R\u00E9ponse +assertion_url_samp=URL Echantillon +assertion_visualizer_title=R\u00E9cepteur d'assertions +attribute=Attribut \: +attribute_field=Attribut \: +attrs=Attributs +auth_base_url=URL de base +auth_manager_clear_per_iter=R\u00E9authentifier \u00E0 chaque it\u00E9ration ? +auth_manager_options=Options +auth_manager_title=Gestionnaire d'autorisation HTTP +auths_stored=Autorisations stock\u00E9es +average=Moyenne +average_bytes=Moy. octets +backend_listener=R\u00E9cepteur asynchrone +backend_listener_classname=Impl\u00E9mentation du r\u00E9cepteur asynchrone +backend_listener_paramtable=Param\u00E8tres +backend_listener_queue_size=Taille de la queue +bind=Connexion de l'unit\u00E9 +bouncy_castle_unavailable_message=Les jars de bouncycastle sont indisponibles, ajoutez les au classpath. +browse=Parcourir... +bsf_sampler_title=Echantillon BSF +bsf_script=Script \u00E0 lancer (variables\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsf_script_file=Fichier script \u00E0 lancer \: +bsf_script_language=Langage de script \: +bsf_script_parameters=Param\u00E8tres \u00E0 passer au script/fichier \: +bsh_assertion_script=Script (IO\: Failure[Message], Response. IN\: Response[Data|Code|Message|Headers], RequestHeaders, Sample[Label|rData]) +bsh_assertion_script_variables=Les variables suivantes sont d\u00E9finies pour le script \:\nEn lecture/\u00E9criture \: Failure, FailureMessage, SampleResult, vars, props, log.\nEn lecture seule \: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData, ctx +bsh_assertion_title=Assertion BeanShell +bsh_function_expression=Expression \u00E0 \u00E9valuer +bsh_sampler_title=Echantillon BeanShell +bsh_script=Script (voir ci-dessous pour les variables qui sont d\u00E9finies) +bsh_script_file=Fichier script \: +bsh_script_parameters=Param\u00E8tres (-> String Parameters et String []bsh.args) +bsh_script_reset_interpreter=R\u00E9initialiser l'interpr\u00E9teur bsh avant chaque appel +bsh_script_variables=Les variables suivantes sont d\u00E9finies pour le script \:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=Je suis occup\u00E9 \u00E0 tester, veuillez arr\u00EAter le test avant de changer le param\u00E8trage +cache_manager_size=Nombre maximum d'\u00E9l\u00E9ments dans le cache +cache_manager_title=Gestionnaire de cache HTTP +cache_session_id=Identifiant de session de cache ? +cancel=Annuler +cancel_exit_to_save=Il y a des \u00E9l\u00E9ments qui n'ont pas \u00E9t\u00E9 sauv\u00E9s. Voulez-vous enregistrer avant de sortir ? +cancel_new_from_template=Il y a des \u00E9l\u00E9ments qui n'ont pas \u00E9t\u00E9 sauv\u00E9s. Voulez-vous enregistrer avant de charger le mod\u00E8le ? +cancel_new_to_save=Il y a des \u00E9l\u00E9ments qui n'ont pas \u00E9t\u00E9 sauv\u00E9s. Voulez-vous enregistrer avant de nettoyer le plan de test ? +cancel_revert_project=Il y a des \u00E9l\u00E9ments qui n'ont pas \u00E9t\u00E9 sauv\u00E9s. Annuler les changements et revenir \u00E0 la derni\u00E8re sauvegarde du plan de test ? +change_parent=Changer le contr\u00F4leur +char_value=Caract\u00E8re num\u00E9rique Unicode (d\u00E9cimal or 0xhex) +check_return_code_title=V\u00E9rifier le code retour +choose_function=Choisir une fonction +choose_language=Choisir une langue +clear=Nettoyer +clear_all=Nettoyer tout +clear_cache_each_iteration=Vider le cache \u00E0 chaque it\u00E9ration ? +clear_cache_per_iter=Nettoyer le cache \u00E0 chaque it\u00E9ration ? +clear_cookies_per_iter=Nettoyer les cookies \u00E0 chaque it\u00E9ration ? +clipboard_node_read_error=Une erreur est survenue lors de la copie du noeud +close=Fermer +closeconnection=Fermer la connexion +column_delete_disallowed=Supprimer cette colonne n'est pas possible +column_number=Num\u00E9ro de colonne du fichier CSV | next | *alias +command_config_box_title=Commande \u00E0 ex\u00E9cuter +command_config_std_streams_title=Flux standard (fichiers) +command_field_title=Commande \: +compare=Comparaison +comparefilt=Filtre de comparaison +comparison_differ_content=Le contenu des r\u00E9ponses est diff\u00E9rent. +comparison_differ_time=La diff\u00E9rence du temps de r\u00E9ponse diff\u00E8re de plus de +comparison_invalid_node=Noeud invalide +comparison_regex_string=Expression r\u00E9guli\u00E8re +comparison_regex_substitution=Substitution +comparison_response_time=Temps de r\u00E9ponse \: +comparison_unit=ms +comparison_visualizer_title=R\u00E9cepteur d'assertions de comparaison +config_element=El\u00E9ment de configuration +config_save_settings=Configurer +configure_wsdl=Configurer +confirm=Confirmer +constant_throughput_timer_memo=Ajouter un d\u00E9lai entre les \u00E9chantillions pour obtenir un d\u00E9bit constant +constant_timer_delay=D\u00E9lai d'attente (en millisecondes) \: +constant_timer_memo=Ajouter un d\u00E9lai fixe entre les \u00E9chantillions de test +constant_timer_title=Compteur de temps fixe +content_encoding=Encodage contenu \: +controller=Contr\u00F4leur +cookie_implementation_choose=Impl\u00E9mentation \: +cookie_manager_policy=Politique des cookies \: +cookie_manager_title=Gestionnaire de cookies HTTP +cookie_options=Options +cookies_stored=Cookies stock\u00E9s +copy=Copier +counter_config_title=Compteur +counter_per_user=Suivre le compteur ind\u00E9pendamment pour chaque unit\u00E9 de test +counter_reset_per_tg_iteration=R\u00E9initialiser le compteur \u00E0 chaque it\u00E9ration du groupe d'unit\u00E9s +countlim=Limiter le nombre d'\u00E9l\u00E9ments retourn\u00E9s \u00E0 +critical_section_controller_label=Nom du verrou +critical_section_controller_title=Contr\u00F4leur Section critique +cssjquery_attribute=Attribut +cssjquery_tester_error=Une erreur s''est produite lors de l''\u00E9valuation de l''expression:{0}, erreur:{1} +cssjquery_impl=Impl\u00E9mentation CSS/JQuery\: +cssjquery_render_no_text=Les donn\u00E9es de r\u00E9ponse ne sont pas du texte. +cssjquery_tester_button_test=Tester +cssjquery_tester_field=S\u00E9lecteur\: +cssjquery_tester_title=Testeur CSS/JQuery +csvread_file_file_name=Fichier CSV pour obtenir les valeurs de | *alias +cut=Couper +cut_paste_function=Fonction de copier/coller de cha\u00EEne de caract\u00E8re +database_conn_pool_max_usage=Utilisation max pour chaque connexion\: +database_conn_pool_props=Pool de connexions \u221A\u2020 la base de donn\u221A\u00A9es +database_conn_pool_size=Nombre de Connexions dans le Pool\: +database_conn_pool_title=Valeurs par d\u221A\u00A9faut du Pool de connexions JDBC +database_driver_class=Classe du Driver\: +database_login_title=Valeurs par d\u221A\u00A9faut de la base de donn\u221A\u00A9es JDBC +database_sql_query_string=Requ\u00EAte SQL \: +database_sql_query_title=Requ\u00EAte SQL JDBC par d\u00E9faut +database_testing_title=Requ\u221A\u2122te JDBC +database_url=URL JDBC\: +database_url_jdbc_props=URL et driver JDBC de la base de donn\u221A\u00A9es +ddn=DN \: +de=Allemand +debug_off=D\u00E9sactiver le d\u00E9bogage +debug_on=Activer le d\u00E9bogage +default_parameters=Param\u00E8tres par d\u00E9faut +default_value_field=Valeur par d\u00E9faut \: +delay=D\u00E9lai avant d\u00E9marrage (secondes) \: +delayed_start=Cr\u00E9er les unit\u00E9s seulement quand n\u00E9cessaire +delete=Supprimer +delete_parameter=Supprimer le param\u00E8tre +delete_test=Suppression +delete_user=Supprimer l'utilisateur +deltest=Suppression +deref=D\u00E9r\u00E9f\u00E9rencement des alias +description=Description +detail=D\u00E9tail +directory_field_title=R\u00E9pertoire d'ex\u00E9cution \: +disable=D\u00E9sactiver +distribution_graph_title=Graphique de distribution (alpha) +distribution_note1=Ce graphique se mettra \u00E0 jour tous les 10 \u00E9chantillons +dn=Racine DN \: +dns_cache_manager_title=Gestionnaire de cache DNS +dns_hostname_or_ip=Nom de machine ou adresse IP +dns_servers=Serveurs DNS +domain=Domaine \: +done=Fait +down=Descendre +duplicate=Dupliquer +duration=Dur\u00E9e (secondes) \: +duration_assertion_duration_test=Dur\u00E9e maximale \u00E0 v\u00E9rifier +duration_assertion_failure=L''op\u00E9ration a dur\u00E9e trop longtemps\: cela a pris {0} millisecondes, mais n''aurait pas d\u00FB durer plus de {1} millisecondes. +duration_assertion_input_error=Veuillez entrer un entier positif valide. +duration_assertion_label=Dur\u00E9e en millisecondes \: +duration_assertion_title=Assertion Dur\u00E9e +edit=Editer +email_results_title=R\u00E9sultat d'email +en=Anglais +enable=Activer +encode=URL Encoder +encode?=Encodage +encoded_value=Valeur de l'URL encod\u00E9e +endtime=Date et heure de fin \: +entry_dn=Entr\u00E9e DN \: +entrydn=Entr\u00E9e DN +environment_panel_title=Variables d'environnement +eolbyte=Valeur byte de l'indicateur de fin de ligne (EOL)\: +error_indicator_tooltip=Affiche le nombre d'erreurs dans le journal(log), cliquer pour afficher la console. +error_loading_help=Erreur au chargement de la page d'aide +error_occurred=Une erreur est survenue +error_title=Erreur +es=Espagnol +escape_html_string=Cha\u00EEne d'\u00E9chappement +eval_name_param=Variable contenant du texte et r\u00E9f\u00E9rences de fonctions +evalvar_name_param=Nom de variable +example_data=Exemple de donn\u00E9e +example_title=Echantillon exemple +exit=Quitter +expand=D\u00E9plier +expected_return_code_title=Code retour attendu \: +expiration=Expiration +expression_field=Expression CSS/JQuery \: +field_name=Nom du champ +file=Fichier +file_already_in_use=Ce fichier est d\u00E9j\u00E0 utilis\u00E9 +file_visualizer_append=Concat\u00E9ner au fichier de donn\u00E9es existant +file_visualizer_auto_flush=Vider automatiquement apr\u00E8s chaque echantillon de donn\u00E9es +file_visualizer_browse=Parcourir... +file_visualizer_close=Fermer +file_visualizer_file_options=Options de fichier +file_visualizer_filename=Nom du fichier \: +file_visualizer_flush=Vider +file_visualizer_missing_filename=Aucun fichier de sortie sp\u00E9cifi\u00E9. +file_visualizer_open=Ouvrir... +file_visualizer_output_file=\u00C9crire les r\u00E9sultats dans un fichier ou lire les r\u00E9sultats depuis un fichier CSV / JTL +file_visualizer_submit_data=Inclure les donn\u00E9es envoy\u00E9es +file_visualizer_title=Rapporteur de fichier +file_visualizer_verbose=Sortie verbeuse +filename=Nom de fichier \: +follow_redirects=Suivre les redirect. +follow_redirects_auto=Rediriger automat. +font.sansserif=Sans Serif +font.serif=Serif +fontstyle.bold=Gras +fontstyle.italic=Italique +fontstyle.normal=Normal +foreach_controller_title=Contr\u00F4leur Pour chaque (ForEach) +foreach_end_index=Indice de fin de la boucle (inclus) +foreach_input=Pr\u00E9fixe de la variable d'entr\u00E9e \: +foreach_output=Nom de la variable de sortie \: +foreach_start_index=Indice de d\u00E9but de la boucle(exclus) +foreach_use_separator=Ajouter un soulign\u00E9 "_" avant le nombre ? +format=Format du nombre \: +fr=Fran\u00E7ais +ftp_binary_mode=Utiliser le mode binaire ? +ftp_get=R\u00E9cup\u00E9rer (get) +ftp_local_file=Fichier local \: +ftp_local_file_contents=Contenus fichier local \: +ftp_put=D\u00E9poser (put) +ftp_remote_file=Fichier distant \: +ftp_sample_title=Param\u00E8tres FTP par d\u00E9faut +ftp_save_response_data=Enregistrer le fichier dans la r\u00E9ponse ? +ftp_testing_title=Requ\u00EAte FTP +function_dialog_menu_item=Assistant de fonctions +function_helper_title=Assistant de fonctions +function_name_param=Nom de la fonction. Utilis\u00E9 pour stocker les valeurs \u00E0 utiliser ailleurs dans la plan de test +function_name_paropt=Nom de variable dans laquelle le r\u00E9sultat sera stock\u00E9 (optionnel) +function_params=Param\u00E8tres de la fonction +functional_mode=Mode de test fonctionnel +functional_mode_explanation=S\u00E9lectionner le mode de test fonctionnel uniquement si vous avez besoin\nd'enregistrer les donn\u00E9es re\u00E7ues du serveur dans un fichier \u00E0 chaque requ\u00EAte. \n\nS\u00E9lectionner cette option affecte consid\u00E9rablement les performances. +gaussian_timer_delay=D\u00E9lai de d\u00E9calage bas\u00E9 gaussian (en millisecondes) \: +gaussian_timer_memo=Ajoute un d\u00E9lai al\u00E9atoire avec une distribution gaussienne +gaussian_timer_range=D\u00E9viation (en millisecondes) \: +gaussian_timer_title=Compteur de temps al\u00E9atoire gaussien +generate=G\u00E9n\u00E9rer +generator=Nom de la classe g\u00E9n\u00E9ratrice +generator_cnf_msg=N'a pas p\u00FB trouver la classe g\u00E9n\u00E9ratrice. Assurez-vous que vous avez plac\u00E9 votre fichier jar dans le r\u00E9pertoire /lib +generator_illegal_msg=N'a pas p\u00FB acc\u00E9der \u00E0 la classes g\u00E9n\u00E9ratrice \u00E0 cause d'une IllegalAccessException. +generator_instantiate_msg=N'a pas p\u00FB cr\u00E9er une instance du parseur g\u00E9n\u00E9rateur. Assurez-vous que le g\u00E9n\u00E9rateur impl\u00E9mente l'interface Generator. +get_xml_from_file=Fichier avec les donn\u00E9es XML SOAP (remplace le texte ci-dessus) +get_xml_from_random=R\u00E9pertoire contenant les fichier(s) \: +graph_apply_filter=Appliquer le filtre +graph_choose_graphs=Graphique \u00E0 afficher +graph_full_results_title=Graphique de r\u00E9sultats complets +graph_pointshape_circle=Cercle +graph_pointshape_diamond=Diamant +graph_pointshape_none=Aucun +graph_pointshape_square=Carr\u00E9 +graph_pointshape_triangle=Triangle +graph_resp_time_interval_label=Interval (ms) \: +graph_resp_time_interval_reload=Appliquer l'interval +graph_resp_time_not_enough_data=Impossible de dessiner le graphique, pas assez de donn\u00E9es +graph_resp_time_series_selection=S\u00E9lection des \u00E9chantillons par libell\u00E9 \: +graph_resp_time_settings_line=Param\u00E9tres de la courbe +graph_resp_time_settings_pane=Param\u00E9tres du graphique +graph_resp_time_shape_label=Forme de la jonction \: +graph_resp_time_stroke_width=Largeur de ligne \: +graph_resp_time_title=Graphique \u00E9volution temps de r\u00E9ponses +graph_resp_time_title_label=Titre du graphique \: +graph_resp_time_xaxis_time_format=Formatage heure (SimpleDateFormat) \: +graph_results_average=Moyenne +graph_results_data=Donn\u00E9es +graph_results_deviation=Ecart type +graph_results_latest_sample=Dernier \u00E9chantillon +graph_results_median=M\u00E9diane +graph_results_ms=ms +graph_results_no_samples=Nombre d'\u00E9chantillons +graph_results_throughput=D\u00E9bit +graph_results_title=Graphique de r\u00E9sultats +grouping_add_separators=Ajouter des s\u00E9parateurs entre les groupes +grouping_in_controllers=Mettre chaque groupe dans un nouveau contr\u00F4leur +grouping_in_transaction_controllers=Mettre chaque groupe dans un nouveau contr\u00F4leur de transaction +grouping_mode=Grouper \: +grouping_no_groups=Ne pas grouper les \u00E9chantillons +grouping_store_first_only=Stocker le 1er \u00E9chantillon pour chaque groupe uniquement +header_manager_title=Gestionnaire d'ent\u00EAtes HTTP +headers_stored=Ent\u00EAtes stock\u00E9es +heap_dump=Cr\u00E9er une image disque de la m\u00E9moire (heap dump) +help=Aide +help_node=Quel est ce noeud ? +html_assertion_file=Ecrire un rapport JTidy dans un fichier +html_assertion_label=Assertion HTML +html_assertion_title=Assertion HTML +html_extractor_title=Extracteur CSS/JQuery +html_extractor_type=Impl\u00E9mentation de l'extracteur CSS/JQuery +html_parameter_mask=Masque de param\u00E8tre HTML (DEPRECATED) +http_implementation=Impl\u00E9mentation \: +http_response_code=Code de r\u00E9ponse HTTP +http_url_rewriting_modifier_title=Transcripteur d'URL HTTP +http_user_parameter_modifier=Modificateur de param\u00E8tre utilisateur HTTP +httpmirror_max_pool_size=Taille maximum du pool d'unit\u00E9s \: +httpmirror_max_queue_size=Taille maximum de la file d'attente \: +httpmirror_settings=Param\u00E8tres +httpmirror_title=Serveur HTTP miroir +id_prefix=Pr\u00E9fixe d'ID +id_suffix=Suffixe d'ID +if_controller_evaluate_all=Evaluer pour tous les fils ? +if_controller_expression=Interpr\u00E9ter la condition comme une expression +if_controller_label=Condition (d\u00E9faut Javascript) \: +if_controller_title=Contr\u00F4leur Si (If) +ignore_subcontrollers=Ignorer les sous-blocs de contr\u00F4leurs +include_controller=Contr\u00F4leur Inclusion +include_equals=Inclure \u00E9gale ? +include_path=Plan de test \u00E0 inclure +increment=Incr\u00E9ment \: +infinite=Infini +initial_context_factory=Fabrique de contexte initiale +insert_after=Ins\u00E9rer apr\u00E8s +insert_before=Ins\u00E9rer avant +insert_parent=Ins\u00E9rer en tant que parent +interleave_control_title=Contr\u00F4leur Interleave +intsum_param_1=Premier entier \u00E0 ajouter +intsum_param_2=Deuxi\u00E8me entier \u00E0 ajouter - les entier(s) suivants peuvent \u00EAtre ajout\u00E9(s) avec les arguments suivants. +invalid_data=Donn\u00E9e invalide +invalid_mail=Une erreur est survenue lors de l'envoi de l'email +invalid_mail_address=Une ou plusieurs adresse(s) invalide(s) ont \u00E9t\u00E9 d\u00E9tect\u00E9e(s) +invalid_mail_server=Le serveur de mail est inconnu (voir le fichier de journalisation JMeter) +invalid_variables=Variables invalides +iteration_counter_arg_1=TRUE, pour que chaque utilisateur ait son propre compteur, FALSE pour un compteur global +iterator_num=Nombre d'it\u00E9rations \: +ja=Japonais +jar_file=Fichiers .jar +java_request=Requ\u00EAte Java +java_request_defaults=Requ\u00EAte Java par d\u00E9faut +javascript_expression=Expression JavaScript \u00E0 \u00E9valuer +jexl_expression=Expression JEXL \u00E0 \u00E9valuer +jms_auth_required=Obligatoire +jms_bytes_message=Message binaire +jms_client_caption=Le client r\u00E9cepteur utilise MessageConsumer.receive () pour \u00E9couter les messages. +jms_client_caption2=MessageListener utilise l'interface onMessage(Message) pour \u00E9couter les nouveaux messages. +jms_client_id=ID du Client +jms_client_type=Client +jms_communication_style=Type de communication \: +jms_concrete_connection_factory=Fabrique de connexion +jms_config=Source du message \: +jms_config_title=Configuration JMS +jms_connection_factory=Fabrique de connexion +jms_correlation_title=Champs alternatifs pour la correspondance de message +jms_dest_setup=Evaluer +jms_dest_setup_dynamic=A chaque \u00E9chantillon +jms_dest_setup_static=Au d\u00E9marrage +jms_durable_subscription_id=ID d'abonnement durable +jms_expiration=Expiration (ms) +jms_file=Fichier +jms_initial_context_factory=Fabrique de connexion initiale +jms_itertions=Nombre d'\u00E9chantillons \u00E0 agr\u00E9ger +jms_jndi_defaults_title=Configuration JNDI par d\u00E9faut +jms_jndi_props=Propri\u00E9t\u00E9s JNDI +jms_map_message=Message Map +jms_message_title=Propri\u00E9t\u00E9s du message +jms_message_type=Type de message \: +jms_msg_content=Contenu +jms_object_message=Message Object +jms_point_to_point=Requ\u00EAte JMS Point-\u00E0-point +jms_priority=Priorit\u00E9 (0-9) +jms_properties=Propri\u00E9t\u00E9s JMS +jms_properties_name=Nom +jms_properties_title=Propri\u00E9t\u00E9s JMS +jms_properties_type=Classe de la Valeur +jms_properties_value=Valeur +jms_props=Propri\u00E9t\u00E9s JMS +jms_provider_url=URL du fournisseur +jms_publisher=Requ\u00EAte JMS Publication +jms_pwd=Mot de passe +jms_queue=File +jms_queue_connection_factory=Fabrique QueueConnection +jms_queueing=Ressources JMS +jms_random_file=Dossier contenant les fichiers al\u00E9atoires +jms_read_response=Lire la r\u00E9ponse +jms_receive_queue=Nom JNDI de la file d'attente Receive +jms_request=Requ\u00EAte seule +jms_requestreply=Requ\u00EAte R\u00E9ponse +jms_sample_title=Requ\u00EAte JMS par d\u00E9faut +jms_selector=S\u00E9lecteur JMS +jms_send_queue=Nom JNDI de la file d'attente Request +jms_separator=S\u00E9parateur +jms_stop_between_samples=Arr\u00EAter entre les \u00E9chantillons ? +jms_subscriber_on_message=Utiliser MessageListener.onMessage() +jms_subscriber_receive=Utiliser MessageConsumer.receive() +jms_subscriber_title=Requ\u00EAte JMS Abonnement +jms_testing_title=Messagerie Request +jms_text_area=Message texte ou Message Objet s\u00E9rialis\u00E9 en XML par XStream +jms_text_message=Message texte +jms_timeout=D\u00E9lai (ms) +jms_topic=Destination +jms_use_auth=Utiliser l'authentification ? +jms_use_file=Depuis un fichier +jms_use_non_persistent_delivery=Utiliser un mode de livraison non persistant ? +jms_use_properties_file=Utiliser le fichier jndi.properties +jms_use_random_file=Fichier al\u00E9atoire +jms_use_req_msgid_as_correlid=Utiliser l'ID du message Request +jms_use_res_msgid_as_correlid=Utiliser l'ID du message Response +jms_use_text=Zone de texte (ci-dessous) +jms_user=Utilisateur +jndi_config_title=Configuration JNDI +jndi_lookup_name=Interface remote +jndi_lookup_title=Configuration Lookup JNDI +jndi_method_button_invoke=Invoquer +jndi_method_button_reflect=R\u00E9flection +jndi_method_home_name=Nom de la m\u00E9thode home +jndi_method_home_parms=Param\u00E8tres de la m\u00E9thode home +jndi_method_name=Configuration m\u00E9thode +jndi_method_remote_interface_list=Interfaces remote +jndi_method_remote_name=Nom m\u00E9thodes remote +jndi_method_remote_parms=Param\u00E8tres m\u00E9thode remote +jndi_method_title=Configuration m\u00E9thode remote +jndi_testing_title=Requ\u00EAte JNDI +jndi_url_jndi_props=Propri\u00E9t\u00E9s JNDI +junit_append_error=Concat\u00E9ner les erreurs d'assertion +junit_append_exception=Concat\u00E9ner les exceptions d'ex\u00E9cution +junit_constructor_error=Impossible de cr\u00E9er une instance de la classe +junit_constructor_string=Libell\u00E9 de cha\u00EEne Constructeur +junit_create_instance_per_sample=Cr\u00E9er une nouvelle instance pour chaque \u00E9chantillon +junit_do_setup_teardown=Ne pas appeler setUp et tearDown +junit_error_code=Code d'erreur +junit_error_default_msg=Une erreur inattendue est survenue +junit_error_msg=Message d'erreur +junit_failure_code=Code d'\u00E9chec +junit_failure_default_msg=Test \u00E9chou\u00E9 +junit_failure_msg=Message d'\u00E9chec +junit_junit4=Rechercher les annotations JUnit 4 (au lieu de JUnit 3) +junit_pkg_filter=Filtre de paquets +junit_request=Requ\u00EAte JUnit +junit_request_defaults=Requ\u00EAte par d\u00E9faut JUnit +junit_success_code=Code de succ\u00E8s +junit_success_default_msg=Test r\u00E9ussi +junit_success_msg=Message de succ\u00E8s +junit_test_config=Param\u00E8tres Test JUnit +junit_test_method=M\u00E9thode de test +ldap_argument_list=Liste d'arguments LDAP +ldap_connto=D\u00E9lai d'attente de connexion (millisecondes) +ldap_parse_results=Examiner les r\u00E9sultats de recherche ? +ldap_sample_title=Requ\u00EAte LDAP par d\u00E9faut +ldap_search_baseobject=Effectuer une recherche 'baseobject' +ldap_search_onelevel=Effectuer une recherche 'onelevel' +ldap_search_subtree=Effectuer une recherche 'subtree' +ldap_secure=Utiliser le protocole LDAP s\u00E9curis\u00E9 (ldaps) ? +ldap_testing_title=Requ\u00EAte LDAP +ldapext_sample_title=Requ\u00EAte LDAP \u00E9tendue par d\u00E9faut +ldapext_testing_title=Requ\u00EAte LDAP \u00E9tendue +library=Librairie +load=Charger +load_wsdl=Charger WSDL +log_errors_only=Erreurs +log_file=Emplacement du fichier de journal (log) +log_function_comment=Commentaire (facultatif) +log_function_level=Niveau de journalisation (INFO par d\u00E9faut), OUT ou ERR +log_function_string=Cha\u00EEne \u00E0 tracer +log_function_string_ret=Cha\u00EEne \u00E0 tracer (et \u00E0 retourner) +log_function_throwable=Texte de l'exception Throwable (optionnel) +log_only=Uniquement \: +log_parser=Nom de la classe de parseur des journaux (log) +log_parser_cnf_msg=N'a pas p\u00FB trouver cette classe. Assurez-vous que vous avez plac\u00E9 votre fichier jar dans le r\u00E9pertoire /lib +log_parser_illegal_msg=N'a pas p\u00FB acc\u00E9der \u00E0 la classe \u00E0 cause d'une exception IllegalAccessException. +log_parser_instantiate_msg=N'a pas p\u00FB cr\u00E9er une instance du parseur de log. Assurez-vous que le parseur impl\u00E9mente l'interface LogParser. +log_sampler=Echantillon Journaux d'acc\u00E8s Tomcat +log_success_only=Succ\u00E8s +logic_controller_title=Contr\u00F4leur Simple +login_config=Configuration Identification +login_config_element=Configuration Identification +longsum_param_1=Premier long \u221A\u2020 ajouter +longsum_param_2=Second long \u221A\u2020 ajouter - les autres longs pourront \u221A\u2122tre cumul\u221A\u00A9s en ajoutant d'autres arguments. +loop_controller_title=Contr\u00F4leur Boucle +looping_control=Contr\u00F4le de boucle +lower_bound=Borne Inf\u00E9rieure +mail_reader_account=Nom utilisateur \: +mail_reader_all_messages=Tous +mail_reader_delete=Supprimer les messages du serveur +mail_reader_folder=Dossier \: +mail_reader_header_only=R\u00E9cup\u00E9rer seulement les ent\u00EAtes +mail_reader_num_messages=Nombre de message \u00E0 r\u00E9cup\u00E9rer \: +mail_reader_password=Mot de passe \: +mail_reader_port=Port (optionnel) \: +mail_reader_server=Serveur \: +mail_reader_server_type=Protocole (ex. pop3, imaps) \: +mail_reader_storemime=Stocker le message en utilisant MIME (brut) +mail_reader_title=Echantillon Lecteur d'email +mail_sent=Email envoy\u00E9 avec succ\u00E8s +mailer_addressees=Destinataire(s) \: +mailer_attributes_panel=Attributs de courrier +mailer_connection_security=S\u00E9curit\u00E9 connexion \: +mailer_error=N'a pas p\u00FB envoyer l'email. Veuillez corriger les erreurs de saisie. +mailer_failure_limit=Limite d'\u00E9chec \: +mailer_failure_subject=Sujet Echec \: +mailer_failures=Nombre d'\u00E9checs \: +mailer_from=Exp\u00E9diteur \: +mailer_host=Serveur \: +mailer_login=Identifiant \: +mailer_msg_title_error=Erreur +mailer_msg_title_information=Information +mailer_password=Mot de passe \: +mailer_port=Port \: +mailer_string=Notification d'email +mailer_success_limit=Limite de succ\u00E8s \: +mailer_success_subject=Sujet Succ\u00E8s \: +mailer_test_mail=Tester email +mailer_title_message=Message +mailer_title_settings=Param\u00E8tres +mailer_title_smtpserver=Serveur SMTP +mailer_visualizer_title=R\u00E9cepteur Notification Email +match_num_field=R\u00E9cup\u00E9rer la Ni\u00E8me corresp. (0 \: Al\u00E9atoire) \: +max=Maximum \: +maximum_param=La valeur maximum autoris\u00E9e pour un \u00E9cart de valeurs +md5hex_assertion_failure=Erreur de v\u00E9rification de la somme MD5 \: obtenu {0} mais aurait d\u00FB \u00EAtre {1} +md5hex_assertion_label=MD5Hex +md5hex_assertion_md5hex_test=MD5Hex \u00E0 v\u00E9rifier +md5hex_assertion_title=Assertion MD5Hex +mechanism=M\u00E9canisme +memory_cache=Cache de m\u00E9moire +menu_assertions=Assertions +menu_close=Fermer +menu_collapse_all=R\u00E9duire tout +menu_config_element=Configurations +menu_edit=Editer +menu_expand_all=Etendre tout +menu_fragments=Fragment d'\u00E9l\u00E9ments +menu_generative_controller=Echantillons +menu_listener=R\u00E9cepteurs +menu_logger_panel=Afficher la console +menu_logic_controller=Contr\u00F4leurs Logiques +menu_merge=Fusionner... +menu_modifiers=Modificateurs +menu_non_test_elements=El\u00E9ments hors test +menu_open=Ouvrir... +menu_post_processors=Post-Processeurs +menu_pre_processors=Pr\u00E9-Processeurs +menu_response_based_modifiers=Modificateurs bas\u00E9s sur la r\u00E9ponse +menu_search=Rechercher +menu_search_reset=Effacer la recherche +menu_tables=Table +menu_threads=Moteurs d'utilisateurs +menu_timer=Compteurs de temps +menu_toolbar=Barre d'outils +metadata=M\u00E9ta-donn\u00E9es +method=M\u00E9thode \: +mimetype=Type MIME +minimum_param=La valeur minimale autoris\u00E9e pour l'\u00E9cart de valeurs +minute=minute +modddn=Ancienne valeur +modification_controller_title=Contr\u00F4leur Modification +modification_manager_title=Gestionnaire Modification +modify_test=Modification +modtest=Modification +module_controller_module_to_run=Module \u00E0 ex\u00E9cuter \: +module_controller_title=Contr\u00F4leur Module +module_controller_warning=Ne peut pas trouver le module \: +monitor_equation_active=Activit\u00E9 \: (occup\u00E9e/max) > 25% +monitor_equation_dead=Mort \: pas de r\u00E9ponse +monitor_equation_healthy=Sant\u00E9 \: (occup\u00E9e/max) < 25% +monitor_equation_load=Charge \: ((occup\u00E9e / max) * 50) + ((m\u00E9moire utilis\u00E9e / m\u00E9moire maximum) * 50) +monitor_equation_warning=Attention \: (occup\u00E9/max) > 67% +monitor_health_tab_title=Sant\u00E9 +monitor_health_title=Moniteur de connecteurs +monitor_is_title=Utiliser comme moniteur +monitor_label_prefix=Pr\u00E9fixe de connecteur \: +monitor_label_right_active=Actif +monitor_label_right_dead=Mort +monitor_label_right_healthy=Sant\u00E9 +monitor_label_right_warning=Attention +monitor_legend_health=Sant\u00E9 +monitor_legend_load=Charge +monitor_legend_memory_per=M\u00E9moire % (utilis\u00E9e/total) +monitor_legend_thread_per=Unit\u00E9 % (occup\u00E9/max) +monitor_performance_servers=Serveurs +monitor_performance_tab_title=Performance +monitor_performance_title=Graphique de performance +name=Nom \: +new=Nouveau +newdn=Nouveau DN +next=Suivant +no=Norv\u00E9gien +notify_child_listeners_fr=Notifier les r\u00E9cepteurs fils des \u00E9chantillons filtr\u00E9s +number_of_threads=Nombre d'unit\u00E9s (utilisateurs) \: +obsolete_test_element=Cet \u00E9l\u00E9ment de test est obsol\u00E8te +once_only_controller_title=Contr\u00F4leur Ex\u00E9cution unique +opcode=Code d'op\u00E9ration +open=Ouvrir... +option=Options +optional_tasks=T\u00E2ches optionnelles +paramtable=Envoyer les param\u00E8tres avec la requ\u00EAte \: +password=Mot de passe \: +paste=Coller +paste_insert=Coller ins\u00E9rer +path=Chemin \: +path_extension_choice=Extension de chemin (utiliser ";" comme separateur) +path_extension_dont_use_equals=Ne pas utiliser \u00E9gale dans l'extension de chemin (Compatibilit\u00E9 Intershop Enfinity) +path_extension_dont_use_questionmark=Ne pas utiliser le point d'interrogation dans l'extension du chemin (Compatiblit\u00E9 Intershop Enfinity) +patterns_to_exclude=URL \: motifs \u00E0 exclure +patterns_to_include=URL \: motifs \u00E0 inclure +pkcs12_desc=Clef PKCS 12 (*.p12) +pl=Polonais +poisson_timer_delay=D\u00E9lai de d\u00E9calage bas\u00E9 sur la loi de poisson (en millisecondes) \: +poisson_timer_memo=Ajoute un d\u00E9lai al\u00E9atoire avec une distribution de type Poisson +poisson_timer_range=D\u00E9viation (en millisecondes) \: +poisson_timer_title=Compteur de temps al\u00E9atoire selon la loi de Poisson +port=Port \: +post_as_parameters=Param\u00E8tres +post_body=Corps de la requ\u00EAte +post_body_raw=Donn\u00E9es de la requ\u00EAte +post_thread_group_title=Groupe d'unit\u00E9s de fin +previous=Pr\u00E9c\u00E9dent +property_as_field_label={0}\: +property_default_param=Valeur par d\u00E9faut +property_edit=Editer +property_editor.value_is_invalid_message=Le texte que vous venez d'entrer n'a pas une valeur valide pour cette propri\u00E9t\u00E9.\nLa propri\u00E9t\u00E9 va revenir \u00E0 sa valeur pr\u00E9c\u00E9dente. +property_editor.value_is_invalid_title=Texte saisi invalide +property_name_param=Nom de la propri\u00E9t\u00E9 +property_returnvalue_param=Revenir \u00E0 la valeur originale de la propri\u00E9t\u00E9 (d\u00E9faut non) ? +property_tool_tip={0}\: {1} +property_undefined=Non d\u00E9fini +property_value_param=Valeur de propri\u00E9t\u00E9 +property_visualiser_title=Afficheur de propri\u00E9t\u00E9s +protocol=Protocole [http] \: +protocol_java_border=Classe Java +protocol_java_classname=Nom de classe \: +protocol_java_config_tile=Configurer \u00E9chantillon Java +protocol_java_test_title=Test Java +provider_url=Provider URL +proxy_assertions=Ajouter une Assertion R\u00E9ponse +proxy_cl_error=Si un serveur proxy est sp\u00E9cifi\u00E9, h\u00F4te et port doivent \u00EAtre donn\u00E9 +proxy_cl_wrong_target_cl=Le contr\u00F4leur cible est configur\u00E9 en mode "Utiliser un contr\u00F4leur enregistreur" \nmais aucun contr\u00F4leur de ce type n'existe, assurez vous de l'ajouter comme fils \nde Groupe d'unit\u00E9s afin de pouvoir d\u00E9marrer l'enregisteur +proxy_content_type_exclude=Exclure \: +proxy_content_type_filter=Filtre de type de contenu +proxy_content_type_include=Inclure \: +proxy_daemon_bind_error=Impossible de lancer le serveur proxy, le port est d\u00E9j\u00E0 utilis\u00E9. Choisissez un autre port. +proxy_daemon_error=Impossible de lancer le serveur proxy, voir le journal pour plus de d\u00E9tails +proxy_daemon_error_from_clipboard=depuis le presse-papier +proxy_daemon_error_not_retrieve=Impossible d'ajouter +proxy_daemon_error_read_args=Impossible de lire les arguments depuis le presse-papiers \: +proxy_daemon_msg_check_details=Svp, v\u00E9rifier les d\u00E9tails ci-dessous lors de l'installation du certificat dans le navigateur +proxy_daemon_msg_created_in_bin=cr\u00E9\u00E9 dans le r\u00E9pertoire bin de JMeter +proxy_daemon_msg_install_as_in_doc=Vous pouvez l'installer en suivant les instructions de la documentation Component Reference (voir Installing the JMeter CA certificate for HTTPS recording paragraph) +proxy_daemon_msg_rootca_cert=Certificat AC ra\u00E7ine \: +proxy_domains=Domaines HTTPS \: +proxy_domains_dynamic_mode_tooltip=Liste de noms de domaine pour les url HTTPS, ex. jmeter.apache.org ou les domaines wildcard comme *.apache.org. Utiliser la virgule comme s\u00E9parateur. +proxy_domains_dynamic_mode_tooltip_java6=Pour activer ce champ, utiliser un environnement d'ex\u00E9cution Java 7+ +proxy_general_settings=Param\u00E8tres g\u00E9n\u00E9raux +proxy_headers=Capturer les ent\u00EAtes HTTP +proxy_regex=Correspondance des variables par regex ? +proxy_sampler_settings=Param\u00E8tres Echantillon HTTP +proxy_sampler_type=Type \: +proxy_separators=Ajouter des s\u00E9parateurs +proxy_settings_port_error_digits=Seuls les chiffres sont autoris\u00E9s. +proxy_settings_port_error_invalid_data=Donn\u00E9es invalides +proxy_target=Contr\u00F4leur Cible \: +proxy_test_plan_content=Param\u00E8tres du plan de test +proxy_title=Enregistreur script de test HTTP(S) +pt_br=Portugais (Br\u00E9sil) +ramp_up=Dur\u00E9e de mont\u00E9e en charge (en secondes) \: +random_control_title=Contr\u00F4leur Al\u00E9atoire +random_order_control_title=Contr\u00F4leur d'Ordre al\u00E9atoire +random_string_chars_to_use=Caract\u00E8res \u00E0 utiliser pour la g\u00E9n\u00E9ration de la cha\u00EEne al\u00E9atoire +random_string_length=Longueur de cha\u00EEne al\u00E9atoire +read_response_message='Lire la r\u00E9ponse SOAP' n'est pas coch\u00E9. Pour voir la r\u00E9ponse, cocher la case dans la requ\u00EAte WebService svp. +read_response_note=Si 'Lire la r\u00E9ponse SOAP' n'est pas coch\u00E9, la requ\u00EAte WebService ne lira pas la r\u00E9ponse. +read_response_note2=et ne remplira pas l'objet SampleResult. Cela am\u00E9liore les performances, mais signifie que +read_response_note3=le contenu de la r\u00E9ponse ne sera pas tra\u00E7\u00E9. +read_soap_response=Lire la r\u00E9ponse SOAP +realm=Univers (realm) +record_controller_title=Contr\u00F4leur Enregistreur +redo=R\u00E9tablir +ref_name_field=Nom de r\u00E9f\u00E9rence \: +regex_extractor_title=Extracteur Expression r\u00E9guli\u00E8re +regex_field=Expression r\u00E9guli\u00E8re \: +regex_params_names_field=Num\u00E9ro du groupe de la Regex pour les noms des param\u00E8tres +regex_params_ref_name_field=Nom de la r\u00E9f\u00E9rence de la Regex +regex_params_title=Param\u00E8tres utilisateurs bas\u00E9s sur RegEx +regex_params_values_field=Num\u00E9ro du groupe de la Regex pour les valeurs des param\u00E8tres +regex_source=Port\u00E9e +regex_src_body=Corps +regex_src_body_as_document=Corps en tant que Document +regex_src_body_unescaped=Corps (non \u00E9chapp\u00E9) +regex_src_hdrs=Ent\u00EAtes (R\u00E9ponse) +regex_src_hdrs_req=Ent\u00EAtes (Requ\u00EAte) +regex_src_url=URL +regexfunc_param_1=Expression r\u00E9guli\u00E8re utilis\u00E9e pour chercher les r\u00E9sultats de la requ\u00EAte pr\u00E9c\u00E9dente. +regexfunc_param_2=Canevas pour la ch\u00EEne de caract\u00E8re de remplacement, utilisant des groupes d'expressions r\u00E9guli\u00E8res. Le format est $[group]$. Exemple $1$. +regexfunc_param_3=Quelle correspondance utiliser. Un entier 1 ou plus grand, RAND pour indiquer que JMeter doit choisir al\u00E9atoirement , A d\u00E9cimal, ou ALL indique que toutes les correspondances doivent \u00EAtre utilis\u00E9es +regexfunc_param_4=Entre le texte. Si ALL est s\u00E9lectionn\u00E9, l'entre-texte sera utilis\u00E9 pour g\u00E9n\u00E9rer les r\u00E9sultats ([""]) +regexfunc_param_5=Text par d\u00E9faut. Utilis\u00E9 \u00E0 la place du canevas si l'expression r\u00E9guli\u00E8re ne trouve pas de correspondance +regexfunc_param_7=Variable en entr\u221A\u00A9e contenant le texte \u221A\u2020 parser ([\u221A\u00A9chantillon pr\u221A\u00A9c\u221A\u00A9dent]) +regexp_render_no_text=Les donn\u00E9es de r\u00E9ponse ne sont pas du texte. +regexp_tester_button_test=Tester +regexp_tester_field=Expression r\u00E9guli\u00E8re \: +regexp_tester_title=Testeur de RegExp +remote_error_init=Erreur lors de l'initialisation du serveur distant +remote_error_starting=Erreur lors du d\u221A\u00A9marrage du serveur distant +remote_exit=Sortie distante +remote_exit_all=Sortie distante de tous +remote_shut=Extinction \u00E0 distance +remote_shut_all=Extinction \u00E0 distance de tous +remote_start=D\u00E9marrage distant +remote_start_all=D\u00E9marrage distant de tous +remote_stop=Arr\u00EAt distant +remote_stop_all=Arr\u00EAt distant de tous +remove=Supprimer +remove_confirm_msg=Etes-vous s\u00FBr de vouloir supprimer ce(s) \u00E9l\u00E9ment(s) ? +remove_confirm_title=Confirmer la suppression ? +rename=Renommer une entr\u00E9e +report=Rapport +report_bar_chart=Graphique \u221A\u2020 barres +report_bar_graph_url=URL +report_base_directory=R\u221A\u00A9pertoire de Base +report_chart_caption=L\u221A\u00A9gende du graph +report_chart_x_axis=Axe X +report_chart_x_axis_label=Libell\u221A\u00A9 de l'Axe X +report_chart_y_axis=Axe Y +report_chart_y_axis_label=Libell\u221A\u00A9 de l'Axe Y +report_line_graph=Graphique Lin\u221A\u00A9aire +report_line_graph_urls=Inclure les URLs +report_output_directory=R\u221A\u00A9pertoire de sortie du rapport +report_page=Page de Rapport +report_page_element=Page Element +report_page_footer=Pied de page +report_page_header=Ent\u221A\u2122te de Page +report_page_index=Cr\u221A\u00A9er la Page d'Index +report_page_intro=Page d'Introduction +report_page_style_url=Url de la feuille de style +report_page_title=Titre de la Page +report_pie_chart=Camembert +report_plan=Plan du rapport +report_select=Selectionner +report_summary=Rapport r\u221A\u00A9sum\u221A\u00A9 +report_table=Table du Rapport +report_writer=R\u221A\u00A9dacteur du Rapport +report_writer_html=R\u221A\u00A9dacteur de rapport HTML +request_data=Donn\u00E9e requ\u00EAte +reset_gui=R\u00E9initialiser l'\u00E9l\u00E9ment +response_save_as_md5=R\u00E9ponse en empreinte MD5 +restart=Red\u00E9marrer +resultaction_title=Op\u00E9rateur R\u00E9sultats Action +resultsaver_addtimestamp=Ajouter un timestamp +resultsaver_errors=Enregistrer seulement les r\u00E9ponses en \u00E9checs +resultsaver_numberpadlen=Taille minimale du num\u00E9ro de s\u00E9quence +resultsaver_prefix=Pr\u00E9fixe du nom de fichier \: +resultsaver_skipautonumber=Ne pas ajouter de nombre au pr\u00E9fixe +resultsaver_skipsuffix=Ne pas ajouter de suffixe +resultsaver_success=Enregistrer seulement les r\u00E9ponses en succ\u00E8s +resultsaver_title=Sauvegarder les r\u00E9ponses vers un fichier +resultsaver_variable=Nom de variable \: +retobj=Retourner les objets +return_code_config_box_title=Configuration du code retour +reuseconnection=R\u00E9-utiliser la connexion +revert_project=Annuler les changements +revert_project?=Annuler les changements sur le projet ? +root=Racine +root_title=Racine +run=Lancer +running_test=Lancer test +runtime_controller_title=Contr\u00F4leur Dur\u00E9e d'ex\u00E9cution +runtime_seconds=Temps d'ex\u00E9cution (secondes) \: +sample_result_save_configuration=Sauvegarder la configuration de la sauvegarde des \u00E9chantillons +sample_scope=Appliquer sur +sample_scope_all=L'\u00E9chantillon et ses ressources li\u00E9es +sample_scope_children=Les ressources li\u00E9es +sample_scope_parent=L'\u00E9chantillon +sample_scope_variable=Une variable \: +sampler_label=Libell\u00E9 +sampler_on_error_action=Action \u00E0 suivre apr\u00E8s une erreur d'\u00E9chantillon +sampler_on_error_continue=Continuer +sampler_on_error_start_next_loop=D\u00E9marrer it\u00E9ration suivante +sampler_on_error_stop_test=Arr\u00EAter le test +sampler_on_error_stop_test_now=Arr\u00EAter le test imm\u00E9diatement +sampler_on_error_stop_thread=Arr\u00EAter l'unit\u00E9 +save=Enregistrer le plan de test +save?=Enregistrer ? +save_all_as=Enregistrer le plan de test sous... +save_as=Enregistrer sous... +save_as_error=Au moins un \u00E9l\u00E9ment doit \u00EAtre s\u00E9lectionn\u00E9 \! +save_as_image=Enregistrer en tant qu'image sous... +save_as_image_all=Enregistrer l'\u00E9cran en tant qu'image... +save_as_test_fragment=Enregistrer comme Fragment de Test +save_as_test_fragment_error=Au moins un \u00E9l\u00E9ment ne peut pas \u00EAtre plac\u00E9 sous un Fragment de Test +save_assertionresultsfailuremessage=Messages d'erreur des assertions +save_assertions=R\u00E9sultats des assertions (XML) +save_asxml=Enregistrer au format XML +save_bytes=Nombre d'octets +save_code=Code de r\u00E9ponse HTTP +save_connecttime=Temps \u00E9tablissement connexion +save_datatype=Type de donn\u00E9es +save_encoding=Encodage +save_fieldnames=Libell\u00E9 des colonnes (CSV) +save_filename=Nom de fichier de r\u00E9ponse +save_graphics=Enregistrer le graphique +save_hostname=Nom d'h\u00F4te +save_idletime=Temps d'inactivit\u00E9 +save_label=Libell\u00E9 +save_latency=Latence +save_message=Message de r\u00E9ponse +save_overwrite_existing_file=Le fichier s\u00E9lectionn\u00E9 existe d\u00E9j\u00E0, voulez-vous l'\u00E9craser ? +save_requestheaders=Ent\u00EAtes de requ\u00EAte (XML) +save_responsedata=Donn\u00E9es de r\u00E9ponse (XML) +save_responseheaders=Ent\u00EAtes de r\u00E9ponse (XML) +save_samplecount=Nombre d'\u00E9chantillon et d'erreur +save_samplerdata=Donn\u00E9es d'\u00E9chantillon (XML) +save_subresults=Sous r\u00E9sultats (XML) +save_success=Succ\u00E8s +save_threadcounts=Nombre d'unit\u00E9s actives +save_threadname=Nom d'unit\u00E9 +save_time=Temps \u00E9coul\u00E9 +save_timestamp=Horodatage +save_url=URL +save_workbench=Sauvegarder le plan de travail +sbind=Simple connexion/d\u00E9connexion +scheduler=Programmateur de d\u00E9marrage +scheduler_configuration=Configuration du programmateur +scope=Port\u00E9e +search=Rechercher +search_base=Base de recherche +search_expand=Rechercher & D\u00E9plier +search_filter=Filtre de recherche +search_test=Recherche +search_text_button_close=Fermer +search_text_button_find=Rechercher +search_text_button_next=Suivant +search_text_chkbox_case=Consid\u00E9rer la casse +search_text_chkbox_regexp=Exp. reguli\u00E8re +search_text_field=Rechercher \: +search_text_msg_not_found=Texte non trouv\u00E9 +search_text_title_not_found=Pas trouv\u00E9 +search_tree_title=Rechercher dans l'arbre +searchbase=Base de recherche +searchfilter=Filtre de recherche +searchtest=Recherche +second=seconde +secure=S\u00E9curis\u00E9 \: +send_file=Envoyer un fichier avec la requ\u00EAte \: +send_file_browse=Parcourir... +send_file_filename_label=Chemin du fichier \: +send_file_mime_label=Type MIME \: +send_file_param_name_label=Nom du param\u00E8tre \: +server=Nom ou IP du serveur \: +servername=Nom du serveur \: +session_argument_name=Nom des arguments de la session +setup_thread_group_title=Groupe d'unit\u00E9s de d\u00E9but +should_save=Vous devez enregistrer le plan de test avant de le lancer. \nSi vous utilisez des fichiers de donn\u00E9es (i.e. Source de donn\u00E9es CSV ou la fonction _StringFromFile), \nalors c'est particuli\u00E8rement important d'enregistrer d'abord votre script de test. \nVoulez-vous enregistrer maintenant votre plan de test ? +shutdown=Eteindre +simple_config_element=Configuration Simple +simple_data_writer_title=Enregistreur de donn\u00E9es +size_assertion_comparator_error_equal=est \u00E9gale \u00E0 +size_assertion_comparator_error_greater=est plus grand que +size_assertion_comparator_error_greaterequal=est plus grand ou \u00E9gale \u00E0 +size_assertion_comparator_error_less=est inf\u00E9rieur \u00E0 +size_assertion_comparator_error_lessequal=est inf\u00E9rieur ou \u00E9gale \u00E0 +size_assertion_comparator_error_notequal=n'est pas \u00E9gale \u00E0 +size_assertion_comparator_label=Type de comparaison +size_assertion_failure=Le r\u00E9sultat n''a pas la bonne taille \: il \u00E9tait de {0} octet(s), mais aurait d\u00FB \u00EAtre de {1} {2} octet(s). +size_assertion_input_error=Entrer un entier positif valide svp. +size_assertion_label=Taille en octets \: +size_assertion_size_test=Taille \u00E0 v\u00E9rifier +size_assertion_title=Assertion Taille +smime_assertion_issuer_dn=Nom unique de l'\u00E9metteur \: +smime_assertion_message_position=V\u00E9rifier l'assertion sur le message \u00E0 partir de la position +smime_assertion_not_signed=Message non sign\u00E9 +smime_assertion_signature=Signature +smime_assertion_signer=Certificat signataire +smime_assertion_signer_by_file=Fichier du certificat \: +smime_assertion_signer_constraints=V\u00E9rifier les valeurs \: +smime_assertion_signer_dn=Nom unique du signataire \: +smime_assertion_signer_email=Adresse courriel du signataire \: +smime_assertion_signer_no_check=Pas de v\u00E9rification +smime_assertion_signer_serial=Num\u00E9ro de s\u00E9rie \: +smime_assertion_title=Assertion SMIME +smime_assertion_verify_signature=V\u00E9rifier la signature +smtp_additional_settings=Param\u00E8tres suppl\u00E9mentaires +smtp_attach_file=Fichier(s) attach\u00E9(s) \: +smtp_attach_file_tooltip=S\u00E9parer les fichiers par le point-virgule ";" +smtp_auth_settings=Param\u00E8tres d'authentification +smtp_bcc=Adresse en copie cach\u00E9e (Bcc) \: +smtp_cc=Adresse en copie (CC) \: +smtp_default_port=(D\u00E9fauts \: SMTP \: 25, SSL \: 465, StartTLS \: 587) +smtp_eml=Envoyer un message .eml \: +smtp_enabledebug=Activer les traces de d\u00E9bogage ? +smtp_enforcestarttls=Forcer le StartTLS +smtp_enforcestarttls_tooltip=Force le serveur a utiliser StartTLS.
Si il n'est pas s\u00E9lectionn\u00E9 et que le serveur SMTP ne supporte pas StartTLS,
une connexion SMTP normale sera utilis\u00E9e \u00E0 la place.
Merci de noter que la case \u00E0 cocher cr\u00E9\u00E9e un fichier dans /tmp/,
donc cela peut poser des probl\u00E8mes sous Windows. +smtp_from=Adresse exp\u00E9diteur (From) \: +smtp_header_add=Ajouter une ent\u00EAte +smtp_header_name=Nom d'ent\u00EAte +smtp_header_remove=Supprimer +smtp_header_value=Valeur d'ent\u00EAte +smtp_mail_settings=Param\u00E8tres du courriel +smtp_message=Message \: +smtp_message_settings=Param\u00E8tres du message +smtp_messagesize=Calculer la taille du message +smtp_password=Mot de passe \: +smtp_plainbody=Envoyer le message en texte (i.e. sans multipart/mixed) +smtp_replyto=Adresse de r\u00E9ponse (Reply-To) \: +smtp_sampler_title=Requ\u00EAte SMTP +smtp_security_settings=Param\u00E8tres de s\u00E9curit\u00E9 +smtp_server=Serveur \: +smtp_server_connection_timeout=D\u00E9lai d'attente de connexion \: +smtp_server_port=Port \: +smtp_server_settings=Param\u00E8tres du serveur +smtp_server_timeout=D\u00E9lai d'attente de r\u00E9ponse \: +smtp_server_timeouts_settings=D\u00E9lais d'attente (milli-secondes) +smtp_subject=Sujet \: +smtp_suppresssubj=Supprimer l'ent\u00EAte Sujet (Subject) +smtp_timestamp=Ajouter un horodatage dans le sujet +smtp_to=Adresse destinataire (To) \: +smtp_trustall=Faire confiance \u00E0 tous les certificats +smtp_trustall_tooltip=Forcer JMeter \u00E0 faire confiance \u00E0 tous les certificats, quelque soit l'autorit\u00E9 de certification du certificat. +smtp_truststore=Coffre de cl\u00E9s local \: +smtp_truststore_tooltip=Le chemin du coffre de confiance.
Les chemins relatifs sont d\u00E9termin\u00E9s \u00E0 partir du r\u00E9pertoire courant.
En cas d'\u00E9chec, c'est le r\u00E9pertoire contenant le script JMX qui est utilis\u00E9. +smtp_useauth=Utiliser l'authentification +smtp_usenone=Pas de fonctionnalit\u00E9 de s\u00E9curit\u00E9 +smtp_username=Identifiant \: +smtp_usessl=Utiliser SSL +smtp_usestarttls=Utiliser StartTLS +smtp_usetruststore=Utiliser le coffre de confiance local +smtp_usetruststore_tooltip=Autoriser JMeter \u00E0 utiliser le coffre de confiance local. +soap_action=Action Soap +soap_data_title=Donn\u00E9es Soap/XML-RPC +soap_sampler_file_invalid=Le nom de fichier r\u00E9f\u00E9rence un fichier absent ou sans droits de lecture\: +soap_sampler_title=Requ\u00EAte SOAP/XML-RPC +soap_send_action=Envoyer l'action SOAP \: +solinger=SO_LINGER\: +spline_visualizer_average=Moyenne \: +spline_visualizer_incoming=Entr\u00E9e \: +spline_visualizer_maximum=Maximum \: +spline_visualizer_minimum=Minimum \: +spline_visualizer_title=Moniteur de courbe (spline) +spline_visualizer_waitingmessage=En attente de r\u00E9sultats d'\u00E9chantillons +split_function_separator=S\u00E9parateur utilis\u00E9 pour scinder le texte. Par d\u00E9faut , (virgule) est utilis\u00E9. +split_function_string=Texte \u00E0 scinder +ssl_alias_prompt=Veuillez entrer votre alias pr\u00E9f\u00E9r\u00E9 +ssl_alias_select=S\u00E9lectionner votre alias pour le test +ssl_alias_title=Alias du client +ssl_error_title=Probl\u00E8me de KeyStore +ssl_pass_prompt=Entrer votre mot de passe +ssl_pass_title=Mot de passe KeyStore +ssl_port=Port SSL +sslmanager=Gestionnaire SSL +start=Lancer +start_no_timers=Lancer sans pauses +starttime=Date et heure de d\u00E9marrage \: +stop=Arr\u00EAter +stopping_test=Arr\u00EAt de toutes les unit\u00E9s de tests en cours. Le nombre d'unit\u00E9s actives est visible dans le coin haut droit de l'interface. Soyez patient, merci. +stopping_test_failed=Au moins une unit\u00E9 non arr\u00EAt\u00E9e; voir le journal. +stopping_test_host=H\u00F4te +stopping_test_title=En train d'arr\u00EAter le test +string_from_file_encoding=Encodage du fichier (optionnel) +string_from_file_file_name=Entrer le chemin (absolu ou relatif) du fichier +string_from_file_seq_final=Nombre final de s\u00E9quence de fichier +string_from_file_seq_start=D\u00E9marer le nombre de s\u00E9quence de fichier +summariser_title=G\u00E9n\u00E9rer les resultats consolid\u00E9s +summary_report=Rapport consolid\u00E9 +switch_controller_label=Aller vers le num\u00E9ro d'\u00E9l\u00E9ment (ou nom) subordonn\u00E9 \: +switch_controller_title=Contr\u00F4leur Aller \u00E0 +system_sampler_stderr=Erreur standard (stderr) \: +system_sampler_stdin=Entr\u00E9e standard (stdin) \: +system_sampler_stdout=Sortie standard (stdout) \: +system_sampler_title=Appel de processus syst\u00E8me +table_visualizer_bytes=Octets +table_visualizer_connect=\u00C9tabl. Conn.(ms) +table_visualizer_latency=Latence +table_visualizer_sample_num=Echantillon \# +table_visualizer_sample_time=Temps (ms) +table_visualizer_start_time=Heure d\u00E9but +table_visualizer_status=Statut +table_visualizer_success=Succ\u00E8s +table_visualizer_thread_name=Nom d'unit\u00E9 +table_visualizer_warning=Alerte +target_server=Serveur cible +tcp_classname=Nom de classe TCPClient \: +tcp_config_title=Param\u00E8tres TCP par d\u00E9faut +tcp_nodelay=D\u00E9finir aucun d\u00E9lai (NoDelay) +tcp_port=Num\u00E9ro de port \: +tcp_request_data=Texte \u00E0 envoyer \: +tcp_sample_title=Requ\u00EAte TCP +tcp_timeout=Expiration (millisecondes) \: +teardown_on_shutdown=Ex\u00E9cuter le Groupe d'unit\u00E9s de fin m\u00EAme apr\u00E8s un arr\u00EAt manuel des Groupes d'unit\u00E9s principaux +template_choose=Choisir le mod\u00E8le +template_create_from=Cr\u00E9er +template_field=Canevas \: +template_load?=Charger le mod\u00E8le ? +template_menu=Mod\u00E8les... +template_merge_from=Fusionner +template_reload=Recharger les mod\u00E8les +template_title=Mod\u00E8les +test=Test +test_action_action=Action \: +test_action_duration=Dur\u00E9e (millisecondes) \: +test_action_pause=Mettre en pause +test_action_restart_next_loop=Passer \u00E0 l'it\u00E9ration suivante de la boucle +test_action_stop=Arr\u00EAter +test_action_stop_now=Arr\u00EAter imm\u00E9diatement +test_action_target=Cible \: +test_action_target_test=Toutes les unit\u00E9s +test_action_target_thread=Unit\u00E9 courante +test_action_title=Action test +test_configuration=Type de test +test_fragment_title=Fragment d'\u00E9l\u00E9ments +test_plan=Plan de test +test_plan_classpath_browse=Ajouter un r\u00E9pertoire ou un fichier 'jar' au 'classpath' +testconfiguration=Tester la configuration +testplan.serialized=Lancer les groupes d'unit\u00E9s en s\u00E9rie (c'est-\u00E0-dire \: lance un groupe \u00E0 la fois) +testplan_comments=Commentaires \: +testt=Test +textbox_cancel=Annuler +textbox_close=Fermer +textbox_save_close=Enregistrer & Fermer +textbox_title_edit=Editer texte +textbox_title_view=Voir texte +textbox_tooltip_cell=Double clic pour voir/editer +thread_delay_properties=Propri\u00E9t\u00E9s de temporisation de l'unit\u00E9 +thread_group_title=Groupe d'unit\u00E9s +thread_properties=Propri\u00E9t\u00E9s du groupe d'unit\u00E9s +threadgroup=Groupe d'unit\u00E9s +throughput_control_bynumber_label=Ex\u00E9cutions totales +throughput_control_bypercent_label=Pourcentage d'ex\u00E9cution +throughput_control_perthread_label=Par utilisateur +throughput_control_title=Contr\u00F4leur D\u00E9bit +throughput_control_tplabel=D\u00E9bit \: +time_format=Chaine de formatage sur le mod\u00E8le SimpleDateFormat (optionnel) +timelim=Limiter le temps de r\u00E9ponses \u00E0 (ms) +timeout_config_box_title=Configuration du d\u00E9lai d'expiration +timeout_title=D\u00E9lai expiration (ms) +toggle=Permuter +toolbar_icon_set_not_found=Le fichier de description des ic\u00F4nes de la barre d'outils n'est pas trouv\u00E9. Voir les journaux. +total_threads_tooltip=Nombre total d'Unit\u00E9s \u00E0 lancer +tr=Turc +transaction_controller_include_timers=Inclure la dur\u00E9e des compteurs de temps et pre/post processeurs dans le calcul du temps +transaction_controller_parent=G\u00E9n\u00E9rer en \u00E9chantillon parent +transaction_controller_title=Contr\u00F4leur Transaction +unbind=D\u00E9connexion de l'unit\u00E9 +undo=Annuler +unescape_html_string=Cha\u00EEne \u00E0 \u00E9chapper +unescape_string=Cha\u00EEne de caract\u00E8res contenant des\u00E9chappements Java +uniform_timer_delay=D\u00E9lai de d\u00E9calage constant (en millisecondes) \: +uniform_timer_memo=Ajoute un d\u00E9lai al\u00E9atoire avec une distribution uniforme +uniform_timer_range=D\u00E9viation al\u00E9atoire maximum (en millisecondes) \: +uniform_timer_title=Compteur de temps al\u00E9atoire uniforme +up=Monter +update=Mettre \u00E0 jour +update_per_iter=Mettre \u00E0 jour une fois par it\u00E9ration +upload=Fichier \u00E0 uploader +upper_bound=Borne sup\u00E9rieure +url=URL +url_config_get=GET +url_config_http=HTTP +url_config_https=HTTPS +url_config_post=POST +url_config_protocol=Protocole \: +url_config_title=Param\u00E8tres HTTP par d\u00E9faut +url_full_config_title=Echantillon d'URL complet +url_multipart_config_title=Requ\u00EAte HTTP Multipart par d\u00E9faut +urldecode_string=Cha\u00EEne de style URL \u00E0 d\u00E9coder +urlencode_string=Cha\u00EEne de caract\u00E8res \u00E0 encoder en style URL +use_custom_dns_resolver=Utiliser un r\u00E9solveur DNS personnalis\u00E9 +use_expires=Utiliser les ent\u00EAtes Cache-Control/Expires lors du traitement des requ\u00EAtes GET +use_keepalive=Connexion persist. +use_multipart_for_http_post=Multipart/form-data +use_multipart_mode_browser=Ent\u00EAtes compat. navigateur +use_recording_controller=Utiliser un contr\u00F4leur enregistreur +use_system_dns_resolver=Utiliser le r\u00E9solveur DNS syst\u00E8me (JVM) +user=Utilisateur +user_defined_test=Test d\u00E9fini par l'utilisateur +user_defined_variables=Variables pr\u00E9-d\u00E9finies +user_param_mod_help_note=(Ne pas changer. A la place, modifier le fichier de ce nom dans le r\u00E9pertoire /bin de JMeter) +user_parameters_table=Param\u00E8tres +user_parameters_title=Param\u00E8tres Utilisateur +userdn=Identifiant +username=Nom d'utilisateur \: +userpw=Mot de passe +value=Valeur \: +value_to_quote_meta=Valeur \u00E0 \u00E9chapper des caract\u00E8res sp\u00E9ciaux utilis\u00E8s par ORO Regexp +var_name=Nom de r\u00E9f\u00E9rence \: +variable_name_param=Nom de variable (peut inclure une r\u00E9f\u00E9rence de variable ou fonction) +view_graph_tree_title=Voir le graphique en arbre +view_results_assertion_error=Erreur d'assertion \: +view_results_assertion_failure=Echec d'assertion \: +view_results_assertion_failure_message=Message d'\u00E9chec d'assertion \: +view_results_autoscroll=D\u00E9filement automatique ? +view_results_childsamples=Echantillons enfants? +view_results_connect_time=Temps \u00E9tablissement connexion \: +view_results_desc=Affiche les r\u00E9sultats d'un \u00E9chantillon dans un arbre de r\u00E9sultats +view_results_error_count=Compteur erreur\: +view_results_fields=champs \: +view_results_in_table=Tableau de r\u00E9sultats +view_results_latency=Latence \: +view_results_load_time=Temps de r\u00E9ponse \: +view_results_render=Rendu \: +view_results_render_document=Document +view_results_render_html=HTML +view_results_render_html_embedded=HTML et ressources +view_results_render_json=JSON +view_results_render_text=Texte brut +view_results_render_xml=XML +view_results_request_headers=Ent\u00EAtes de requ\u00EAte \: +view_results_response_code=Code de retour \: +view_results_response_headers=Ent\u00EAtes de r\u00E9ponse \: +view_results_response_message=Message de retour \: +view_results_response_missing_tika=Manque l'archive tika-app.jar dans le classpath. Impossible de convertir en texte ce type de document.\nT\u00E9l\u00E9charger le fichier tika-app-x.x.jar depuis http\://tika.apache.org/download.html\nPuis ajouter ce fichier dans le r\u00E9pertoire /lib +view_results_response_partial_message=D\u00E9but du message\: +view_results_response_too_large_message=R\u00E9ponse d\u00E9passant la taille maximale d'affichage. Taille \: +view_results_sample_count=Compteur \u00E9chantillon \: +view_results_sample_start=Date d\u00E9but \u00E9chantillon \: +view_results_search_pane=Volet recherche +view_results_size_body_in_bytes=Taille du corps en octets \: +view_results_size_headers_in_bytes=Taille de l'ent\u00EAte en octets \: +view_results_size_in_bytes=Taille en octets \: +view_results_tab_assertion=R\u00E9sultats d'assertion +view_results_tab_request=Requ\u00EAte +view_results_tab_response=Donn\u00E9es de r\u00E9ponse +view_results_tab_sampler=R\u00E9sultat de l'\u00E9chantillon +view_results_table_fields_key=Champ suppl\u00E9mentaire +view_results_table_fields_value=Valeur +view_results_table_headers_key=Ent\u00EAtes de r\u00E9ponse +view_results_table_headers_value=Valeur +view_results_table_request_headers_key=Ent\u00EAtes de requ\u00EAte +view_results_table_request_headers_value=Valeur +view_results_table_request_http_cookie=Cookie +view_results_table_request_http_host=H\u00F4te +view_results_table_request_http_method=M\u00E9thode +view_results_table_request_http_nohttp=N'est pas un \u00E9chantillon HTTP +view_results_table_request_http_path=Chemin +view_results_table_request_http_port=Port +view_results_table_request_http_protocol=Protocole +view_results_table_request_params_key=Nom de param\u00E8tre +view_results_table_request_params_value=Valeur +view_results_table_request_raw_nodata=Pas de donn\u00E9es \u00E0 afficher +view_results_table_request_tab_http=HTTP +view_results_table_request_tab_raw=Brut +view_results_table_result_tab_parsed=D\u00E9cod\u00E9 +view_results_table_result_tab_raw=Brut +view_results_thread_name=Nom d'unit\u00E9 \: +view_results_title=Voir les r\u00E9sultats +view_results_tree_title=Arbre de r\u00E9sultats +warning=Attention \! +web_cannot_convert_parameters_to_raw=Ne peut pas convertir les param\u00E8tres en Donn\u00E9es POST brutes\ncar l'un des param\u00E8tres a un nom. +web_cannot_switch_tab=Vous ne pouvez pas basculer car ces donn\u00E9es ne peuvent \u00EAtre converties.\nVider les donn\u00E9es pour basculer. +web_parameters_lost_message=Basculer vers les Donn\u00E9es POST brutes va convertir en format brut\net perdre le format tabulaire quand vous s\u00E9lectionnerez un autre noeud\nou \u00E0 la sauvegarde du plan de test, \u00EAtes-vous s\u00FBr ? +web_proxy_server_title=Requ\u00EAte via un serveur proxy +web_request=Requ\u00EAte HTTP +web_server=Serveur web +web_server_client=Impl\u00E9mentation client \: +web_server_domain=Nom ou adresse IP \: +web_server_port=Port \: +web_server_timeout_connect=Connexion \: +web_server_timeout_response=R\u00E9ponse \: +web_server_timeout_title=D\u00E9lai expiration (ms) +web_testing2_title=Requ\u00EAte HTTP HTTPClient +web_testing_concurrent_download=Utiliser pool unit\u00E9. Nbre \: +web_testing_embedded_url_pattern=Les URL \u00E0 inclure doivent correspondre \u00E0 \: +web_testing_retrieve_images=R\u00E9cup\u00E9rer les ressources incluses +web_testing_retrieve_title=Ressources incluses dans les pages HTML +web_testing_source_ip=Adresse source +web_testing_source_ip_device=Interface +web_testing_source_ip_device_ipv4=Interface IPv4 +web_testing_source_ip_device_ipv6=Interface IPv6 +web_testing_source_ip_hostname=IP/Nom d'h\u00F4te +web_testing_title=Requ\u00EAte HTTP +webservice_configuration_wizard=Assistant de configuration WSDL +webservice_get_xml_from_random_title=Utiliser al\u00E9atoirement des messages SOAP +webservice_maintain_session=Maintenir la Session HTTP +webservice_message_soap=Message WebService +webservice_methods=M\u00E9thode(s) WebService \: +webservice_proxy_host=H\u00F4te proxy +webservice_proxy_note=Si 'utiliser un proxy HTTP' est coch\u00E9e, mais qu'aucun h\u00F4te ou port est fournit, l'\u00E9chantillon +webservice_proxy_note2=regardera les options de ligne de commandes. Si aucun h\u00F4te ou port du proxy sont fournit +webservice_proxy_note3=non plus, il \u00E9chouera silencieusement. +webservice_proxy_port=Port proxy +webservice_sampler_title=Requ\u00EAte WebService (SOAP) (DEPRECATED) +webservice_soap_action=Action SOAP \: +webservice_timeout=D\u00E9lai expiration \: +webservice_use_proxy=Utiliser un proxy HTTP +while_controller_label=Condition (fonction ou variable) \: +while_controller_title=Contr\u00F4leur Tant Que +workbench_title=Plan de travail +wsdl_helper_error=Le WSDL n'est pas valide, veuillez rev\u00E9rifier l'URL. +wsdl_url=URL du WSDL +wsdl_url_error=Le WSDL est vide. +xml_assertion_title=Assertion XML +xml_download_dtds=R\u00E9cup\u00E9rer les DTD externes +xml_namespace_button=Utiliser les espaces de noms +xml_tolerant_button=Utiliser Tidy (analyseur tol\u00E9rant) +xml_validate_button=Validation XML +xml_whitespace_button=Ignorer les espaces +xmlschema_assertion_label=Nom de fichier \: +xmlschema_assertion_title=Assertion Sch\u00E9ma XML +xpath_assertion_button=Valider +xpath_assertion_check=V\u00E9rifier l'expression XPath +xpath_assertion_error=Erreur avec XPath +xpath_assertion_failed=Expression XPath invalide +xpath_assertion_label=XPath +xpath_assertion_negate=Vrai si aucune correspondance trouv\u00E9e +xpath_assertion_option=Options d'analyse XML +xpath_assertion_test=V\u00E9rificateur XPath +xpath_assertion_tidy=Essayer et nettoyer l'entr\u00E9e +xpath_assertion_title=Assertion XPath +xpath_assertion_valid=Expression XPath valide +xpath_assertion_validation=Valider le code XML \u00E0 travers le fichier DTD +xpath_assertion_whitespace=Ignorer les espaces +xpath_expression=Expression XPath de correspondance +xpath_extractor_fragment=Retourner le fragment XPath entier au lieu du contenu +xpath_extractor_query=Requ\u00EAte XPath \: +xpath_extractor_title=Extracteur XPath +xpath_file_file_name=Fichier XML contenant les valeurs +xpath_tester=Testeur XPath +xpath_tester_button_test=Tester +xpath_tester_field=Expression XPath +xpath_tester_fragment=Retourner le fragment XPath entier au lieu du contenu ? +xpath_tester_no_text=Les donn\u00E9es de r\u00E9ponse ne sont pas du texte. +xpath_tester_title=Testeur XPath +xpath_tidy_quiet=Silencieux +xpath_tidy_report_errors=Rapporter les erreurs +xpath_tidy_show_warnings=Afficher les alertes +you_must_enter_a_valid_number=Vous devez entrer un nombre valide +zh_cn=Chinois (simplifi\u00E9) +zh_tw=Chinois (traditionnel) diff --git a/src/core/org/apache/jmeter/resources/messages_ja.properties b/src/core/org/apache/jmeter/resources/messages_ja.properties new file mode 100644 index 00000000000..38d581bf324 --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_ja.properties @@ -0,0 +1,480 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=Apache JMeter \u306B\u3064\u3044\u3066 +add=\u8FFD\u52A0 +add_as_child=\u5B50\u3068\u3057\u3066\u8FFD\u52A0 +add_parameter=\u5909\u6570\u306E\u8FFD\u52A0 +add_pattern=\u30D1\u30BF\u30FC\u30F3\u8FFD\u52A0\: +add_test=\u30C6\u30B9\u30C8\u306E\u8FFD\u52A0 +add_user=\u30E6\u30FC\u30B6\u30FC\u306E\u8FFD\u52A0 +add_value=\u5024\u306E\u8FFD\u52A0 +aggregate_report=\u7D71\u8A08\u30EC\u30DD\u30FC\u30C8 +aggregate_report_total_label=\u5408\u8A08 +als_message=\u6CE8\u610F\: \u30A2\u30AF\u30BB\u30B9\u30ED\u30B0\u30D1\u30FC\u30B5\u306F\u6C4E\u7528\u7684\u306B\u8A2D\u8A08\u3055\u308C\u3066\u3044\u308B\u306E\u3067\u3001\u72EC\u81EA\u30D1\u30FC\u30B5\u3092 +als_message2=\u30D7\u30E9\u30B0\u30A4\u30F3\u53EF\u80FD\u3067\u3059\u3002\u305D\u306E\u305F\u3081\u306B\u306F\u3001LogParser\u3092\u5B9F\u88C5\u3057\u3066/lib\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B +als_message3=jar\u30D5\u30A1\u30A4\u30EB\u3092\u8FFD\u52A0\u3057\u3001\u30B5\u30F3\u30D7\u30E9\u30FC\u3067\u30AF\u30E9\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +analyze=\u30C7\u30FC\u30BF\u30D5\u30A1\u30A4\u30EB\u3092\u5206\u6790... +anchor_modifier_title=HTML \u30EA\u30F3\u30AF\u30D1\u30FC\u30B5 +appearance=\u30EB\u30C3\u30AF&\u30D5\u30A3\u30FC\u30EB +argument_must_not_be_negative=\u5F15\u6570\u306F\u8CA0\u306E\u5024\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093\uFF01 +assertion_code_resp=\u5FDC\u7B54\u30B3\u30FC\u30C9 +assertion_contains=\u542B\u3080 +assertion_matches=\u4E00\u81F4\u3059\u308B +assertion_message_resp=\u5FDC\u7B54\u30E1\u30C3\u30BB\u30FC\u30B8 +assertion_not=\u5426\u5B9A +assertion_pattern_match_rules=\u30D1\u30BF\u30FC\u30F3\u30DE\u30C3\u30C1\u30F3\u30B0\u30EB\u30FC\u30EB +assertion_patterns_to_test=\u30C6\u30B9\u30C8\u30D1\u30BF\u30FC\u30F3 +assertion_resp_field=\u30C6\u30B9\u30C8\u3059\u308B\u30EC\u30B9\u30DD\u30F3\u30B9\u30D5\u30A3\u30FC\u30EB\u30C9 +assertion_text_resp=\u30C6\u30AD\u30B9\u30C8\u306E\u30EC\u30B9\u30DD\u30F3\u30B9 +assertion_textarea_label=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3\: +assertion_title=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +assertion_url_samp=\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3055\u308C\u305F URL +assertion_visualizer_title=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 \u7D50\u679C +auth_base_url=\u57FA\u5E95URL +auth_manager_title=HTTP \u8A8D\u8A3C\u30DE\u30CD\u30FC\u30B8\u30E3 +auths_stored=\u8A8D\u8A3C\u30DE\u30CD\u30FC\u30B8\u30E3\u306B\u4FDD\u5B58\u3055\u308C\u3066\u3044\u308B\u8A8D\u8A3C +browse=\u53C2\u7167... +bsf_sampler_title=BSF\u30B5\u30F3\u30D7\u30E9\u30FC +bsf_script=\u5B9F\u884C\u3059\u308B\u30B9\u30AF\u30EA\u30D7\u30C8 +bsf_script_file=\u5B9F\u884C\u3059\u308B\u30B9\u30AF\u30EA\u30D7\u30C8\u30D5\u30A1\u30A4\u30EB +bsf_script_language=\u30B9\u30AF\u30EA\u30D7\u30C8\u8A00\u8A9E\: +bsf_script_parameters=\u30B9\u30AF\u30EA\u30D7\u30C8/\u30D5\u30A1\u30A4\u30EB\u3078\u6E21\u3059\u30D1\u30E9\u30E1\u30FC\u30BF\: +bsh_assertion_script=\u30B9\u30AF\u30EA\u30D7\u30C8(Response[Data|Code|Message|Headers], RequestHeaders, Sample[Label|rData], Result, Failure[Message]) +bsh_assertion_title=BeanShell\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +bsh_function_expression=\u8A55\u4FA1\u5BFE\u8C61\u306E\u5F0F +bsh_sampler_title=BeanShell\u30B5\u30F3\u30D7\u30E9\u30FC +bsh_script=\u30B9\u30AF\u30EA\u30D7\u30C8 (variables\: SampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName) +bsh_script_file=\u30B9\u30AF\u30EA\u30D7\u30C8\u30D5\u30A1\u30A4\u30EB +bsh_script_parameters=\u30D1\u30E9\u30E1\u30FC\u30BF\uFF08-> String Parameters and String []bsh.args\uFF09 +busy_testing=\u73FE\u5728\u30C6\u30B9\u30C8\u4E2D\u3067\u3059\u3002\u8A2D\u5B9A\u5909\u66F4\u306E\u524D\u306B\u30C6\u30B9\u30C8\u3092\u505C\u6B62\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +cancel=\u30AD\u30E3\u30F3\u30BB\u30EB +cancel_exit_to_save=\u4FDD\u5B58\u3055\u308C\u3066\u3044\u306A\u3044\u30C6\u30B9\u30C8\u9805\u76EE\u304C\u3042\u308A\u307E\u3059\u3002\u7D42\u4E86\u3059\u308B\u524D\u306B\u4FDD\u5B58\u3057\u307E\u3059\u304B\uFF1F +cancel_new_to_save=\u4FDD\u5B58\u3055\u308C\u3066\u3044\u306A\u3044\u30C6\u30B9\u30C8\u9805\u76EE\u304C\u3042\u308A\u307E\u3059\u3002\u30C6\u30B9\u30C8\u8A08\u753B\u3092\u6D88\u53BB\u3059\u308B\u524D\u306B\u4FDD\u5B58\u3057\u307E\u3059\u304B\uFF1F +choose_function=\u95A2\u6570\u306E\u9078\u629E +choose_language=\u8A00\u8A9E\u306E\u9078\u629E +clear=\u6D88\u53BB +clear_all=\u5168\u3066\u6D88\u53BB +clear_cookies_per_iter=\u7E70\u308A\u8FD4\u3057\u3054\u3068\u306B\u30AF\u30C3\u30AD\u30FC\u3092\u7834\u68C4\u3057\u307E\u3059\u304B\uFF1F +column_delete_disallowed=\u3053\u306E\u30AB\u30E9\u30E0\u306E\u524A\u9664\u6A29\u9650\u304C\u3042\u308A\u307E\u305B\u3093 +column_number=CSV\u30D5\u30A1\u30A4\u30EB\u306E\u30AB\u30E9\u30E0\u756A\u53F7 +config_element=\u8A2D\u5B9A\u30A8\u30EC\u30E1\u30F3\u30C8 +configure_wsdl=\u8A2D\u5B9A +constant_throughput_timer_memo=\u4E00\u5B9A\u306E\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8\u306B\u5230\u9054\u3057\u305F\u3089\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u9593\u306B\u9045\u5EF6\u3092\u8FFD\u52A0 +constant_timer_delay=\u30B9\u30EC\u30C3\u30C9\u9045\u5EF6\u6642\u9593 (\u30DF\u30EA\u79D2)\: +constant_timer_memo=\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u9593\u306B\u4E00\u5B9A\u306E\u9045\u5EF6\u3092\u8FFD\u52A0 +constant_timer_title=\u5B9A\u6570\u30BF\u30A4\u30DE +controller=\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +cookie_manager_title=HTTP \u30AF\u30C3\u30AD\u30FC\u30DE\u30CD\u30FC\u30B8\u30E3 +cookies_stored=\u30AF\u30C3\u30AD\u30FC\u30DE\u30CD\u30FC\u30B8\u30E3\u306B\u4FDD\u5B58\u3055\u308C\u3066\u3044\u308B\u30AF\u30C3\u30AD\u30FC +copy=\u30B3\u30D4\u30FC +counter_config_title=\u30AB\u30A6\u30F3\u30BF +counter_per_user=\u5404\u30E6\u30FC\u30B6\u72EC\u7ACB\u306E\u30C8\u30E9\u30C3\u30AF\u30AB\u30A6\u30F3\u30BF +csvread_file_file_name=\u5024\u3092\u8AAD\u307F\u8FBC\u3080CSV\u30D5\u30A1\u30A4\u30EB +cut=\u30AB\u30C3\u30C8 +cut_paste_function=\u751F\u6210\u3055\u308C\u305F\u95A2\u6570\u6587\u5B57\u5217\u3092\u30B3\u30D4\u30FC\u3057\u8CBC\u308A\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044\u3002 +database_sql_query_string=SQL \u30AF\u30A8\u30EA\u30FC\u6587\u5B57\u5217\: +database_sql_query_title=JDBC SQL \u30AF\u30A8\u30EA\u30FC\u521D\u671F\u5024\u8A2D\u5B9A +de=\u30C9\u30A4\u30C4\u8A9E +default_parameters=\u30C7\u30D5\u30A9\u30EB\u30C8\u30D1\u30E9\u30E1\u30FC\u30BF +default_value_field=\u521D\u671F\u5024\uFF1A +delay=\u8D77\u52D5\u9045\u5EF6\uFF08\u79D2\uFF09 +delete=\u524A\u9664 +delete_parameter=\u5909\u6570\u306E\u524A\u9664 +delete_test=\u30C6\u30B9\u30C8\u306E\u524A\u9664 +delete_user=\u30E6\u30FC\u30B6\u30FC\u306E\u524A\u9664 +disable=\u7121\u52B9 +dn=\uFF24\uFF2E +domain=\u30C9\u30E1\u30A4\u30F3 +duration=\u6301\u7D9A\u6642\u9593\uFF08\u79D2\uFF09 +duration_assertion_duration_test=\u30A2\u30B5\u30FC\u30C8\u306E\u6301\u7D9A +duration_assertion_failure=\u64CD\u4F5C\u306B\u6642\u9593\u304C\u304B\u304B\u308A\u3059\u304E\u307E\u3057\u305F\:{0}\u30DF\u30EA\u79D2\u304B\u304B\u308A\u307E\u3057\u305F\u304C\u3001{1}\u30DF\u30EA\u79D2\u3088\u308A\u3082\u9577\u304F\u304B\u304B\u308B\u3079\u304D\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002 +duration_assertion_input_error=\u59A5\u5F53\u306A\u6B63\u306E\u6574\u6570\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +duration_assertion_label=\u6301\u7D9A\u6642\u9593(\u30DF\u30EA\u79D2) +duration_assertion_title=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3\u306E\u6301\u7D9A +edit=\u7DE8\u96C6 +email_results_title=\u7D50\u679C\u3092\u30E1\u30FC\u30EB\u3067\u9001\u4FE1 +en=\u82F1\u8A9E +enable=\u6709\u52B9 +encoded_value=URL\u30A8\u30F3\u30B3\u30FC\u30C9\u5024 +endtime=\u7D42\u4E86\u6642\u523B +entry_dn=\u30A8\u30F3\u30C8\u30EADN +error_loading_help=\u30D8\u30EB\u30D7\u30DA\u30FC\u30B8\u30ED\u30FC\u30C9\u4E2D\u306E\u30A8\u30E9\u30FC +error_occurred=\u30A8\u30E9\u30FC\u304C\u767A\u751F +example_data=\u30B5\u30F3\u30D7\u30EB\u30C7\u30FC\u30BF +example_title=Example\u30B5\u30F3\u30D7\u30E9\u30FC +exit=\u7D42\u4E86 +expiration=\u671F\u9650 +field_name=\u30D5\u30A3\u30FC\u30EB\u30C9\u540D +file=\u30D5\u30A1\u30A4\u30EB +file_already_in_use=\u305D\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u3059\u3067\u306B\u4F7F\u7528\u4E2D\u3067\u3059\u3002 +file_visualizer_append=\u65E2\u306B\u5B58\u5728\u3059\u308B\u30C7\u30FC\u30BF\u30D5\u30A1\u30A4\u30EB\u3078\u8FFD\u52A0 +file_visualizer_auto_flush=\u5404\u30C7\u30FC\u30BF\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3057\u305F\u3042\u3068\u306B\u81EA\u52D5\u7684\u306B\u66F8\u51FA\u3057 +file_visualizer_browse=\u53C2\u7167... +file_visualizer_close=\u9589\u3058\u308B +file_visualizer_file_options=\u30D5\u30A1\u30A4\u30EB\u30AA\u30D7\u30B7\u30E7\u30F3 +file_visualizer_filename=\u30D5\u30A1\u30A4\u30EB\u540D +file_visualizer_flush=\u66F8\u51FA\u3057 +file_visualizer_missing_filename=\u51FA\u529B\u30D5\u30A1\u30A4\u30EB\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002 +file_visualizer_open=\u958B\u304F +file_visualizer_output_file=\u5168\u3066\u306E\u30C7\u30FC\u30BF\u3092\u30D5\u30A1\u30A4\u30EB\u306B\u51FA\u529B +file_visualizer_submit_data=\u9001\u4FE1\u30C7\u30FC\u30BF\u3092\u542B\u307E\u305B\u308B +file_visualizer_title=\u30D5\u30A1\u30A4\u30EB\u30EC\u30DD\u30FC\u30BF +file_visualizer_verbose=\u8A73\u7D30\u306A\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B +filename=\u30D5\u30A1\u30A4\u30EB\u540D +follow_redirects=\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u306B\u5BFE\u5FDC +follow_redirects_auto=\u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8 +foreach_controller_title=ForEach\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +foreach_input=Input\u5909\u6570\u540D\u63A5\u982D\u8F9E +foreach_output=Output\u5909\u6570\u540D +ftp_sample_title=FTP \u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +ftp_testing_title=FTP \u30EA\u30AF\u30A8\u30B9\u30C8 +function_dialog_menu_item=\u95A2\u6570\u30D8\u30EB\u30D1\u30FC\u30C0\u30A4\u30A2\u30ED\u30B0 +function_helper_title=\u95A2\u6570\u30D8\u30EB\u30D1\u30FC +function_name_param=\u95A2\u6570\u540D\u3002\u30C6\u30B9\u30C8\u8A08\u753B\u3067\u4F7F\u7528\u3059\u308B\u5024\u3092\u4FDD\u6301\u3059\u308B\u306E\u306B\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002 +function_params=\u95A2\u6570\u30D1\u30E9\u30E1\u30FC\u30BF +functional_mode=Functional \u30C6\u30B9\u30C8\u30E2\u30FC\u30C9 +functional_mode_explanation=\u5404\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5BFE\u3059\u308B\u30B5\u30FC\u30D0\u30FC\u306E\u5FDC\u7B54\u3092\n\u30D5\u30A1\u30A4\u30EB\u3078\u66F8\u304D\u8FBC\u307F\u305F\u3044\u5834\u5408\u306E\u307FFunctional \u30C6\u30B9\u30C8\u30E2\u30FC\u30C9\u3092\u9078\u629E\u3057\u3066\u4E0B\u3055\u3044\u3002\n\n\u3053\u306E\u30AA\u30D7\u30B7\u30E7\u30F3\u3092\u9078\u3093\u3060\u3068\u304D\u306E\u6027\u80FD\u306B\u5BFE\u3059\u308B\u5F71\u97FF\u306B\u7559\u610F\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +gaussian_timer_delay=\u9045\u5EF6\u6642\u9593\u30AA\u30D5\u30BB\u30C3\u30C8\u5B9A\u6570 (\u30DF\u30EA\u79D2)\: +gaussian_timer_memo=\u30AC\u30A6\u30B9\u5206\u5E03\u306B\u3088\u308B\u30E9\u30F3\u30C0\u30E0\u306A\u9045\u5EF6\u3092\u8FFD\u52A0 +gaussian_timer_range=\u504F\u5DEE (\u30DF\u30EA\u79D2)\: +gaussian_timer_title=\u30AC\u30A6\u30B9\u4E71\u6570\u30BF\u30A4\u30DE +generate=\u751F\u6210 +generator=\u751F\u6210\u30AF\u30E9\u30B9\u540D +generator_cnf_msg=\u751F\u6210\u30AF\u30E9\u30B9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002/lib\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u751F\u6210\u30AF\u30E9\u30B9\u3092\u542B\u3080jar\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002 +generator_illegal_msg=IllegalAcessException\u306B\u3088\u308A\u751F\u6210\u30AF\u30E9\u30B9\u3078\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002 +generator_instantiate_msg=\u751F\u6210\u30D1\u30FC\u30B5\u306E\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002Generator\u30A4\u30F3\u30BF\u30D5\u30A7\u30FC\u30B9\u3092\u5B9F\u88C5\u3059\u308B\u751F\u6210\u30AF\u30E9\u30B9\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002 +get_xml_from_file=SOAP XML\u30C7\u30FC\u30BF\u306E\u30D5\u30A1\u30A4\u30EB\uFF08\u4E0A\u8A18\u30C6\u30AD\u30B9\u30C8\u306F\u7121\u8996\u3055\u308C\u307E\u3059\uFF09 +get_xml_from_random=\u30E1\u30C3\u30BB\u30FC\u30B8\u30D5\u30A9\u30EB\u30C0 +graph_choose_graphs=\u8868\u793A\u3059\u308B\u30B0\u30E9\u30D5 +graph_full_results_title=\u7D50\u679C\u3092\u30B0\u30E9\u30D5\u8868\u793A(\u8A73\u7D30) +graph_results_average=\u5E73\u5747 +graph_results_data=\u30C7\u30FC\u30BF +graph_results_deviation=\u504F\u5DEE +graph_results_latest_sample=\u6700\u65B0\u306E\u30B5\u30F3\u30D7\u30EB +graph_results_median=\u4E2D\u592E\u5024 +graph_results_ms=\u30DF\u30EA\u79D2(ms) +graph_results_no_samples=\u30B5\u30F3\u30D7\u30EB\u6570 +graph_results_throughput=\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8 +graph_results_title=\u30B0\u30E9\u30D5\u8868\u793A +grouping_add_separators=\u30B0\u30EB\u30FC\u30D7\u9593\u306B\u30BB\u30D1\u30EC\u30FC\u30BF\u3092\u8FFD\u52A0 +grouping_in_controllers=\u65B0\u898F\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9\u3078\u5404\u30B0\u30EB\u30FC\u30D7\u3092\u7F6E\u304F +grouping_mode=\u30B0\u30EB\u30FC\u30D7\u306B\u3059\u308B\: +grouping_no_groups=\u30B5\u30F3\u30D7\u30E9\u30FC\u3092\u30B0\u30EB\u30FC\u30D7\u306B\u3057\u306A\u3044 +grouping_store_first_only=\u5404\u30B0\u30EB\u30FC\u30D7\u306E\u6700\u521D\u306E\u30B5\u30F3\u30D7\u30E9\u30FC\u3060\u3051\u4FDD\u5B58 +header_manager_title=HTTP \u30D8\u30C3\u30C0\u30DE\u30CD\u30FC\u30B8\u30E3 +headers_stored=\u30D8\u30C3\u30C0\u30FC\u30DE\u30CD\u30FC\u30B8\u30E3\u306B\u4FDD\u5B58\u3055\u308C\u3066\u3044\u308B\u30D8\u30C3\u30C0 +help=\u30D8\u30EB\u30D7 +html_parameter_mask=HTML\u30D1\u30E9\u30E1\u30FC\u30BF\u30DE\u30B9\u30AF +http_response_code=HTTP\u5FDC\u7B54\u30B3\u30FC\u30C9 +http_url_rewriting_modifier_title=HTTP URL-Rewriting \u4FEE\u98FE\u5B50 +http_user_parameter_modifier=HTTP\u30E6\u30FC\u30B6\u30FC\u30D1\u30E9\u30E1\u30FC\u30BF\u306E\u5909\u66F4 +id_prefix=ID\u63A5\u982D\u8F9E +id_suffix=ID \u63A5\u5C3E\u8F9E +if_controller_label=\u6761\u4EF6 +if_controller_title=If \u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +ignore_subcontrollers=\u30B5\u30D6\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9\u30D6\u30ED\u30C3\u30AF\u3092\u7121\u8996 +include_equals=\u7B49\u53F7\u542B\u3080\uFF1F +increment=\u5897\u5206 +infinite=\u7121\u9650\u30EB\u30FC\u30D7 +insert_after=\u5F8C\u3078\u633F\u5165 +insert_before=\u524D\u3078\u633F\u5165 +insert_parent=\u4E0A\u306E\u968E\u5C64\u306B\u633F\u5165 +interleave_control_title=\u30A4\u30F3\u30BF\u30EA\u30FC\u30D6\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +intsum_param_1=\u6700\u521D\u306B\u8FFD\u52A0\u3059\u308Bint +intsum_param_2=\uFF12\u56DE\u76EE\u306B\u8FFD\u52A0\u3059\u308Bint - \u3055\u3089\u306A\u308B\u5F15\u6570\u3092\u8FFD\u52A0\u3057\u3066\u5408\u8A08\u304C\u8A08\u7B97\u3055\u308C\u308B +invalid_data=\u9069\u5207\u3067\u306A\u3044\u30C7\u30FC\u30BF +invalid_mail_server=\u4E0D\u660E\u306A\u30E1\u30FC\u30EB\u30B5\u30FC\u30D0\u30FC\u3067\u3059\u3002 +iteration_counter_arg_1=TRUE, \u30E6\u30FC\u30B6\u30FC\u6BCE\u306B\u30AB\u30A6\u30F3\u30BF\u30FC\u3092\u6301\u3064\u3053\u3068\u304C\u3067\u304D\u308B, FALSE \u30B0\u30ED\u30FC\u30D0\u30EB\u30AB\u30A6\u30F3\u30BF\u30FC\u3068\u306A\u308B +iterator_num=\u30EB\u30FC\u30D7\u56DE\u6570\: +java_request=Java \u30EA\u30AF\u30A8\u30B9\u30C8 +java_request_defaults=Java \u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +jndi_config_title=JNDI \u8A2D\u5B9A +jndi_lookup_name=\u30EA\u30E2\u30FC\u30C8\u30A4\u30F3\u30BF\u30D5\u30A7\u30FC\u30B9 +jndi_lookup_title=JNDI \u30EB\u30C3\u30AF\u30A2\u30C3\u30D7\u8A2D\u5B9A +jndi_method_button_invoke=\u547C\u3073\u51FA\u3057 +jndi_method_button_reflect=\u30EA\u30D5\u30EC\u30AF\u30C8 +jndi_method_home_name=\u30DB\u30FC\u30E0\u30E1\u30BD\u30C3\u30C9\u540D +jndi_method_home_parms=\u30DB\u30FC\u30E0\u30E1\u30BD\u30C3\u30C9\u306E\u5F15\u6570 +jndi_method_name=\u30E1\u30BD\u30C3\u30C9\u8A2D\u5B9A +jndi_method_remote_interface_list=\u30EA\u30E2\u30FC\u30C8\u30A4\u30F3\u30BF\u30D5\u30A7\u30FC\u30B9 +jndi_method_remote_name=\u30EA\u30E2\u30FC\u30C8\u30E1\u30BD\u30C3\u30C9\u540D +jndi_method_remote_parms=\u30EA\u30E2\u30FC\u30C8\u30E1\u30BD\u30C3\u30C9\u306E\u5F15\u6570 +jndi_method_title=\u30EA\u30E2\u30FC\u30C8\u30E1\u30BD\u30C3\u30C9\u8A2D\u5B9A +jndi_testing_title=JNDI \u30EA\u30AF\u30A8\u30B9\u30C8 +jndi_url_jndi_props=JNDI \u30D7\u30ED\u30D1\u30C6\u30A3 +ja=\u65E5\u672C\u8A9E +ldap_sample_title=LDAP\u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +ldap_testing_title=LDAP\u30EA\u30AF\u30A8\u30B9\u30C8 +load=\u8AAD\u8FBC +load_wsdl=WSDL\u306E\u30ED\u30FC\u30C9 +log_errors_only=\u30ED\u30B0\u30A8\u30E9\u30FC\u306E\u307F +log_file=\u30ED\u30B0\u30D5\u30A1\u30A4\u30EB\u306E\u5834\u6240 +log_parser=Log\u30D1\u30FC\u30B5\u30AF\u30E9\u30B9\u540D +log_parser_cnf_msg=\u30AF\u30E9\u30B9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002/lib\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u30AF\u30E9\u30B9\u3092\u542B\u3080jar\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002 +log_parser_illegal_msg=IllegalAcessException\u306B\u3088\u308A\u30AF\u30E9\u30B9\u3078\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002 +log_parser_instantiate_msg=\u30ED\u30B0\u30D1\u30FC\u30B5\u306E\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002LogParser\u30A4\u30F3\u30BF\u30D5\u30A7\u30FC\u30B9\u3092\u5B9F\u88C5\u3059\u308B\u30D1\u30FC\u30B5\u30AF\u30E9\u30B9\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002 +log_sampler=Tomcat\u30A2\u30AF\u30BB\u30B9\u30ED\u30B0\u30B5\u30F3\u30D7\u30E9\u30FC +logic_controller_title=\u30B7\u30F3\u30D7\u30EB\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +login_config=\u30ED\u30B0\u30A4\u30F3\u8A2D\u5B9A +login_config_element=\u30ED\u30B0\u30A4\u30F3\u8A2D\u5B9A\u30A8\u30EC\u30E1\u30F3\u30C8 +loop_controller_title=\u30EB\u30FC\u30D7\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +looping_control=\u30EB\u30FC\u30D7\u30B3\u30F3\u30C8\u30ED\u30FC\u30EB +lower_bound=\u4E0B\u9650 +mailer_attributes_panel=\u30E1\u30FC\u30EB\u306E\u5C5E\u6027 +mailer_error=\u30E1\u30FC\u30EB\u3092\u9001\u4FE1\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u5165\u529B\u5185\u5BB9\u306B\u9593\u9055\u3044\u304C\u306A\u3044\u304B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +mailer_visualizer_title=\u30E1\u30FC\u30E9\u30FC\u30D3\u30B8\u30E5\u30A2\u30E9\u30A4\u30B6 +match_num_field=\u4E00\u81F4\u756A\u53F7\uFF080\u304B\u3089\u4E71\u6570\uFF09\uFF1A +max=\u6700\u5927\u5024 +maximum_param=\u5024\u57DF\u306E\u6700\u5927\u5024 +md5hex_assertion_failure=MD5 sum \u30A2\u30B5\u30FC\u30C8\u30A8\u30E9\u30FC \: \u7D50\u679C\u306F {0} \u3067\u3057\u305F\u304C\u3001{1}\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093\u3002 +md5hex_assertion_md5hex_test=\u30A2\u30B5\u30FC\u30C8\u5BFE\u8C61\u306EMD5Hex +md5hex_assertion_title=MD5Hex\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +memory_cache=\u30E1\u30E2\u30EA\u30FC\u30AD\u30E3\u30C3\u30B7\u30E5 +menu_assertions=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +menu_close=\u9589\u3058\u308B +menu_config_element=\u8A2D\u5B9A\u30A8\u30EC\u30E1\u30F3\u30C8 +menu_edit=\u7DE8\u96C6 +menu_generative_controller=\u30B5\u30F3\u30D7\u30E9\u30FC +menu_listener=\u30EA\u30B9\u30CA\u30FC +menu_logic_controller=\u30ED\u30B8\u30C3\u30AF\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +menu_merge=\u4F75\u5408\uFF08\u30DE\u30FC\u30B8\uFF09 +menu_modifiers=\u4FEE\u98FE\u5B50 +menu_non_test_elements=Non-Test\u30A8\u30EC\u30E1\u30F3\u30C8 +menu_open=\u958B\u304F +menu_post_processors=\u5F8C\u51E6\u7406 +menu_pre_processors=\u524D\u51E6\u7406 +menu_response_based_modifiers=\u30EC\u30B9\u30DD\u30F3\u30B9\u57FA\u6E96\u306E\u4FEE\u98FE\u5B50 +menu_timer=\u30BF\u30A4\u30DE +metadata=\u30E1\u30BF\u30C7\u30FC\u30BF +method=\u30E1\u30BD\u30C3\u30C9\: +mimetype=Mime\u30BF\u30A4\u30D7 +minimum_param=\u5024\u57DF\u306E\u6700\u5C0F\u5024 +minute=\u5206 +modification_controller_title=\u5909\u66F4\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +modification_manager_title=\u5909\u66F4\u30DE\u30CD\u30FC\u30B8\u30E3 +modify_test=\u30C6\u30B9\u30C8\u306E\u5909\u66F4 +module_controller_title=\u30E2\u30B8\u30E5\u30FC\u30EB\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +monitor_equation_dead=Dead\: \u5FDC\u7B54\u306A\u3057 +monitor_health_title=\u30E2\u30CB\u30BF\u7D50\u679C +monitor_is_title=\u30E2\u30CB\u30BF\u3068\u3057\u3066\u4F7F\u7528 +monitor_legend_memory_per=\u30E1\u30E2\u30EA % (used/total) +monitor_legend_thread_per=\u30B9\u30EC\u30C3\u30C9 % (busy/max) +monitor_performance_servers=\u30B5\u30FC\u30D0 +monitor_performance_tab_title=\u30D1\u30D5\u30A9\u30FC\u30DE\u30F3\u30B9 +monitor_performance_title=\u30D1\u30D5\u30A9\u30FC\u30DE\u30F3\u30B9\u30B0\u30E9\u30D5 +name=\u540D\u524D\: +new=\u65B0\u898F +no=\u30CE\u30EB\u30A6\u30A7\u30FC\u8A9E +number_of_threads=\u30B9\u30EC\u30C3\u30C9\u6570\: +once_only_controller_title=\u4E00\u5EA6\u3060\u3051\u5B9F\u884C\u3055\u308C\u308B\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +open=\u958B\u304F... +option=\u30AA\u30D7\u30B7\u30E7\u30F3 +optional_tasks=\u30AA\u30D7\u30B7\u30E7\u30F3\u30BF\u30B9\u30AF +paramtable=\u30EA\u30AF\u30A8\u30B9\u30C8\u3067\u9001\u308B\u30D1\u30E9\u30E1\u30FC\u30BF\: +password=\u30D1\u30B9\u30EF\u30FC\u30C9 +paste=\u30DA\u30FC\u30B9\u30C8 +paste_insert=\u633F\u5165\u3068\u3057\u3066\u30DA\u30FC\u30B9\u30C8 +path=\u30D1\u30B9\: +path_extension_choice=\u30D1\u30B9\u306E\u62E1\u5F35(\u533A\u5207\u308A\u306B\u306F";"\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044) +path_extension_dont_use_equals=\u30D1\u30B9\u306E\u62E1\u5F35\u306B\u7B49\u53F7\u3092\u4F7F\u308F\u306A\u3044\uFF08Intershop Enfinity \u4E92\u63DB\u306E\u305F\u3081\uFF09 +patterns_to_exclude=\u9664\u5916\u3059\u308B\u30D1\u30BF\u30FC\u30F3 +patterns_to_include=\u633F\u5165\u3059\u308B\u30D1\u30BF\u30FC\u30F3 +port=\u30DD\u30FC\u30C8\: +property_default_param=\u521D\u671F\u5024 +property_edit=\u7DE8\u96C6 +property_editor.value_is_invalid_message=\u5165\u529B\u3055\u308C\u305F\u30C6\u30AD\u30B9\u30C8\u306F\u3053\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u306B\u9069\u3057\u3066\u3044\u307E\u305B\u3093\u3002\n\u5143\u306E\u5024\u306B\u623B\u3057\u307E\u3059\u3002 +property_editor.value_is_invalid_title=\u9069\u5207\u3067\u306A\u3044\u5165\u529B +property_name_param=\u30D7\u30ED\u30D1\u30C6\u30A3\u540D +property_undefined=\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u306A\u3044 +protocol=\u30D7\u30ED\u30C8\u30B3\u30EB\: +protocol_java_border=Java \u30AF\u30E9\u30B9 +protocol_java_classname=\u30AF\u30E9\u30B9\u540D\: +protocol_java_config_tile=Java \u30B5\u30F3\u30D7\u30EB\u306E\u8A2D\u5B9A +protocol_java_test_title=Java \u30C6\u30B9\u30C8 +proxy_assertions=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3\u306E\u8FFD\u52A0 +proxy_cl_error=\u30D7\u30ED\u30AD\u30B7\u30FC\u30B5\u30FC\u30D0\u30FC\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u308B\u5834\u5408\u306F\u3001\u30DB\u30B9\u30C8\u540D\u3068\u30DD\u30FC\u30C8\u3082\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +proxy_headers=HTTP\u30D8\u30C3\u30C0\u306E\u53D6\u308A\u8FBC\u307F +proxy_separators=\u30BB\u30D1\u30EC\u30FC\u30BF\u306E\u8FFD\u52A0 +proxy_target=\u5BFE\u8C61\u3068\u306A\u308B\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9\: +proxy_title=HTTP \u30D7\u30ED\u30AD\u30B7\u30B5\u30FC\u30D0 +ramp_up=Ramp-Up \u671F\u9593 (\u79D2)\: +random_control_title=\u4E71\u6570\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +random_order_control_title=\u30E9\u30F3\u30C0\u30E0\u9806\u5E8F\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +read_response_message=\u5FDC\u7B54\u8AAD\u307F\u8FBC\u307F\u304C\u30C1\u30A7\u30C3\u30AF\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5FDC\u7B54\u3092\u898B\u308B\u305F\u3081\u306B\u306F\u30B5\u30F3\u30D7\u30E9\u30FC\u306E\u5FDC\u7B54\u3092\u30C1\u30A7\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +read_response_note=\u5FDC\u7B54\u8AAD\u307F\u8FBC\u307F\u304C\u30C1\u30A7\u30C3\u30AF\u3055\u308C\u3066\u3044\u306A\u3051\u308C\u3070\u3001\u30B5\u30F3\u30D7\u30E9\u30FC\u306F\u5FDC\u7B54\u305B\u305A\u3001 +read_response_note2=SampleResult\u3092\u8A2D\u5B9A\u3057\u307E\u305B\u3093\u3002\u3053\u308C\u306F\u30D1\u30D5\u30A9\u30FC\u30DE\u30F3\u30B9\u3092\u826F\u304F\u3057\u307E\u3059\u304C\u3001 +read_response_note3=\u5FDC\u7B54\u5185\u5BB9\u306F\u30ED\u30B0\u306B\u6B8B\u3055\u308C\u306A\u3044\u3053\u3068\u306B\u306A\u308A\u307E\u3059\u3002 +read_soap_response=SOAP\u30EC\u30B9\u30DD\u30F3\u30B9\u8AAD\u8FBC +record_controller_title=\u8A18\u9332\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +ref_name_field=\u53C2\u7167\u540D\uFF1A +regex_extractor_title=\u6B63\u898F\u8868\u73FE\u62BD\u51FA +regex_field=\u6B63\u898F\u8868\u73FE\uFF1A +regexfunc_param_1=\u76F4\u524D\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u7D50\u679C\u304B\u3089\u691C\u7D22\u3059\u308B\u305F\u3081\u306E\u6B63\u898F\u8868\u73FE\u3067\u3059\u3002 +regexfunc_param_2=\u6587\u5B57\u5217\u3092\u7F6E\u63DB\u3059\u308B\u305F\u3081\u306E\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3067\u3001\u6B63\u898F\u8868\u73FE\u306E\u30B0\u30EB\u30FC\u30D7\u5316\u3092\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002\u66F8\u5F0F\u306F$[group]$\u3002\u4F8B\uFF09 $1$\u3002 +regexfunc_param_3=\u30DE\u30C3\u30C1\u30F3\u30B0\u3067\u4F7F\u7528\u3057\u307E\u3059\u30021\u4EE5\u4E0A\u306E\u6574\u6570\u3001RAND(JMeter\u304C\u30E9\u30F3\u30C0\u30E0\u306B\u9078\u629E\u3059\u308B)\u3001\u6D6E\u52D5\u5C0F\u6570\u70B9\u3001ALL(\u5168\u3066\u306B\u4E00\u81F4\u3059\u308B)\u3001\u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002 +regexfunc_param_4=\u30C6\u30AD\u30B9\u30C8\u306E\u7BC4\u56F2\u3067\u3059\u3002ALL\u304C\u9078\u629E\u3055\u308C\u305F\u5834\u5408\u3001\u7D50\u679C\u3092\u751F\u6210\u3059\u308B\u305F\u3081\u306B\u4F7F\u308F\u308C\u307E\u3059\u3002 +regexfunc_param_5=\u521D\u671F\u30C6\u30AD\u30B9\u30C8\u3067\u3059\u3002\u6B63\u898F\u8868\u73FE\u3068\u4E00\u81F4\u3059\u308B\u6587\u5B57\u5217\u304C\u306A\u304B\u3063\u305F\u5834\u5408\u306B\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u306E\u4EE3\u308F\u308A\u3068\u3057\u3066\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002 +remote_exit=\u7D42\u4E86(\u30EA\u30E2\u30FC\u30C8) +remote_exit_all=\u5168\u3066\u7D42\u4E86(\u30EA\u30E2\u30FC\u30C8) +remote_start=\u958B\u59CB(\u30EA\u30E2\u30FC\u30C8) +remote_start_all=\u5168\u3066\u958B\u59CB(\u30EA\u30E2\u30FC\u30C8) +remote_stop=\u505C\u6B62(\u30EA\u30E2\u30FC\u30C8) +remote_stop_all=\u5168\u3066\u505C\u6B62(\u30EA\u30E2\u30FC\u30C8) +remove=\u524A\u9664 +report=\u30EC\u30DD\u30FC\u30C8 +request_data=\u30EA\u30AF\u30A8\u30B9\u30C8\u30C7\u30FC\u30BF +restart=\u30EA\u30B9\u30BF\u30FC\u30C8 +resultaction_title=\u30A2\u30AF\u30B7\u30E7\u30F3\u30CF\u30F3\u30C9\u30E9\u306E\u7D42\u4E86\u72B6\u614B +resultsaver_prefix=\u30D5\u30A1\u30A4\u30EB\u540D\u306E\u63A5\u982D\u8F9E\: +resultsaver_title=\u5FDC\u7B54\u3092\u30D5\u30A1\u30A4\u30EB\u3078\u4FDD\u5B58 +root=\u30EB\u30FC\u30C8 +root_title=\u30EB\u30FC\u30C8 +run=\u5B9F\u884C +running_test=\u30C6\u30B9\u30C8\u5B9F\u884C\u4E2D +sampler_on_error_action=\u30B5\u30F3\u30D7\u30E9\u30FC\u30A8\u30E9\u30FC\u5F8C\u306E\u30A2\u30AF\u30B7\u30E7\u30F3 +sampler_on_error_continue=\u7D9A\u884C +sampler_on_error_stop_test=\u30C6\u30B9\u30C8\u505C\u6B62 +sampler_on_error_stop_thread=\u30B9\u30EC\u30C3\u30C9\u505C\u6B62 +save=\u30C6\u30B9\u30C8\u8A08\u753B\u3092\u4FDD\u5B58 +save?=\u4FDD\u5B58? +save_all_as=\u30C6\u30B9\u30C8\u8A08\u753B\u306B\u540D\u524D\u3092\u3064\u3051\u3066\u4FDD\u5B58 +save_as=\u5225\u540D\u3067\u4FDD\u5B58... +scheduler=\u30B9\u30B1\u30B8\u30E5\u30FC\u30E9 +scheduler_configuration=\u30B9\u30B1\u30B8\u30E5\u30FC\u30E9\u8A2D\u5B9A +search_base=\u691C\u7D22\u57FA\u6E96 +search_filter=\u691C\u7D22\u30D5\u30A3\u30EB\u30BF +search_test=\u30C6\u30B9\u30C8\u306E\u691C\u7D22 +second=\u79D2 +secure=\u30BB\u30AD\u30E5\u30A2 +send_file=\u30EA\u30AF\u30A8\u30B9\u30C8\u3068\u4E00\u7DD2\u306B\u9001\u4FE1\u3055\u308C\u308B\u30D5\u30A1\u30A4\u30EB\: +send_file_browse=\u53C2\u7167... +send_file_filename_label=\u30D5\u30A1\u30A4\u30EB\u540D\: +send_file_mime_label=MIME \u30BF\u30A4\u30D7\: +send_file_param_name_label=\u30D1\u30E9\u30E1\u30FC\u30BF\u540D\: +server=\u30B5\u30FC\u30D0\u540D\u307E\u305F\u306F IP\: +servername=\u30B5\u30FC\u30D0\u540D\uFF1A +session_argument_name=\u30BB\u30C3\u30B7\u30E7\u30F3\u5F15\u6570\u540D +shutdown=\u30B7\u30E3\u30C3\u30C8\u30C0\u30A6\u30F3 +simple_config_element=\u30B7\u30F3\u30D7\u30EB\u8A2D\u5B9A\u30A8\u30EC\u30E1\u30F3\u30C8 +simple_data_writer_title=\u30B7\u30F3\u30D7\u30EB\u30C7\u30FC\u30BF\u30E9\u30A4\u30BF +size_assertion_comparator_error_equal=\u7B49\u3057\u3044 +size_assertion_comparator_error_greater=\u5927\u306A\u308A\u5C0F +size_assertion_comparator_error_greaterequal=\u4EE5\u4E0A +size_assertion_comparator_error_less=\u5C0F\u306A\u308A\u5927 +size_assertion_comparator_error_lessequal=\u4EE5\u4E0B +size_assertion_comparator_error_notequal=\u7B49\u3057\u304F\u306A\u3044 +size_assertion_comparator_label=\u6BD4\u8F03\u306E\u578B +size_assertion_failure=\u7D50\u679C\u306F\u6B63\u3057\u304F\u306A\u3044\u30B5\u30A4\u30BA\u3067\u3059\u3002\:{0}\u30D0\u30A4\u30C8\u3067\u3059\u304C\u3001{1}\u30D0\u30A4\u30C8\u304B{2}\u30D0\u30A4\u30C8\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093\u3002 +size_assertion_input_error=\u9069\u5207\u306A\u6B63\u306E\u6574\u6570\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +size_assertion_label=\u30D0\u30A4\u30C8\u30B5\u30A4\u30BA\: +size_assertion_size_test=\u30A2\u30B5\u30FC\u30C8\u306E\u30B5\u30A4\u30BA +size_assertion_title=\u30B5\u30A4\u30BA\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +soap_action=Soap\u30A2\u30AF\u30B7\u30E7\u30F3 +soap_data_title=Soap/XML-RPC \u30C7\u30FC\u30BF +soap_sampler_title=Soap/XML-RPC\u30EA\u30AF\u30A8\u30B9\u30C8 +spline_visualizer_average=\u5E73\u5747 +spline_visualizer_incoming=\u5230\u7740 +spline_visualizer_maximum=\u6700\u5927 +spline_visualizer_minimum=\u6700\u5C0F +spline_visualizer_title=\u30B9\u30D7\u30E9\u30A4\u30F3\u30D3\u30B8\u30E5\u30A2\u30E9\u30A4\u30B6 +spline_visualizer_waitingmessage=\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u958B\u59CB\u307E\u3067\u304A\u5F85\u3061\u4E0B\u3055\u3044\u3002 +ssl_alias_prompt=\u5B9A\u7FA9\u6E08\u307F\u306E\u30A8\u30A4\u30EA\u30A2\u30B9\u3092\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002 +ssl_alias_select=\u30C6\u30B9\u30C8\u3059\u308B\u30A8\u30A4\u30EA\u30A2\u30B9\u3092\u9078\u629E\u3057\u3066\u4E0B\u3055\u3044\u3002 +ssl_alias_title=\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u30A8\u30A4\u30EA\u30A2\u30B9 +ssl_error_title=\u30AD\u30FC\u30B9\u30C8\u30A2\u30A8\u30E9\u30FC +ssl_pass_prompt=\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002 +ssl_pass_title=\u30AD\u30FC\u30B9\u30C8\u30A2\u30D1\u30B9\u30EF\u30FC\u30C9 +ssl_port=SSL\u30DD\u30FC\u30C8 +sslmanager=SSL \u30DE\u30CD\u30FC\u30B8\u30E3 +start=\u958B\u59CB +starttime=\u958B\u59CB\u6642\u523B +stop=\u505C\u6B62 +stopping_test=\u3059\u3079\u3066\u306E\u30C6\u30B9\u30C8\u7528\u30B9\u30EC\u30C3\u30C9\u3092\u505C\u6B62\u4E2D\u3067\u3059\u3002\u3057\u3070\u3089\u304F\u304A\u5F85\u3061\u304F\u3060\u3055\u3044\u3002 +stopping_test_title=\u30C6\u30B9\u30C8\u306E\u505C\u6B62\u4E2D +string_from_file_file_name=\u30D5\u30A1\u30A4\u30EB\u306E\u30D5\u30EB\u30D1\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044 +string_from_file_seq_final=\u6700\u7D42\u30D5\u30A1\u30A4\u30EB\u30B7\u30FC\u30B1\u30F3\u30B9\u756A\u53F7 +string_from_file_seq_start=\u958B\u59CB\u30D5\u30A1\u30A4\u30EB\u30B7\u30FC\u30B1\u30F3\u30B9\u756A\u53F7 +summariser_title=\u7D50\u679C\u306E\u6982\u8981\u3092\u751F\u6210 +tcp_config_title=TCP\u30B5\u30F3\u30D7\u30E9\u30FC\u8A2D\u5B9A +tcp_nodelay=\u9045\u5EF6\u306A\u3057\u3092\u8A2D\u5B9A +tcp_port=\u30DD\u30FC\u30C8\u756A\u53F7\: +tcp_request_data=\u9001\u4FE1\u3059\u308B\u30C6\u30AD\u30B9\u30C8 +tcp_sample_title=TCP\u30B5\u30F3\u30D7\u30E9\u30FC +tcp_timeout=\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\: +template_field=\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\uFF1A +test=\u30C6\u30B9\u30C8 +test_configuration=\u30C6\u30B9\u30C8\u8A2D\u5B9A +test_plan=\u30C6\u30B9\u30C8\u8A08\u753B +testplan.serialized=\u5404\u30B9\u30EC\u30C3\u30C9\u30B0\u30EB\u30FC\u30D7\u3092\u5225\u3005\u306B\u5B9F\u884C +testplan_comments=\u30B3\u30E1\u30F3\u30C8\: +thread_delay_properties=\u30B9\u30EC\u30C3\u30C9\u9045\u5EF6\u6642\u9593\u30D7\u30ED\u30D1\u30C6\u30A3 +thread_group_title=\u30B9\u30EC\u30C3\u30C9\u30B0\u30EB\u30FC\u30D7 +thread_properties=\u30B9\u30EC\u30C3\u30C9\u30D7\u30ED\u30D1\u30C6\u30A3 +threadgroup=\u30B9\u30EC\u30C3\u30C9\u30B0\u30EB\u30FC\u30D7 +throughput_control_bynumber_label=\u5168\u4F53\u5B9F\u884C +throughput_control_bypercent_label=\u30D1\u30FC\u30BB\u30F3\u30C8\u5B9F\u884C +throughput_control_perthread_label=\u30E6\u30FC\u30B6\u30FC\u3054\u3068 +throughput_control_title=\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +throughput_control_tplabel=\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8 +transaction_controller_title=\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +uniform_timer_delay=\u9045\u5EF6\u6642\u9593\u30AA\u30D5\u30BB\u30C3\u30C8\u5B9A\u6570 (\u30DF\u30EA\u79D2)\: +uniform_timer_memo=\u4E00\u69D8\u5206\u5E03\u306B\u3088\u308B\u30E9\u30F3\u30C0\u30E0\u306A\u9045\u5EF6\u3092\u8FFD\u52A0 +uniform_timer_range=\u6700\u5927\u9045\u5EF6\u6642\u9593 (\u30DF\u30EA\u79D2)\: +uniform_timer_title=\u4E00\u69D8\u4E71\u6570\u30BF\u30A4\u30DE +update_per_iter=\u7E70\u308A\u8FD4\u3057\u3054\u3068\u306B\u66F4\u65B0 +upload=\u30D5\u30A1\u30A4\u30EB\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9 +upper_bound=\u4E0A\u9650 +url_config_protocol=\u30D7\u30ED\u30C8\u30B3\u30EB\: +url_config_title=HTTP \u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +url_full_config_title=UrlFull \u30B5\u30F3\u30D7\u30EB +url_multipart_config_title=HTTP\u30DE\u30EB\u30C1\u30D1\u30FC\u30C8\u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +use_keepalive=KeepAlive \u3092\u6709\u52B9\u306B\u3059\u308B +use_recording_controller=\u8A18\u9332\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9\u306E\u4F7F\u7528 +user=\u30E6\u30FC\u30B6\u30FC +user_defined_test=\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u30C6\u30B9\u30C8 +user_defined_variables=\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5909\u6570 +user_param_mod_help_note=(\u5909\u66F4\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\u5909\u66F4\u3059\u308B\u5834\u5408\u306F\u3001JMeter\u306E/bin\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u3042\u308B\u540C\u540D\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u5909\u66F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002) +user_parameters_table=\u30D1\u30E9\u30E1\u30FC\u30BF +user_parameters_title=\u30E6\u30FC\u30B6\u30FC\u30D1\u30E9\u30E1\u30FC\u30BF +username=\u30E6\u30FC\u30B6\u30FC\u540D +value=\u5024 +var_name=\u53C2\u7167\u540D +view_graph_tree_title=\u7D50\u679C\u3092\u30B0\u30E9\u30D5\u3068\u30C4\u30EA\u30FC\u3067\u8868\u793A +view_results_in_table=\u7D50\u679C\u3092\u8868\u3067\u8868\u793A +view_results_tab_request=\u30EA\u30AF\u30A8\u30B9\u30C8 +view_results_tab_response=\u5FDC\u7B54\u30C7\u30FC\u30BF +view_results_title=\u7D50\u679C\u8868\u793A +view_results_tree_title=\u7D50\u679C\u3092\u30C4\u30EA\u30FC\u3067\u8868\u793A +web_request=HTTP \u30EA\u30AF\u30A8\u30B9\u30C8 +web_server=Web \u30B5\u30FC\u30D0 +web_server_domain=\u30B5\u30FC\u30D0\u540D\u307E\u305F\u306F IP\: +web_server_port=\u30DD\u30FC\u30C8\u756A\u53F7\: +web_testing_retrieve_images=\u5168\u3066\u306E\u30A4\u30E1\u30FC\u30B8\u3068\u30A2\u30D7\u30EC\u30C3\u30C8\u3092\u7E70\u308A\u8FD4\u3057\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3059\u308B(HTML \u30D5\u30A1\u30A4\u30EB\u306E\u307F) +web_testing_title=HTTP \u30EA\u30AF\u30A8\u30B9\u30C8 +webservice_proxy_host=\u30D7\u30ED\u30AD\u30B7\u30DB\u30B9\u30C8 +webservice_proxy_note=HTTP\u30D7\u30ED\u30AD\u30B7\u304C\u30C1\u30A7\u30C3\u30AF\u3055\u308C\u3066\u3044\u308B\u5834\u5408\u3001\u30DB\u30B9\u30C8\u540D\u3068\u30DD\u30FC\u30C8\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u306A\u3044\u3068\u30B5\u30F3\u30D7\u30E9\u30FC\u306F +webservice_proxy_note2=\u30B3\u30DE\u30F3\u30C9\u30E9\u30A4\u30F3\u30AA\u30D7\u30B7\u30E7\u30F3\u3092\u53C2\u7167\u3057\u307E\u3059\u3002\u30D7\u30ED\u30AD\u30B7\u30DB\u30B9\u30C8\u3084\u30D7\u30ED\u30AD\u30B7\u30DD\u30FC\u30C8\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u306A\u3044\u5834\u5408\u306F +webservice_proxy_note3=\u3060\u307E\u3063\u305F\u307E\u307E\u7D42\u4E86\u3057\u307E\u3059\u3002 +webservice_proxy_port=\u30D7\u30ED\u30AD\u30B7\u30DD\u30FC\u30C8 +webservice_sampler_title=Web\u30B5\u30FC\u30D3\u30B9(SOAP)\u30EA\u30AF\u30A8\u30B9\u30C8 (DEPRECATED) +webservice_soap_action=Soap\u30A2\u30AF\u30B7\u30E7\u30F3 +webservice_use_proxy=HTTP\u30D7\u30ED\u30AD\u30B7\u306E\u4F7F\u7528 +workbench_title=\u30EF\u30FC\u30AF\u30D9\u30F3\u30C1 +wsdl_helper_error=WSDL\u304C\u4E0D\u9069\u5207\u3067\u3059\u3002URL\u3092\uFF12\u91CD\u30C1\u30A7\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +wsdl_url_error=WSDL\u304C\u7A7A\u3067\u3059\u3002 +xml_assertion_title=XML \u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +you_must_enter_a_valid_number=\u9069\u5207\u306A\u6570\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044 diff --git a/src/core/org/apache/jmeter/resources/messages_no.properties b/src/core/org/apache/jmeter/resources/messages_no.properties new file mode 100644 index 00000000000..eab9a7ca8d9 --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_no.properties @@ -0,0 +1,155 @@ +# 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. + +about=Om Apache JMeter +add=Legg til +add_pattern=Legg til m\u00F8nster\: +add_value=Legg til verdi +analyze=Analyser data fil... +assertion_contains=Inneholder +assertion_matches=Matcher +assertion_not=Ikke +assertion_pattern_match_rules=M\u00F8nster matching regler +assertion_patterns_to_test=M\u00F8nster \u00E5 teste +assertion_resp_field=Svarfelt \u00E5 teste +assertion_text_resp=Tektssvar +auth_base_url=Basis URL +auth_manager_title=HTTP autentiseringsmanager +auths_stored=Autentiseringer lagret hos autentiseringsmanager +browse=Bla gjennom +clear_all=Nullstill alle +clear=Nullstill +config_element=Konfigurasjonselement +constant_timer_delay=Tr\u00E5dforsinkelse (i millisekund)\: +constant_timer_title=Konstant timer +controller=Kontroller +cookies_stored=Cookies lagret hos cookie manager +database_sql_query_string=SQL foresp\u00F8rsel\: +database_sql_query_title=JDBC SQL foresp\u00F8rsel standard instillinger +default_parameters=Standard parametre +delete=Slett +domain=Domene +edit=Rediger +endtime=EndTime +exit=Avslutt +expiration=Utl\u00F8per +file=Fil +file_visualizer_append=Legg til en eksisterende fil +file_visualizer_auto_flush=Automatisk t\u00F8mming etter hver m\u00E5ledata +file_visualizer_browse=Bla gjennom... +file_visualizer_close=Lukk +file_visualizer_filename=Skriv et nytt filnavn, eller bla gjennom til en eksisterende fil +file_visualizer_file_options=Fil egenskaper +file_visualizer_flush=T\u00F8m +file_visualizer_missing_filename=Ingen utfil spesifisert. +file_visualizer_open=\u00C5pne +file_visualizer_output_file=Skriv alle data til en fil +file_visualizer_submit_data=Inkluder sendte data +file_visualizer_title=Filrapport\u00F8r +file_visualizer_verbose=Utf\u00F8rlig output +ftp_sample_title=FTP foresp\u00F8rsel standard instillinger +ftp_testing_title=FTP foresp\u00F8rsel +gaussian_timer_delay=Konstant forsinkelsesoffset (i millisekund)\: +gaussian_timer_range=Avvik (i millisekund)\: +gaussian_timer_title=Gaussisk tilfeldig timer +graph_full_results_title=Graf full resultater +graph_results_average=Gjennomsnitt +graph_results_deviation=Avvik +graph_results_title=Graf resultater +headers_stored=Headere lagret hos headermanager +help=Hjelp +infinite=Uendelig +interleave_control_title=Vekslende kontroller +iterator_num=L\u00F8kketeller\: +jndi_config_title=JNDI konfigurasjon +jndi_lookup_title=JNDI lookup konfigurasjon +jndi_method_home_name=Home metode navn +jndi_method_home_parms=Home metode parametre +jndi_method_name=Metode konfigurasjon +jndi_method_remote_name=Remote metode navn +jndi_method_remote_parms=Remote metode parametre +jndi_method_title=Remote metode konfigurasjon +jndi_testing_title=JNDI foresp\u00F8rsel +jndi_url_jndi_props=JNDI egenskaper +load=Hent +logic_controller_title=Enkel kontroller +login_config=Innlogging konfigurasjon +loop_controller_title=L\u00F8kke kontroller +looping_control=L\u00F8kkekontroll +menu_edit=Rediger +method=Metode\: +modification_manager_title=Modifiseringsmanager +name=Navn\: +number_of_threads=Antall tr\u00E5der\: +once_only_controller_title=En gang kontroller +open=\u00C5pne... +optional_tasks=Valgfrie oppgaver +option=Innstillinger +paramtable=Send parametre med foresp\u00F8rselen\: +password=Passord +path=Sti\: +patterns_to_exclude=URL M\u00F8nster \u00E5 ekskludere +patterns_to_include=URL M\u00F8nster \u00E5 inkludere +protocol=Protokoll\: +proxy_title=HTTP proxy server +ramp_up=Oppstartsperiode (i sekunder)\: +random_control_title=Random kontroller +remote_start=Remote start +remote_stop=Remote stopp +remove=Fjern +report=Rapport +root=Rot +root_title=Rot +run=Kj\u00F8r +save_all_as=Lagre alle som... +save_as=Lagre som... +save_as_image=Lagre som Image +save=Lagre alle +secure=Sikker +send_file_browse=Bla gjennom... +send_file_filename_label=Filnavn\: +send_file_mime_label=MIME type\: +send_file_param_name_label=Parameter navn\: +send_file=Send en fil med foresp\u00F8rselen\: +server=Server navn eller IP\: +spline_visualizer_average=Gjennomsnitt +spline_visualizer_incoming=Innkommende +spline_visualizer_maximum=Maksimum +spline_visualizer_title=Spline visualiserer +spline_visualizer_waitingmessage=Venter p\u00E5 m\u00E5linger +ssl_alias_prompt=Tast inn ditt preferred alias +ssl_alias_select=Velg ditt alias for testeen +ssl_alias_title=klient alias +sslmanager=SSL manager +ssl_pass_prompt=Tast inn ditt passord +starttime=StartTime +stop=Stopp +thread_delay_properties=Tr\u00E5dforsinkelse egenskaper +thread_group_title=Tr\u00E5dgruppe +threadgroup=Tr\u00E5dgruppe +uniform_timer_delay=Konstant forsinkelsesoffset (i millisekund)\: +uniform_timer_range=Tilfeldig forsinkelse maksimum (i millisekund)\: +uniform_timer_title=Uniform tilfeldig timer +upload=Fil opplasting +url_config_title=HTTP foresp\u00F8rsel standard instillinger +username=Brukernavn +value=Verdi +view_results_title=Vis resultat +view_results_tree_title=Vis resultattre +web_server_domain=Server navn eller IP\: +web_server_port=Port nummer\: +web_testing_retrieve_images=Hent alle bilder og Java Applets (kun HTML) +web_testing_title=HTTP foresp\u00F8rsel +workbench_title=Arbeidsbenk diff --git a/src/core/org/apache/jmeter/resources/messages_pl.properties b/src/core/org/apache/jmeter/resources/messages_pl.properties new file mode 100644 index 00000000000..5f375af18d0 --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_pl.properties @@ -0,0 +1,239 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +# 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. +# Warning: JMeterUtils.getResString() replaces space with '_' +# and converts keys to lowercase before lookup +#=> All keys in this file must also be lower case or they won't match +# +about=O programie Apache JMeter +add=Dodaj +add_parameter=Dodaj parametr +add_pattern=Dodaj wzorzec: +add_test=Dodaj test +add_user=Dodaj u\u017Cytkownika +add_value=Dodaj warto\u015B\u0107 +addtest=Dodaj test +aggregate_graph=Wykresy statystyczne +aggregate_graph_column=Kolumna +aggregate_graph_display=Wy\u015Bwietl wykres +aggregate_graph_height=Wysoko\u015B\u0107 +aggregate_graph_max_length_xaxis_label=Maksymalna wysoko\u015B\u0107 etykiety osi OX +aggregate_graph_ms=Milisekund +aggregate_graph_response_time=Czas odpowiedzi +aggregate_graph_save=Zapisz wykres +aggregate_graph_save_table=Zapisz dane z tabeli +aggregate_graph_save_table_header=Zapisz nag\u0142\u00F3wek tabeli +aggregate_graph_title=Wykres skumulowany +aggregate_graph_user_title=Tytu\u0142 wykresu +aggregate_graph_width=Szeroko\u015B\u0107 +aggregate_report=Dane zagregowane +aggregate_report_bandwidth=KB/sek +aggregate_report_count=Liczba pr\u00F3bek +aggregate_report_error=B\u0142\u0105d +aggregate_report_error%=% b\u0142\u0119d\u00F3w +aggregate_report_median=Mediana +aggregate_report_rate=Przepustowo\u015B\u0107 +aggregate_report_stddev=Odch. std. +aggregate_report_total_label=RAZEM +ajp_sampler_title=Pr\u00F3bnik AJP/1.3 +analyze=Analizuj plik z danymi... +anchor_modifier_title=Parser link\u00F3w HTML +argument_must_not_be_negative=Parametr musi by\u0107 nieujemny! +assertion_assume_success=Ignoruj status +assertion_code_resp=Kod odpowiedzi +assertion_contains=Zawiera +assertion_equals=R\u00F3wna si\u0119 +assertion_headers=Nag\u0142\u00F3wki odpowiedzi +assertion_matches=Pasuje do +assertion_message_resp=Tre\u015B\u0107 odpowiedzi +assertion_not=Nie +assertion_text_resp=Tekst odpowiedzi +assertion_textarea_label=Asercje: +attribute=Atrybut +attrs=Atrybuty +average=\u015Arednia +average_bytes=bit\u00F3w \u015Brednio +browse=Przegl\u0105daj... +bsf_sampler_title=Pr\u00F3bnik BSF +bsf_script_language=J\u0119zyk skryptowy: +bsf_script_parameters=Parametry do przekazania do skryptu/pliku: +bsh_assertion_script=Skrypt (see below for variables that are defined) +bsh_script=Skrypt (see below for variables that are defined) +bsh_script_file=Plik ze skryptem +cancel=Anuluj +choose_function=Wybierz funkcj\u0119 +choose_language=Wybierz j\u0119zyk +clear=Wyczy\u015B\u0107 +clear_all=Wyczy\u015B\u0107 wszystko +clear_cache_per_iter=Czy\u015Bci\u0107 cache po ka\u017Cdej iteracji? +column_delete_disallowed=Tej kolumny nie mo\u017Cna usuwa\u0107 +compare=Por\u00F3wnaj +config_element=Element konfiguruj\u0105cy +config_save_settings=Konfiguruj +configure_wsdl=Konfiguruj +controller=Kontroler +copy=Kopiuj +counter_config_title=Licznik +counter_per_user=Osobny licznik dla ka\u017Cdego u\u017Cytkownika +countlim=Limit rozmiaru +cut=Wytnij +de=Niemiecki +debug_off=Wy\u0142\u0105cz debugowanie +debug_on=W\u0142\u0105cz debugowanie +default_parameters=Parametry domy\u015Blne +default_value_field=Domy\u015Blna warto\u015B\u0107: +delay=Uruchom w ci\u0105gu (sekund) +delete=Usu\u0144 +delete_parameter=Usu\u0144 parametr +delete_test=Usu\u0144 test +delete_user=Usu\u0144 u\u017Cytkownika +deltest=Test usuwania +disable=Wy\u0142\u0105cz +distribution_graph_title=Rozk\u0142ad funkcji g\u0119sto\u015Bci (alpha) +distribution_note1=Wykres uaktualnia si\u0119 automatycznie co 10 pr\u00F3bek +domain=Domena +done=Gotowe +duration=Czas trwania (sekund) +edit=Edytuj +en=Angielski +enable=W\u0142\u0105cz +endtime=Czas zako\u0144czenia +error_loading_help=Wyst\u0105pi\u0142 b\u0142\u0105d przy pr\u00F3bie wy\u015Bwietlenia strony z pomoc\u0105 +error_occurred=Wyst\u0105pi\u0142 b\u0142\u0105d +error_title=B\u0142\u0105d +es=Hiszpa\u0144ski +exit=Edycja +file=Plik +file_visualizer_close=Zamknij +file_visualizer_filename=Nazwa pliku +file_visualizer_flush=Zapisz +file_visualizer_open=Otw\u00f3rz +file_visualizer_output_file=Zapisuj/Czytaj wyniki z pliku +filename=Nazwa pliku +functional_mode=Tryb test\u00F3w funkcjonalnych (i.e. zapisuj dane wej\u015bciowe i wyj\u015bciowe pr\u00f3bnika) +functional_mode_explanation=W\u0142\u0105czenie trybu test\u00F3w funkcjonalnych mo\u017Ce wyra\u017Anie obni\u017Cy\u0107 wydajno\u015B\u0107. +help=Pomoc +help_node=Co to za w\u0119ze\u0142? +iterator_num=Liczba powt\u00F3rze\u0144: +ja=Japo\u0144ski +jms_client_type=Klient +jms_config=Konfiguracja +jms_config_title=Konfiguracja JMS +jms_message_type=Typ wiadomo\u015Bci +jms_pwd=Has\u0142o +jms_queue=Kolejka +jms_user=U\u017Cytkownik +load=Wczytaj +load_wsdl=Wczytaj WSDL +log_errors_only=B\u0142\u0119dy +log_file=Po\u0142o\u017Cenie pliku log\u00F3w +log_function_comment=Dodatkowy komentarz (opcjonalnie) +log_function_level=Szczeg\u00F3\u0142owo\u015B\u0107 logowania (domy\u015Blnie INFO) lub OUT lub ERR +log_only=Zapisuj do loga/Wy\u015bwietlaj tylko: +log_success_only=Sukcesy +mail_reader_account=U\u017Cytkownik: +mail_reader_password=Has\u0142o: +mail_reader_server=Serwer: +mail_reader_server_type=Typ serwera: +mail_sent=Wiadomo\u015B\u0107 zosta\u0142a wys\u0142ana +max=Maksimum +menu_assertions=Assercje +menu_close=Zamknij +menu_collapse_all=Zwi\u0144 wszystko +menu_config_element=Element konfiguruj\u0105cy +menu_edit=Edytuj +menu_expand_all=Rozwi\u0144 wszystko +menu_listener=S\u0142uchacze +menu_merge=Scal +menu_open=Otw\u00F3rz +menu_post_processors=Post Procesory +menu_pre_processors=Pre Procesory +name=Nazwa: +number_of_threads=Liczba w\u0105tk\u00F3w (u\u017Cytkownik\u00F3w): +open=Otw\u00F3rz... +option=Opcje +password=Has\u0142o +paste=Wklej +pl=Polski +property_default_param=Warto\u015B\u0107 domy\u015Blna +property_edit=Edytuj +property_undefined=Niezdefiniowano +property_value_param=Warto\u015B\u0107 parametru +proxy_assertions=Dodaj asercje +proxy_headers=Przechwytuj nag\u0142\u00F3wki HTTP +proxy_sampler_type=Typ: +proxy_title=Serwer proxy HTTP +ramp_up=Uruchom w ci\u0105gu (sekund): +regex_field=Wyra\u017Cenie regularne: +remove=Usu\u0144 +report_chart_x_axis=O\u015B X +report_chart_x_axis_label=Etykieta osi X +report_chart_y_axis=O\u015B Y +report_chart_y_axis_label=Etykieta osi Y +report_line_graph=Wykres liniowy +report_page_title=Tytu\u0142 strony +reset_gui=Resetuj Gui +resultsaver_prefix=Prefiks do nazwy pliku: +revert_project=Cofnij +run=Uruchom +sample_scope=Kt\u00F3re pr\u00F3bki testowa\u0107 +sampler_label=Etykieta +sampler_on_error_action=Co robi\u0107 je\u015Bli Pr\u00F3bnik zg\u0142osi b\u0142\u0105d? +sampler_on_error_continue=Kontunuuj +sampler_on_error_stop_test=Przerwij test +sampler_on_error_stop_test_now=Natychmiast przerwij test +sampler_on_error_stop_thread=Przerwij w\u0105tek +save=Zapisz +save?=Zapisa\u0107? +save_all_as=Zapisz plan test\u00F3w jako +save_as=Zapisz zaznaczenie jako... +save_as_image=Zapisz w\u0119ze\u0142 jako obrazek +save_as_image_all=Zapisz ekran jako obrazek +save_asxml=Zapisz jako XML +save_bytes=Zapisz liczb\u0119 bit\u00F3w +save_code=Zapisz kod odpowiedzi +save_datatype=Zapisz typ danych +save_encoding=Zapisz stron\u0119 kodow\u0105 +save_fieldnames=Zapisz nazwy kolumn (CSV) +scheduler=Kalendarz +scheduler_configuration=Konfiguracja kalendarza +scope=Zakres +second=sekund +send_file_browse=Przegl\u0105daj ... +send_file_filename_label=\u015acie\u017cka do pliku: +ssl_pass_prompt=Prosz\u0119 wpisz has\u0142o +template_field=Szablon: +test_action_duration=Czas trwania (milisekund) +test_action_pause=Pauza +test_plan=Plan test\u00F3w +test_plan_classpath_browse=Dodaj katalog lub jara do classpatha +testplan.serialized=Uruchamiaj grupy w\u0105tk\u00F3w jedna po drugiej (tzn. jedn\u0105 na raz) +testplan_comments=Uwagi: +thread_group_title=Grupa w\u0105tk\u00F3w +thread_properties=W\u0105tki +threadgroup=Grupa w\u0105tk\u00F3w +user_defined_test=Test zdefiniowany przez u\u017Cytkownika +user_defined_variables=Zmienne zdefiniowane przez u\u017Cytkownika +user_parameters_table=Parametry +user_parameters_title=Parametry u\u017Cytkownika +userdn=U\u017Cytkownik +username=U\u017Cytkownik +userpw=Has\u0142o +value=Warto\u015B\u0107: +workbench_title=Brudnopis +xpath_tidy_show_warnings=Pokazuj ostrze\u017Cenia +you_must_enter_a_valid_number=Tu trzeba wpisa\u0107 liczb\u0119 +zh_cn=Chi\u0144ski (Uproszczony) +zh_tw=Chi\u0144ski (Tradycyjny) diff --git a/src/core/org/apache/jmeter/resources/messages_pt_BR.properties b/src/core/org/apache/jmeter/resources/messages_pt_BR.properties new file mode 100644 index 00000000000..cc3301f1539 --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_pt_BR.properties @@ -0,0 +1,899 @@ +# 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. + +# Warning: JMeterUtils.getResString() replaces space with '_' +# and converts keys to lowercase before lookup +# => All keys in this file must also be lower case or they won't match +# + +# Please add new entries in alphabetical order + +about=Sobre Apache JMeter +add=Adicionar +add_as_child=Adicionar como filho +add_parameter=Adicionar Var\u00E1vel +add_pattern=Adicionar Padr\u00E3o\: +add_test=Adicionar Teste +add_user=Adicionar Usu\u00E1rio +add_value=Adicionar Valor +addtest=Adicionar teste +aggregate_graph=Gr\u00E1ficos Estat\u00EDsticos +aggregate_graph_column=Coluna +aggregate_graph_display=Exibir Gr\u00E1fico +aggregate_graph_height=Altura +aggregate_graph_max_length_xaxis_label=Largura m\u00E1xima do r\u00F3tulo do eixo x +aggregate_graph_ms=Milisegundos +aggregate_graph_response_time=Tempo de Tesposta +aggregate_graph_save=Salvar Gr\u00E1fico +aggregate_graph_save_table=Salvar Dados da Tabela +aggregate_graph_save_table_header=Salvar Cabe\u00E7alho da Tabela +aggregate_graph_title=Gr\u00E1fico Agregado +aggregate_graph_use_group_name=Incluir nome do grupo no r\u00F3tulo? +aggregate_graph_user_title=T\u00EDtulo para o Gr\u00E1fico +aggregate_graph_width=Largura +aggregate_report=Relat\u00F3rio Agregado +aggregate_report_bandwidth=KB/s +aggregate_report_count=\# Amostras +aggregate_report_error=Erro +aggregate_report_error%=% de Erro +aggregate_report_max=M\u00E1x. +aggregate_report_median=Mediana +aggregate_report_min=M\u00EDn. +aggregate_report_rate=Vaz\u00E3o +aggregate_report_stddev=Desvio Padr\u00E3o +ajp_sampler_title=Testador AJP/1.3 +als_message=Nota\: O Processador de Logs de Acesso \u00E9 gen\u00E9rico em seu projeto e permite que voc\u00EA o especialize +als_message2=seu pr\u00F3prio processador. Para tanto, implementar o LogParser, e adicionar o arquivo jar +als_message3=diret\u00F3rio /lib e entre com a classe no testador +analyze=Analizar Arquivo de Dados... +anchor_modifier_title=Processador de Links HTML +appearance=Apar\u00EAncia +argument_must_not_be_negative=O Argumento n\u00E3o pode ser negativo\! +assertion_assume_success=Ignorar estado +assertion_code_resp=C\u00F3digo de Resposta +assertion_contains=Cont\u00E9m +assertion_equals=Igual +assertion_headers=Cabe\u00E7alhos da Resposta +assertion_matches=Combina +assertion_message_resp=Mensagem da Resposta +assertion_not=N\u00E3o +assertion_pattern_match_rules=Regras para Combina\u00E7\u00E3o de Padr\u00F5es +assertion_patterns_to_test=Padr\u00F5es a serem Testados +assertion_resp_field=Testar que Campo da Resposta +assertion_text_resp=Resposta de Texto +assertion_textarea_label=Asser\u00E7\u00F5es\: +assertion_title=Asser\u00E7\u00F5es de Resposta +assertion_url_samp=URL Amostrada +assertion_visualizer_title=Resultados de Asser\u00E7\u00E3o +attribute=Atributo +attrs=Atributos +auth_base_url=URL Base +auth_manager_title=Gerenciador de Autoriza\u00E7\u00E3o HTTP +auths_stored=Autoriza\u00E7\u00F5es Armazenadas no Gerenciador de Autoriza\u00E7\u00E3o +average=M\u00E9dia +average_bytes=M\u00E9dia de Bytes +bind=Liga\u00E7\u00E3o do Usu\u00E1rio Virtual +browse=Procurar... +bsf_sampler_title=Testador BSF +bsf_script=Script a ser executado (vari\u00E1veis\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsf_script_file=Arquivo de script a ser executado +bsf_script_language=Linguagem de scripting\: +bsf_script_parameters=Par\u00E2metros a serem passados ao script/arquivo\: +bsh_assertion_script=Script (veja abaixo quais vari\u00E1veis que est\u00E3o definidas) +bsh_assertion_script_variables=As seguintes vari\u00E1veis est\u00E3o definidas para o script\:\nEscrita/Leitura\: Failure, FailureMessage, SampleResult, vars, props, log.\nSomente Leitura\: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData, ctx +bsh_assertion_title=Asser\u00E7\u00E3o BeanShell +bsh_function_expression=Express\u00E3o a ser avaliada +bsh_sampler_title=Testador BeanShell +bsh_script=Script (veja abaixo quais vari\u00E1veis est\u00E3o definidas) +bsh_script_file=Arquivo de script +bsh_script_parameters=Par\u00E2metros (\=> String Parameters e String []bsh.args) +bsh_script_reset_interpreter=Reiniciar bsh.Interpreter antes de cada chamada +bsh_script_variables=As seguintes vari\u00E1veis est\u00E3o definidas para o script\:\nSampleResult, RespondeCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=Eu estou ocupado testando, por favor pare o teste antes de alterar as configura\u00E7\u00F5es +cache_manager_title=Gerenciador de Cache HTTP +cache_session_id=Fazer cache do ID da sess\u00E3o? +cancel=Cancelar +cancel_exit_to_save=Existem itens n\u00E3o salvos. Voc\u00EA deseja salvar antes de sair? +cancel_new_to_save=Existem itens n\u00E3o salvos. Voc\u00EA deseja salvar antes de limpar o plano de teste? +cancel_revert_project=Existem itens n\u00E3o salvos. Voc\u00EA deseja reverter para o plano de teste salvo previamente? +char_value=N\u00FAmero unicode do caracter (decimal ou 0xhex) +choose_function=Escolher Fun\u00E7\u00E3o +choose_language=Escolher Linguagem +clear=Limpar +clear_all=Limpar Tudo +clear_cache_per_iter=Limpar cache a cada itera\u00E7\u00E3o? +clear_cookies_per_iter=Limpar cookies a cada itera\u00E7\u00E3o? +column_delete_disallowed=N\u00E3o \u00E9 permitida a exclus\u00E3o desta coluna. +column_number=N\u00FAmero da coluna no arquivo CSV | pr\u00F3x | *apelido +compare=Comparar +comparefilt=Filtro de compara\u00E7\u00E3o +config_element=Elemento de Configura\u00E7\u00E3o +config_save_settings=Configurar +configure_wsdl=Configurar +constant_throughput_timer_memo=Adicionar um atraso entre amostragens para obter vaz\u00E3o constante +constant_timer_delay=Atraso do usu\u00E1rio virtual (em milisegundos) +constant_timer_memo=Adicionar um atraso constante entre amostragens +constant_timer_title=Temporizador Constante +content_encoding=Codifica\u00E7\u00E3o do conte\u00FAdo\: +controller=Controlador +cookie_manager_policy=Pol\u00EDtica de Cookie +cookie_manager_title=Gerenciador de Cookie HTTP +cookies_stored=Cookies Definidos pelo Usu\u00E1rio +copy=Copiar +counter_config_title=Contador +counter_per_user=Realiza contagem independentemente para cada usu\u00E1rio +countlim=Tamanho limite +csvread_file_file_name=Arquivo CSV de onde os valores ser\u00E3o obtidos | *apelido +cut=Recortar +cut_paste_function=Copiar e colar texto da fun\u00E7\u00E3o +database_conn_pool_max_usage=Uso M\u00E1ximo Para Cada Conex\u00E3o\: +database_conn_pool_props=Grupo de Conex\u00F5es com o Banco de Dados +database_conn_pool_size=N\u00FAmero de Conex\u00F5es no Grupo de Conex\u00F5es +database_conn_pool_title=Padr\u00F5es JDBC do Grupo de Conex\u00F5es ao Banco de Dados +database_driver_class=Classe do Driver\: +database_login_title=Padr\u00F5es JDBC para Acesso ao Banco de Dados +database_sql_query_string=Consulta SQL\: +database_sql_query_title=Padr\u00F5es JDBC para Consultas SQL +database_testing_title=Requisi\u00E7\u00E3o JDBC +database_url=URL JDBC\: +database_url_jdbc_props=URL do Banco de Dados e Driver JDBC +de=Alem\u00E3o +debug_off=Desabilitar debug +debug_on=Habilitar debug +default_parameters=Par\u00E2metros Padr\u00E3o +default_value_field=Valor Padr\u00E3o\: +delay=Atraso para in\u00EDcio (segundos) +delete=Excluir +delete_parameter=Excluir Vari\u00E1vel +delete_test=Excluir Teste +delete_user=Excluir Usu\u00E1rio +deltest=Teste de exclus\u00E3o +deref=Dereferenciar apelidos +disable=Desabilitar +distribution_graph_title=Gr\u00E1fico de Distribui\u00E7\u00E3o (alfa) +distribution_note1=O gr\u00E1fico ser\u00E1 atualizado a cada 10 amostras +domain=Dom\u00EDnio +done=Pronto +duration=Dura\u00E7\u00E3o (segundos) +duration_assertion_duration_test=Dura\u00E7\u00E3o para Avaliar +duration_assertion_failure=A opera\u00E7\u00E3o tomou muito tempo\: levou {0} milisegundos, mas n\u00E3o deveria ter levado mais do que {1} milisegundos +duration_assertion_input_error=Favor entrar com um inteiro positivo v\u00E1lido. +duration_assertion_label=Dura\u00E7\u00E3o em milisegundos\: +duration_assertion_title=Asser\u00E7\u00E3o de Dura\u00E7\u00E3o +edit=Editar +email_results_title=Enviar Resultados por Email +en=Ingl\u00EAs +enable=Habilitar +encode?=Codificar? +encoded_value=Valor da URL Codificada +endtime=Tempo de T\u00E9rmino +entry_dn=Entrada DN +entrydn=Entrada DN +error_loading_help=Erro ao carregar p\u00E1gina de ajuda +error_occurred=Um erro ocorreu +error_title=Erro +es=Espanhol +escape_html_string=String a ser escapada +eval_name_param=Texto contendo refer\u00EAncias de fun\u00E7\u00F5es e vari\u00E1veis +evalvar_name_param=Nome da vari\u00E1vel +example_data=Dados da amostra +example_title=Testador de Exemplo +exit=Sair +expiration=Expira\u00E7\u00E3o +field_name=Nome do campo +file=Arquivo +file_already_in_use=Este arquivo j\u00E1 est\u00E1 em uso +file_visualizer_append=Adicionar a um Arquivo de Dados Existente +file_visualizer_auto_flush=Automaticamente descarregar dados (flush) ap\u00F3s cada amostra de dados +file_visualizer_browse=Procurar... +file_visualizer_close=Fechar +file_visualizer_file_options=Op\u00E7\u00F5es do Arquivo +file_visualizer_filename=Nome do arquivo +file_visualizer_flush=Descarregar (flush) +file_visualizer_missing_filename=N\u00E3o foi especificado nenhum nome de arquivo de sa\u00EDda. +file_visualizer_open=Abrir +file_visualizer_output_file=Escrever resultados para arquivo / Ler a partir do arquivo +file_visualizer_submit_data=Incluir Dados Enviados +file_visualizer_title=Relat\u00F3rios de Arquivo +file_visualizer_verbose=Sa\u00EDda detalhada (verbose) +filename=Nome do arquivo +follow_redirects=Seguir redire\u00E7\u00F5es +follow_redirects_auto=Redirecionar automaticamente +foreach_controller_title=Controlador ParaCada (ForEach) +foreach_input=Prefixo da vari\u00E1vel de entrada +foreach_output=Nome da vari\u00E1vel de sa\u00EDda +foreach_use_separator=Adicionar "_" antes do n\u00FAmero? +format=Formato do n\u00FAmero +fr=Franc\u00EAs +ftp_binary_mode=Usar modo bin\u00E1rio? +ftp_local_file=Arquivo local\: +ftp_local_file_contents=Conte\u00FAdo do Arquivo Local\: +ftp_remote_file=Arquivo remoto\: +ftp_sample_title=Padr\u00F5es para Requisi\u00E7ao FTP +ftp_save_response_data=Salvar Arquivos na Resposta? +ftp_testing_title=Requisi\u00E7\u00E3o FTP +function_dialog_menu_item=Di\u00E1logo de Fun\u00E7\u00E3o de Ajuda +function_helper_title=Fun\u00E7\u00E3o de Ajuda +function_name_param=Nome da vari\u00E1vel na qual ser\u00E1 armazenado o resultado (requerido) +function_name_paropt=Nome da vari\u00E1vel na qual ser\u00E1 armazenado o resultado (opcional) +function_params=Par\u00E2metros da Fun\u00E7\u00E3o +functional_mode=Modo de Teste Funcional (ex\: salvar dados das respostas e dados dos testadores) +functional_mode_explanation=Selecionando Modo de Teste Funcional pode afetar o desempenho de modo adverso. +gaussian_timer_delay=Offset do Atraso Constante (em milisegundos)\: +gaussian_timer_memo=Adiciona um atraso aleat\u00F3rio atrav\u00E9s de uma distribui\u00E7\u00E3o gaussiana. +gaussian_timer_range=Desvio (em milisegundos)\: +gaussian_timer_title=Temporizador Aleat\u00F3rio Gaussiano +generate=Gerar +generator=Nome da classe Geradora +generator_cnf_msg=N\u00E3o foi poss\u00EDvel encontrar a classe geradora. Verifique se voc\u00EA colocou o jar correto no diret\u00F3rio /lib. +generator_illegal_msg=N\u00E3o foi poss\u00EDvel acessar a classe geradora devido a uma IllegalAccessException. +generator_instantiate_msg=N\u00E3o foi poss\u00EDvel criar uma inst\u00E2ncia do processador gerador. Verifique se o gerador implementa a interface Generator. +get_xml_from_file=Arquivo com dados XML SOAP (substitui o texto acima) +get_xml_from_random=Diret\u00F3rio das Mensagens +graph_choose_graphs=Gr\u00E1ficos a serem Exibidos +graph_full_results_title=Gr\u00E1fico de Resultados Completos +graph_results_average=M\u00E9dia +graph_results_data=Dados +graph_results_deviation=Desvio +graph_results_latest_sample=\u00DAltima Amostra +graph_results_median=Mediana +graph_results_no_samples=N\u00FAm. de Amostras +graph_results_throughput=Vaz\u00E3o +graph_results_title=Gr\u00E1fico de Resultados +grouping_add_separators=Adicionar separadores entre grupos +grouping_in_controllers=Colocar cada grupo em um novo controlador +grouping_mode=Agrupamento\: +grouping_no_groups=N\u00E3o agrupar testadores +grouping_store_first_only=Armazenar primeiro testador de cada grupo apenas +header_manager_title=Gerenciador de Cabe\u00E7alhos HTTP +headers_stored=Cabe\u00E7alhos Armazenados no Gerenciador de Cabe\u00E7alhos +help=Ajuda +help_node=O que \u00E9 este n\u00F3? +html_assertion_file=Escrever relat\u00F3rio do JTidy em arquivo +html_assertion_label=Asser\u00E7\u00E3o HTML +html_assertion_title=Asser\u00E7\u00E3o HTML +html_parameter_mask=M\u00E1scara de Par\u00E2metro HTML +http_implementation=Implementa\u00E7\u00E3o\: +http_response_code=C\u00F3digo da Resposta HTTP +http_url_rewriting_modifier_title=Modificador de Re-escrita de URL HTTP +http_user_parameter_modifier=Modificador de Par\u00E2metros HTTP do Usu\u00E1rio +httpmirror_title=Servidor Espelho HTTP +id_prefix=Prefixo do ID +id_suffix=Sufixo do ID +if_controller_evaluate_all=Avaliar para todos os filhos? +if_controller_expression=Interpretar Condi\u00E7\u00E3o como Express\u00E3o de Vari\u00E1vel? +if_controller_label=Condi\u00E7\u00E3o (padr\u00E3o\: Javascript) +if_controller_title=Controlador Se +ignore_subcontrollers=Ignorar blocos de sub-controladores +include_controller=Controlador de Inclus\u00E3o +include_equals=Incluir Igual? +include_path=Incluir Plano de Teste +increment=Incremento +infinite=Infinito +initial_context_factory=F\u00E1brica de Contexto Inicial +insert_after=Inserir Depois +insert_before=Inserir Antes +insert_parent=Inserir como Pai +interleave_control_title=Controlador de Intercala\u00E7\u00E3o +intsum_param_1=Primeiro inteiro para adicionar. +intsum_param_2=Segundo inteiro a ser adicionado - posteriormente inteiros podem ser somados atrav\u00E9s da adi\u00E7\u00E3o de novos argumentos. +invalid_data=Dados inv\u00E1lidos +invalid_mail=Ocorreu um erro ao enviar o e-mail +invalid_mail_address=Foram detectados um ou mais endere\u00E7os de email inv\u00E1lidos +invalid_mail_server=Houve um problema ao contactar o servidor de email (veja arquivo de log do JMeter) +invalid_variables=Vari\u00E1veis inv\u00E1lidas +iteration_counter_arg_1=TRUE, para que cada usu\u00E1rio tenha seu pr\u00F3prio contador, FALSE para um contador global +iterator_num=Contador de Itera\u00E7\u00E3o +ja=Japon\u00EAs +jar_file=Arquivos Jar +java_request=Requisi\u00E7\u00E3o Java +java_request_defaults=Padr\u00F5es de Requisi\u00E7\u00E3o Java +javascript_expression=Express\u00E3o JavaScript a ser avaliada +jexl_expression=Express\u00E3o JEXL a ser avaliada +jms_auth_required=Requerido +jms_client_caption=Cliente recebedor usa TopicSubscriber.receive() para aguardar uma mensagem. +jms_client_caption2=MessageListener usa a interface onMessage(Message) para aguardar novas mensagens. +jms_client_type=Cliente +jms_communication_style=Estilo de comunica\u00E7\u00E3o +jms_concrete_connection_factory=F\u00E1brica Concreta de Conex\u00E3o +jms_config=Configura\u00E7\u00E3o +jms_config_title=Configura\u00E7\u00E3o JMS +jms_connection_factory=F\u00E1brica de Conex\u00E3o +jms_file=Arquivo +jms_initial_context_factory=F\u00E1brica de Contexto Inicial +jms_itertions=N\u00FAmeros de amostras para agregar +jms_jndi_defaults_title=Configura\u00E7\u00E3o Padr\u00E3o de JNDI +jms_jndi_props=Propriedades JNDI +jms_message_title=Propriedades das mensagens +jms_message_type=Tipo das Mensagens +jms_msg_content=Conte\u00FAdo +jms_object_message=Mensagens de Objetos +jms_point_to_point=JMS Ponto a Ponto +jms_props=Propriedades JMS +jms_provider_url=URL do Provedor +jms_publisher=Publicador JMS +jms_pwd=Senha +jms_queue=Fila +jms_queue_connection_factory=F\u00E1brica de Conex\u00F5es em Fila +jms_queueing=Recursos JMS +jms_random_file=Arquivo Aleat\u00F3rio +jms_read_response=Resposta Lida +jms_receive_queue=Nome da Fila de Recebimento JNDI +jms_request=Somente Requisitar +jms_requestreply=Requisitar e Responder +jms_sample_title=Padr\u00F5es de Requisi\u00E7\u00E3o JMS +jms_send_queue=Fila de Requisi\u00E7\u00E3o de nomes JNDI +jms_subscriber_on_message=Utilizar MessageListener.onMessage() +jms_subscriber_receive=Usar TopicSubscriber.receive() +jms_subscriber_title=Assinante JMS +jms_testing_title=Requisi\u00E7\u00E3o de Mensagens +jms_text_message=Mensagem de Texto +jms_timeout=Tempo limite (timeout) em milisegundos +jms_topic=T\u00F3pico +jms_use_auth=Usar Autoriza\u00E7\u00E3o? +jms_use_file=Do Arquivo +jms_use_non_persistent_delivery=Utilizar modo de entrega n\u00E3o persistente +jms_use_properties_file=Utilizar aquivo jndi.properties +jms_use_random_file=Arquivo Aleat\u00F3rio +jms_use_req_msgid_as_correlid=Utilizar ID de Mensagem de Requisi\u00E7\u00E3o como ID de Correla\u00E7\u00E3o +jms_use_text=\u00C1rea de texto +jms_user=Usu\u00E1rio +jndi_config_title=Configura\u00E7\u00E3o JNDI +jndi_lookup_name=Interface Remota +jndi_lookup_title=Configura\u00E7\u00E3o de Lookup JNDI +jndi_method_button_invoke=Invocar +jndi_method_button_reflect=Refletir +jndi_method_home_name=Nome do M\u00E9todo Home +jndi_method_home_parms=Par\u00E2metros do M\u00E9todo Home +jndi_method_name=Configura\u00E7\u00E3o do M\u00E9todo +jndi_method_remote_interface_list=Interfaces Remotas +jndi_method_remote_name=Nome do M\u00E9todo Remoto +jndi_method_remote_parms=Par\u00E2metros do M\u00E9todo Remoto +jndi_method_title=Configura\u00E7\u00E3o do M\u00E9todo Remoto +jndi_testing_title=Requisi\u00E7\u00E3o JNDI +jndi_url_jndi_props=Propriedades JNDI +junit_append_error=Adicionar erros de asser\u00E7\u00E3o +junit_append_exception=Adicionar exce\u00E7\u00F5es de tempo de execu\u00E7\u00E3o +junit_constructor_error=N\u00E3o foi poss\u00EDvel criar uma inst\u00E2ncia da classe +junit_constructor_string=R\u00F3tulo String do Construtor +junit_do_setup_teardown=N\u00E3o chamar setUp e tearDown +junit_error_code=C\u00F3digo de Erro +junit_error_default_msg=Houve um erro inesperado +junit_error_msg=Mensagem de Erro +junit_failure_code=C\u00F3digo da Falha +junit_failure_default_msg=O teste falhou +junit_failure_msg=Mensagns de Falha +junit_pkg_filter=Filtro de Pacote +junit_request=Requisi\u00E7\u00E3o JUnit +junit_request_defaults=Padr\u00F5es de Requisi\u00E7\u00E3o JUnit +junit_success_code=C\u00F3digo de Sucesso +junit_success_default_msg=Teste com sucesso +junit_success_msg=Mensagem de Sucesso +junit_test_config=Par\u00E2metros de Teste JUnit +junit_test_method=M\u00E9todo de Teste +ldap_argument_list=Lista de Argumentos LDAP +ldap_connto=Tempo limite de Conex\u00E3o (timeout) em milisegundos +ldap_parse_results=Processar os resultados da busca? +ldap_sample_title=Padr\u00F5es de Requisi\u00E7\u00E3o LDAP +ldap_search_baseobject=Realizar busca b\u00E1sica de objetos +ldap_search_onelevel=Realizar busca de um n\u00EDvel +ldap_search_subtree=Realizar busca em sub-\u00E1rvore +ldap_secure=Utilizar Protocolo LDAP Seguro? +ldap_testing_title=Requisi\u00E7\u00E3o LDAP +ldapext_sample_title=Padr\u00F5es de Requisi\u00E7\u00E3o LDAP Estendidas +ldapext_testing_title=Requisi\u00E7\u00E3o LDAP Estendida +library=Biblioteca +load=Carregar +load_wsdl=Carregar WSDL +log_errors_only=Erros +log_file=Localiza\u00E7\u00E3o do arquivo de log +log_function_comment=Coment\u00E1rios adicionais (opcional) +log_function_level=N\u00EDvel de log (padr\u00E3o INFO) ou OUT ou ERR +log_function_string=String a ser logada +log_function_string_ret=String a ser logada (e retornada) +log_function_throwable=Texto a ser lan\u00E7ado (opcional) +log_only=Apenas Logar/Exibir +log_parser=Nome da Classe Processadora de Logs +log_parser_cnf_msg=N\u00E3o foi poss\u00EDvel encontrar a classe. Verifique se o jar se encontra no diret\u00F3rio /lib. +log_parser_illegal_msg=N\u00E3o foi poss\u00EDvel acessar a classe devido a IllegalAccessException. +log_parser_instantiate_msg=N\u00E3o foi poss\u00EDvel criar uma inst\u00E2ncia do processador de logs. Verifique se o processador implementa a interface LogParser. +log_sampler=Testador de Log de Acessp do Tomcat +log_success_only=Sucessos +logic_controller_title=Controlador Simples +login_config=Configura\u00E7\u00E3o de Login +login_config_element=Elemento de Configura\u00E7\u00E3o de Login +longsum_param_1=Primeiro long a ser adicionado +longsum_param_2=Segundo long a ser adicionado - adicionalmente novos longs podem ser somados atrav\u00E9s da adi\u00E7\u00E3o de novos argumentos +loop_controller_title=Controlador de Itera\u00E7\u00E3o +looping_control=Controle de Itera\u00E7\u00E3o +lower_bound=Limite Inferior +mail_reader_account=Nome do usu\u00E1rio\: +mail_reader_all_messages=Todos +mail_reader_delete=Excluir mensagens do servidor +mail_reader_folder=Diret\u00F3rio\: +mail_reader_num_messages=N\u00FAmero de mensagens a serem recuperadas\: +mail_reader_password=Senha\: +mail_reader_server=Servidor\: +mail_reader_server_type=Tipo do Servidor\: +mail_reader_storemime=Armazenar as mensagens utilizando MIME +mail_reader_title=Testador Leitor de Emails +mail_sent=Email enviado com sucesso +mailer_attributes_panel=Atributos de envio de emails +mailer_error=N\u00E3o foi poss\u00EDvel enviar email. Favor verificar as configura\u00E7\u00F5es. +mailer_visualizer_title=Vizualizador de Email +match_num_field=N\u00FAmero para Combina\u00E7\u00E3o (0 para aleat\u00F3rio) +max=M\u00E1ximo +maximum_param=O valor m\u00E1ximo permitido para um intervalo de valores +md5hex_assertion_failure=Erro avaliando soma MD5\: encontrado {0} mas deveria haver {1} +md5hex_assertion_md5hex_test=MD5Hex para Asser\u00E7\u00E3o +md5hex_assertion_title=Asser\u00E7\u00E3o MD5Hex +memory_cache=Cache em Mem\u00F3ria +menu_assertions=Asser\u00E7\u00F5es +menu_close=Fechar +menu_collapse_all=Fechar Todos +menu_config_element=Elemento de Configura\u00E7\u00E3o +menu_edit=Editar +menu_expand_all=Expandir Todos +menu_generative_controller=Testador +menu_listener=Ouvinte +menu_logic_controller=Controlador L\u00F3gico +menu_merge=Mesclar +menu_modifiers=Modificadores +menu_non_test_elements=Elementos que n\u00E3o s\u00E3o de Teste +menu_open=Abrir +menu_post_processors=P\u00F3s-Processadores +menu_pre_processors=Pr\u00E9-Processadores +menu_response_based_modifiers=Modificadores Baseados na Resposta +menu_timer=Temporizador +metadata=Metadados +method=M\u00E9todo\: +minimum_param=Valor m\u00EDnimo permitido para um intervalo de valores +minute=minuto +modddn=Velho nome da entrada +modification_controller_title=Controlador de Modifica\u00E7\u00E3o +modification_manager_title=Gerenciador de Modifica\u00E7\u00E3o +modify_test=Modificar Teste +modtest=Teste de Modifica\u00E7\u00E3o +module_controller_module_to_run=M\u00F3dulo a ser executado +module_controller_title=Controlador de M\u00F3dulo +module_controller_warning=N\u00E3o foi poss\u00EDvel encontrar o m\u00F3dulo\: +monitor_equation_active=Ativo\: (ocupado / max) > 25% +monitor_equation_dead=Morto\: sem resposta +monitor_equation_healthy=Saud\u00E1vel\: (ocupado / max) < 25% +monitor_equation_load=Carga\: ((ocupado / max) * 50) + ((mem\u00F3ria utilizada / max mem\u00F3ria) * 50) +monitor_equation_warning=Alerta\: (ocupado / max) > 67% +monitor_health_tab_title=Sa\u00FAde +monitor_health_title=Monitorar Resultados +monitor_is_title=Usar como Monitor +monitor_label_prefix=Prefixo de Conex\u00E3o +monitor_label_right_active=Ativo +monitor_label_right_dead=Morto +monitor_label_right_healthy=Saud\u00E1vel +monitor_label_right_warning=Alerta +monitor_legend_health=Sa\u00FAde +monitor_legend_load=Carga +monitor_legend_memory_per=Mem\u00F3ria % (usada / total) +monitor_legend_thread_per=Thread % (ocupado / max) +monitor_performance_servers=Servidores +monitor_performance_tab_title=Desempenho +monitor_performance_title=Gr\u00E1fico de Desempenho +name=Nome\: +new=Novo +newdn=Novo nome distingu\u00EDvel +no=Noruegu\u00EAs +number_of_threads=N\u00FAmero de Usu\u00E1rios Virtuais (threads)\: +obsolete_test_element=O elemento de teste est\u00E1 obsoleto. +once_only_controller_title=Controlador de Uma \u00DAnica Vez +open=Abrir... +option=Op\u00E7\u00F5es +optional_tasks=Tarefas Opcionais +paramtable=Enviar Par\u00E2metros Com a Requisi\u00E7\u00E3o +password=Senha +paste=Colar +paste_insert=Colar como Inser\u00E7\u00E3o +path=Caminho\: +path_extension_choice=Extens\u00F5es do Caminho (usar ";" como separador) +path_extension_dont_use_equals=N\u00E3o usar igual nas extens\u00F5es do caminho (compatibilidae com Intershop Enfinity) +path_extension_dont_use_questionmark=N\u00E3o utilizar s\u00EDmbolo de interroga\u00E7\u00E3o nas extens\u00F5es do caminho (Compatibilidade com Intershop Enfinity) +patterns_to_exclude=Padr\u00F5es de URL a serem exclu\u00EDdos +patterns_to_include=Padr\u00F5es de URL a serem inclu\u00EDdos +pl=Polon\u00EAs +port=Porta\: +property_default_param=Valor padr\u00E3o +property_edit=Editar +property_editor.value_is_invalid_message=O texto informado n\u00E3o \u00E9 um valor v\u00E1lido para esta propriedade.\nA propriedade ser\u00E1 revertida para o seu valor pr\u00E9vio. +property_editor.value_is_invalid_title=Entrada inv\u00E1lida. +property_name_param=Nome da propriedade +property_returnvalue_param=Retornar Valor Original da Propriedade (padr\u00E3o\: false)? +property_undefined=Indefinido +property_value_param=Valor da propriedade +property_visualiser_title=Exibi\u00E7\u00E3o da Propriedade +protocol=Protocolo [http]\: +protocol_java_border=Classe Java +protocol_java_classname=Nome da classe\: +protocol_java_config_tile=Configurar amostra Java +protocol_java_test_title=Teste Java +provider_url=URL do Provedor +proxy_assertions=Adicionar Asser\u00E7\u00F5es +proxy_cl_error=Se estiver especificando um servidor proxy, nome do servidor e porta precisam ser informados +proxy_content_type_exclude=Excluir\: +proxy_content_type_filter=Filtro de tipo de conte\u00FAdo\: +proxy_content_type_include=Incluir\: +proxy_daemon_bind_error=N\u00E3o foi poss\u00EDvel criar o proxy - porta em uso\: Escolha outra porta. +proxy_daemon_error=N\u00E3o foi poss\u00EDvel criar o proxy - veja log para detalhes +proxy_headers=Capturar Cabe\u00E7alhos HTTP +proxy_regex=Combina\u00E7\u00E3o de express\u00E3o regular +proxy_sampler_settings=Configura\u00E7\u00F5es do Testador HTTP +proxy_sampler_type=Tipo\: +proxy_separators=Adicionar Separadores +proxy_target=Controlador alvo\: +proxy_test_plan_content=Conte\u00FAdo do Plano de Teste +proxy_title=Servidor HTTP Proxy +pt_br=Portugu\u00EAs (Brasileiro) +ramp_up=Tempo de inicializa\u00E7\u00E3o (em segundos) +random_control_title=Controlador Aleat\u00F3rio +random_order_control_title=Controlador de Ordem Aleat\u00F3ria +read_response_message=N\u00E3o est\u00E1 configurado para ler a resposta. Para ver a resposta, favor marcar esta op\u00E7\u00E3o no testador. +read_response_note=Se ler resposta est\u00E1 desmarcado, o testador n\u00E3o ir\u00E1 ler a resposta +read_response_note2=ou configurar o SampleResult. Isto melhora o desempenho, mas significa que +read_response_note3=o conte\u00FAdo da resposta n\u00E3o ser\u00E1 logado. +read_soap_response=Ler Respostas SOAP +realm=Reino (realm) +record_controller_title=Controlador de Grava\u00E7\u00E3o +ref_name_field=Nome de Refer\u00EAncia\: +regex_extractor_title=Extractor de Express\u00E3o Regular +regex_field=Express\u00E3o Regular +regex_source=Campo da Resposta a ser verificado +regex_src_body=Corpo (body) +regex_src_body_unescaped=Corpo (body) - n\u00E3o escapado +regex_src_hdrs=Cabe\u00E7alhos (headers) +regexfunc_param_1=Express\u00E3o regular usada para buscar amostras anteriores - ou vari\u00E1vel. +regexfunc_param_2=Modelo (template) para substitui\u00E7\u00E3o de string, usando grupos de express\u00F5es reuglares. Formato \u00E9 ${grupo}$. Exemplo $1$. +regexfunc_param_3=Que combina\u00E7\u00E3o usar. Um inteiro 1 ou maior, RAND indica que JMeter deve escolher aleatoriamente, um ponto flutuante (float) ou ALL indicando todas as combina\u00E7\u00F5es devem ser usadas ([1]) +regexfunc_param_4=Entre texto. Se ALL est\u00E1 selecionado, o que estiver ENTRE o texto informado ser\u00E1 utilizado para gerar os resultados ([""]) +regexfunc_param_5=Texto padr\u00E3o. Usado no lugar do modelo (template) se a express\u00E3o regular n\u00E3o econtrar nenhuma combina\u00E7\u00E3o ([""]) +regexfunc_param_7=Nome da vari\u00E1vel de entrada contidas no texto a ser processado ([amostra anterior]) +remote_error_init=Erro inicializando o servidor remoto +remote_error_starting=Erro inicializando o servidor remoto +remote_exit=Sa\u00EDda Remota +remote_exit_all=Sair de Todos Remotos +remote_start=Inicializar Remoto +remote_start_all=Inicializar Todos Remotos +remote_stop=Parar Remoto +remote_stop_all=Parar Todos Remotos +remove=Remover +rename=Renomear entrada +report=Relat\u00F3rio +report_bar_chart=Gr\u00E1fico de Barras +report_base_directory=Diret\u00F3rio Base +report_chart_caption=Legenda do Gr\u00E1fico +report_chart_x_axis=Eixo X +report_chart_x_axis_label=R\u00F3tulo do Eixo X +report_chart_y_axis=Eixo Y +report_chart_y_axis_label=R\u00F3tulo do Eixo Y +report_line_graph=Gr\u00E1fico de Linha +report_line_graph_urls=Incluir URLs +report_output_directory=Diret\u00F3rio de Sa\u00EDda do Relat\u00F3rio +report_page=P\u00E1gina do Relat\u00F3rio +report_page_element=Elemento da P\u00E1gina +report_page_footer=Rodap\u00E9 da P\u00E1gina +report_page_header=Cabe\u00E7alho da P\u00E1gina +report_page_index=Criar \u00CDndice de P\u00E1ginas +report_page_intro=P\u00E1gina de Introdu\u00E7\u00E3o +report_page_style_url=URL da folha de estilos (stylesheet) +report_page_title=T\u00EDtulo da P\u00E1gina +report_pie_chart=Gr\u00E1fico de Pizza +report_plan=Plano de Relat\u00F3rio +report_select=Selecionar +report_summary=Sum\u00E1rio do Relat\u00F3rio +report_table=Tabela do Relat\u00F3rio +report_writer=Escritor de Relat\u00F3rio +report_writer_html=Escritor de Relat\u00F3rio HTML +request_data=Requisitar Dados +reset_gui=Reiniciar GUI +response_save_as_md5=Salvar respostas como chave MD5? +restart=Reiniciar +resultaction_title=Manuseador de A\u00E7\u00F5es de Estados do Resultado +resultsaver_errors=Somente Salvar Respostas que Falharam +resultsaver_prefix=Prefixo do nome do arquivo +resultsaver_skipautonumber=N\u00E3o adicionar n\u00FAmeros ao prefixo +resultsaver_success=Somente Salvar Respostas de Sucesso +resultsaver_title=Salvar respostas para arquivo +resultsaver_variable=Nome da Vari\u00E1vel\: +retobj=Objeto de retorno +reuseconnection=Reusar conex\u00E3o +revert_project=Reverter +revert_project?=Reverter projeto? +root=Raiz +root_title=Raiz +run=Executar +running_test=Testes executando +runtime_controller_title=Controlador de Tempo de Execu\u00E7\u00E3o +runtime_seconds=Tempo de execu\u00E7\u00E3o (segundos) +sample_result_save_configuration=Configura\u00E7\u00E3o de Salvar Resultados da Amostra +sample_scope=Quais amostras testar +sample_scope_all=Amostras principais e sub-amostras +sample_scope_children=Somente Sub-amostras +sample_scope_parent=Somente Amostras principais +sampler_label=R\u00F3tulo +sampler_on_error_action=A\u00E7\u00E3o a ser tomada depois de erro do testador +sampler_on_error_continue=Continuar +sampler_on_error_stop_test=Interromper Teste +sampler_on_error_stop_test_now=Interrompe Teste Agora +sampler_on_error_stop_thread=Interromper Usu\u00E1rio Virtual +save=Salvar +save?=Salvar? +save_all_as=Salvar Plano de Teste como +save_as=Salvar Sele\u00E7\u00E3o Como... +save_as_error=Mais de um item selecionado\! +save_as_image=Salvar N\u00F3 como Imagem +save_as_image_all=Salvar Tela Como Imagem +save_assertionresultsfailuremessage=Salvar Mensagens de Falha de Asser\u00E7\u00E3o +save_assertions=Salvar Resultados de Asser\u00E7\u00F5es (XML) +save_asxml=Salvar como XML +save_bytes=Salvar quantidade de bytes +save_code=Salvar C\u00F3digo da Resposta +save_datatype=Salvar Tipo de Dados +save_encoding=Salvar Codifica\u00E7\u00E3o +save_fieldnames=Salvar Nomes dos Campos (CSV) +save_filename=Nome do Arquivo para Salvar Respostas +save_graphics=Salvar Gr\u00E1fico +save_hostname=Salvar Nome do Host +save_label=Salvar R\u00F3tulo +save_latency=Salvar Lat\u00EAncia +save_message=Salvar Mensagens das Respostas +save_overwrite_existing_file=O arquivo selecionado j\u00E1 existe, voc\u00EA quer substitu\u00ED-lo? +save_requestheaders=Salvar Cabe\u00E7alhos das Requisi\u00E7\u00F5es (XML) +save_responsedata=Salvar Dados das Respostas (XML) +save_responseheaders=Salvar Cabe\u00E7alhos das Respostas (XML) +save_samplecount=Salvar Amostra e Contador de Erros +save_samplerdata=Salvar Dados do Testador (XML) +save_subresults=Salvar sub resultados (XML) +save_success=Salvar Sucessos +save_threadcounts=Salvar Contador de Usu\u00E1rios Virtuais Ativos +save_threadname=Salvar Nome do Usu\u00E1rio Virtual +save_time=Salvar Tempo Decorrido +save_timestamp=Salvar Data e Hora +save_url=Salvar URL +sbind=Ligar/Desligar \u00FAnico (single bind/unbind) +scheduler=Agendador +scheduler_configuration=Configura\u00E7\u00E3o do Agendador +scope=Escopo +search_base=Base de busca +search_filter=Filtro de busca +search_test=Teste de busca +searchbase=Base de busca +searchfilter=Filtro de Busca +searchtest=Teste de Busca +second=segundo +secure=Seguro +send_file=Enviar Arquivos com a Requisi\u00E7\u00E3o +send_file_browse=Procurar... +send_file_filename_label=Caminho do Arquivo\: +send_file_param_name_label=Nome do Par\u00E2metro\: +server=Nome do servidor ou IP\: +servername=Nome do servidor\: +session_argument_name=Nome do Argumento de Sess\u00E3o +should_save=Voc\u00EA deveria salvar seu plano de teste antes de execut\u00E1-lo.\nSe voc\u00EA est\u00E1 usando suporte a arquivos de dados (ex\: Conjunto de Dados CSV ou _StringFromFile),\nent\u00E3o \u00E9 particularmente importante salvar seu script de teste.\nVoc\u00EA quer salvar seu plano de teste primeiro? +shutdown=Desligar +simple_config_element=Elemento de Configura\u00E7\u00E3o Simples +simple_data_writer_title=Escritor de Dados Simples +size_assertion_comparator_error_equal=igual a +size_assertion_comparator_error_greater=maior que +size_assertion_comparator_error_greaterequal=maior ou igual que +size_assertion_comparator_error_less=menor que +size_assertion_comparator_error_lessequal=menor ou igual que +size_assertion_comparator_error_notequal=diferente de +size_assertion_comparator_label=Tipo de Compara\u00E7\u00E3o +size_assertion_failure=O resultado estava com tamanho errado\: Tinha {0} bytes, mas deveria ter {1} {2} bytes +size_assertion_input_error=Favor entrar um inteiro positivo v\u00E1lido. +size_assertion_label=Tamanho em bytes\: +size_assertion_size_test=Tamanho para Asser\u00E7\u00E3o +size_assertion_title=Asser\u00E7\u00E3o de Tamanho +soap_action=A\u00E7\u00E3o SOAP +soap_data_title=Dados Soap/XML-RPC +soap_sampler_title=Requisi\u00E7\u00E3o SOAP/XML-RPC +soap_send_action=Enviar a\u00E7\u00E3o SOAP\: +spline_visualizer_average=M\u00E9dia +spline_visualizer_incoming=Recebidos +spline_visualizer_maximum=M\u00E1ximo +spline_visualizer_minimum=M\u00EDnimo +spline_visualizer_title=Visualizador Spline +spline_visualizer_waitingmessage=Aguardando amostras +split_function_separator=Separador. Padr\u00E3o \u00E9 , (v\u00EDrgula) +split_function_string=String a ser separada +ssl_alias_prompt=Favor digitar seu apelido preferido +ssl_alias_select=Selecione seu apelido para o teste +ssl_alias_title=Apelido do Cliente +ssl_error_title=Problema com Armazenamento de Chave +ssl_pass_prompt=Favor digitar sua senha +ssl_pass_title=Senha do Armaz\u00E9m de Chaves (KeyStore) +ssl_port=Porta SSL +sslmanager=Gerenciador SSL +start=Iniciar +start_no_timers=Iniciar sem pausas +starttime=Tempo de In\u00EDcio +stop=Interromper +stopping_test=Interrompendo todos os usu\u00E1rios virtuais. Por favor, seja paciente. +stopping_test_failed=Um ou mais usu\u00E1rios virtuais n\u00E3o pararam; veja arquivo de log. +stopping_test_title=Interrompendo Teste +string_from_file_file_name=Entre com o caminho completo do arquivo +string_from_file_seq_final=N\u00FAmero de sequ\u00EAncia final do arquivo (opcional) +string_from_file_seq_start=Numero de sequ\u00EAncia inicial do arquivo (opcional) +summariser_title=Gerar Sum\u00E1rio de Resultados +summary_report=Relat\u00F3rio de Sum\u00E1rio +switch_controller_label=Valor de Sele\u00E7\u00E3o +switch_controller_title=Controlador de Sele\u00E7\u00E3o +table_visualizer_sample_num=Amostra \# +table_visualizer_sample_time=Tempo da amostra (ms) +table_visualizer_start_time=Tempo de in\u00EDcio +table_visualizer_status=Estado +table_visualizer_success=Sucesso +table_visualizer_thread_name=Nome do Usu\u00E1rio Virtual +table_visualizer_warning=Alerta +tcp_classname=Nome da classe TCPClient\: +tcp_config_title=Configura\u00E7\u00E3o do Testador TCP +tcp_nodelay=Alterar "Sem Atrasos" +tcp_port=N\u00FAmero da Porta\: +tcp_request_data=Texto para envio +tcp_sample_title=Testador TCP +tcp_timeout=Tempo limite (ms) +template_field=Modelo\: +test=Teste +test_action_action=A\u00E7\u00E3o +test_action_duration=Dura\u00E7\u00E3o (ms) +test_action_pause=Pausar +test_action_stop=Encerrar +test_action_stop_now=Encerrar Agora +test_action_target=Alvo +test_action_target_test=Todos Usu\u00E1ros Virtuais +test_action_target_thread=Usu\u00E1rio Virtual Atual +test_action_title=A\u00E7\u00E3o de Teste +test_configuration=Configura\u00E7\u00E3o do Teste +test_plan=Plano de Teste +test_plan_classpath_browse=Adiconar diret\u00F3rio ou jar ao caminho de classes (classpath) +testconfiguration=Configura\u00E7\u00E3o de Teste +testplan.serialized=Executar Grupos de Usu\u00E1rios consecutivamente (ex\: executar um grupo de cada vez) +testplan_comments=Coment\u00E1rios\: +testt=Teste +thread_delay_properties=Propriedades de Atraso do Usu\u00E1rio Virtual +thread_group_title=Grupo de Usu\u00E1rios +thread_properties=Propriedades do Usu\u00E1rio Virtual +threadgroup=Grupo de Usu\u00E1rios +throughput_control_bynumber_label=Total de Execu\u00E7\u00F5es +throughput_control_bypercent_label=Percentagem de Execu\u00E7\u00F5es +throughput_control_perthread_label=Por Usu\u00E1rio +throughput_control_title=Controlador de Vaz\u00E3o +throughput_control_tplabel=Vaz\u00E3o +time_format=Formato de string para o SimpleDateFormat (opcional) +timelim=Tempo limite +tr=Turco +transaction_controller_include_timers=Incluem dura\u221a\u00df\u221a\u00a3o do temporizador e pr\u221a\u00a9-p\u221a\u2265s processadores em amostra gerada +transaction_controller_parent=Gerar amostras do pai +transaction_controller_title=Controlador de Transa\u00E7\u00E3o +unbind=Liberar Usu\u00E1rio Virtual +unescape_html_string=String a ser escapada +unescape_string=String contendo escapes de Java +uniform_timer_delay=Limite de Atraso Constante (em ms) +uniform_timer_memo=Adiciona um atraso aleat\u00F3rio com uma distribui\u00E7\u00E3o uniforme +uniform_timer_range=Atraso M\u00E1ximo Aleat\u00F3rio (ms) +uniform_timer_title=Temporizador Aleat\u00F3rio Uniforme +update_per_iter=Atualizar uma \u00FAnica vez por itera\u00E7\u00E3o +upload=Subir Arquivo +upper_bound=Limite Superior +url_config_protocol=Protocolo\: +url_config_title=Padr\u00F5es de Requisi\u00E7\u00E3o HTTP +url_full_config_title=Amostra de URL Completa +url_multipart_config_title=Padr\u00F5es de Requisi\u00E7\u00E3o HTTP Multiparte +use_keepalive=Usar Manter Ativo (KeepAlive) +use_multipart_for_http_post=Usar multipart/form-data para HTTP POST +use_recording_controller=Usar Controlador de Grava\u00E7\u00E3o +user=Usu\u00E1rio +user_defined_test=Teste Definido pelo Usu\u00E1rio +user_defined_variables=Vari\u00E1veis Definidas Pelo Usu\u00E1rio +user_param_mod_help_note=(N\u00E3o modifique isto. Modifique o arquivo com aquele nome no diret\u00F3rio /bin do JMeter) +user_parameters_table=Par\u00E2metros +user_parameters_title=Par\u00E2metros do Usu\u00E1rio +userdn=Nome do Usu\u00E1rio +username=Nome do Usu\u00E1rio +userpw=Senha +value=Valor +var_name=Nome de Refer\u00EAncia +variable_name_param=Nome da vari\u00E1vel (pode incluir refer\u00EAncias de vari\u00E1veis e fun\u00E7\u00F5es) +view_graph_tree_title=Ver Gr\u00E1fico de \u00C1rvore +view_results_assertion_error=Erro de asser\u00E7\u00E3o\: +view_results_assertion_failure=Falha de asser\u00E7\u00E3o\: +view_results_assertion_failure_message=Mensagem de falha de asser\u00E7\u00E3o\: +view_results_desc=Exibe os resultados de amostragem na forma de \u00E1rvore +view_results_error_count=Contador de Erros\: +view_results_fields=campos\: +view_results_in_table=Ver Resultados em Tabela +view_results_latency=Lat\u00EAncia\: +view_results_load_time=Tempo de Carga\: +view_results_render_html=Renderizar HTML +view_results_render_json=Renderizar JSON +view_results_render_text=Exibir Texto +view_results_render_xml=Renderizar XML +view_results_request_headers=Cabe\u00E7alhos das Requisi\u00E7\u00F5es +view_results_response_code=C\u00F3digo de Resposta\: +view_results_response_headers=Cabe\u00E7alhos da Resposta\: +view_results_response_message=Mensagem de resposta\: +view_results_response_too_large_message=Resposta muito grande para ser exibida. Tamanho\: +view_results_response_partial_message=In\u00EDcio da mensagem: +view_results_sample_count=Contagem de amostras\: +view_results_sample_start=In\u00EDcio da Amostra\: +view_results_size_in_bytes=Tamanho em bytes\: +view_results_tab_assertion=Resultados da asser\u00E7\u00E3o +view_results_tab_request=Requisi\u00E7\u00E3o +view_results_tab_response=Dados da resposta +view_results_tab_sampler=Resultados do testador +view_results_thread_name=Nome do Usu\u00E1rio Virtual\: +view_results_title=Ver Resultados +view_results_tree_title=Ver \u00C1rvore de Resultados +warning=Alerta\! +web_request=Requisi\u00E7\u00E3o HTTP +web_server=Servidor Web +web_server_client=Implementa\u00E7\u00E3o do Cliente\: +web_server_domain=Nome do Servidor ou IP\: +web_server_port=N\u00FAmero da Porta\: +web_server_timeout_connect=Conectar\: +web_server_timeout_response=Resposta\: +web_server_timeout_title=Tempo limite (ms) +web_testing2_title=Cliente HTTP de Requisi\u00E7\u00E3o HTTP +web_testing_embedded_url_pattern=URLs embutidas precisam combinar\: +web_testing_retrieve_images=Recuperar todos recursos embutidos a partir de arquivos HTML +web_testing_title=Requisi\u00E7\u00E3o HTTP +webservice_proxy_note=Se Usar Proxy HTTP estiver marcado, mas nenhum host ou porta s\u00E3o fornecidos, o testador +webservice_proxy_note2=ir\u00E1 analizar as op\u00E7\u00F5es de linha de comando. Se nenhum host proxy ou portas forem providos +webservice_proxy_note3=tamb\u00E9m, ele ir\u00E1 falhar em modo silencioso. +webservice_proxy_port=Porta do Proxy +webservice_sampler_title=Requisi\u00E7\u00E3o (SOAP) Requisi\u00E7\u00E3o (DEPRECATED) +webservice_soap_action=A\u00E7\u00E3o SOAP +webservice_timeout=Tempo limite\: +webservice_use_proxy=Usar Proxy HTTP +while_controller_label=Condi\u00E7\u00F5e (fun\u00E7\u00E3o ou vari\u00E1vel) +while_controller_title=Controlador de Enquanto +workbench_title=\u00C1rea de Trabalho +wsdl_helper_error=O WSDL \u00E9 inv\u00E1lido, favor verificar a url. +wsdl_url_error=O WSDL estava vazio. +xml_assertion_title=Asser\u00E7\u00E3o XML +xml_namespace_button=Usar Espa\u00E7o de Nome (namespace) +xml_tolerant_button=Processador de XML/HTML Tolerante +xml_validate_button=Validar XML +xml_whitespace_button=Ignorar espa\u00E7os em branco +xmlschema_assertion_label=Nome do arquivo\: +xmlschema_assertion_title=Asser\u00E7\u00E3o de Esquema de XML +xpath_assertion_button=Validar +xpath_assertion_check=Verificar Express\u00E3o XPath +xpath_assertion_error=Erro com XPath +xpath_assertion_failed=Express\u00F5es XPath Inv\u00E1lidas +xpath_assertion_negate=Verdadeiro se nada combina +xpath_assertion_option=Op\u00E7\u00F5es de Processamento de XML +xpath_assertion_test=Asser\u00E7\u00E3o XPath +xpath_assertion_tidy=Tentar e melhorar entrada +xpath_assertion_title=Asser\u00E7\u00E3o XPath +xpath_assertion_valid=Express\u00E3o XPath V\u00E1lida +xpath_assertion_validation=Validar XML de acordo com DTD +xpath_assertion_whitespace=Ignorar espa\u00E7os em branco +xpath_expression=Express\u00F5es XPath que ser\u00E3o combinadas +xpath_extractor_query=Consulta XPath +xpath_extractor_title=Extractor XPath +xpath_file_file_name=Arquivo XML de onde os valores ser\u00E3o extra\u00EDdos +xpath_tidy_quiet=Quieto +xpath_tidy_report_errors=Reportar erros +xpath_tidy_show_warnings=Exibir alertas +you_must_enter_a_valid_number=Voc\u00EA precisa entrar com um n\u00FAmero v\u00E1lido +zh_cn=Chin\u00EAs (Simplificado) +zh_tw=Chin\u00EAs (Tradicional) diff --git a/src/core/org/apache/jmeter/resources/messages_tr.properties b/src/core/org/apache/jmeter/resources/messages_tr.properties new file mode 100644 index 00000000000..4b7cec01559 --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_tr.properties @@ -0,0 +1,837 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=Apache JMeter Hakk\u0131nda +add=Ekle +add_as_child=\u00C7ocuk Olarak Ekle +add_parameter=De\u011Fi\u015Fken Olarak Ekle +add_pattern=Desen Ekle\: +add_test=Test Ekle +add_user=Kullan\u0131c\u0131 Ekle +add_value=De\u011Fer Ekle +addtest=Test ekle +aggregate_graph=\u0130statiksel Grafikler +aggregate_graph_column=Kolon +aggregate_graph_display=Grafik G\u00F6ster +aggregate_graph_height=Y\u00FCkseklik +aggregate_graph_max_length_xaxis_label=X-ekseni etiketinin maximum uzunlu\u011Fu +aggregate_graph_ms=Milisaniyeler +aggregate_graph_response_time=Cevap Zaman\u0131 +aggregate_graph_save=Grafi\u011Fi kaydet +aggregate_graph_save_table=Tablo Verisini Kaydet +aggregate_graph_save_table_header=Tablo Ba\u015Fl\u0131\u011F\u0131n\u0131 Kaydet +aggregate_graph_title=Toplu Grafik +aggregate_graph_user_title=Grafik Ba\u015Fl\u0131\u011F\u0131 +aggregate_graph_width=Geni\u015Flik +aggregate_report=Toplu Rapor +aggregate_report_bandwidth=KB/sn +aggregate_report_count=\# \u00D6rnek +aggregate_report_error=Hata +aggregate_report_error%=Hata % +aggregate_report_max=En \u00C7ok +aggregate_report_median=Ortalama +aggregate_report_min=En Az +aggregate_report_rate=Transfer Oran\u0131 +aggregate_report_total_label=TOPLAM +als_message=Not\: Eri\u015Fim Logu Ayr\u0131\u015Ft\u0131r\u0131c\u0131s\u0131 eklentiye izin veren genel-ge\u00E7er bir tasar\u0131ma sahiptir +als_message2=\u00F6zg\u00FCn ayr\u0131\u015Ft\u0131r\u0131c\u0131. Bu \u015Fekilde yapmak i\u00E7in, LogParser'i ger\u00E7ekle, jar'\u0131 \u015Furaya ekle\: +als_message3=/lib dizini ve \u00F6rnekleyicideki s\u0131n\u0131f\u0131 gir. +analyze=Veri Dosyas\u0131n\u0131 Analiz Et... +anchor_modifier_title=HTML Ba\u011Flant\u0131s\u0131 Ayr\u0131\u015Ft\u0131r\u0131c\u0131s\u0131 +appearance=Temalar +argument_must_not_be_negative=Ba\u011F\u0131ms\u0131z de\u011Fi\u015Fken negatif olmamal\u0131\! +assertion_assume_success=Durumu Yoksay +assertion_code_resp=Cevap Kodu +assertion_contains=\u0130\u00E7erir +assertion_equals=E\u015Fittir +assertion_headers=Cevap Ba\u015Fl\u0131klar\u0131 +assertion_matches=\u00D6rt\u00FC\u015F\u00FCr +assertion_message_resp=Cevap Mesaj\u0131 +assertion_pattern_match_rules=Desen \u00D6rt\u00FC\u015Fme Kurallar\u0131 +assertion_patterns_to_test=Test Edilecek Desenler +assertion_resp_field=Test Edilecek Cevap Alan\u0131 +assertion_text_resp=Metin Cevap +assertion_textarea_label=Do\u011Frulamalar\: +assertion_title=Cevap Do\u011Frulamas\u0131 +assertion_url_samp=URL \u00D6rneklendi +assertion_visualizer_title=Do\u011Frulama Sonu\u00E7lar\u0131 +attribute=\u00D6znitelik +attrs=\u00D6znitelikler +auth_base_url=Temel URL +auth_manager_title=HTTP Yetkilendirme Y\u00F6neticisi +auths_stored=Yetkilendirme Y\u00F6neticisinde Tutulan Yetkilendirmeler +average=Ortalama +average_bytes=Ort. Byte +bind=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Ba\u011Flamas\u0131 +browse=G\u00F6zat... +bsf_sampler_title=BSF \u00D6rnekleyicisi +bsf_script=\u00C7al\u0131\u015Ft\u0131r\u0131lacak betik (de\u011Fi\u015Fkenler\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsf_script_file=\u00C7al\u0131\u015Ft\u0131r\u0131lacak betik dosyas\u0131 +bsf_script_language=Betik dili\: +bsf_script_parameters=Beti\u011Fe veya betik dosyas\u0131na ge\u00E7ilecek parametreler +bsh_assertion_script=Betik (tan\u0131ml\u0131 de\u011Fi\u015Fkenler i\u00E7in a\u015Fa\u011F\u0131ya bak\u0131n) +bsh_assertion_script_variables=Betik i\u00E7in \u015Fu de\u011Fi\u015Fkenler tan\u0131mlanm\u0131\u015Ft\u0131r\:\nOkuma/Yazma\: Failure, FailureMessage, SampleResult, vars, props, log.\nSalt Okunur\: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData, ctx +bsh_assertion_title=BeanShell Do\u011Frulamas\u0131 +bsh_function_expression=De\u011Ferlendirilecek ifade +bsh_sampler_title=BeanShell \u00D6rnekleyici +bsh_script=Betik (tan\u0131ml\u0131 de\u011Fi\u015Fkenler i\u00E7in a\u015Fa\u011F\u0131ya bak\u0131n) +bsh_script_file=Betik Dosyas\u0131 +bsh_script_parameters=Parametreler (-> Dizgi (String) Parametreler ve String []bsh.args) +bsh_script_variables=Betik i\u00E7in \u015Fu de\u011Fi\u015Fkenler tan\u0131ml\u0131d\u0131r\:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=Testle me\u015Fgul\u00FCm, l\u00FCtfen ayarlar\u0131 de\u011Fi\u015Ftirmeden \u00F6nce testi durdurun +cache_session_id=\u00D6nbellek oturum Id'si? +cancel=\u0130ptal +cancel_exit_to_save=Kaydedilmemi\u015F test maddeleri var. \u00C7\u0131kmadan \u00F6nce kaydetmek ister misiniz? +cancel_new_to_save=Kaydedilmemi\u015F test maddeleri var. Testi temizlemeden \u00F6nce kaydetmek ister misin? +cancel_revert_project=Kaydedilmemi\u015F test maddeleri var. Daha \u00F6nce kaydedilmi\u015F olan test plan\u0131na d\u00F6nmek ister misiniz? +choose_function=Fonksiyon se\u00E7in +choose_language=Dil se\u00E7in +clear=Temizle +clear_all=Hepsini Temizle +clear_cookies_per_iter=Her tekrar i\u00E7in \u00E7erezleri temizle? +column_delete_disallowed=Bu kolonu silmek i\u00E7in izin yok +column_number=CSV dosyas\u0131 i\u00E7in kolon numaras\u0131 | ileri | *takma ad +compare=Kar\u015F\u0131la\u015Ft\u0131r +comparefilt=Filtreyi kar\u015F\u0131la\u015Ft\u0131r +config_element=Ayar Eleman\u0131 +config_save_settings=Ayarla +configure_wsdl=Ayarla +constant_throughput_timer_memo=Sabit bir transfer oran\u0131 elde etmek i\u00E7in \u00F6rneklemeler aras\u0131na gecikme ekle +constant_timer_delay=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Gecikmesi (milisaniyeler) +constant_timer_memo=\u00D6rneklemeler aras\u0131na sabit bir gecikme ekle +constant_timer_title=Sabit Zamanlay\u0131c\u0131 +content_encoding=\u0130\u00E7erik kodlamas\u0131\: +controller=Denet\u00E7i +cookie_manager_policy=\u00C7erez Politikas\u0131 +cookie_manager_title=HTTP \u00C7erez Y\u00F6neticisi +cookies_stored=\u00C7erez Y\u00F6neticisinde Tutulan \u00C7erezler +copy=Kopyala +counter_config_title=Saya\u00E7 +counter_per_user=Sayac\u0131 her kullan\u0131c\u0131 i\u00E7in ba\u011F\u0131ms\u0131z \u00E7al\u0131\u015Ft\u0131r +countlim=Boyut s\u0131n\u0131r\u0131 +csvread_file_file_name=De\u011Ferlerin okunaca\u011F\u0131 CSV dosyas\u0131 | *k\u0131saltma +cut=Kes +cut_paste_function=Fonksiyon metnini kopyala ve yap\u0131\u015Ft\u0131r +database_conn_pool_max_usage=Her Ba\u011Flant\u0131 i\u00E7in En Fazla Kullan\u0131m\: +database_conn_pool_props=Veritaban\u0131 Ba\u011Flant\u0131s\u0131 Havuzu +database_conn_pool_size=Havuzdaki Ba\u011Flant\u0131 Say\u0131s\u0131 +database_conn_pool_title=JDBC Veritaban Ba\u011Flant\u0131s\u0131 Havuzu \u00D6ntan\u0131ml\u0131 De\u011Ferleri +database_driver_class=S\u00FCr\u00FCc\u00FC S\u0131n\u0131f\u0131\: +database_login_title=JDBC Veritaban\u0131 Giri\u015Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +database_sql_query_string=SQL Sorgusu Metni\: +database_sql_query_title=JDBC SQL Sorgusu \u00D6ntan\u0131ml\u0131 De\u011Ferleri +database_testing_title=JDBC \u0130ste\u011Fi +database_url=JDBC Adresi\: +database_url_jdbc_props=Veritaban\u0131 Adresi ve JDBC S\u00FCr\u00FCc\u00FCs\u00FC +de=Alman +debug_off=Hata ay\u0131klamas\u0131n\u0131 saf d\u0131\u015F\u0131 b\u0131rak +debug_on=Hata ay\u0131klamas\u0131n\u0131 etkinle\u015Ftir +default_parameters=\u00D6ntan\u0131ml\u0131 Parametreler +default_value_field=\u00D6ntan\u0131ml\u0131 De\u011Fer\: +delay=Ba\u015Flang\u0131\u00E7 gecikmesi (saniye) +delete=Sil +delete_parameter=De\u011Fi\u015Fkeni Sil +delete_test=Testi Sil +delete_user=Kullan\u0131c\u0131y\u0131 Sil +deltest=Testi sil +deref=K\u0131saltmalar\u0131 g\u00F6ster +disable=Safd\u0131\u015F\u0131 b\u0131rak +distribution_graph_title=Da\u011F\u0131t\u0131m Grafi\u011Fi (alfa) +distribution_note1=Grafik 10 \u00F6rnekte bir g\u00FCncellenecek +domain=Etki Alan\u0131 +done=Bitti +duration=S\u00FCre (saniye) +duration_assertion_duration_test=Do\u011Frulama S\u00FCresi +duration_assertion_failure=\u0130\u015Flem \u00E7ok uzun s\u00FCrd\u00FC\: {0} milisaniye s\u00FCrmesi gerekirken, {1} milisaniyeden fazla s\u00FCrd\u00FC. +duration_assertion_input_error=Pozitif bir tamsay\u0131 giriniz. +duration_assertion_label=Milisaniye olarak s\u00FCre\: +duration_assertion_title=S\u00FCre Do\u011Frulamas\u0131 +edit=D\u00FCzenle +email_results_title=E-posta Sonu\u00E7lar\u0131 +en=\u0130ngilizce +enable=Etkinle\u015Ftir +encode?=Kodlama? +encoded_value=URL Kodlanm\u0131\u015F De\u011Fer +endtime=Biti\u015F Zaman\u0131 +entry_dn=Giri\u015F DN'i +entrydn=Giri\u015F DN'i +error_loading_help=Yard\u0131m sayfas\u0131n\u0131 y\u00FCklerken hata +error_occurred=Hata Olu\u015Ftu +error_title=Hata +es=\u0130spanyolca +eval_name_param=De\u011Fi\u015Fken ve fonksiyon referanslar\u0131 i\u00E7eren metin +evalvar_name_param=De\u011Fi\u015Fken ismi +example_data=\u00D6rnek Veri +example_title=\u00D6rnekleyici \u00F6rne\u011Fi +exit=\u00C7\u0131k\u0131\u015F +expiration=S\u00FCre dolumu +field_name=Alan ismi +file=Dosya +file_already_in_use=Bu dosya zaten kullan\u0131l\u0131yor +file_visualizer_append=Varolan Veri Dosyas\u0131na Ekle +file_visualizer_auto_flush=Her \u00D6rnekten Sonra Otomatik Olarak Temizle +file_visualizer_browse=G\u00F6zat... +file_visualizer_close=Kapat +file_visualizer_file_options=Dosya Se\u00E7enekleri +file_visualizer_filename=Dosya ismi +file_visualizer_flush=Temizle +file_visualizer_missing_filename=\u00C7\u0131kt\u0131 dosyas\u0131 belirtilmedi. +file_visualizer_open=A\u00E7 +file_visualizer_output_file=Sonu\u00E7lar\u0131 dosyaya yaz / Dosyadan oku +file_visualizer_submit_data=Girilen Veriyi Ekle +file_visualizer_title=Dosya Raprolay\u0131c\u0131 +file_visualizer_verbose=\u00C7\u0131kt\u0131y\u0131 Detayland\u0131r +filename=Dosya \u0130smi +follow_redirects=Y\u00F6nlendirmeleri \u0130zle +follow_redirects_auto=Otomatik Olarak Y\u00F6nlendir +foreach_controller_title=ForEach Denet\u00E7isi +foreach_input=Giri\u015F de\u011Fi\u015Fkeni \u00F6neki +foreach_output=\u00C7\u0131k\u0131\u015F de\u011Fi\u015Fkeni ismi +foreach_use_separator=Numara \u00F6n\u00FCne "_" ekle ? +format=Numara bi\u00E7imi +fr=Frans\u0131zca +ftp_binary_mode=\u0130kili kipi kullan ? +ftp_local_file=Yerel Dosya\: +ftp_remote_file=Uzak Dosya\: +ftp_sample_title=FTP \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +ftp_save_response_data=Cevab\u0131 Dosyaya Kaydet ? +ftp_testing_title=FTP \u0130ste\u011Fi +function_dialog_menu_item=Fonksiyon Yard\u0131m\u0131 Diyalo\u011Fu +function_helper_title=Fonksiyon Yard\u0131m\u0131 +function_name_param=Sonucu tutacak de\u011F\u015Fkenin ismi (gerekli) +function_name_paropt=Sonucu tutacak de\u011Fi\u015Fkenin ismi (iste\u011Fe ba\u011Fl\u0131) +function_params=Fonksiyon Parametresi +functional_mode=Fonksiyonel Test Kipi (\u00F6r\: Cevap Verisini ve \u00D6rnekleyici Verisini kaydet) +functional_mode_explanation=Ba\u015Far\u0131m\u0131 olumsuz etkileyecek olmas\u0131na ra\u011Fmen Fonksiyonel Test Kipi se\u00E7iliyor. +gaussian_timer_delay=Sabit Gecikme S\u0131n\u0131r\u0131 (milisaniye) +gaussian_timer_memo=Gauss da\u011F\u0131l\u0131m\u0131na g\u00F6re rastgele bir gecikme ekler +gaussian_timer_range=Sapma (milisaniye) +gaussian_timer_title=Gauss Rastgele Zamanlay\u0131c\u0131 +generate=\u00DCret +generator=\u00DCretici S\u0131n\u0131f\u0131n \u0130smi +generator_cnf_msg=\u00DCretici s\u0131n\u0131f\u0131 bulamad\u0131. L\u00FCtfen jar dosyas\u0131n\u0131 /lib dizini alt\u0131na yerle\u015Ftirdi\u011Finizden emin olun. +generator_illegal_msg=IllegalAccessException nedeniyle \u00FCretici s\u0131n\u0131fa eri\u015Femedi. +generator_instantiate_msg=\u00DCretici ayr\u0131\u015Ft\u0131r\u0131c\u0131 i\u00E7in \u00F6rnek yaratamad\u0131. L\u00FCtfen \u00FCreticinin "Generator" arabirimini ger\u00E7ekledi\u011Finden emin olun. +get_xml_from_file=SOAP XML Verisini i\u00E7eren Dosya (A\u015Fa\u011F\u0131daki metnin \u00FCzerine yazar) +get_xml_from_random=Mesaj Klas\u00F6r\u00FC +graph_choose_graphs=G\u00F6sterilecek Grafikler +graph_full_results_title=Grafik Tam Sonu\u00E7lar\u0131 +graph_results_average=Ortalama +graph_results_data=Veri +graph_results_deviation=Sapma +graph_results_latest_sample=Son \u00D6rnek +graph_results_median=Orta +graph_results_no_samples=\u00D6rnek Say\u0131s\u0131 +graph_results_throughput=Transfer Oran\u0131 +graph_results_title=Grafik Sonu\u00E7lar\u0131 +grouping_add_separators=Gruplar aras\u0131na ayra\u00E7 ekle +grouping_in_controllers=Her grubu yeni bir denet\u00E7iye koy +grouping_mode=Gruplama\: +grouping_no_groups=\u00D6rnekleyicileri gruplama +grouping_store_first_only=Her grubun sadece 1. \u00F6rnekleyicilerini tut +header_manager_title=HTTP Ba\u015Fl\u0131k Y\u00F6neticisi +headers_stored=Ba\u015Fl\u0131k Y\u00F6neticisinde Tutulan Ba\u015Fl\u0131klar +help=Yard\u0131m +help_node=Bu d\u00FC\u011F\u00FCm nedir? +html_assertion_file=JTidy raporunu dosyaya yaz +html_assertion_label=HTML Do\u011Frulama +html_assertion_title=HTML Do\u011Frulama +html_parameter_mask=HTML Parametre Maskesi +http_implementation=Uygulamas\u0131\: +http_response_code=HTTP cevap kodu +http_url_rewriting_modifier_title=HTTP URL Yeniden Yazma Niteleyicisi +http_user_parameter_modifier=HTTP Kullan\u0131c\u0131 Parametresi Niteleyicisi +httpmirror_title=HTTP Ayna Sunucusu +id_prefix=ID \u00D6neki +id_suffix=ID Soneki +if_controller_evaluate_all=T\u00FCm \u00E7ocuklar i\u00E7in hesapla? +if_controller_label=Durum (Javascript) +if_controller_title=If Denet\u00E7isi +ignore_subcontrollers=Alt denet\u00E7i bloklar\u0131n\u0131 yoksay +include_controller=\u0130\u00E7erme Denet\u00E7isi +include_equals=E\u015Fleniyorsa i\u00E7er? +include_path=Test Plan\u0131n\u0131 \u0130\u00E7er +increment=Artt\u0131r +infinite=Her zaman +initial_context_factory=\u0130lk Ba\u011Flam Fabrikas\u0131 +insert_after=Arkas\u0131na Ekle +insert_before=\u00D6n\u00FCne Ekle +insert_parent=Ebeveynine Ekle +interleave_control_title=Aral\u0131k Denet\u00E7isi +intsum_param_1=Eklenecek ilk tamsay\u0131. +intsum_param_2=Eklenecek ikinci tamsay\u0131 - sonraki tamsay\u0131lar di\u011Fer argumanlar\u0131 ekleyerek toplanabilir. +invalid_data=Ge\u00E7ersiz veri +invalid_mail=E-posta g\u00F6nderirken hata olu\u015Ftu +invalid_mail_address=Bir veya daha fazla ge\u00E7ersiz e-posta adresi tespit edildi +invalid_mail_server=E-posta sunucusuna ba\u011Flan\u0131rken problem (JMeter log dosyas\u0131na bak\u0131n\u0131z) +invalid_variables=Ge\u00E7ersiz de\u011Fi\u015Fkenler +iteration_counter_arg_1=TRUE, her kullan\u0131c\u0131n\u0131n kendi sayac\u0131na sahip olmas\u0131 i\u00E7in, FALSE genel-ge\u00E7er saya\u00E7 i\u00E7in +iterator_num=D\u00F6ng\u00FC Say\u0131s\u0131\: +ja=Japonca +jar_file=Jar Dosyalar\u0131 +java_request=Java \u0130ste\u011Fi +java_request_defaults=Java \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +javascript_expression=De\u011Ferlendirilecek javascript ifadesi +jexl_expression=De\u011Ferlendirilecek JEXL ifadesi +jms_auth_required=Gerekli +jms_client_caption=Mesaj dinlemek i\u00E7in TopicSubscriber.receive() kullanan istemciyi al. +jms_client_caption2=MessageListener yeni mesajlar\u0131 dinlemek i\u00E7in onMessage(Message)'\u0131 kullan\u0131r. +jms_client_type=\u0130stemci +jms_communication_style=\u0130leti\u015Fim \u015Eekli +jms_concrete_connection_factory=Somut Ba\u011Flant\u0131 Fabrikas\u0131 +jms_config=Ayarlar +jms_config_title=JMS Ayar\u0131 +jms_connection_factory=Ba\u011Flant\u0131 Fabrikas\u0131 +jms_file=Dosya +jms_initial_context_factory=Ba\u015Flang\u0131\u00E7 Ba\u011Flam Fabrikas\u0131 +jms_itertions=Toplanacak istek say\u0131s\u0131 +jms_jndi_defaults_title=JNDI \u00D6ntan\u0131ml\u0131 Ayarlar\u0131 +jms_jndi_props=JDNI \u00D6zellikleri +jms_message_title=Mesaj \u00F6zellikleri +jms_message_type=Mesaj Tipi +jms_msg_content=\u0130\u00E7erik +jms_object_message=Nesne Mesaj\u0131 +jms_point_to_point=JMS U\u00E7tan Uca +jms_props=JMS \u00D6zellikleri +jms_provider_url=Sa\u011Flay\u0131c\u0131 Adresi (URL) +jms_publisher=JMS Yay\u0131nc\u0131s\u0131 +jms_pwd=\u015Eifre +jms_queue=S\u0131ra +jms_queue_connection_factory=QueueConnection Fabrikas\u0131 +jms_queueing=JMS Kaynaklar\u0131 +jms_random_file=Rastgele Dosyas\u0131 +jms_read_response=Cevab\u0131 Oku +jms_receive_queue=S\u0131ray\u0131 alan JNDI ismi +jms_request=Sadece \u0130stek +jms_requestreply=\u0130stek Cevap +jms_sample_title=JMS \u00D6ntan\u0131ml\u0131 \u0130ste\u011Fi +jms_send_queue=S\u0131ray\u0131 alan JNDI ismi +jms_subscriber_on_message=MessageListener.onMessage()'\u0131 kullan +jms_subscriber_receive=TopicSubscriber.receive()'\u0131 kullan +jms_subscriber_title=JMS Abonesi +jms_testing_title=Mesajla\u015Fma \u0130ste\u011Fi +jms_text_message=Metin Mesaj\u0131 +jms_timeout=Zaman A\u015F\u0131m\u0131 (milisaniye) +jms_topic=Konu +jms_use_file=Dosyadan +jms_use_non_persistent_delivery=S\u00FCrekli olmayan da\u011F\u0131t\u0131m kipini kullan? +jms_use_properties_file=jndi.properties dosyas\u0131n\u0131 kullan +jms_use_random_file=Rastgele Dosyas\u0131 +jms_use_text=Metin alan\u0131 +jms_user=Kullan\u0131c\u0131 +jndi_config_title=JNDI Ayar\u0131 +jndi_lookup_name=Uzak Arabirim +jndi_lookup_title=JNDI Arama Ayar\u0131 +jndi_method_button_invoke=\u00C7a\u011F\u0131r +jndi_method_button_reflect=Yans\u0131t +jndi_method_home_name=Yerel Metod \u0130smi +jndi_method_home_parms=Yerel Metod Parametreleri +jndi_method_name=Metod Ayar\u0131 +jndi_method_remote_interface_list=Uzak Arabirimler +jndi_method_remote_name=Uzak Metod \u0130smi +jndi_method_remote_parms=Uzak Metod Parametreleri +jndi_method_title=Uzak Metod Ayar\u0131 +jndi_testing_title=JNDI \u0130ste\u011Fi +jndi_url_jndi_props=JNDI \u00D6zellikleri +junit_append_error=Do\u011Frulama hatalar\u0131n\u0131 ekle +junit_append_exception=\u00C7al\u0131\u015Fma zaman\u0131 istisnalar\u0131n\u0131 ekle +junit_constructor_error=S\u0131n\u0131f \u00F6rne\u011Fi yarat\u0131lamad\u0131 +junit_constructor_string=Yap\u0131c\u0131 Metin Etiketi +junit_do_setup_teardown=setUp ve tearDown'u \u00E7a\u011F\u0131rma +junit_error_code=Hata Kodu +junit_error_default_msg=Beklenmedik hata olu\u015Ftu +junit_error_msg=Hata Mesaj\u0131 +junit_failure_code=Ba\u015Far\u0131s\u0131zl\u0131k Kodu +junit_failure_default_msg=Test ba\u015Far\u0131s\u0131z oldu. +junit_failure_msg=Ba\u015Far\u0131s\u0131zl\u0131k Mesaj\u0131 +junit_pkg_filter=Paket Filtresi +junit_request=JUnit \u0130ste\u011Fi +junit_request_defaults=JUnit \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +junit_success_code=Ba\u015Far\u0131 Kodu +junit_success_default_msg=Test ba\u015Far\u0131s\u0131z +junit_success_msg=Ba\u015Far\u0131 Mesaj\u0131 +junit_test_config=JUnit Test Parametreleri +junit_test_method=Test Metodu +ldap_argument_list=LDAP Arg\u00FCman Listesi +ldap_connto=Ba\u011Flant\u0131 zaman a\u015F\u0131m\u0131 (milisaniye) +ldap_parse_results=Arama sonu\u00E7lar\u0131n\u0131 ayr\u0131\u015Ft\u0131r ? +ldap_sample_title=LDAP \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 Ayarlar\u0131 +ldap_search_baseobject=Temel-nesne aramas\u0131 ger\u00E7ekle\u015Ftir +ldap_search_onelevel=Tek-seviye aramas\u0131 ger\u00E7ekle\u015Ftir +ldap_search_subtree=Alt-a\u011Fa\u00E7 aramas\u0131 ger\u00E7ekle\u015Ftir +ldap_secure=G\u00FCvenli LDAP Protokulu kullan ? +ldap_testing_title=LDAP \u0130ste\u011Fi +ldapext_sample_title=LDAP Geli\u015Fmi\u015F \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +ldapext_testing_title=LDAP Geli\u015Fmi\u015F \u0130ste\u011Fi +load=Y\u00FCkle +load_wsdl=WSDL Y\u00FCkle +log_errors_only=Hatalar +log_file=Log Dosyas\u0131 Yolu +log_function_comment=Ek yorum (iste\u011Fe ba\u011Fl\u0131) +log_function_level=Log seviyesi (\u00F6ntan\u0131ml\u0131 INFO) ya OUT ya da ERR +log_function_string=Loglanacak metin +log_function_string_ret=Loglanacak (ve d\u00F6n\u00FClecek) metin +log_function_throwable=At\u0131lacak metin (iste\u011Fe ba\u011Fl\u0131) +log_only=Sadece Log/G\u00F6r\u00FCnt\u00FCleme\: +log_parser=Log Ayr\u0131\u015Ft\u0131r\u0131c\u0131s\u0131 S\u0131n\u0131f\u0131n\u0131n \u0130smi +log_parser_cnf_msg=S\u0131n\u0131f\u0131 bulamad\u0131. L\u00FCtfen jar dosyas\u0131n\u0131 /lib dizini alt\u0131na yerle\u015Ftirdi\u011Finizden emin olun. +log_parser_illegal_msg=IllegalAcessException nedeniyle s\u0131n\u0131fa eri\u015Femedi. +log_parser_instantiate_msg=Log ayr\u0131\u015Ft\u0131r\u0131c\u0131 \u00F6rne\u011Fi yaratamad\u0131. L\u00FCtfen ayr\u0131\u015Ft\u0131r\u0131c\u0131n\u0131n LogParser arabirimini ger\u00E7ekledi\u011Finden emin olun. +log_sampler=Tomcat Eri\u015Fimi Log \u00D6rnekleyicisi +log_success_only=Ba\u015Far\u0131lar +logic_controller_title=Basit Denet\u00E7i +login_config=Kullan\u0131c\u0131 Giri\u015Fi Ayar\u0131 +login_config_element=Kullan\u0131c\u0131 Giri\u015Fi Eleman\u0131 +longsum_param_1=Eklenek ilk b\u00FCy\u00FCk say\u0131 (long) +longsum_param_2=Eklenecek ikinci b\u00FCy\u00FCk say\u0131 (long) - di\u011Fer b\u00FCy\u00FCk say\u0131lar di\u011Fer arg\u00FCmanlar\u0131n toplanmas\u0131yla elde edilebilir. +loop_controller_title=D\u00F6ng\u00FC Denet\u00E7isi +looping_control=D\u00F6ng\u00FC Kontrol\u00FC +lower_bound=A\u015Fa\u011F\u0131 S\u0131n\u0131r +mail_reader_account=Kullan\u0131c\u0131 ismi\: +mail_reader_all_messages=Hepsi +mail_reader_delete=Sunucudan mesajlar\u0131 sil +mail_reader_folder=Klas\u00F6r\: +mail_reader_num_messages=\u00C7ekilecek mesajlar\u0131n say\u0131s\u0131\: +mail_reader_password=\u015Eifre\: +mail_reader_server=Sunucu\: +mail_reader_server_type=Sunucu Tipi\: +mail_reader_title=Eposta Okuyucu \u00D6rnekleyicisi +mail_sent=Eposta ba\u015Far\u0131yla g\u00F6nderildi +mailer_attributes_panel=Eposta \u00F6znitelikleri +mailer_error=Eposta g\u00F6nderilemedi. L\u00FCtfen sorunlu girdileri d\u00FCzeltin. +mailer_visualizer_title=Eposta G\u00F6r\u00FCnt\u00FCleyicisi +match_num_field=E\u015Fle\u015Fme Numaras\u0131. (Rastgele i\u00E7in 0) +max=En Fazla +maximum_param=\u0130zin verilen de\u011Fer aral\u0131\u011F\u0131 i\u00E7in en b\u00FCy\u00FCk de\u011Fer +md5hex_assertion_failure=MD5 toplam\u0131 do\u011Frulamas\u0131 hatas\u0131\: {1} beklenirken {0} al\u0131nd\u0131 +md5hex_assertion_label=MDBHex +md5hex_assertion_md5hex_test=Do\u011Frulanacak MD5Hex +md5hex_assertion_title=MD5Hex Do\u011Frulamas\u0131 +memory_cache=\u00D6nbellek +menu_assertions=Do\u011Frulamalar +menu_close=Kapat +menu_collapse_all=Hepsini Kapat +menu_config_element=Ayar Eleman\u0131 +menu_edit=D\u00FCzenle +menu_expand_all=Hepsini A\u00E7 +menu_generative_controller=\u00D6rnekleyici +menu_listener=Dinleyici +menu_logic_controller=Mant\u0131k Denet\u00E7isi +menu_merge=Birle\u015Ftir +menu_modifiers=Niteleyiciler +menu_non_test_elements=Test-d\u0131\u015F\u0131 Elemanlar +menu_open=A\u00E7 +menu_post_processors=Test Sonras\u0131 \u0130\u015Flemciler +menu_pre_processors=Test \u00D6ncesi \u0130\u015Flemciler +menu_response_based_modifiers=Cevap Temelli Niteleyiciler +menu_timer=Zamanlay\u0131c\u0131 +metadata=Veri hakk\u0131nda veri (metadata) +method=Metod\: +mimetype=Mime tipi +minimum_param=\u0130zin verilen de\u011Fer aral\u0131\u011F\u0131 i\u00E7in en k\u00FC\u00E7\u00FCk de\u011Fer +minute=dakika +modddn=Eski girdi ismi +modification_controller_title=De\u011Fi\u015Fiklik Denet\u00E7isi +modification_manager_title=De\u011Fi\u015Fiklik Y\u00F6neticisi +modify_test=Testi De\u011Fi\u015Ftir +modtest=De\u011Fi\u015Fiklik testi +module_controller_module_to_run=\u00C7al\u0131\u015Ft\u0131r\u0131lacak Birim +module_controller_title=Birim Denet\u00E7isi +module_controller_warning=Birim bulunamad\u0131\: +monitor_equation_active=Aktif\: (me\u015Fgul/maksimum) > 25% +monitor_equation_dead=\u00D6l\u00FC\: cevap yok +monitor_equation_healthy=Sa\u011Fl\u0131kl\u0131\: (me\u015Fgul/maksimum) < 25% +monitor_equation_load=Y\u00FCk\: ((me\u015Fgul / maksimum * 50) + ((kullan\u0131lan bellek / maksimum bellek)) > 25% +monitor_equation_warning=Uyar\u0131\: (me\u015Fgul/maksimum) > 67% +monitor_health_tab_title=Sa\u011Fl\u0131k +monitor_health_title=\u0130zleme Sonu\u00E7lar\u0131 +monitor_is_title=\u0130zleyici olarak kullan +monitor_label_right_active=Aktif +monitor_label_right_dead=\u00D6l\u00FC +monitor_label_right_healthy=Sa\u011Fl\u0131kl\u0131 +monitor_label_right_warning=Uyar\u0131 +monitor_legend_health=Sa\u011Fl\u0131k +monitor_legend_load=Y\u00FCk +monitor_legend_memory_per=Bellek % (kullan\u0131lan/toplam) +monitor_legend_thread_per=\u0130\u015F par\u00E7ac\u0131\u011F\u0131 % (me\u015Fgul/maksimum) +monitor_performance_servers=Sunucular +monitor_performance_tab_title=Ba\u015Far\u0131m +monitor_performance_title=Ba\u015Far\u0131m Grafi\u011Fi +name=\u0130sim\: +new=Yeni +newdn=Yeni ay\u0131rt edici isim +no=Norve\u00E7ce +number_of_threads=\u0130\u015F par\u00E7ac\u0131\u011F\u0131 say\u0131s\u0131 +obsolete_test_element=Test eleman\u0131 belirsiz +once_only_controller_title=Bir Kerelik Denet\u00E7i +open=A\u00E7... +option=Se\u00E7enekler +optional_tasks=\u0130ste\u011Fe Ba\u011Fl\u0131 G\u00F6revler +paramtable=\u0130stekle parametreleri g\u00F6nder\: +password=\u015Eifre +paste=Yap\u0131\u015Ft\u0131r +paste_insert=Ekleme Olarak Yap\u0131\u015Ft\u0131r +path=Yol\: +path_extension_choice=Yol Uzatmas\u0131 (ayra\u00E7 olarak ";" kullan) +path_extension_dont_use_equals=Yol uzatmas\u0131nda e\u015Fitlik kullanmay\u0131n (Intershop Enfinity uyumlulu\u011Fu) +path_extension_dont_use_questionmark=Yol uzatmas\u0131nda soru i\u015Fareti kullanmay\u0131n (Intershop Enfinity uyumlulu\u011Fu) +patterns_to_exclude=Hari\u00E7 Tutulacak URL Desenleri +patterns_to_include=Dahil Edilecek URL Desenleri +pkcs12_desc=PKCS 12 Anahtar (*.p12) +property_default_param=\u00D6ntan\u0131ml\u0131 de\u011Fer +property_edit=D\u00FCzenle +property_editor.value_is_invalid_message=Girdi\u011Finiz metin bu \u00F6zellik i\u00E7in ge\u00E7erli de\u011Fil.\n\u00D6zellik \u00F6nceki de\u011Ferine geri d\u00F6nd\u00FCr\u00FClecek. +property_editor.value_is_invalid_title=Ge\u00E7ersiz girdi +property_name_param=\u00D6zellik ismi +property_returnvalue_param=\u00D6zelli\u011Fin orjinal de\u011Ferini d\u00F6n (\u00F6ntan\u0131ml\u0131 false)? +property_undefined=Tan\u0131ms\u0131z +property_value_param=\u00D6zelli\u011Fin de\u011Feri +property_visualiser_title=\u00D6zellik G\u00F6r\u00FCnt\u00FCleme +protocol=Protokol [http]\: +protocol_java_border=Java s\u0131n\u0131f\u0131 +protocol_java_classname=S\u0131n\u0131f ismi\: +protocol_java_config_tile=Java \u00D6rne\u011Fi Ayarla +protocol_java_test_title=Java Testi +provider_url=Sa\u011Flay\u0131c\u0131 Adresi (URL) +proxy_assertions=Do\u011Frulamalar\u0131 Ekle +proxy_cl_error=Vekil sunucu belirtiliyorsa, sunucu ve port verilmeli +proxy_content_type_exclude=Hari\u00E7 tut\: +proxy_content_type_filter=\u0130\u00E7erik-tipi filtresi +proxy_content_type_include=\u0130\u00E7eren\: +proxy_headers=HTTP Ba\u015Fl\u0131klar\u0131n\u0131 Yakala +proxy_regex=D\u00FCzenli ifade e\u015Fle\u015Fmesi +proxy_sampler_settings=HTTP \u00D6rnekleyici Ayarlar\u0131 +proxy_sampler_type=Tip\: +proxy_separators=Ayra\u00E7lar\u0131 Ekle +proxy_target=Hedef Denet\u00E7isi\: +proxy_test_plan_content=Test plan\u0131 i\u00E7eri\u011Fi +proxy_title=HTTP Vekil Sunucusu +ramp_up=Rampa S\u00FCresi (saniyeler)\: +random_control_title=Rastgele Denet\u00E7isi +random_order_control_title=Rastgele S\u0131ra Denet\u00E7isi +read_response_message="Cevap Oku" se\u00E7ene\u011Fi i\u015Faretli de\u011Fil. Cevab\u0131 g\u00F6rmek i\u00E7in, l\u00FCtfen \u00F6rnekleyicideki ilgili kutuyu i\u015Faretleyin. +read_response_note=E\u011Fer "cevap oku" se\u00E7ene\u011Fi se\u00E7ili de\u011Filse, \u00F6rnekleyici cevab\u0131 okumaz +read_response_note2=ya da SampleResult'\u0131 kur. Bu ba\u015Far\u0131m\u0131 artt\u0131r\u0131r, ama \u015Fu anlama gelir +read_response_note3=cevap i\u00E7eri\u011Fi loglanmayacak. +read_soap_response=SOAP Cevab\u0131n\u0131 Oku +realm=Alan (Realm) +record_controller_title=Kaydetme Denet\u00E7isi +ref_name_field=Referans \u0130smi\: +regex_extractor_title=D\u00FCzenli \u0130fade \u00C7\u0131kar\u0131c\u0131 +regex_field=D\u00FCzenli \u0130fade\: +regex_source=Se\u00E7ilecek Cevap Alanlar\u0131 +regex_src_body=G\u00F6vde +regex_src_hdrs=Ba\u015Fl\u0131klar +regex_src_url=Adres (URL) +regexfunc_param_1=\u00D6nceki istekte sonu\u00E7lar\u0131 aramak i\u00E7in kullan\u0131lan d\u00FCzenli ifade +regexfunc_param_2=De\u011Fi\u015Ftirme metni i\u00E7in, d\u00FCzenli ifadeden gruplar\u0131 kullanan \u015Fablon.\nBi\u00E7im $[group]$. \u00D6rnek $1$. +regexfunc_param_3=Hangi e\u015Fle\u015Fme kullan\u0131lacak. 1 ya da 1'den b\u00FCy\u00FCk bir tamsay\u0131, rastgele se\u00E7im i\u00E7in RAND, b\u00FCy\u00FCk say\u0131 (float) i\u00E7in A, t\u00FCm e\u015Fle\u015Fmelerin kullan\u0131lmas\u0131 i\u00E7in ALL ([1]) +regexfunc_param_4=Metinler aras\u0131nda. E\u011Fer ALL se\u00E7iliyse, aradaki metin sonu\u00E7lar\u0131 yaratmak i\u00E7in kullan\u0131lacak ([""]) +regexfunc_param_5=\u00D6ntan\u0131ml\u0131 metin. E\u011Fer d\u00FCzenli ifade bir e\u015Fle\u015Fme yakalayamazsa, yerine kullan\u0131lacak \u015Fablon ([""]) +remote_error_init=Uzak sunucuyu s\u0131f\u0131rlarken hata +remote_error_starting=Uzak sunucuyu ba\u015Flat\u0131rken hata +remote_exit=Uzakta \u00C7\u0131k +remote_exit_all=Uzakta Hepsinden \u00C7\u0131k +remote_start=Uzakta Ba\u015Flat +remote_start_all=Uzakta Hepsini Ba\u015Flat +remote_stop=Uzakta Durdur +remote_stop_all=Uzakta Hepsini Durdur +remove=Kald\u0131r +rename=Girdiyi yeniden adland\u0131r +report=Rapor +report_bar_chart=Bar Grafi\u011Fi +report_bar_graph_url=Adres (URL) +report_base_directory=Temel Dizin +report_chart_caption=Grafik Ba\u015Fl\u0131\u011F\u0131 +report_chart_x_axis=X Ekseni +report_chart_x_axis_label=X Ekseni Etiketi +report_chart_y_axis=Y Ekseni +report_chart_y_axis_label=Y Ekseni Etiketi +report_line_graph=\u00C7izgi Grafik +report_line_graph_urls=\u0130\u00E7erilen Adresler (URLler) +report_output_directory=Rapor i\u00E7in \u00C7\u0131kt\u0131 Dizini +report_page=Rapor Sayfas\u0131 +report_page_element=Sayfa Eleman\u0131 +report_page_footer=Sayfa Altl\u0131\u011F\u0131 +report_page_header=Sayfa Ba\u015Fl\u0131\u011F\u0131 +report_page_index=Sayfa \u0130ndeksi Yarat +report_page_intro=Sayfa Giri\u015Fi +report_page_style_url=CSS Adresi (URL) +report_page_title=Sayfa Ba\u015Fl\u0131\u011F\u0131 +report_pie_chart=Elma Grafi\u011Fi +report_plan=Rapor Plan\u0131 +report_select=Se\u00E7 +report_summary=Rapor \u00D6zeti +report_table=Rapor Tablosu +report_writer=Rapor Yaz\u0131c\u0131 +report_writer_html=HTML Raporu Yaz\u0131c\u0131 +request_data=\u0130stek Verisi +reset_gui=Aray\u00FCz\u00FC S\u0131f\u0131rla +restart=Ba\u015Ftan ba\u015Flat +resultaction_title=Sonu\u00E7 Durumu Eylem \u0130\u015Fleyici +resultsaver_errors=Sadece Ba\u015Far\u0131s\u0131z Cevaplar\u0131 Kaydet +resultsaver_prefix=Dosya ismi \u00F6neki\: +resultsaver_title=Cevaplar\u0131 dosyaya kaydet +retobj=Nesne d\u00F6n +reuseconnection=Ba\u011Flant\u0131y\u0131 tekrar kullan +revert_project=Geri d\u00F6nd\u00FCr +revert_project?=Projeyi geri d\u00F6nd\u00FCr? +root=K\u00F6k +root_title=K\u00F6k +run=\u00C7al\u0131\u015Ft\u0131r +running_test=Testi \u00E7al\u0131\u015Ft\u0131r +runtime_controller_title=\u00C7al\u0131\u015Fma Zaman\u0131 Denet\u00E7isi +runtime_seconds=\u00C7al\u0131\u015Fma Zaman\u0131 (saniyeler) +sample_result_save_configuration=\u00D6rnek Sonu\u00E7 Kaydetme Ayar\u0131 +sampler_label=Etiket +sampler_on_error_action=\u00D6rnekleyici hatas\u0131ndan sonra yap\u0131lacak hareket +sampler_on_error_continue=Devam et +sampler_on_error_stop_test=Testi Durdur +sampler_on_error_stop_thread=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Durdur +save=Kaydet +save?=Kaydet? +save_all_as=Test Plan\u0131 olarak Kaydet +save_as=Se\u00E7imi Farkl\u0131 Kaydet... +save_as_error=Birden fazla madde se\u00E7ili\! +save_as_image=D\u00FC\u011F\u00FCm\u00FC Resim olarak Kaydet +save_as_image_all=Ekran\u0131 Resim olarak Kaydet +save_assertionresultsfailuremessage=Do\u011Frulama Ba\u015Far\u0131s\u0131zl\u0131\u011F\u0131 Mesaj\u0131n\u0131 Kaydet +save_assertions=Do\u011Frulama Sonu\u00E7lar\u0131n\u0131 Kaydet (XML) +save_asxml=XML olarak Kaydet +save_bytes=Bayt say\u0131s\u0131n\u0131 kaydet +save_code=Cevap Kodunu Kaydet +save_datatype=Data Tipini Kaydet +save_encoding=Kodlamay\u0131 Kaydet +save_fieldnames=Alan \u0130simlerini Kaydet (CSV) +save_filename=Cevap Dosya \u0130smini Kaydet +save_graphics=Grafi\u011Fi Kaydet +save_hostname=Sunucu \u0130smini Kaydet +save_label=Etiketi Kaydet +save_latency=Gecikme S\u00FCresi Kaydet +save_message=Cevap Mesaj\u0131n\u0131 Kaydet +save_overwrite_existing_file=Se\u00E7ili dosya zaten mevcut, \u00FCst\u00FCne yazmak ister misiniz? +save_requestheaders=\u0130stek Ba\u015Fl\u0131klar\u0131n\u0131 Kaydet (XML) +save_responsedata=Cevap Verisini Kaydet (XML) +save_responseheaders=Cevap Ba\u015Fl\u0131klar\u0131n\u0131 Kaydet (XML) +save_samplecount=\u00D6rnek ve Hata Say\u0131s\u0131n\u0131 Kaydet +save_samplerdata=\u00D6rnekleyici Verisini Kaydet (XML) +save_subresults=Alt Sonu\u00E7lar\u0131 Kaydet (XML)\n +save_success=Ba\u015Far\u0131y\u0131 Kaydet +save_threadcounts=Aktif \u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Say\u0131s\u0131n\u0131 Kaydet +save_threadname=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 \u0130smini Kaydet +save_time=Ge\u00E7en Zaman\u0131 Kaydet +save_timestamp=Tarih Bilgisini Kaydet +save_url=URL Adresini Kaydet +sbind=Yaln\u0131z ba\u011Fla/ba\u011Flama +scheduler=Planla +scheduler_configuration=Planlama Ayar\u0131 +scope=Kapsam +search_base=Ara\u015Ft\u0131rma temeli +search_filter=Ara\u015Ft\u0131rma Filtresi +search_test=Ara\u015Ft\u0131rma Testi +searchbase=Ara\u015Ft\u0131rma temeli +searchfilter=Ara\u015Ft\u0131rma Filtresi +searchtest=Ara\u015Ft\u0131rma testi +second=saniye +secure=G\u00FCvenli +send_file=\u0130stekle Beraber Dosya G\u00F6nder\: +send_file_browse=G\u00F6zat... +send_file_filename_label=Dosya Yolu\: +send_file_mime_label=MIME Tipi\: +send_file_param_name_label=Parametre \u0130smi\: +server=Sunucu \u0130smi veya IP\: +servername=Sunucu \u0130smi \: +session_argument_name=Oturum Arg\u00FCman\u0131 \u0130smi +should_save=Testi \u00E7al\u0131\u015Ft\u0131rmadan \u00F6nce test plan\u0131n\u0131 kaydetmeniz tavsiye edilir.\nE\u011Fer destek veri dosyalar\u0131 kullan\u0131yorsan\u0131z (\u00F6r\: CSV Veri K\u00FCmesi ya da _StringFromFile), \u00F6ncelikle test beti\u011Fini kaydetmeniz \u00F6nemlidir.\n\u00D6ncelikle test plan\u0131n\u0131 kaydetmek istiyor musunuz? +shutdown=Kapat +simple_config_element=Basit Ayar Eleman\u0131 +simple_data_writer_title=Basit Veri Yaz\u0131c\u0131 +size_assertion_comparator_error_equal=e\u015Fittir +size_assertion_comparator_error_greater=b\u00FCy\u00FCkt\u00FCr +size_assertion_comparator_error_greaterequal=b\u00FCy\u00FCkt\u00FCr ya da e\u015Fittir +size_assertion_comparator_error_less=k\u00FC\u00E7\u00FCkt\u00FCr +size_assertion_comparator_error_lessequal=k\u00FC\u00E7\u00FCkt\u00FCr ya da e\u015Fittir +size_assertion_comparator_error_notequal=e\u015Fit de\u011Fildir +size_assertion_comparator_label=Kar\u015F\u0131la\u015Ft\u0131rma Tipi +size_assertion_failure=Sonu\u00E7 boyutunda yanl\u0131\u015Fl\u0131k\: {0} bayt, halbuki {1} {2} bayt olmas\u0131 bekleniyordu. +size_assertion_input_error=L\u00FCtfen ge\u00E7erli pozitif bir tamsay\u0131 girin. +size_assertion_label=Boyut (bayt)\: +size_assertion_size_test=Do\u011Frulanacak Boyut +size_assertion_title=Boyut Do\u011Frulamas\u0131 +soap_data_title=Soap/XML-RPC Verisi +soap_sampler_title=SOAP/XML-RPC \u0130ste\u011Fi +soap_send_action=SOAPAction g\u00F6nder\: +spline_visualizer_average=Ortalama +spline_visualizer_incoming=Gelen +spline_visualizer_maximum=Maksimum +spline_visualizer_title=Cetvel G\u00F6r\u00FCnt\u00FCleyici +spline_visualizer_waitingmessage=\u00D6rnekler i\u00E7in bekliyor +ssl_alias_prompt=L\u00FCtfen tercih etti\u011Finiz k\u0131saltmay\u0131 girin +ssl_alias_select=Test k\u0131saltman\u0131z\u0131 se\u00E7iniz +ssl_alias_title=\u0130stemci K\u0131saltmas\u0131 +ssl_error_title=Anahtar Kayd\u0131 Problemi +ssl_pass_prompt=L\u00FCtfen \u015Fifrenizi girin +ssl_pass_title=Anahtar Kayd\u0131 Problemi +ssl_port=SSL Portu +sslmanager=SSL Y\u00F6neticisi +start=Ba\u015Flat +starttime=Ba\u015Flama Zaman\u0131 +stop=Durdur +stopping_test=T\u00FCm i\u015F par\u00E7ac\u0131klar\u0131n\u0131 kapat\u0131yor. L\u00FCtfen sab\u0131rl\u0131 olun. +stopping_test_title=Testleri Durduruyor +string_from_file_file_name=Dosya tam yolunu girin +string_from_file_seq_final=Dosya s\u0131ra numaras\u0131n\u0131 sonland\u0131r (iste\u011Fe ba\u011Fl\u0131) +string_from_file_seq_start=Dosya s\u0131ra numaras\u0131n\u0131 ba\u015Flat (iste\u011Fe ba\u011Fl\u0131) +summariser_title=\u00D6zet Sonu\u00E7lar Olu\u015Ftur +summary_report=\u00D6zet Rapor +switch_controller_label=Dallanma (Switch) De\u011Feri +switch_controller_title=Dallanma (Switch) Denet\u00E7isi +table_visualizer_bytes=Bayt +table_visualizer_sample_num=\u00D6rnek \# +table_visualizer_sample_time=\u00D6rnek Zaman\u0131(ms) +table_visualizer_start_time=Ba\u015Flama Zaman\u0131 +table_visualizer_status=Durum +table_visualizer_success=Ba\u015Far\u0131 +table_visualizer_thread_name=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 \u0130smi +table_visualizer_warning=Uyar\u0131 +tcp_config_title=TCP \u00D6rnekleyici Ayar\u0131 +tcp_nodelay=Hi\u00E7 Gecikmeye Ayarla +tcp_port=Port Numaras\u0131\: +tcp_request_data=G\u00F6nderilecek metin +tcp_sample_title=TCP \u00D6rnekleyici +tcp_timeout=Zaman A\u015F\u0131m\u0131 (milisaniye) +template_field=\u015Eablon\: +test_action_action=Hareket +test_action_duration=S\u00FCre (milisaniye) +test_action_pause=Duraklat +test_action_stop=Durdur +test_action_target=Hedef +test_action_target_test=T\u00FCm \u0130\u015F Par\u00E7ac\u0131klar\u0131 +test_action_target_thread=\u015Eu Anki \u0130\u015F Par\u00E7ac\u0131\u011F\u0131 +test_action_title=Test Hareketi +test_configuration=Test Ayar\u0131 +test_plan=Test Plan\u0131 +test_plan_classpath_browse=S\u0131n\u0131f yoluna (classpath) dizin veya jar ekle +testconfiguration=Test Ayar\u0131 +testplan.serialized=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Gruplar\u0131n\u0131 Ard\u0131\u015F\u0131k \u00C7al\u0131\u015Ft\u0131r (\u00F6r\: her defas\u0131nda bir grup \u00E7al\u0131\u015Ft\u0131r) +testplan_comments=Yorumlar\: +thread_delay_properties=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Gecikme \u00D6zellikleri +thread_group_title=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Grubu +thread_properties=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 \u00D6zellikleri +threadgroup=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Grubu +throughput_control_bynumber_label=Toplam Y\u00FCr\u00FCtme +throughput_control_bypercent_label=Y\u00FCr\u00FCtme Y\u00FCzdesi +throughput_control_perthread_label=Kullan\u0131c\u0131 Ba\u015F\u0131na +throughput_control_title=Transfer Oran\u0131 Denet\u00E7isi +throughput_control_tplabel=Transfer Oran\u0131 +time_format=Metni SimpleDateFormat i\u00E7in bi\u00E7imlendir (iste\u011Fe ba\u011Fl\u0131) +timelim=Zaman s\u0131n\u0131r\u0131 +tr=T\u00FCrk\u00E7e +transaction_controller_parent=Ebeveyn \u00F6rnek olu\u015Ftur +transaction_controller_title=\u0130\u015Flem (transaction) Denet\u00E7isi +unbind=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131n\u0131 B\u0131rak +uniform_timer_delay=Sabit Gecikme S\u0131n\u0131r\u0131 (milisaniye)\: +uniform_timer_memo=Tek bi\u00E7imli da\u011F\u0131l\u0131mla rastgele gecikme ekler +uniform_timer_range=Maksimum Rastgele Gecikme (milisaniye)\: +uniform_timer_title=Tek Bi\u00E7imli Rastgele Zamanlay\u0131c\u0131 +update_per_iter=Her Tekrar i\u00E7in Bir Defa Yenile +upload=Dosya Y\u00FCkleme +upper_bound=\u00DCst S\u0131n\u0131r +url=Adres (URL) +url_config_protocol=Protokol\: +url_config_title=HTTP \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +url_full_config_title=UrlFull \u00D6rne\u011Fi +url_multipart_config_title=HTTP Multipart \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +use_keepalive=Canl\u0131Tut (KeepAlive) kullan +use_multipart_for_http_post=HTTP POST i\u00E7in multipart/form-data kullan +use_recording_controller=Kaydetme Denet\u00E7isi Kullan +user=Kullan\u0131c\u0131 +user_defined_test=Kullan\u0131c\u0131 Tan\u0131ml\u0131 Test +user_defined_variables=Kullan\u0131c\u0131 Tan\u0131ml\u0131 De\u011Fi\u015Fkenler +user_param_mod_help_note=(Buray\u0131 de\u011Fi\u015Ftirme. Onun yerine, JMeter'in /bin dizinindeki dosya ismini d\u00FCzenle) +user_parameters_table=Parametreler +user_parameters_title=Kullan\u0131c\u0131 Parametreleri +userdn=Kullan\u0131c\u0131 ismi +username=Kullan\u0131c\u0131 ismi +userpw=\u015Eifre +value=De\u011Fer +var_name=Referans \u0130smi +variable_name_param=De\u011Fi\u015Fken ismi (de\u011Fi\u015Fken ve fonksiyon referanslar\u0131 i\u00E7erebilir) +view_graph_tree_title=Grafik A\u011Fac\u0131n\u0131 G\u00F6ster +view_results_in_table=Sonu\u00E7 Tablosunu G\u00F6ster +view_results_render_html=HTML i\u015Fle +view_results_render_json=JSON i\u015Fle +view_results_render_text=Metin G\u00F6ster +view_results_render_xml=XML \u0130\u015Fle +view_results_tab_assertion=Do\u011Frulama sonucu +view_results_tab_request=\u0130stek +view_results_tab_response=Cevap verisi +view_results_tab_sampler=\u00D6rnekleyici sonucu +view_results_title=Sonu\u00E7lar\u0131 G\u00F6ster +view_results_tree_title=Sonu\u00E7lar\u0131 G\u00F6sterme A\u011Fac\u0131 +warning=Uyar\u0131\! +web_request=HTTP \u0130ste\u011Fi +web_server=A\u011F Sunucusu +web_server_client=\u0130stemci uygulamas\u0131\: +web_server_domain=Sunucu \u0130smi veya IP\: +web_server_port=Port Numaras\u0131\: +web_testing2_title=HTTP \u0130ste\u011Fi HTTPClient +web_testing_embedded_url_pattern=G\u00F6m\u00FCl\u00FC Adresler (URL) \u00F6rt\u00FC\u015Fmeli\: +web_testing_retrieve_images=HTML Dosyalardan T\u00FCm G\u00F6m\u00FCl\u00FC Kaynaklar\u0131 Al +web_testing_title=HTTP \u0130ste\u011Fi +webservice_proxy_host=Vekil Makine +webservice_proxy_note=E\u011Fer "HTTP Vekil Sunucu kullan" se\u00E7iliyse, ama sunucu veya port belirtilmemi\u015Fse, \u00F6rnekleyici +webservice_proxy_note2=komut sat\u0131r\u0131 se\u00E7eneklerine bakacakt\u0131r. E\u011Fer vekil sunucu veya port belirtilmediyse +webservice_proxy_note3=ya da, sessizce ba\u015Far\u0131s\u0131z olacakt\u0131r. +webservice_proxy_port=Vekil Port +webservice_sampler_title=WebService(SOAP) \u0130ste\u011Fi (DEPRECATED) +webservice_timeout=Zaman A\u015F\u0131m\u0131\: +webservice_use_proxy=HTTP Vekil Sunucusunu kullan +while_controller_label=Ko\u015Ful (fonksiyon veya de\u011Fi\u015Fken) +while_controller_title=While Denet\u00E7isi +workbench_title=Tezgah +wsdl_helper_error=WSDL ge\u00E7erli de\u011Fil, l\u00FCtfen url adresini iki defa kontrol edin. +wsdl_url=WSDL Adresi +wsdl_url_error=WSDL bo\u015F. +xml_assertion_title=XML Do\u011Frulamas\u0131 +xml_namespace_button=Namespace kullan +xml_tolerant_button=Ho\u015Fg\u00F6r\u00FClen XML/HTML Ayr\u0131\u015Ft\u0131r\u0131c\u0131 +xml_validate_button=XML'i do\u011Frula +xml_whitespace_button=G\u00F6r\u00FCnmeyen Karakterleri Yoksay +xmlschema_assertion_label=Dosya \u0130smi\: +xmlschema_assertion_title=XML \u015Eemas\u0131 Do\u011Frulamas\u0131 +xpath_assertion_button=Do\u011Frula +xpath_assertion_check=XPath \u0130fadesini Kontrol Et +xpath_assertion_error=XPath'te Hata +xpath_assertion_failed=Ge\u00E7ersiz XPath \u0130fadesi +xpath_assertion_negate=E\u011Fer e\u015Fle\u015Fen yoksa True +xpath_assertion_option=XML Ayr\u0131\u015Ft\u0131r\u0131c\u0131 Se\u00E7enekleri +xpath_assertion_test=XPath Do\u011Frulamas\u0131 +xpath_assertion_tidy=Dene ve girdiyi d\u00FCzenle +xpath_assertion_title=XPath Do\u011Frulamas\u0131 +xpath_assertion_valid=Ge\u00E7erli XPath \u0130fadesi +xpath_assertion_validation=XML'i DTD'ye g\u00F6re kontrol et +xpath_assertion_whitespace=G\u00F6r\u00FCnmeyen Karakterleri Yoksay +xpath_expression=Kar\u015F\u0131la\u015Ft\u0131r\u0131lacak XPath ifadesi +xpath_extractor_query=XPath sorgusu\: +xpath_extractor_title=XPath \u00C7\u0131kar\u0131c\u0131 +xpath_file_file_name=De\u011Ferlerin okunaca\u011F\u0131 XML dosyas\u0131 +xpath_tidy_quiet=Sessiz +xpath_tidy_report_errors=Hatalar\u0131 raporla +xpath_tidy_show_warnings=Uyar\u0131lar\u0131 g\u00F6ster +you_must_enter_a_valid_number=Ge\u00E7erli bir rakam girmelisiniz +zh_cn=\u00C7ince (Basitle\u015Ftirilmi\u015F) +zh_tw=\u00C7ince (Geleneksel) diff --git a/src/core/org/apache/jmeter/resources/messages_zh_CN.properties b/src/core/org/apache/jmeter/resources/messages_zh_CN.properties new file mode 100644 index 00000000000..04bf72eb899 --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_zh_CN.properties @@ -0,0 +1,439 @@ +# 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. + +about=\u5173\u4E8EApache JMeter +add=\u6DFB\u52A0 +add_as_child=\u6DFB\u52A0\u5B50\u8282\u70B9 +add_parameter=\u6DFB\u52A0\u53D8\u91CF +add_pattern=\u6DFB\u52A0\u6A21\u5F0F\uFF1A +add_test=\u6DFB\u52A0\u6D4B\u8BD5 +add_user=\u6DFB\u52A0\u7528\u6237 +add_value=\u6DFB\u52A0\u6570\u503C +aggregate_report=\u805A\u5408\u62A5\u544A +aggregate_report_total_label=\u603B\u4F53 +als_message=\u6CE8\u610F\uFF1A\u8BBF\u95EE\u65E5\u5FD7\u89E3\u6790\u5668\uFF08Access Log Parser\uFF09\u662F\u901A\u7528\u7684\u5E76\u5141\u8BB8\u5B9A\u4E49\u63D2\u4EF6 +als_message2=\u81EA\u5B9A\u4E49\u7684\u89E3\u6790\u5668\u3002\u8981\u8FD9\u4E48\u505A\uFF0C\u5B9E\u73B0LogParser\uFF0C\u6DFB\u52A0jar\u5230 +als_message3=/lib\u76EE\u5F55\u5E76\u5728sampler\u4E2D\u8F93\u5165\u7C7B\u540D\u79F0\u3002 +analyze=\u5206\u6790\u6570\u636E\u6587\u4EF6... +anchor_modifier_title=HTML\u94FE\u63A5\u89E3\u6790\u5668 +appearance=\u5916\u89C2 +argument_must_not_be_negative=\u53C2\u6570\u4E0D\u5141\u8BB8\u662F\u8D1F\u503C\uFF01 +assertion_code_resp=\u54CD\u5E94\u4EE3\u7801 +assertion_contains=\u5305\u62EC +assertion_matches=\u5339\u914D +assertion_message_resp=\u54CD\u5E94\u4FE1\u606F +assertion_not=\u5426 +assertion_pattern_match_rules=\u6A21\u5F0F\u5339\u914D\u89C4\u5219 +assertion_patterns_to_test=\u8981\u6D4B\u8BD5\u7684\u6A21\u5F0F +assertion_resp_field=\u8981\u6D4B\u8BD5\u7684\u54CD\u5E94\u5B57\u6BB5 +assertion_text_resp=\u54CD\u5E94\u6587\u672C +assertion_textarea_label=\u65AD\u8A00\uFF1A +assertion_title=\u54CD\u5E94\u65AD\u8A00 +assertion_url_samp=URL\u6837\u672C +assertion_visualizer_title=\u65AD\u8A00\u7ED3\u679C +auth_base_url=\u57FA\u7840URL +auth_manager_title=HTTP\u6388\u6743\u7BA1\u7406\u5668 +auths_stored=\u5B58\u50A8\u5728\u6388\u6743\u7BA1\u7406\u5668\u4E2D\u7684\u6388\u6743 +browse=\u6D4F\u89C8... +bsf_sampler_title=BSF \u53D6\u6837\u5668 +bsf_script=\u8981\u8FD0\u884C\u7684\u811A\u672C +bsf_script_file=\u8981\u8FD0\u884C\u7684\u811A\u672C\u6587\u4EF6 +bsf_script_language=\u811A\u672C\u8BED\u8A00\uFF1A +bsf_script_parameters=\u4F20\u9012\u7ED9\u811A\u672C/\u6587\u4EF6\u7684\u53C2\u6570\uFF1A +bsh_assertion_title=BeanShell\u65AD\u8A00 +bsh_function_expression=\u8868\u8FBE\u5F0F\u6C42\u503C +bsh_script_file=\u811A\u672C\u6587\u4EF6 +bsh_script_parameters=\u53C2\u6570\uFF08-> String Parameters \u548C String [ ]bash.args\uFF09 +busy_testing=\u6B63\u5728\u6D4B\u8BD5\uFF0C\u8BF7\u5728\u4FEE\u6539\u8BBE\u7F6E\u524D\u505C\u6B62\u6D4B\u8BD5 +cancel=\u53D6\u6D88 +cancel_exit_to_save=\u6D4B\u8BD5\u6761\u76EE\u672A\u5B58\u50A8\u3002\u4F60\u60F3\u5728\u9000\u51FA\u524D\u5B58\u50A8\u5417\uFF1F +cancel_new_to_save=\u6D4B\u8BD5\u6761\u76EE\u672A\u5B58\u50A8\u3002\u4F60\u60F3\u5728\u6E05\u7A7A\u6D4B\u8BD5\u8BA1\u5212\u524D\u5B58\u50A8\u5417\uFF1F +choose_function=\u9009\u62E9\u4E00\u4E2A\u529F\u80FD +choose_language=\u9009\u62E9\u8BED\u8A00 +clear=\u6E05\u9664 +clear_all=\u6E05\u9664\u5168\u90E8 +clear_cookies_per_iter=\u6BCF\u6B21\u53CD\u590D\u6E05\u9664Cookies \uFF1F +column_delete_disallowed=\u4E0D\u5141\u8BB8\u5220\u9664\u6B64\u5217 +column_number=CSV\u6587\u4EF6\u5217\u53F7| next| *alias +configure_wsdl=\u914D\u7F6E +constant_throughput_timer_memo=\u5728\u53D6\u6837\u95F4\u6DFB\u52A0\u5EF6\u8FDF\u6765\u83B7\u5F97\u56FA\u5B9A\u7684\u541E\u5410\u91CF +constant_timer_delay=\u7EBF\u7A0B\u5EF6\u8FDF\uFF08\u6BEB\u79D2\uFF09\uFF1A +constant_timer_memo=\u5728\u53D6\u6837\u95F4\u6DFB\u52A0\u56FA\u5B9A\u5EF6\u8FDF +constant_timer_title=\u56FA\u5B9A\u5B9A\u65F6\u5668 +controller=\u63A7\u5236\u5668 +cookie_manager_title=HTTP Cookie \u7BA1\u7406\u5668 +cookies_stored=\u5B58\u50A8\u5728Cookie\u7BA1\u7406\u5668\u4E2D\u7684Cookie +copy=\u590D\u5236 +counter_config_title=\u8BA1\u6570\u5668 +counter_per_user=\u4E0E\u6BCF\u7528\u6237\u72EC\u7ACB\u7684\u8DDF\u8E2A\u8BA1\u6570\u5668 +cut=\u526A\u5207 +cut_paste_function=\u62F7\u8D1D\u5E76\u7C98\u8D34\u51FD\u6570\u5B57\u7B26\u4E32 +database_sql_query_string=SQL\u67E5\u8BE2\u5B57\u7B26\u4E32\uFF1A +database_sql_query_title=JDBC SQL \u67E5\u8BE2\u7F3A\u7701\u503C +de=\u5FB7\u8BED +default_parameters=\u7F3A\u7701\u53C2\u6570 +default_value_field=\u7F3A\u7701\u503C\uFF1A +delay=\u542F\u52A8\u5EF6\u8FDF\uFF08\u79D2\uFF09 +delete=\u5220\u9664 +delete_parameter=\u5220\u9664\u53D8\u91CF +delete_test=\u5220\u9664\u6D4B\u8BD5 +delete_user=\u5220\u9664\u7528\u6237 +disable=\u7981\u7528 +domain=\u57DF +duration=\u6301\u7EED\u65F6\u95F4\uFF08\u79D2\uFF09 +duration_assertion_duration_test=\u65AD\u8A00\u6301\u7EED\u65F6\u95F4 +duration_assertion_failure=\u64CD\u4F5C\u6301\u7EED\u592A\u957F\u65F6\u95F4\uFF1A\u4ED6\u82B1\u8D39\u4E86{0}\u6BEB\u79D2\uFF0C\u4F46\u4E0D\u5E94\u8BE5\u8D85\u8FC7{1}\u6BEB\u79D2\u3002 +duration_assertion_input_error=\u8BF7\u8F93\u5165\u4E00\u4E2A\u6709\u6548\u7684\u6B63\u6574\u6570\u3002 +duration_assertion_label=\u6301\u7EED\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF1A +duration_assertion_title=\u65AD\u8A00\u6301\u7EED\u65F6\u95F4 +edit=\u7F16\u8F91 +email_results_title=\u7535\u5B50\u90AE\u4EF6\u7ED3\u679C +en=\u82F1\u8BED +enable=\u542F\u7528 +encode?=\u7F16\u7801\uFF1F +encoded_value=URL\u7F16\u7801\u540E\u7684\u503C +endtime=\u7ED3\u675F\u65F6\u95F4 +entry_dn=\u5165\u53E3DN +error_loading_help=\u52A0\u8F7D\u5E2E\u52A9\u9875\u9762\u51FA\u9519 +error_occurred=\u53D1\u751F\u9519\u8BEF +example_data=\u6837\u672C\u6570\u636E +example_title=\u793A\u4F8B\u53D6\u6837\u5668 +exit=\u9000\u51FA +expiration=\u8FC7\u671F +field_name=\u5B57\u6BB5\u540D\u6210 +file=\u6587\u4EF6 +file_already_in_use=\u6587\u4EF6\u6B63\u5728\u4F7F\u7528 +file_visualizer_append=\u6DFB\u52A0\u5230\u5DF2\u7ECF\u5B58\u5728\u7684\u6570\u636E\u6587\u4EF6 +file_visualizer_auto_flush=\u5728\u6BCF\u6B21\u6570\u636E\u53D6\u6837\u540E\u81EA\u52A8\u66F4\u65B0 +file_visualizer_browse=\u6D4F\u89C8... +file_visualizer_close=\u5173\u95ED +file_visualizer_file_options=\u6587\u4EF6\u64CD\u4F5C +file_visualizer_filename=\u6587\u4EF6\u540D +file_visualizer_flush=\u66F4\u65B0 +file_visualizer_missing_filename=\u6CA1\u6709\u6307\u5B9A\u8F93\u51FA\u6587\u4EF6\u540D\u3002 +file_visualizer_open=\u6253\u5F00 +file_visualizer_output_file=\u6240\u6709\u6570\u636E\u5199\u5165\u4E00\u4E2A\u6587\u4EF6 +file_visualizer_submit_data=\u5305\u62EC\u88AB\u63D0\u4EA4\u7684\u6570\u636E +file_visualizer_title=\u6587\u4EF6\u62A5\u544A\u5668 +file_visualizer_verbose=\u8BE6\u7EC6\u7684\u8F93\u51FA +filename=\u6587\u4EF6\u540D\u79F0 +follow_redirects=\u8DDF\u968F\u91CD\u5B9A\u5411 +follow_redirects_auto=\u81ea\u52a8\u91cd\u5b9a\u5411 +foreach_controller_title=ForEach\u63A7\u5236\u5668 +foreach_input=\u8F93\u5165\u53D8\u91CF\u524D\u7F00 +foreach_output=\u8F93\u51FA\u53D8\u91CF\u540D\u79F0 +ftp_sample_title=FTP\u8BF7\u6C42\u7F3A\u7701\u503C +ftp_testing_title=FTP\u8BF7\u6C42 +function_dialog_menu_item=\u51FD\u6570\u52A9\u624B\u5BF9\u8BDD\u6846 +function_helper_title=\u51FD\u6570\u52A9\u624B +function_name_param=\u51FD\u6570\u540D\u79F0\u3002\u7528\u4E8E\u5B58\u50A8\u5728\u6D4B\u8BD5\u8BA1\u5212\u4E2D\u5176\u4ED6\u7684\u65B9\u5F0F\u4F7F\u7528\u7684\u503C\u3002 +function_params=\u51FD\u6570\u53C2\u6570 +functional_mode=\u51FD\u6570\u6D4B\u8BD5\u6A21\u5F0F +functional_mode_explanation=\u53EA\u6709\u5F53\u4F60\u9700\u8981\u8BB0\u5F55\u6BCF\u4E2A\u8BF7\u6C42\u4ECE\u670D\u52A1\u5668\u53D6\u5F97\u7684\u6570\u636E\u5230\u6587\u4EF6\u65F6\n\u624D\u9700\u8981\u9009\u62E9\u51FD\u6570\u6D4B\u8BD5\u6A21\u5F0F\u3002\n\n\u9009\u62E9\u8FD9\u4E2A\u9009\u9879\u5F88\u5F71\u54CD\u6027\u80FD\u3002\n +gaussian_timer_delay=\u56FA\u5B9A\u5EF6\u8FDF\u504F\u79FB\uFF08\u6BEB\u79D2\uFF09\uFF1A +gaussian_timer_memo=\u6DFB\u52A0\u4E00\u4E2A\u968F\u673A\u7684\u9AD8\u65AF\u5206\u5E03\u5EF6\u8FDF +gaussian_timer_range=\u504F\u5DEE\uFF08\u6BEB\u79D2\uFF09\uFF1A +gaussian_timer_title=\u9AD8\u65AF\u968F\u673A\u5B9A\u65F6\u5668 +generate=\u751F\u6210 +generator=\u751F\u6210\u5668\u7C7B\u540D\u79F0 +generator_cnf_msg=\u4E0D\u80FD\u627E\u5230\u751F\u6210\u5668\u7C7B\u3002\u8BF7\u786E\u5B9A\u4F60\u5C06jar\u6587\u4EF6\u653E\u7F6E\u5728/lib\u76EE\u5F55\u4E2D\u3002 +generator_illegal_msg=\u7531\u4E8EIllegalAcessException\uFF0C\u4E0D\u80FD\u8BBF\u95EE\u751F\u6210\u5668\u7C7B\u3002 +generator_instantiate_msg=\u4E0D\u80FD\u521B\u5EFA\u751F\u6210\u5668\u89E3\u6790\u5668\u7684\u5B9E\u4F8B\u3002\u8BF7\u786E\u4FDD\u751F\u6210\u5668\u5B9E\u73B0\u4E86Generator\u63A5\u53E3\u3002 +get_xml_from_file=\u5E26SOAP XML \u6570\u636E\u7684\u6587\u4EF6\uFF08\u8986\u76D6\u4E0A\u9762\u7684\u6587\u672C\uFF09 +get_xml_from_random=\u6D88\u606F\u6587\u4EF6\u5939 +graph_choose_graphs=\u8981\u663E\u793A\u7684\u56FE\u5F62 +graph_full_results_title=\u56FE\u5F62\u7ED3\u679C +graph_results_average=\u5E73\u5747 +graph_results_data=\u6570\u636E +graph_results_deviation=\u504F\u79BB +graph_results_latest_sample=\u6700\u65B0\u6837\u672C +graph_results_median=\u4E2D\u503C +graph_results_no_samples=\u6837\u672C\u6570\u76EE +graph_results_throughput=\u541E\u5410\u91CF +graph_results_title=\u56FE\u5F62\u7ED3\u679C +grouping_add_separators=\u5728\u7EC4\u95F4\u6DFB\u52A0\u5206\u9694 +grouping_in_controllers=\u6BCF\u4E2A\u7EC4\u653E\u5165\u4E00\u4E2A\u65B0\u7684\u63A7\u5236\u5668 +grouping_mode=\u5206\u7EC4\uFF1A +grouping_no_groups=\u4E0D\u5BF9\u6837\u672C\u5206\u7EC4 +grouping_store_first_only=\u53EA\u5B58\u50A8\u6BCF\u4E2A\u7EC4\u7684\u7B2C\u4E00\u4E2A\u6837\u672C +header_manager_title=HTTP\u4FE1\u606F\u5934\u7BA1\u7406\u5668 +headers_stored=\u4FE1\u606F\u5934\u5B58\u50A8\u5728\u4FE1\u606F\u5934\u7BA1\u7406\u5668\u4E2D +help=\u5E2E\u52A9 +html_parameter_mask=HTML\u53C2\u6570\u63A9\u7801 +http_response_code=HTTP\u578B\u5E94\u4EE3\u7801 +http_url_rewriting_modifier_title=HTTP URL \u91CD\u5199\u4FEE\u9970\u7B26 +http_user_parameter_modifier=HTTP \u7528\u6237\u53C2\u6570\u4FEE\u9970\u7B26 +id_prefix=ID\u524D\u7F00 +id_suffix=ID\u540E\u7F00 +if_controller_label=\u6761\u4EF6 +if_controller_title=\u5982\u679C\uFF08If\uFF09\u63A7\u5236\u5668 +ignore_subcontrollers=\u5FFD\u7565\u8D44\u63A7\u5236\u5668\u5757 +include_equals=\u5305\u542B\u7B49\u4E8E\uFF1F +increment=\u9012\u589E +infinite=\u6C38\u8FDC +insert_after=\u4E4B\u540E\u63D2\u5165 +insert_before=\u4E4B\u524D\u63D2\u5165 +insert_parent=\u63D2\u5165\u4E0A\u7EA7 +interleave_control_title=\u4EA4\u66FF\u63A7\u5236\u5668 +intsum_param_1=\u8981\u6DFB\u52A0\u7684\u7B2C\u4E00\u4E2A\u6574\u6570\u3002 +intsum_param_2=\u8981\u6DFB\u52A0\u7684\u7B2C\u4E8C\u4E2A\u6574\u6570\u2014\u2014\u66F4\u591A\u7684\u6574\u6570\u53EF\u4EE5\u901A\u8FC7\u6DFB\u52A0\u66F4\u591A\u7684\u53C2\u6570\u6765\u6C42\u548C\u3002 +invalid_data=\u65E0\u6548\u6570\u636E +invalid_mail_server=\u90AE\u4EF6\u670D\u52A1\u5668\u4E0D\u53EF\u77E5\u3002 +iteration_counter_arg_1=TRUE\uFF0C\u6BCF\u4E2A\u7528\u6237\u6709\u81EA\u5DF1\u7684\u8BA1\u6570\u5668\uFF1BFALSE\uFF0C\u4F7F\u7528\u5168\u5C40\u8BA1\u6570\u5668 +iterator_num=\u5FAA\u73AF\u6B21\u6570 +java_request=Java\u8BF7\u6C42 +java_request_defaults=Java\u8BF7\u6C42\u9ED8\u8BA4\u503C +jndi_config_title=JNDI\u914D\u7F6E +jndi_lookup_name=\u8FDC\u7A0B\u63A5\u53E3 +jndi_lookup_title=JNDI\u67E5\u8BE2\u914D\u7F6E +jndi_method_button_invoke=\u8C03\u7528 +jndi_method_button_reflect=\u53CD\u5C04 +jndi_method_home_name=\u672C\u5730\u65B9\u6CD5\u540D\u79F0 +jndi_method_home_parms=\u672C\u5730\u65B9\u6CD5\u53C2\u6570 +jndi_method_name=\u65B9\u6CD5\u914D\u7F6E +jndi_method_remote_interface_list=\u8FDC\u7A0B\u63A5\u53E3 +jndi_method_remote_name=\u8FDC\u7A0B\u65B9\u6CD5\u540D\u79F0 +jndi_method_remote_parms=\u8FDC\u7A0B\u65B9\u6CD5\u53C2\u6570 +jndi_method_title=\u8FDC\u7A0B\u65B9\u6CD5\u914D\u7F6E +jndi_testing_title=JNDI\u8BF7\u6C42 +jndi_url_jndi_props=JNDI\u5C5E\u6027 +ja=\u65E5\u8BED +ldap_sample_title=LDAP\u8BF7\u6C42\u9ED8\u8BA4\u503C +ldap_testing_title=LDAP\u8BF7\u6C42 +load=\u8F7D\u5165 +load_wsdl=\u8F7D\u5165WSDL +log_errors_only=\u4EC5\u65E5\u5FD7\u9519\u8BEF +log_file=\u65E5\u5FD7\u6587\u4EF6\u4F4D\u7F6E +log_parser=\u65E5\u5FD7\u89E3\u6790\u5668\u7C7B\u540D +log_parser_cnf_msg=\u627E\u4E0D\u5230\u7C7B\u3002\u786E\u8BA4\u4F60\u5C06jar\u6587\u4EF6\u653E\u5728\u4E86/lib\u76EE\u5F55\u4E2D\u3002 +log_parser_illegal_msg=\u56E0\u4E3AIllegalAccessException\u4E0D\u80FD\u8BBF\u95EE\u7C7B\u3002 +log_parser_instantiate_msg=\u4E0D\u80FD\u521B\u5EFA\u65E5\u5FD7\u89E3\u6790\u5668\u5B9E\u4F8B\u3002\u786E\u8BA4\u89E3\u6790\u5668\u5B9E\u73B0\u4E86LogParser\u63A5\u53E3\u3002 +log_sampler=Tomcat\u8BBF\u95EE\u65E5\u5FD7\u53D6\u6837\u5668 +logic_controller_title=\u7B80\u5355\u63A7\u5236\u5668 +login_config=\u767B\u9646\u914D\u7F6E +login_config_element=\u767B\u9646\u914D\u7F6E\u5143\u4EF6/\u7D20 +loop_controller_title=\u5FAA\u73AF\u63A7\u5236\u5668 +looping_control=\u5FAA\u73AF\u63A7\u5236 +lower_bound=\u8F83\u4F4E\u8303\u56F4 +mailer_attributes_panel=\u90AE\u4EF6\u5C5E\u6027 +mailer_error=\u4E0D\u80FD\u53D1\u9001\u90AE\u4EF6\u3002\u8BF7\u4FEE\u6B63\u9519\u8BEF\u3002 +mailer_visualizer_title=\u90AE\u4EF6\u89C2\u5BDF\u4EEA +match_num_field=\u5339\u914D\u6570\u5B57\uFF080\u4EE3\u8868\u968F\u673A\uFF09\uFF1A +max=\u6700\u5927\u503C +maximum_param=\u4E00\u4E2A\u8303\u56F4\u5185\u5141\u8BB8\u7684\u6700\u5927\u503C +md5hex_assertion_failure=MD5\u603B\u5408\u65AD\u8A00\u9519\u8BEF\uFF1A\u5F97\u5230\u4E86{0}\uFF0C\u4F46\u5E94\u8BE5\u662F{1} +md5hex_assertion_md5hex_test=\u8981\u65AD\u8A00\u7684MD5Hex +md5hex_assertion_title=MD5Hex\u65AD\u8A00 +memory_cache=\u5185\u5B58\u7F13\u5B58 +menu_assertions=\u65AD\u8A00 +menu_close=\u5173\u95ED +menu_config_element=\u914D\u7F6E\u5143\u4EF6 +menu_edit=\u7F16\u8F91 +menu_listener=\u76D1\u542C\u5668 +menu_logic_controller=\u903B\u8F91\u63A7\u5236\u5668 +menu_merge=\u5408\u5E76 +menu_modifiers=\u4FEE\u9970\u7B26 +menu_non_test_elements=\u975E\u6D4B\u8BD5\u5143\u4EF6 +menu_open=\u6253\u5F00 +menu_post_processors=\u540E\u7F6E\u5904\u7406\u5668 +menu_pre_processors=\u524D\u7F6E\u5904\u7406\u5668 +menu_response_based_modifiers=\u57FA\u4E8E\u76F8\u5E94\u7684\u4FEE\u9970\u7B26 +menu_timer=\u5B9A\u65F6\u5668 +metadata=\u539F\u6570\u636E +method=\u65B9\u6CD5\uFF1A +mimetype=MIME\u7C7B\u578B +minimum_param=\u4E00\u4E2A\u8303\u56F4\u5185\u7684\u6700\u5C0F\u503C +minute=\u5206\u949F +modification_controller_title=\u4FEE\u6B63\u63A7\u5236\u5668 +modification_manager_title=\u4FEE\u6B63\u7BA1\u7406\u5668 +modify_test=\u4FEE\u6539\u6D4B\u8BD5 +module_controller_title=\u6A21\u5757\u63A7\u5236\u5668 +monitor_equation_active=\u6D3B\u52A8\uFF1A(busy/max)>25% +monitor_equation_dead=\u975E\u6D3B\u52A8\uFF1A\u6CA1\u6709\u54CD\u5E94 +monitor_equation_healthy=Healthy\:(busy/max)<25% +monitor_health_title=\u76D1\u89C6\u5668\u7ED3\u679C +monitor_is_title=\u7528\u4F5C\u76D1\u89C6\u5668 +monitor_label_right_active=\u6D3B\u52A8\u7684 +monitor_label_right_dead=\u975E\u6D3B\u52A8\u7684 +monitor_label_right_warning=\u8B66\u544A +monitor_legend_load=\u8D1F\u8F7D +monitor_legend_thread_per=\u7EBF\u7A0B%(busy/max) +monitor_performance_servers=\u670D\u52A1\u5668 +monitor_performance_tab_title=\u6027\u80FD +monitor_performance_title=\u6027\u80FD\u56FE +name=\u540D\u79F0\uFF1A +new=\u65B0\u5EFA +no=\u632A\u5A01\u8BED +number_of_threads=\u7EBF\u7A0B\u6570\uFF1A +once_only_controller_title=\u4EC5\u4E00\u6B21\u63A7\u5236\u5668 +open=\u6253\u5F00... +option=\u9009\u9879 +optional_tasks=\u5176\u4ED6\u4EFB\u52A1 +paramtable=\u540C\u8BF7\u6C42\u4E00\u8D77\u53D1\u9001\u53C2\u6570\uFF1A +password=\u5BC6\u7801 +paste=\u7C98\u8D34 +paste_insert=\u4F5C\u4E3A\u63D2\u5165\u7C98\u8D34 +path=\u8DEF\u5F84\uFF1A +path_extension_choice=\u8DEF\u5F84\u6269\u5C55\uFF08\u4F7F\u7528";"\u4F5C\u5206\u9694\u7B26\uFF09 +patterns_to_exclude=\u6392\u9664\u6A21\u5F0F +patterns_to_include=\u5305\u542B\u6A21\u5F0F +port=\u7AEF\u53E3\uFF1A +property_default_param=\u9ED8\u8BA4\u503C +property_edit=\u7F16\u8F91 +property_editor.value_is_invalid_title=\u65E0\u6548\u8F93\u5165 +property_name_param=\u5C5E\u6027\u540D\u79F0 +property_undefined=\u672A\u5B9A\u4E49 +protocol=\u534F\u8BAE\uFF1A +protocol_java_border=Java\u7C7B +protocol_java_classname=\u7C7B\u540D\u79F0\uFF1A +protocol_java_config_tile=\u914D\u7F6EJava\u6837\u672C +protocol_java_test_title=Java\u6D4B\u8BD5 +proxy_assertions=\u6DFB\u52A0\u65AD\u8A00 +proxy_cl_error=\u5982\u679C\u6307\u5B9A\u4EE3\u7406\u670D\u52A1\u5668\uFF0C\u4E3B\u673A\u548C\u7AEF\u53E3\u5FC5\u987B\u6307\u5B9A +proxy_headers=\u8BB0\u5F55HTTP\u4FE1\u606F\u5934 +proxy_separators=\u6DFB\u52A0\u5206\u9694\u7B26 +proxy_target=\u76EE\u6807\u63A7\u5236\u5668\uFF1A +proxy_title=HTTP\u4EE3\u7406\u670D\u52A1\u5668 +random_control_title=\u968F\u673A\u63A7\u5236\u5668 +random_order_control_title=\u968F\u673A\u987A\u5E8F\u63A7\u5236\u5668 +read_response_message=\u8BFB\u53D6\u54CD\u5E94\u6CA1\u6709\u9009\u4E2D\u3002\u8981\u770B\u5230\u54CD\u5E94\uFF0C\u8BF7\u5728\u53D6\u6837\u5668\u4E2D\u9009\u4E2D\u8BFB\u53D6\u54CD\u5E94\u590D\u9009\u6846\u3002 +read_response_note=\u5982\u679C\u8BFB\u53D6\u54CD\u5E94\u6CA1\u6709\u9009\u4E2D\uFF0C\u53D6\u6837\u5668\u4E0D\u4F1A\u8BFB\u53D6\u54CD\u5E94\u3002 +read_response_note2=\u6216\u8BBE\u7F6E\u6837\u672C\u7ED3\u679C\u3002\u8FD9\u4F1A\u63D0\u9AD8\u6027\u80FD\u3002 +read_response_note3=\u54CD\u5E94\u5185\u5BB9\u4E0D\u4F1A\u8BB0\u5F55\u3002 +read_soap_response=\u8BFB\u53D6SOAP\u54CD\u5E94 +record_controller_title=\u5F55\u5236\u63A7\u5236\u5668 +ref_name_field=\u5F15\u7528\u540D\u79F0\uFF1A +regex_extractor_title=\u6B63\u5219\u8868\u8FBE\u5F0F\u63D0\u53D6\u5668 +regex_field=\u6B63\u5219\u8868\u8FBE\u5F0F\uFF1A +regex_source=\u8981\u68C0\u67E5\u7684\u54CD\u5E94\u5B57\u6BB5 +regex_src_body=\u4E3B\u4F53 +regex_src_hdrs=\u4FE1\u606F\u5934 +regexfunc_param_1=\u7528\u4E8E\u4ECE\u524D\u4E00\u4E2A\u8BF7\u6C42\u641C\u7D22\u7ED3\u679C\u7684\u6B63\u5219\u8868\u8FBE\u5F0F +remote_exit=\u8FDC\u7A0B\u9000\u51FA +remote_exit_all=\u8FDC\u7A0B\u5168\u90E8\u9000\u51FA +remote_start=\u8FDC\u7A0B\u542F\u52A8 +remote_start_all=\u8FDC\u7A0B\u5168\u90E8\u542F\u52A8 +remote_stop=\u8FDC\u7A0B\u505C\u6B62 +remote_stop_all=\u8FDC\u7A0B\u5168\u90E8\u505C\u6B62 +remove=\u5220\u9664 +report=\u62A5\u544A +request_data=\u8BF7\u6C42\u6570\u636E +restart=\u91CD\u542F +resultsaver_prefix=\u6587\u4EF6\u540D\u79F0\u524D\u7F00\uFF1A +resultsaver_title=\u4FDD\u5B58\u54CD\u5E94\u5230\u6587\u4EF6 +root=\u6839 +root_title=\u6839 +run=\u8FD0\u884C +running_test=\u6B63\u5728\u8FD0\u884C\u7684\u6D4B\u8BD5 +sampler_on_error_action=\u5728\u53D6\u6837\u5668\u9519\u8BEF\u540E\u8981\u6267\u884C\u7684\u52A8\u4F5C +sampler_on_error_continue=\u7EE7\u7EED +sampler_on_error_stop_test=\u505C\u6B62\u6D4B\u8BD5 +sampler_on_error_stop_thread=\u505C\u6B62\u7EBF\u7A0B +save=\u4FDD\u5B58\u6D4B\u8BD5\u8BA1\u5212 +save?=\u4FDD\u5B58\uFF1F +save_all_as=\u4FDD\u5B58\u6D4B\u8BD5\u8BA1\u5212\u4E3A +save_as=\u4FDD\u5B58\u4E3A... +scheduler=\u8C03\u5EA6\u5668 +scheduler_configuration=\u8C03\u5EA6\u5668\u914D\u7F6E +search_filter=\u641C\u7D22\u8FC7\u6EE4\u5668 +search_test=\u641C\u7D22\u6D4B\u8BD5 +secure=\u5B89\u5168 +send_file=\u540C\u8BF7\u6C42\u4E00\u8D77\u53D1\u9001\u6587\u4EF6\uFF1A +send_file_browse=\u6D4F\u89C8... +send_file_filename_label=\u6587\u4EF6\u540D\u79F0\uFF1A +send_file_mime_label=MIME\u7C7B\u578B\uFF1A +send_file_param_name_label=\u53C2\u6570\u540D\u79F0\uFF1A +server=\u670D\u52A1\u5668\u540D\u79F0\u6216IP\uFF1A +servername=\u670D\u52A1\u5668\u540D\u79F0\uFF1A +session_argument_name=\u4F1A\u8BDD\u53C2\u6570\u540D\u79F0\uFF1A +shutdown=\u5173\u95ED +simple_config_element=\u7B80\u5355\u914D\u7F6E\u5143\u4EF6 +size_assertion_comparator_label=\u6BD4\u8F83\u7C7B\u578B +size_assertion_input_error=\u8BF7\u8F93\u5165\u4E00\u4E2A\u6709\u6548\u7684\u6B63\u6574\u6570\u3002 +size_assertion_label=\u5B57\u8282\u5927\u5C0F\uFF1A +soap_action=Soap\u52A8\u4F5C +spline_visualizer_average=\u5E73\u5747\u503C +spline_visualizer_incoming=\u8FDB\u5165 +spline_visualizer_maximum=\u6700\u5927\u503C +spline_visualizer_minimum=\u6700\u5C0F\u503C +spline_visualizer_waitingmessage=\u7B49\u5F85\u6837\u672C +ssl_alias_prompt=\u8BF7\u8F93\u5165\u9996\u9009\u7684\u522B\u540D +ssl_alias_select=\u4E3A\u6D4B\u8BD5\u9009\u62E9\u4F60\u7684\u522B\u540D +ssl_alias_title=\u5BA2\u6237\u7AEF\u522B\u540D +ssl_pass_prompt=\u8BF7\u8F93\u5165\u4F60\u7684\u5BC6\u7801 +ssl_port=SSL\u7AEF\u53E3 +sslmanager=SSL\u7BA1\u7406\u5668 +start=\u542F\u52A8 +starttime=\u542F\u52A8\u65F6\u95F4 +stop=\u505C\u6B62 +stopping_test=\u505C\u6B62\u5168\u90E8\u6D4B\u8BD5\u7EBF\u7A0B\u3002\u8BF7\u8010\u5FC3\u7B49\u5F85\u3002 +stopping_test_title=\u6B63\u5728\u505C\u6B62\u6D4B\u8BD5 +string_from_file_file_name=\u8F93\u5165\u6587\u4EF6\u7684\u5168\u8DEF\u5F84 +summariser_title=\u751F\u6210\u6982\u8981\u7ED3\u679C +tcp_config_title=TCP\u53D6\u6837\u5668\u914D\u7F6E +tcp_nodelay=\u8BBE\u7F6E\u65E0\u5EF6\u8FDF +tcp_port=\u7AEF\u53E3\u53F7\uFF1A +tcp_request_data=\u8981\u53D1\u9001\u7684\u6587\u672C +tcp_sample_title=TCP\u53D6\u6837\u5668 +tcp_timeout=\u8D85\u65F6\uFF1A +template_field=\u6A21\u677F\uFF1A +test=\u6D4B\u8BD5 +test_configuration=\u6D4B\u8BD5\u914D\u7F6E +test_plan=\u6D4B\u8BD5\u8BA1\u5212 +testplan.serialized=\u72EC\u7ACB\u8FD0\u884C\u6BCF\u4E2A\u7EBF\u7A0B\u7EC4\uFF08\u4F8B\u5982\u5728\u4E00\u4E2A\u7EC4\u8FD0\u884C\u7ED3\u675F\u540E\u542F\u52A8\u4E0B\u4E00\u4E2A\uFF09 +testplan_comments=\u6CE8\u91CA\uFF1A +thread_delay_properties=\u7EBF\u7A0B\u5EF6\u8FDF\u5C5E\u6027 +thread_group_title=\u7EBF\u7A0B\u7EC4 +thread_properties=\u7EBF\u7A0B\u5C5E\u6027 +threadgroup=\u7EBF\u7A0B\u7EC4 +throughput_control_title=\u541E\u5410\u91CF\u63A7\u5236\u5668 +throughput_control_tplabel=\u541E\u5410\u91CF +transaction_controller_title=\u4E8B\u52A1\u63A7\u5236\u5668 +update_per_iter=\u6BCF\u6B21\u8DCC\u4EE3\u66F4\u65B0\u4E00\u6B21 +upload=\u6587\u4EF6\u4E0A\u8F7D +upper_bound=\u4E0A\u9650 +url_config_protocol=\u534F\u8BAE\uFF1A +url_config_title=HTTP\u8BF7\u6C42\u9ED8\u8BA4\u503C +use_recording_controller=\u4F7F\u7528\u5F55\u5236\u63A7\u5236\u5668 +user=\u7528\u6237 +user_defined_test=\u7528\u6237\u5B9A\u4E49\u7684\u6D4B\u8BD5 +user_defined_variables=\u7528\u6237\u5B9A\u4E49\u7684\u53D8\u91CF +user_parameters_table=\u53C2\u6570 +user_parameters_title=\u7528\u6237\u53C2\u6570 +username=\u7528\u6237\u540D +value=\u503C +var_name=\u5F15\u7528\u540D\u79F0 +view_graph_tree_title=\u5BDF\u770B\u7ED3\u679C\u6811 +view_results_in_table=\u7528\u8868\u683C\u5BDF\u770B\u7ED3\u679C +view_results_tab_request=\u8BF7\u6C42 +view_results_tab_response=\u54CD\u5E94\u6570\u636E +view_results_tab_sampler=\u53D6\u6837\u5668\u7ED3\u679C +view_results_title=\u5BDF\u770B\u7ED3\u679C +view_results_tree_title=\u5BDF\u770B\u7ED3\u679C\u6811 +web_request=HTTP\u8BF7\u6C42 +web_server=Web\u670D\u52A1\u5668 +web_server_domain=\u670D\u52A1\u5668\u540D\u79F0\u6216IP\uFF1A +web_server_port=\u7AEF\u53E3\u53F7\uFF1A +web_testing_retrieve_images=\u4ECEHTML\u6587\u4EF6\u83B7\u53D6\u6240\u6709\u5185\u542B\u7684\u8D44\u6E90 +web_testing_title=HTTP\u8BF7\u6C42 +webservice_proxy_host=\u4EE3\u7406\u670D\u52A1\u5668\u4E3B\u673A +webservice_proxy_port=\u4EE3\u7406\u670D\u52A1\u5668\u7AEF\u53E3 +webservice_use_proxy=\u4F7F\u7528\u4EE3\u7406\u670D\u52A1\u5668 +workbench_title=\u5DE5\u4F5C\u53F0 +wsdl_helper_error=WSDL\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5URL\u3002 +wsdl_url_error=WSDL\u662F\u7A7A\u7684\u3002 +xml_assertion_title=XML\u65AD\u8A00 +you_must_enter_a_valid_number=\u5FC5\u987B\u8F93\u5165\u6709\u6548\u7684\u6570\u5B57 + diff --git a/src/core/org/apache/jmeter/resources/messages_zh_TW.properties b/src/core/org/apache/jmeter/resources/messages_zh_TW.properties new file mode 100644 index 00000000000..2a447f45ff4 --- /dev/null +++ b/src/core/org/apache/jmeter/resources/messages_zh_TW.properties @@ -0,0 +1,653 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=\u95DC\u65BC Apache JMeter +add=\u65B0\u589E +add_as_child=\u65B0\u589E\u70BA\u5B50\u5143\u4EF6 +add_parameter=\u65B0\u589E\u53C3\u6578 +add_pattern=\u65B0\u589E\u6A23\u5F0F\: +add_test=\u65B0\u589E\u6E2C\u8A66 +add_user=\u65B0\u589E\u4F7F\u7528\u8005 +add_value=\u65B0\u589E\u503C +addtest=\u65B0\u589E\u6E2C\u8A66 +aggregate_report=\u5F59\u6574\u5831\u544A +aggregate_report_bandwidth=\u6BCF\u79D2\u4EDF\u4F4D\u5143\u7D44 +aggregate_report_count=\u53D6\u6A23\u6578 +aggregate_report_error%=\u932F\u8AA4\u7387 +aggregate_report_max=\u6700\u5927\u503C +aggregate_report_median=\u4E2D\u9593\u503C +aggregate_report_min=\u6700\u5C0F\u503C +aggregate_report_rate=\u8655\u7406\u91CF +aggregate_report_total_label=\u7E3D\u8A08 +als_message=\u8A3B\uFF1AAccess Log Parser \u8A2D\u8A08\u6210\u901A\u7528\uFF0C\u4F60\u53EF\u4EE5\u81EA\u884C\u52A0\u5F37\u529F\u80FD +als_message2=\u8981\u5BE6\u4F5C LogParser\uFF0C\u4E26\u5C07 jar \u653E\u5728 +als_message3=/lib \u76EE\u9304\uFF0C\u7136\u5F8C\u5728\u53D6\u6A23\u4E2D\u8F38\u5165 class \u540D\u7A31 +analyze=\u5206\u6790\u8CC7\u6599\u6A94\u6848\u4E2D... +anchor_modifier_title=HTML \u93C8\u7D50\u5256\u6790\u5668 +argument_must_not_be_negative=\u53C3\u6578\u4E0D\u53EF\u4EE5\u70BA\u8CA0\u503C\uFF01 +assertion_assume_success=\u5FFD\u7565\u72C0\u614B +assertion_code_resp=\u56DE\u8986\u4EE3\u78BC +assertion_contains=\u5305\u542B +assertion_matches=\u76F8\u7B26 +assertion_message_resp=\u56DE\u8986\u8A0A\u606F +assertion_not=\u975E +assertion_pattern_match_rules=\u6A23\u5F0F\u6BD4\u5C0D\u898F\u5247 +assertion_patterns_to_test=\u6E2C\u8A66\u7528\u6A23\u5F0F +assertion_resp_field=\u9808\u6AA2\u67E5\u7684\u56DE\u8986\u6B04\u4F4D +assertion_text_resp=\u56DE\u8986\u6587\u5B57 +assertion_textarea_label=\u9A57\u8B49\uFF1A +assertion_title=\u9A57\u8B49\u56DE\u8986 +assertion_url_samp=\u53D6\u6A23\u7684 URL +assertion_visualizer_title=\u9A57\u8B49\u7D50\u679C +attribute=\u5C6C\u6027 +attrs=\u5C6C\u6027 +auth_manager_title=HTTP \u6388\u6B0A\u7BA1\u7406\u54E1 +auths_stored=\u6388\u6B0A\u7BA1\u7406\u54E1\u4E2D\u8A18\u8F09\u7684\u6388\u6B0A\u8CC7\u6599 +average=\u5E73\u5747\u503C +bind=\u57F7\u884C\u7DD2\u9023\u7D50 +browse=\u700F\u89BD... +bsf_sampler_title=BSF \u53D6\u6A23 +bsf_script=\u8173\u672C +bsf_script_file=\u8173\u672C\u6A94 +bsf_script_language=\u8173\u672C\u8A9E\u8A00\uFF1A +bsf_script_parameters=\u50B3\u7D66\u8173\u672C(\u6A94\u6848)\u7684\u53C3\u6578\uFF1A +bsh_assertion_script=\u8173\u672C +bsh_assertion_script_variables=\u56DE\u8986[\u8CC7\u6599|\u4EE3\u78BC|\u8A0A\u606F|\u8868\u982D], \u8981\u6C42\u8868\u982D, \u53D6\u6A23\u6A19\u984C, \u53D6\u6A23\u8CC7\u6599 +bsh_assertion_title=BeanShell \u9A57\u8B49 +bsh_function_expression=\u88AB\u9A57\u8B49\u7684\u8868\u793A\u5F0F +bsh_sampler_title=BeanShell \u53D6\u6A23 +bsh_script=\u8173\u672C(\u8B8A\u6578\uFF1A\u53D6\u6A23\u7D50\u679C,\u56DE\u8986\u4EE3\u78BC,\u56DE\u8986\u8A0A\u606F,\u662F\u5426\u6210\u529F,\u6A19\u984C,\u6A94\u540D) +bsh_script_file=\u8173\u672C\u6A94\u6848 +bsh_script_parameters=\u53C3\u6578(->\u5B57\u4E32\u53C3\u6578\u548C String []bsh.args) +busy_testing=\u6211\u6B63\u5FD9\u8457\u6E2C\u5462, \u8981\u6539\u8A2D\u5B9A\u503C\u8ACB\u5148\u505C\u6B62\u6E2C\u8A66 +cancel=\u53D6\u6D88 +cancel_exit_to_save=\u5C1A\u672A\u5132\u5B58, \u5148\u5132\u5B58\u518D\u96E2\u958B\u597D\u55CE\uFF1F +cancel_new_to_save=\u5C1A\u672A\u5132\u5B58, \u5148\u5132\u5B58\u518D\u6E05\u9664\u597D\u55CE\uFF1F +choose_function=\u9078\u64C7\u4E00\u500B\u529F\u80FD +choose_language=\u9078\u64C7\u4E00\u7A2E\u8A9E\u8A00 +clear=\u6E05\u9664 +clear_all=\u5168\u90E8\u6E05\u9664 +clear_cookies_per_iter=\u6BCF\u56DE\u5408\u90FD\u5148\u6E05\u9664 Cookies\uFF1F +column_delete_disallowed=\u6B64\u6B04\u4F4D\u4E0D\u5141\u8A31\u522A\u9664 +compare=\u6BD4\u8F03 +comparefilt=\u6BD4\u8F03\u904E\u6FFE\u5668 +config_element=\u8A2D\u5B9A +config_save_settings=\u8A2D\u5B9A +configure_wsdl=\u8A2D\u5B9A +constant_throughput_timer_memo=\u70BA\u4F7F\u8655\u7406\u91CF\u70BA\u56FA\u5B9A\u503C, \u5728\u53D6\u6A23\u9593\u52A0\u5165\u5EF6\u9072\u6642\u9593 +constant_timer_delay=\u5EF6\u9072\u6642\u9593(\u55AE\u4F4D\u662F\u5343\u5206\u4E4B\u4E00\u79D2) +constant_timer_memo=\u5728\u53D6\u6A23\u9593\u52A0\u5165\u56FA\u5B9A\u7684\u5EF6\u9072\u6642\u9593 +constant_timer_title=\u56FA\u5B9A\u503C\u8A08\u6642\u5668 +controller=\u63A7\u5236\u5668 +cookie_manager_title=HTTP Cookie \u7BA1\u7406\u54E1 +cookies_stored=Cookie \u7BA1\u7406\u54E1\u4E2D\u8A18\u9304\u7684 Cookies +copy=\u8907\u88FD +counter_config_title=\u8A08\u6578\u5668 +counter_per_user=\u6BCF\u500B\u4F7F\u7528\u8005\u7684\u8A08\u6578\u5668 +countlim=\u5927\u5C0F\u9650\u5236 +cut=\u526A\u4E0B +cut_paste_function=\u8907\u88FD\u548C\u8CBC\u4E0A\u5B57\u4E32 +database_conn_pool_max_usage=\u6BCF\u500B\u9023\u7DDA\u6700\u5927\u4F7F\u7528\u91CF\uFF1A +database_conn_pool_props=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60 +database_conn_pool_size=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u4E2D\u7684\u9023\u7DDA\u6578\uFF1A +database_conn_pool_title=JDBC \u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u9810\u8A2D\u503C +database_driver_class=\u9A45\u52D5\u7A0B\u5F0F Class \uFF1A +database_login_title=JDBC \u8CC7\u6599\u5EAB\u767B\u5165\u9810\u8A2D\u503C +database_sql_query_string=SQL \u654D\u8FF0\uFF1A +database_sql_query_title=JDBC SQL \u654D\u8FF0\u9810\u8A2D\u503C +database_testing_title=JDBC \u8981\u6C42 +database_url=JDBC URL\uFF1A +database_url_jdbc_props=\u8CC7\u6599\u5EAB URL \u548C JDBC \u9A45\u52D5\u7A0B\u5F0F +de=\u5FB7\u570B +default_parameters=\u9810\u8A2D\u53C3\u6578 +default_value_field=\u9810\u8A2D\u503C\uFF1A +delay=\u555F\u52D5\u5EF6\u9072\u6642\u9593(\u79D2) +delete=\u522A\u9664 +delete_parameter=\u522A\u9664\u503C +delete_test=\u522A\u9664\u6E2C\u8A66 +delete_user=\u522A\u9664\u4F7F\u7528\u8005 +deltest=\u522A\u9664\u6E2C\u8A66 +deref=\u4E0D\u5F15\u7528\u5225\u540D +disable=\u4E0D\u81F4\u80FD +distribution_graph_title=\u5206\u6563\u5716\u5F62(alpha) +distribution_note1=\u672C\u5716\u65BC\u6BCF10\u500B\u53D6\u6A23\u9032\u884C\u66F4\u65B0 +domain=\u7DB2\u57DF +done=\u5B8C\u6210 +duration=\u671F\u9593 +duration_assertion_duration_test=\u88AB\u9A57\u8B49\u7684\u671F\u9593 +duration_assertion_failure=\u6B64\u52D5\u4F5C\u592A\u4E45\u4E86,\u61C9\u8A72\u5728{1}\u5FAE\u79D2\u4E4B\u5167\u5B8C\u6210,\u537B\u82B1\u4E86{0}\u5FAE\u79D2 +duration_assertion_input_error=\u8ACB\u8F38\u5165\u5408\u6CD5\u6B63\u6574\u6578\u503C +duration_assertion_label=\u671F\u9593(\u5FAE\u79D2) +duration_assertion_title=\u9A57\u8B49\u671F\u9593 +edit=\u7DE8\u8F2F +email_results_title=\u96FB\u90F5\u7D50\u679C +en=\u82F1\u6587 +enable=\u555F\u52D5 +encode?=\u7DE8\u78BC\uFF1F +encoded_value=URL \u7DE8\u78BC\u503C +endtime=\u7D50\u675F\u6642\u9593 +entry_dn=\u9032\u5165 DN +entrydn=\u9032\u5165 DN +error_loading_help=\u8F09\u5165\u8F14\u52A9\u8AAA\u660E\u5931\u6557 +error_occurred=\u767C\u751F\u932F\u8AA4 +example_data=\u8CC7\u6599\u7BC4\u4F8B +example_title=\u53D6\u6A23\u7BC4\u4F8B +exit=\u96E2\u958B +expiration=\u5230\u671F +field_name=\u6B04\u4F4D\u540D\u7A31 +file=\u6A94\u6848 +file_already_in_use=\u6A94\u6848\u5DF2\u5728\u4F7F\u7528\u4E2D +file_visualizer_append=\u52A0\u5165\u65E2\u6709\u7684\u6A94\u6848 +file_visualizer_auto_flush=\u53D6\u5F97\u6BCF\u8CC7\u6599\u5F8C\u81EA\u52D5 Flush +file_visualizer_browse=\u700F\u89BD +file_visualizer_close=\u95DC\u9589 +file_visualizer_file_options=\u6A94\u6848\u9078\u9805 +file_visualizer_filename=\u6A94\u540D +file_visualizer_missing_filename=\u6A94\u540D\u672A\u6307\u5B9A +file_visualizer_open=\u958B\u555F +file_visualizer_output_file=\u5C07\u5168\u90E8\u8CC7\u6599\u5BEB\u6210\u6A94\u6848 +file_visualizer_submit_data=\u5305\u542B\u5DF2\u50B3\u9001\u8CC7\u6599 +file_visualizer_title=\u6A94\u6848\u5831\u544A\u54E1 +file_visualizer_verbose=\u700F\u89BD\u8F38\u51FA +filename=\u6A94\u540D +follow_redirects=\u8DDF\u96A8\u91CD\u5C0E +follow_redirects_auto=\u81EA\u52D5\u8DDF\u96A8\u91CD\u5C0E +foreach_controller_title=ForEach \u63A7\u5236\u5668 +foreach_input=\u8B8A\u6578\u524D\u7F6E\u5B57\u4E32 +foreach_output=\u8F38\u51FA\u8B8A\u6578\u540D\u7A31 +foreach_use_separator=\u5728\u7DE8\u865F\u524D\u52A0\u4E0A\u5E95\u7DDA\u7B26\u865F\uFF1F +fr=\u6CD5\u6587 +ftp_sample_title=FTP \u8981\u6C42\u9810\u8A2D\u503C +ftp_testing_title=FTP \u8981\u6C42 +function_dialog_menu_item=\u529F\u80FD\u8F14\u52A9\u8AAA\u660E\u5C0D\u8A71 +function_helper_title=\u529F\u80FD\u8F14\u52A9\u8AAA\u660E +function_name_param=\u529F\u80FD\u540D\u7A31. \u6703\u51FA\u73FE\u5728\u6574\u4EFD\u6E2C\u8A66\u8A08\u756B\u4E2D +function_params=\u529F\u80FD\u53C3\u6578 +functional_mode=\u529F\u80FD\u6027\u6E2C\u8A66\u6A21\u5F0F +functional_mode_explanation=\u53EA\u6709\u5728\u9700\u8981\u5C07\u6240\u6709\u56DE\u8986\u90FD\u5B58\u6210\u6A94\u6848\u6642\u624D\u9078\u7528\u9019\u7A2E\u6A21\u5F0F\n\n\u9078\u7528\u9019\u7A2E\u6A21\u5F0F\u5C0D\u6548\u80FD\u6703\u6709\u986F\u8457\u5F71\u97FF +gaussian_timer_delay=\u5B9A\u503C\u5EF6\u9072\u5DEE(\u5FAE\u79D2) +gaussian_timer_memo=\u4EE5\u9AD8\u65AF\u5206\u914D\u6CD5\u52A0\u5165\u96A8\u6A5F\u5EF6\u9072\u6642\u9593 +gaussian_timer_range=\u96E2\u5DEE(\u5FAE\u79D2) +gaussian_timer_title=\u9AD8\u65AF\u96A8\u6A5F\u8A08\u6642\u5668 +generate=\u7522\u751F +generator=\u9AD8\u65AF\u6CD5 class \u540D\u7A31 +generator_cnf_msg=\u627E\u4E0D\u5230\u7522\u751F\u5668 class. \u8ACB\u78BA\u8A8D jar \u6A94\u653E\u5728 /lib \u76EE\u9304 +generator_illegal_msg=\u7121\u6CD5\u5B58\u53D6\u7522\u751F\u5668 class (iIllegalAcessException) +generator_instantiate_msg=\u7121\u6CD5\u4F7F\u7528\u7522\u751F\u5668, \u8ACB\u78BA\u8A8D\u5DF2\u5BE6\u4F5C Generator \u4ECB\u9762 +get_xml_from_file=SOAP XML \u6A94(\u53D6\u4EE3\u4E0A\u9762\u6587\u5B57) +get_xml_from_random=\u8A0A\u606F\u8CC7\u6599\u593E +graph_choose_graphs=\u8981\u986F\u793A\u7684\u5716\u5F62 +graph_full_results_title=\u5B8C\u6574\u7D50\u679C\u5716\u5F62 +graph_results_average=\u5E73\u5747 +graph_results_data=\u8CC7\u6599 +graph_results_deviation=\u8B8A\u7570\u5DEE +graph_results_latest_sample=\u6700\u8FD1\u7684\u53D6\u6A23 +graph_results_median=\u4E2D\u9593\u503C +graph_results_ms=\u5FAE\u79D2 +graph_results_no_samples=\u53D6\u6A23\u7684\u7DE8\u865F +graph_results_throughput=\u8655\u7406\u91CF +graph_results_title=\u7D50\u679C\u5716\u5F62 +grouping_add_separators=\u7FA4\u7D44\u9593\u7684\u5206\u9694\u7DDA +grouping_in_controllers=\u6BCF\u500B\u7FA4\u7D44\u5206\u653E\u81F3\u4E0D\u540C\u63A7\u5236\u5668 +grouping_mode=\u7FA4\u7D44\uFF1A +grouping_no_groups=\u4E0D\u8981\u5C07\u53D6\u6A23\u5206\u7FA4\u7D44 +grouping_store_first_only=\u50C5\u5132\u5B58\u6BCF\u500B\u7FA4\u7D44\u7684\u7B2C\u4E00\u500B\u53D6\u6A23 +header_manager_title=HTTP \u6A19\u982D\u7BA1\u7406\u54E1 +headers_stored=\u6A19\u982D\u7BA1\u7406\u54E1\u4E2D\u5132\u5B58\u7684\u6A19\u982D\u8CC7\u6599 +help=\u8F14\u52A9\u8AAA\u660E +html_assertion_label=HTML \u9A57\u8B49 +html_assertion_title=HTML \u9A57\u8B49 +html_parameter_mask=HTML \u53C3\u6578\u906E\u7F69 +http_response_code=HTTP \u56DE\u61C9\u4EE3\u78BC +http_url_rewriting_modifier_title=HTTP URL \u91CD\u5C0E\u4FEE\u98FE\u8A5E +http_user_parameter_modifier=HTTP \u4F7F\u7528\u8005\u53C3\u6578\u4FEE\u98FE\u8A5E +id_prefix=ID \u524D\u7F6E\u5B57\u4E32 +id_suffix=ID \u5F8C\u7F6E\u5B57\u4E32 +if_controller_label=\u689D\u4EF6 +if_controller_title=\u82E5...\u63A7\u5236\u5668 +ignore_subcontrollers=\u5FFD\u7565\u5B50\u63A7\u5236\u5668\u5167\u5BB9 +include_equals=\u5305\u542B\u76F8\u7B49\uFF1F +increment=\u589E\u91CF +infinite=\u6C38\u4E45 +insert_after=\u52A0\u5728\u4E4B\u5F8C +insert_before=\u52A0\u5728\u4E4B\u524D +insert_parent=\u52A0\u5230\u4E0A\u4E00\u968E\u5C64 +interleave_control_title=\u4EA4\u932F\u63A7\u5236\u5668 +intsum_param_1=\u7B2C\u4E00\u500B\u6574\u6578\u53C3\u6578 +intsum_param_2=\u7B2C\u4E8C\u500B\u6574\u6578\u53C3\u6578\u2500\u5176\u4ED6\u7684\u6574\u6578\u53EF\u4EE5\u7531\u65B0\u589E\u7684\u53C3\u6578\u52A0\u7E3D +invalid_data=\u7121\u6548\u8CC7\u6599 +invalid_mail=\u50B3\u9001\u96FB\u90F5\u6642\u767C\u751F\u932F\u8AA4 +invalid_mail_address=\u5075\u6E2C\u5230\u4E00\u500B\u4EE5\u4E0A\u7684\u7121\u6548\u96FB\u90F5\u5730\u5740 +invalid_mail_server=\u7121\u6CD5\u9023\u4E0A\u96FB\u90F5\u4F3A\u670D\u5668(\u8A73\u898BJMeter\u6B77\u7A0B\u6A94) +iteration_counter_arg_1=\u6BCF\u500B\u4F7F\u7528\u8005\u4F7F\u7528\u4E0D\u540C\u8A08\u6578\u5668(TRUE)\u6216\u5171\u7528\u4E00\u500B\u5168\u57DF\u8A08\u6578\u5668(FALSE) +iterator_num=\u8FF4\u5708\u6B21\u6578\uFF1A +java_request=Java \u8981\u6C42 +java_request_defaults=Java \u8981\u6C42\u9810\u8A2D\u503C +jms_auth_required=\u5FC5\u8981 +jms_client_caption=\u63A5\u6536\u7AEF\u900F\u904ETopicSubscriber.receive()\u63A5\u807D\u8A0A\u606F +jms_client_caption2=MessageListener\u900F\u904EonMessage(Message\u4ECB\u9762\u63A5\u807D\u8A0A\u606F +jms_client_type=\u7528\u6236\u7AEF +jms_communication_style=\u6E9D\u901A\u6A21\u5F0F +jms_concrete_connection_factory=\u5805\u56FA\u9023\u7DDA\u5DE5\u5EE0 +jms_config=\u8A2D\u7F6E +jms_config_title=JMS \u8A2D\u7F6E +jms_connection_factory=\u9023\u7DDA\u5DE5\u5EE0 +jms_file=\u6A94\u6848 +jms_initial_context_factory=JNDI \u521D\u59CB\u672C\u6587\u5DE5\u5EE0 +jms_itertions=\u8981\u7D2F\u8A08\u7684\u53D6\u6A23\u6578 +jms_jndi_defaults_title=JNDI \u9810\u8A2D\u914D\u7F6E +jms_jndi_props=JNDI \u5C6C\u6027 +jms_message_title=\u8A0A\u606F +jms_message_type=\u8A0A\u606F\u7A2E\u985E +jms_msg_content=\u8A0A\u606F\u5167\u5BB9 +jms_object_message=\u7269\u4EF6\u8A0A\u606F +jms_props=JMS \u5C6C\u6027 +jms_provider_url=\u63D0\u4F9B\u8005 URL +jms_publisher=JMS \u767C\u4F48\u8005 +jms_pwd=\u5BC6\u78BC +jms_queue=\u4F47\u5217 +jms_queue_connection_factory=\u4F47\u5217\u9023\u7DDA\u5DE5\u5EE0 +jms_queueing=JMS \u8CC7\u6E90 +jms_random_file=\u96A8\u6A5F\u6A94\u6848 +jms_read_response=\u8B80\u53D6\u56DE\u8986 +jms_receive_queue=\u63A5\u6536\u4F47\u5217 +jms_request=\u55AE\u5411\u8981\u6C42 +jms_requestreply=\u8981\u6C42\u4E14\u56DE\u8986 +jms_sample_title=JMS \u9810\u8A2D\u8981\u6C42 +jms_send_queue=\u50B3\u9001\u4F47\u5217 +jms_subscriber_on_message=\u4F7F\u7528 MessageListener.onMessage() +jms_subscriber_receive=\u4F7F\u7528 TopicSubscriber.receive() +jms_subscriber_title=JMS \u8A02\u95B1\u8005 +jms_testing_title=\u8981\u6C42\u8A0A\u606F +jms_text_message=\u6587\u5B57\u8A0A\u606F +jms_timeout=\u903E\u6642 +jms_topic=\u984C\u76EE +jms_use_file=\u5F9E\u6A94\u6848 +jms_use_properties_file=\u4F7F\u7528 jndi.properties \u6A94 +jms_use_random_file=\u96A8\u6A5F\u6A94\u6848 +jms_use_text=\u6587\u5B57\u5340\u57DF +jms_user=\u4F7F\u7528\u8005 +jndi_config_title=JNDI \u914D\u7F6E +jndi_lookup_name=\u9060\u7AEF\u4ECB\u9762 +jndi_lookup_title=JNDI \u5C0B\u67E5\u914D\u7F6E +jndi_method_button_invoke=\u8D77\u52D5 +jndi_method_button_reflect=\u53CD\u6620 +jndi_method_home_name=\u672C\u7AEF\u65B9\u6CD5 +jndi_method_home_parms=\u672C\u7AEF\u65B9\u6CD5\u53C3\u6578 +jndi_method_name=\u65B9\u6CD5\u914D\u7F6E +jndi_method_remote_interface_list=\u9060\u7AEF\u4ECB\u9762 +jndi_method_remote_name=\u9060\u7AEF\u65B9\u6CD5\u540D\u7A31 +jndi_method_remote_parms=\u9060\u7AEF\u65B9\u6CD5\u53C3\u6578 +jndi_method_title=\u9060\u7AEF\u65B9\u6CD5\u914D\u7F6E +jndi_testing_title=JNDI \u8981\u6C42 +jndi_url_jndi_props=JNDI \u5C6C\u6027 +ja=\u65E5\u6587 +ldap_argument_list=LDAP\u53C3\u6578\u5217\u8868 +ldap_sample_title=LDAP \u8981\u6C42\u9810\u8A2D\u503C +ldap_testing_title=LDAP \u8981\u6C42 +ldapext_sample_title=LDAP \u5EF6\u4F38\u8981\u6C42\u9810\u8A2D\u503C +ldapext_testing_title=LDAP \u5EF6\u4F38\u8981\u6C42 +load=\u8F09\u5165 +load_wsdl=\u8F09\u5165 WSDL +log_errors_only=\u53EA\u8A18\u9304\u932F\u8AA4 +log_file=\u6B77\u7A0B\u6A94\u4F4D\u7F6E +log_parser=\u6B77\u7A0B\u6A94\u5256\u6790\u7A0B\u5F0F +log_parser_cnf_msg=\u627E\u4E0D\u5230\u8A72 class, \u8ACB\u78BA\u5B9A jar \u6A94\u653E\u5728 /lib \u76EE\u9304\u4E0B +log_parser_illegal_msg=\u7121\u6CD5\u5B58\u53D6 class (IllegalAcessException) +log_parser_instantiate_msg=\u7121\u6CD5\u5EFA\u7ACB log parser. \u8ACB\u5B9A\u6709\u5BE6\u4F5C LogParser \u4ECB\u9762 +log_sampler=Tomcat \u5B58\u53D6\u8A18\u9304\u5256\u6790\u5668 +logic_controller_title=\u7C21\u6613\u63A7\u5236\u5668 +login_config=\u767B\u5165\u914D\u7F6E +login_config_element=\u767B\u5165\u914D\u7F6E\u5143\u7D20 +loop_controller_title=\u8FF4\u5708\u63A7\u5236\u5668 +looping_control=\u8FF4\u5708\u63A7\u5236 +lower_bound=\u4F4E\u9650 +mail_reader_account=\u4F7F\u7528\u8005 +mail_reader_all_messages=\u5168\u90E8 +mail_reader_delete=\u5F9E\u4F3A\u670D\u5668\u522A\u9664 +mail_reader_folder=\u8CC7\u6599\u593E +mail_reader_num_messages=\u5F85\u63A5\u6536\u8A0A\u606F\u6578 +mail_reader_password=\u5BC6\u78BC +mail_reader_server=\u4F3A\u670D\u5668 +mail_reader_server_type=\u4F3A\u670D\u5668\u7A2E\u985E +mail_reader_title=\u90F5\u4EF6\u8B80\u53D6\u8005\u53D6\u6A23 +mail_sent=\u90F5\u4EF6\u50B3\u9001\u6210\u529F +mailer_attributes_panel=\u90F5\u5BC4\u5C6C\u6027 +mailer_error=\u7121\u6CD5\u5BC4\u51FA. \u8ACB\u66F4\u6B63\u932F\u8AA4\u503C +mailer_visualizer_title=\u90F5\u4EF6\u8996\u89BA\u5316 +match_num_field=\u7B26\u5408\u6578\u5B57(0\u8868\u793A\u96A8\u6A5F) +max=\u6700\u5927\u503C +maximum_param=\u5141\u8A31\u7BC4\u570D\u4E2D\u7684\u6700\u5927\u503C +md5hex_assertion_failure=\u9A57\u8B49 MD5 \u932F\u8AA4,\u61C9\u8A72\u662F{1}\u537B\u5F97\u5230{0} +md5hex_assertion_md5hex_test=\u88AB\u9A57\u8B49\u7684 MD5Hex +md5hex_assertion_title=MD5Hex \u9A57\u8B49 +memory_cache=\u8A18\u61B6\u5FEB\u53D6 +menu_assertions=\u9A57\u8B49 +menu_close=\u95DC\u9589 +menu_config_element=\u8A2D\u5B9A\u5143\u7D20 +menu_edit=\u7DE8\u8F2F +menu_generative_controller=\u53D6\u6A23 +menu_listener=\u63A5\u807D +menu_logic_controller=\u908F\u8F2F\u63A7\u5236\u5668 +menu_merge=\u5408\u4F75 +menu_modifiers=\u4FEE\u98FE\u5143 +menu_non_test_elements=\u975E\u6E2C\u8A66\u5143\u7D20 +menu_open=\u958B\u555F +menu_post_processors=\u5F8C\u7F6E\u8655\u7406\u5668 +menu_pre_processors=\u524D\u7F6E\u8655\u7406\u5668 +menu_response_based_modifiers=\u4EE5\u56DE\u8986\u70BA\u57FA\u6E96\u7684\u4FEE\u98FE\u5143 +menu_timer=\u8A08\u6642\u5668 +metadata=\u8CC7\u6599\u5B9A\u7FA9 +method=\u65B9\u6CD5 +mimetype=MIME\u7A2E\u985E +minimum_param=\u5141\u8A31\u7BC4\u570D\u4E2D\u7684\u6700\u5C0F\u503C +minute=\u5206\u9418 +modddn=\u820A\u8F38\u5165\u540D\u7A31 +modification_controller_title=\u4FEE\u98FE\u63A7\u5236\u5668 +modification_manager_title=\u4FEE\u98FE\u7BA1\u7406\u54E1 +modify_test=\u4FEE\u98FE\u6E2C\u8A66 +modtest=\u4FEE\u98FE\u6E2C\u8A66 +module_controller_title=\u6A21\u7D44\u63A7\u5236\u5668 +monitor_equation_active=\u6B63\u5E38\u904B\u4F5C (\u5FD9\u788C/\u6700\u5927) \u5927\u65BC 25 % +monitor_equation_dead=\u505C\u6A5F \: \u6C92\u6709\u56DE\u61C9 +monitor_equation_healthy=\u5065\u5EB7 (\u5FD9\u788C/\u6700\u5927) < 25% +monitor_equation_load=\u8CA0\u8F09 ( (busy / max) * 50) + ( (used memory / max memory) * 50) +monitor_equation_warning=\u8B66\u544A (busy/max) > 67% +monitor_health_tab_title=\u5065\u5EB7 +monitor_health_title=\u76E3\u8996\u7D50\u679C +monitor_is_title=\u7576\u6210\u76E3\u8996\u5668 +monitor_label_right_active=\u6B63\u5E38\u904B\u4F5C +monitor_label_right_dead=\u505C\u6A5F +monitor_label_right_healthy=\u5065\u5EB7 +monitor_label_right_warning=\u8B66\u793A +monitor_legend_health=\u5065\u5EB7 +monitor_legend_load=\u8CA0\u8F09 +monitor_legend_memory_per=\u8A18\u61B6\u9AD4\u4F7F\u7528\u7387 % (userd/total) +monitor_legend_thread_per=\u57F7\u884C\u7DD2 % (busy/max) +monitor_performance_servers=\u4F3A\u670D\u5668 +monitor_performance_tab_title=\u6548\u80FD +monitor_performance_title=\u6548\u80FD\u5716\u5F62 +name=\u540D\u7A31 +new=\u65B0 +newdn=\u65B0\u7684\u8B58\u5225\u540D\u7A31 +no=\u632A\u5A01 +number_of_threads=\u57F7\u884C\u7DD2\u6578\u91CF +once_only_controller_title=\u53EA\u6709\u4E00\u6B21\u63A7\u5236\u5668 +open=\u958B\u555F... +option=\u9078\u9805 +optional_tasks=\u9078\u64C7\u6027\u5DE5\u4F5C +paramtable=\u9001\u51FA\u542B\u53C3\u6578\u7684\u8981\u6C42 +password=\u5BC6\u78BC +paste=\u8CBC\u4E0A +paste_insert=\u8CBC\u4E0A(\u63D2\u5165) +path=\u8DEF\u5F91 +path_extension_choice=\u5EF6\u4F38\u8DEF\u5F91(\u4F7F\u7528\u5206\u865F\u505A\u70BA\u5206\u9694\u865F) +path_extension_dont_use_equals=\u4E0D\u8981\u5728\u5EF6\u4F38\u8DEF\u5F91\u4E2D\u4F7F\u7528\u7B49\u865F(Intershop Enfinity compatibility) +path_extension_dont_use_questionmark=\u4E0D\u8981\u5728\u5EF6\u4F38\u8DEF\u5F91\u4E2D\u4F7F\u7528\u554F\u865F(Intershop Enfinity compatibility) +patterns_to_exclude=\u9664\u5916\u7684\u578B\u5F0F +patterns_to_include=\u8981\u5305\u542B\u7684\u578B\u5F0F +port=\u7AEF\u53E3 +property_default_param=\u9810\u8A2D\u503C +property_edit=\u7DE8\u8F2F +property_editor.value_is_invalid_message=\u7531\u65BC\u4F60\u7684\u8F38\u5165\u503C\u4E0D\u5408\u6CD5.\u81EA\u52D5\u56DE\u5FA9\u5230\u539F\u503C +property_editor.value_is_invalid_title=\u7121\u6548\u8F38\u5165 +property_name_param=\u5C6C\u6027\u540D\u7A31 +property_undefined=\u672A\u5B9A\u7FA9 +protocol=\u5354\u5B9A +protocol_java_config_tile=\u8A2D\u5B9A Java \u7BC4\u4F8B +protocol_java_test_title=Java \u6E2C\u8A66 +proxy_assertions=\u589E\u52A0\u9A57\u8B49 +proxy_cl_error=\u82E5\u8981\u6307\u5B9A\u4EE3\u7406\u4F3A\u670D\u5668,\u9808\u63D0\u4F9B\u4E3B\u6A5F\u540D\u7A31\u548C\u7AEF\u53E3 +proxy_headers=\u622A\u53D6 HTTP \u8868\u982D +proxy_separators=\u589E\u52A0\u5206\u9694 +proxy_target=\u76EE\u6A19\u63A7\u5236\u5668 +proxy_title=HTTP \u4EE3\u7406\u4F3A\u670D\u5668 +ramp_up=\u555F\u52D5\u5EF6\u9072(\u79D2) +random_control_title=\u96A8\u6A5F\u63A7\u5236\u5668 +random_order_control_title=\u96A8\u6A5F\u9806\u5E8F\u63A7\u5236\u5668 +read_response_message=\u56DE\u8986\u4E0D\u6703\u88AB\u6AA2\u67E5. \u8981\u770B\u5230\u56DE\u8986\u7684\u8A71, \u8ACB\u9078\u53D6\u53D6\u6A23\u4E2D\u7684\u9078\u53D6\u5340 +read_response_note=\u5982\u679C\u6C92\u9EDE\u9078 read response, \u53D6\u6A23\u5C07\u4E0D\u6703\u8B80\u53D6\u56DE\u8986\u8CC7\u6599 +read_response_note2=\u4E5F\u4E0D\u6703\u8A2D\u5B9A SampleResult. \u5982\u6B64\u53EF\u4EE5\u6539\u5584\u6548\u80FD, \u4F46\u4E5F\u8868\u793A +read_response_note3=\u5C07\u4E0D\u6703\u8A18\u9304\u56DE\u8986\u7684\u5167\u5BB9 +read_soap_response=\u8B80\u53D6 SOAP \u56DE\u8986 +record_controller_title=\u9304\u88FD\u63A7\u5236\u5668 +ref_name_field=\u53C3\u7167\u540D\u7A31 +regex_extractor_title=\u6B63\u898F\u8868\u793A\u5F0F\u5256\u6790\u5668 +regex_field=\u6B63\u898F\u8868\u793A\u5F0F +regex_source=\u6B63\u898F\u8868\u793A\u5F0F\u6B04\u4F4D +regex_src_body=\u672C\u6587 +regex_src_hdrs=\u8868\u982D +remote_exit=\u9060\u7AEF\u96E2\u958B +remote_exit_all=\u9060\u7AEF\u96E2\u958B\u5168\u90E8 +remote_start=\u9060\u7AEF\u555F\u52D5 +remote_start_all=\u9060\u7AEF\u555F\u52D5\u5168\u90E8 +remote_stop=\u9060\u7AEF\u505C\u6B62 +remote_stop_all=\u9060\u7AEF\u505C\u6B62\u5168\u90E8 +remove=\u79FB\u9664 +rename=\u66F4\u540D +report=\u5831\u544A +request_data=\u8981\u6C42\u8CC7\u6599 +restart=\u91CD\u65B0\u555F\u52D5 +resultaction_title=\u7D50\u679C\u72C0\u614B\u52D5\u4F5C\u8655\u7406\u5668 +resultsaver_errors=\u53EA\u5132\u5B58\u5931\u6557\u7684\u56DE\u8986 +resultsaver_prefix=\u6A94\u540D\u524D\u7F6E\u5B57\u4E32 +resultsaver_title=\u5C07\u56DE\u8986\u5B58\u5230\u6A94\u6848 +retobj=\u50B3\u56DE\u7269\u4EF6 +root=\u6839 +root_title=\u6839 +run=\u57F7\u884C +running_test=\u57F7\u884C\u6E2C\u8A66 +runtime_controller_title=\u57F7\u884C\u6642\u671F\u63A7\u5236\u5668 +runtime_seconds=\u57F7\u884C\u6642\u671F(\u79D2) +sample_result_save_configuration=\u53D6\u6A23\u7D50\u679C\u5132\u5B58\u914D\u7F6E +sampler_on_error_action=\u53D6\u6A23\u932F\u8AA4\u5F8C\u63A1\u53D6\u7684\u52D5\u4F5C +sampler_on_error_continue=\u7E7C\u7E8C +sampler_on_error_stop_test=\u505C\u6B62\u6E2C\u8A66 +sampler_on_error_stop_thread=\u505C\u6B62\u57F7\u884C\u7DD2 +save=\u5132\u5B58\u6E2C\u8A66\u8A08\u756B +save?=\u5132\u5B58\uFF1F +save_all_as=\u5C07\u6E2C\u8A66\u8A08\u756B\u5132\u5B58\u6210... +save_as=\u5132\u5B58\u6210... +save_as_image=\u5132\u5B58\u6210\u5716\u5F62 +save_assertionresultsfailuremessage=\u5132\u5B58\u9A57\u8B49\u7D50\u679C\u5931\u6557\u8A0A\u606F +save_assertions=\u5132\u5B58\u9A57\u8B49\u7D50\u679C +save_asxml=\u5132\u5B58\u6210 XML +save_code=\u5132\u5B58\u56DE\u8986\u4EE3\u78BC +save_datatype=\u5132\u5B58\u8CC7\u6599\u578B\u614B +save_encoding=\u5132\u5B58\u7DE8\u78BC +save_fieldnames=\u5132\u5B58\u6B04\u4F4D\u540D\u7A31 +save_graphics=\u5132\u5B58\u5716\u5F62 +save_label=\u5132\u5B58\u6A19\u984C +save_latency=\u5132\u5B58 Latency +save_message=\u5132\u5B58\u56DE\u8986\u8A0A\u606F +save_requestheaders=\u5132\u5B58\u8981\u6C42\u8868\u982D +save_responsedata=\u5132\u5B58\u56DE\u8986\u8CC7\u6599 +save_responseheaders=\u5132\u5B58\u56DE\u8986\u8868\u982D +save_samplerdata=\u5132\u5B58\u53D6\u6A23\u8CC7\u6599 +save_subresults=\u5132\u5B58\u5B50\u7D50\u679C +save_success=\u5132\u5B58\u6210\u529F +save_threadname=\u5132\u5B58\u57F7\u884C\u7DD2\u540D\u7A31 +save_time=\u5132\u5B58\u6642\u9593 +save_timestamp=\u5132\u5B58\u6642\u9593\u6233\u8A18 +sbind=\u55AE\u4E00\u7E6B\u7D50/\u4E0D\u7E6B\u7D50 +scheduler=\u5B9A\u6642\u5668 +scheduler_configuration=\u5B9A\u6642\u5668\u914D\u7F6E +scope=\u7BC4\u570D +search_base=\u641C\u5C0B\u57FA\u6E96 +search_filter=\u641C\u5C0B\u904E\u6FFE\u689D\u4EF6 +search_test=\u641C\u5C0B\u6E2C\u8A66 +searchbase=\u641C\u5C0B\u57FA\u6E96 +searchfilter=\u641C\u5C0B\u904E\u6FFE\u689D\u4EF6 +searchtest=\u641C\u5C0B\u6E2C\u8A66 +second=\u79D2 +secure=\u5B89\u5168 +send_file=\u8207\u8981\u6C42\u4E00\u540C\u50B3\u9001\u6A94\u6848 +send_file_browse=\u700F\u89BD... +send_file_filename_label=\u6A94\u540D +send_file_mime_label=MIME \u578B\u5F0F +send_file_param_name_label=\u53C3\u6578\u540D\u7A31 +server=\u4F3A\u670D\u5668\u540D\u7A31\u6216 IP +servername=\u4F3A\u670D\u5668\u540D\u7A31 +session_argument_name=\u9023\u7DDA\u968E\u6BB5\u53C3\u6578\u540D\u7A31 +should_save=\u57F7\u884C\u6E2C\u8A66\u524D\u8981\u5148\u5C07\u6E2C\u8A66\u8173\u672C\u5B58\u6A94. \u5C24\u5176\u662F\u7576\u4F60\u4F7F\u7528 CSV Data Set \u6216 _StringFromFile \u6642 +shutdown=\u95DC\u9589 +simple_config_element=\u7C21\u6613\u8A2D\u7F6E\u5143\u7D20 +simple_data_writer_title=\u7C21\u6613\u8CC7\u6599\u5BEB\u4F5C\u8005 +size_assertion_comparator_error_equal=\u7B49\u65BC +size_assertion_comparator_error_greater=\u5927\u65BC +size_assertion_comparator_error_greaterequal=\u5927\u65BC\u6216\u7B49\u65BC +size_assertion_comparator_error_less=\u5C0F\u65BC +size_assertion_comparator_error_lessequal=\u5C0F\u65BC\u6216\u7B49\u65BC +size_assertion_comparator_error_notequal=\u4E0D\u7B49\u65BC +size_assertion_comparator_label=\u6BD4\u8F03\u7684\u985E\u5225 +size_assertion_failure=\u5927\u5C0F\u932F\u8AA4, \u61C9\u8A72\u6709 {1}{2}\u4F4D\u5143\u7D44, \u537B\u6709 {0} \u4F4D\u5143\u7D44 +size_assertion_input_error=\u8ACB\u8F38\u5165\u6709\u6548\u6B63\u6574\u6578 +size_assertion_label=\u5927\u5C0F(\u4F4D\u5143\u7D44) +size_assertion_size_test=\u9A57\u8B49\u5927\u5C0F +size_assertion_title=\u9A57\u8B49\u5927\u5C0F +soap_action=Soap \u52D5\u4F5C +soap_data_title=Soap/XML-RPC \u8CC7\u6599 +soap_sampler_title=SOAP/XML-RPC \u8981\u6C42 +spline_visualizer_average=\u5E73\u5747 +spline_visualizer_incoming=\u6B63\u4F86\u81E8\u7684 +spline_visualizer_maximum=\u6700\u5927\u503C +spline_visualizer_minimum=\u6700\u5C0F\u503C +spline_visualizer_title=\u6A23\u689D\u5716 +spline_visualizer_waitingmessage=\u7B49\u5F85\u53D6\u6A23\u7D50\u679C +ssl_alias_prompt=\u8ACB\u8F38\u5165\u9810\u9078\u7684 alias +ssl_alias_select=\u8ACB\u9078\u64C7\u6E2C\u8A66\u8981\u7528\u7684 alias +ssl_error_title=KeyStore \u554F\u984C +ssl_pass_prompt=\u8ACB\u8F38\u5165\u5BC6\u78BC +ssl_pass_title=KeyStore \u5BC6\u78BC +ssl_port=SSL \u7AEF\u53E3 +sslmanager=SSL \u7BA1\u7406\u54E1 +start=\u958B\u59CB +starttime=\u958B\u59CB\u6642\u9593 +stop=\u505C\u6B62 +stopping_test=\u6B63\u5728\u505C\u6B62\u6240\u6709\u6E2C\u8A66\u7DD2. \u8ACB\u8010\u5FC3\u7B49\u5F85 +stopping_test_title=\u505C\u6B62\u6E2C\u8A66 +string_from_file_file_name=\u8F38\u5165\u6A94\u6848\u5B8C\u6574\u8DEF\u5F91 +string_from_file_seq_final=\u6A94\u6848\u5E8F\u865F(\u7D50\u675F) +string_from_file_seq_start=\u6A94\u6848\u5E8F\u865F(\u958B\u59CB) +summariser_title=\u7522\u751F\u7E3D\u8A08\u7D50\u679C +switch_controller_label=\u5207\u63DB\u503C +switch_controller_title=\u5207\u63DB\u63A7\u5236\u5668 +table_visualizer_bytes=\u4F4D\u5143\u7D44 +table_visualizer_sample_num=\u53D6\u6A23\u7DE8\u865F \# +table_visualizer_sample_time=\u53D6\u6A23\u6642\u9593(\u5FAE\u79D2) +tcp_config_title=TCP \u53D6\u6A23\u8A2D\u5B9A +tcp_nodelay=\u8A2D\u70BA\u4E0D\u5EF6\u9072 +tcp_port=\u7AEF\u53E3\u865F\u78BC +tcp_request_data=\u6B32\u50B3\u9001\u6587\u5B57 +tcp_sample_title=TCP \u53D6\u6A23 +tcp_timeout=\u903E\u6642(\u5FAE\u79D2) +template_field=\u7BC4\u672C +test=\u6E2C\u8A66 +testconfiguration=\u6E2C\u8A66\u914D\u7F6E +test_action_action=\u52D5\u4F5C +test_action_duration=\u671F\u9593 +test_action_pause=\u66AB\u505C +test_action_stop=\u505C\u6B62 +test_action_target=\u6A19\u7684 +test_action_target_test=\u6240\u6709\u57F7\u884C\u7DD2 +test_action_target_thread=\u76EE\u524D\u57F7\u884C\u7DD2 +test_action_title=\u6E2C\u8A66\u52D5\u4F5C +test_configuration=\u6E2C\u8A66\u914D\u7F6E +test_plan=\u6E2C\u8A66\u8A08\u756B +testplan.serialized=\u4F9D\u5E8F\u57F7\u884C\u57F7\u884C\u7DD2\u7FA4\u7D44,\u57F7\u884C\u5B8C\u4E00\u500B\u624D\u6703\u57F7\u884C\u4E0B\u4E00\u500B +testplan_comments=\u5099\u8A3B +testt=\u6E2C\u8A66 +thread_delay_properties=\u57F7\u884C\u7DD2\u5EF6\u9072\u5C6C\u6027 +thread_group_title=\u57F7\u884C\u7DD2\u7FA4\u7D44 +thread_properties=\u57F7\u884C\u7DD2\u5C6C\u6027 +threadgroup=\u57F7\u884C\u7DD2\u7FA4\u7D44 +throughput_control_bynumber_label=\u7E3D\u57F7\u884C\u6578 +throughput_control_bypercent_label=\u767E\u5206\u6BD4\u57F7\u884C +throughput_control_perthread_label=\u6BCF\u500B\u4F7F\u7528\u8005 +throughput_control_title=\u8655\u7406\u91CF\u63A7\u5236\u5668 +throughput_control_tplabel=\u8655\u7406\u91CF +timelim=\u6642\u9593\u9650\u5236 +transaction_controller_title=\u4EA4\u6613\u63A7\u5236\u5668 +unbind=\u672A\u7E6B\u7D50\u57F7\u884C\u7DD2 +uniform_timer_delay=\u5E38\u6578\u5EF6\u9072\u5DEE(\u5FAE\u79D2) +uniform_timer_memo=\u52A0\u5165\u4E00\u81F4\u5206\u4F48\u7684\u96A8\u6A5F\u5EF6\u9072 +uniform_timer_range=\u96A8\u6A5F\u5EF6\u9072\u6700\u5927\u503C(\u5FAE\u79D2) +uniform_timer_title=\u4E00\u81F4\u96A8\u6A5F\u8A08\u6642\u5668 +update_per_iter=\u6BCF\u56DE\u5408\u8B8A\u66F4\u4E00\u6B21 +upload=\u6A94\u6848\u4E0A\u50B3 +upper_bound=\u4E0A\u9650 +url_config_protocol=\u5354\u5B9A +url_config_title=HTTP \u8981\u6C42\u9810\u8A2D\u503C +url_full_config_title=UrlFull \u7BC4\u4F8B +url_multipart_config_title=HTTP Multipart \u8981\u6C42\u9810\u8A2D\u503C +use_recording_controller=\u4F7F\u7528\u9304\u88FD\u63A7\u5236\u5668 +user=\u4F7F\u7528\u8005 +user_defined_test=\u4F7F\u7528\u8005\u81EA\u8A02\u6E2C\u8A66 +user_defined_variables=\u4F7F\u7528\u8005\u81EA\u8A02\u8B8A\u6578 +user_param_mod_help_note=(\u4E0D\u8981\u8B8A\u66F4\u9019\u88E1, \u8981\u6539\u5C31\u6539\u5728 /bin \u76EE\u9304\u4E0B\u540C\u540D\u7684\u6A94\u6848\u5167\u5BB9) +user_parameters_table=\u53C3\u6578 +user_parameters_title=\u4F7F\u7528\u8005\u53C3\u6578 +userdn=\u4F7F\u7528\u8005\u540D\u7A31 +username=\u4F7F\u7528\u8005\u540D\u7A31 +userpw=\u5BC6\u78BC +value=\u503C +var_name=\u53C3\u7167\u540D\u7A31 +view_graph_tree_title=\u6AA2\u8996\u5716\u5F62\u6A39 +view_results_in_table=\u6AA2\u8996\u8868\u683C\u5F0F\u7D50\u679C +view_results_tab_request=\u8981\u6C42 +view_results_tab_response=\u56DE\u8986\u8CC7\u6599 +view_results_tab_sampler=\u53D6\u6A23\u7D50\u679C +view_results_title=\u6AA2\u8996\u7D50\u679C +view_results_tree_title=\u6AA2\u8996\u7D50\u679C\u6A39 +warning=\u8B66\u544A\uFF01 +web_request=HTTP \u8981\u6C42 +web_server=Web \u4F3A\u670D\u5668 +web_server_domain=\u4E3B\u6A5F\u540D\u7A31\u6216 IP +web_server_port=\u7AEF\u53E3\u865F\u78BC +web_testing_retrieve_images=\u53D6\u56DE\u6240\u6709\u5D4C\u5165 HTML \u7684\u8CC7\u6E90 +web_testing_title=HTTP \u8981\u6C42 +webservice_proxy_host=\u4EE3\u7406\u4F3A\u670D\u5668 +webservice_proxy_note=\u5982\u679C\u9078\u7528HTTP\u4EE3\u7406\u4F3A\u670D\u5668,\u537B\u6C92\u6709\u6307\u5B9A\u4E3B\u6A5F\u548C\u7AEF\u53E3\u7684\u8A71 +webservice_proxy_note2=\u53D6\u6A23\u6703\u7531\u57F7\u884C\u547D\u4EE4\u5217\u53C3\u6578\u53D6\u5F97, \u5982\u679C\u547D\u4EE4\u5217\u53C3\u6578 +webservice_proxy_note3=\u4E5F\u6C92\u8A2D\u5B9A, \u5373\u6703\u5931\u6557 +webservice_proxy_port=\u4EE3\u7406\u4F3A\u670D\u5668\u7AEF\u53E3 +webservice_sampler_title=WebService(SOAP) \u8981\u6C42 (DEPRECATED) +webservice_use_proxy=\u4F7F\u7528 HTTP \u4EE3\u7406\u4F3A\u670D\u5668 +while_controller_label=\u689D\u4EF6 (blank/LAST \u6216 true) +while_controller_title=\u7576.. \u63A7\u5236\u5668 +workbench_title=\u5DE5\u4F5C\u53F0 +wsdl_helper_error=\u7121\u6548\u7684 WSDL,\u8ACB\u78BA\u8A8D URL \u7121\u8AA4 +wsdl_url_error=WSDL \u662F\u7A7A\u7684 +xml_assertion_title=XML \u9A57\u8B49 +xml_namespace_button=\u4F7F\u7528\u540D\u7A31\u7A7A\u9593 +xml_tolerant_button=Tolerant XML/HTML \u5256\u6790\u5668 +xml_validate_button=\u9A57\u8B49 XML +xml_whitespace_button=\u5FFD\u7565\u767D\u7A7A\u5B57\u5143 +xpath_assertion_button=\u9A57\u8B49 +xpath_assertion_check=\u6AA2\u67E5 XPath \u654D\u8FF0 +xpath_assertion_error=XPath \u932F\u8AA4 +xpath_assertion_failed=\u7121\u6548 XPath \u654D\u8FF0 +xpath_assertion_negate=\u5982\u679C\u5168\u4E0D\u7B26\u5408\u5247\u70BA True +xpath_assertion_option=\u5256\u6790 XML \u9078\u9805 +xpath_assertion_test=\u9A57\u8B49 XPath +xpath_assertion_tidy=\u8A66\u8457\u4E26\u5C07\u8F38\u5165\u8CC7\u6599 tidy up +xpath_assertion_title=\u9A57\u8B49 XPath +xpath_assertion_valid=\u5408\u6CD5 XPath \u654D\u8FF0 +xpath_assertion_validation=\u4EE5 DTD \u6AA2\u67E5 XML +xpath_assertion_whitespace=\u5FFD\u7565\u767D\u7A7A\u5B57\u5143 +xpath_expression=\u8981\u6BD4\u5C0D\u7528\u7684 XPath \u654D\u8FF0 +xpath_file_file_name=\u53D6\u503C\u4F86\u6E90\u7684 XML \u6A94\u540D +you_must_enter_a_valid_number=\u5FC5\u9808\u8F38\u5165\u4E00\u500B\u5408\u6CD5\u6578\u5B57 diff --git a/src/core/org/apache/jmeter/samplers/AbstractSampleSender.java b/src/core/org/apache/jmeter/samplers/AbstractSampleSender.java new file mode 100644 index 00000000000..ad16b8079cb --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/AbstractSampleSender.java @@ -0,0 +1,52 @@ +/* + * 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.samplers; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Base class for SampleSender implementations + */ +public abstract class AbstractSampleSender implements SampleSender { + + // Note: this is an instance field (and is not transient), so is created by the JMeter client + // and propagated to the server instance by RMI. + // [a static field would be recreated on the server, and would pick up the server properties] + private final boolean isClientConfigured = JMeterUtils.getPropDefault("sample_sender_client_configured", true); // $NON-NLS-1$ + + /** + * @return boolean indicates how SampleSender configuration is done, true means use client properties and send to servers, false means use server configurations + */ + public boolean isClientConfigured() { + return isClientConfigured; + } + + /** + * + */ + public AbstractSampleSender() { + super(); + } + + @Override + public void testEnded() { + // Not used + } + +} diff --git a/src/core/org/apache/jmeter/samplers/AbstractSampler.java b/src/core/org/apache/jmeter/samplers/AbstractSampler.java new file mode 100644 index 00000000000..bf5302a61d9 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/AbstractSampler.java @@ -0,0 +1,35 @@ +/* + * 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.samplers; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.testelement.AbstractTestElement; + +public abstract class AbstractSampler extends AbstractTestElement implements Sampler, ConfigMergabilityIndicator { + private static final long serialVersionUID = 240L; + + /** + * {@inheritDoc} + */ + @Override + public boolean applies(ConfigTestElement configElement) { + return true; + } +} diff --git a/src/core/org/apache/jmeter/samplers/AsynchSampleSender.java b/src/core/org/apache/jmeter/samplers/AsynchSampleSender.java new file mode 100644 index 00000000000..123c7a2fe90 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/AsynchSampleSender.java @@ -0,0 +1,168 @@ +/* + * 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.samplers; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +/** + * Sends samples in a separate Thread and in Batch mode + */ +public class AsynchSampleSender extends AbstractSampleSender implements Serializable { + + private static final long serialVersionUID = 251L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Create unique object as marker for end of queue + private transient static final SampleEvent FINAL_EVENT = new SampleEvent(); + + private static final int DEFAULT_QUEUE_SIZE = 100; + + private static final int serverConfiguredCapacity = JMeterUtils.getPropDefault("asynch.batch.queue.size", DEFAULT_QUEUE_SIZE); // $NON-NLS-1$ + + private final int clientConfiguredCapacity = JMeterUtils.getPropDefault("asynch.batch.queue.size", DEFAULT_QUEUE_SIZE); // $NON-NLS-1$ + + // created by client + private final RemoteSampleListener listener; + + private transient BlockingQueue queue; // created by server in readResolve method + + private transient long queueWaits; // how many times we had to wait to queue a sample + + private transient long queueWaitTime; // how long we had to wait (nanoSeconds) + + /** + * Processed by the RMI server code. + * + * @return this + * @throws ObjectStreamException never + */ + private Object readResolve() throws ObjectStreamException{ + int capacity = getCapacity(); + log.info("Using batch queue size (asynch.batch.queue.size): " + capacity); // server log file + queue = new ArrayBlockingQueue(capacity); + Worker worker = new Worker(queue, listener); + worker.setDaemon(true); + worker.start(); + return this; + } + + /** + * @deprecated only for use by test code + */ + @Deprecated + public AsynchSampleSender(){ + this(null); + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + + // Created by SampleSenderFactory + protected AsynchSampleSender(RemoteSampleListener listener) { + this.listener = listener; + log.info("Using Asynch Remote Sampler for this test run, queue size "+getCapacity()); // client log file + } + + /** + * @return capacity + */ + private int getCapacity() { + return isClientConfigured() ? + clientConfiguredCapacity : serverConfiguredCapacity; + } + + @Override + public void testEnded(String host) { + log.debug("Test Ended on " + host); + try { + listener.testEnded(host); + queue.put(FINAL_EVENT); + } catch (Exception ex) { + log.warn("testEnded(host)"+ex); + } + if (queueWaits > 0) { + log.info("QueueWaits: "+queueWaits+"; QueueWaitTime: "+queueWaitTime+" (nanoseconds)"); + } + } + + @Override + public void sampleOccurred(SampleEvent e) { + try { + if (!queue.offer(e)){ // we failed to add the element first time + queueWaits++; + long t1 = System.nanoTime(); + queue.put(e); + long t2 = System.nanoTime(); + queueWaitTime += t2-t1; + } + } catch (Exception err) { + log.error("sampleOccurred; failed to queue the sample", err); + } + } + + private static class Worker extends Thread { + + private final BlockingQueue queue; + + private final RemoteSampleListener listener; + + private Worker(BlockingQueue q, RemoteSampleListener l){ + queue = q; + listener = l; + } + + @Override + public void run() { + try { + boolean eof = false; + while (!eof) { + List l = new ArrayList(); + SampleEvent e = queue.take(); + while (!(eof = (e == FINAL_EVENT)) && e != null) { // try to process as many as possible + l.add(e); + e = queue.poll(); // returns null if nothing on queue currently + } + int size = l.size(); + if (size > 0) { + try { + listener.processBatch(l); + } catch (RemoteException err) { + if (err.getCause() instanceof java.net.ConnectException){ + throw new JMeterError("Could not return sample",err); + } + log.error("Failed to return sample", err); + } + } + } + } catch (InterruptedException e) { + } + log.debug("Worker ended"); + } + } +} diff --git a/src/core/org/apache/jmeter/samplers/BatchSampleSender.java b/src/core/org/apache/jmeter/samplers/BatchSampleSender.java new file mode 100644 index 00000000000..547bfa85a3c --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/BatchSampleSender.java @@ -0,0 +1,212 @@ +/* + * 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.samplers; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.log.Logger; +import org.apache.jorphan.logging.LoggingManager; + +import java.util.List; +import java.util.ArrayList; +import java.rmi.RemoteException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** + * Implements batch reporting for remote testing. + * + */ +public class BatchSampleSender extends AbstractSampleSender implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final int DEFAULT_NUM_SAMPLE_THRESHOLD = 100; + + private static final long DEFAULT_TIME_THRESHOLD = 60000L; + + // Static fields are resolved on the server + private static final int NUM_SAMPLES_THRESHOLD = + JMeterUtils.getPropDefault("num_sample_threshold", DEFAULT_NUM_SAMPLE_THRESHOLD); // $NON-NLS-1$ + + private static final long TIME_THRESHOLD_MS = + JMeterUtils.getPropDefault("time_threshold", DEFAULT_TIME_THRESHOLD); // $NON-NLS-1$ + + // instance fields are copied from the client instance + private final int clientConfiguredNumSamplesThreshold = + JMeterUtils.getPropDefault("num_sample_threshold", DEFAULT_NUM_SAMPLE_THRESHOLD); // $NON-NLS-1$ + + private final long clientConfiguredTimeThresholdMs = + JMeterUtils.getPropDefault("time_threshold", DEFAULT_TIME_THRESHOLD); // $NON-NLS-1$ + + private final RemoteSampleListener listener; + + private final List sampleStore = new ArrayList(); + + // Server-only work item + private transient long batchSendTime = -1; + + // Configuration items, set up by readResolve + private transient volatile int numSamplesThreshold; + + private transient volatile long timeThresholdMs; + + + /** + * @deprecated only for use by test code + */ + @Deprecated + public BatchSampleSender(){ + this(null); + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + /** + * Constructor + * + * @param listener + * that the List of sample events will be sent to. + */ + // protected added: Bug 50008 - allow BatchSampleSender to be subclassed + protected BatchSampleSender(RemoteSampleListener listener) { + this.listener = listener; + if (isClientConfigured()) { + log.info("Using batching (client settings) for this run." + + " Thresholds: num=" + clientConfiguredNumSamplesThreshold + + ", time=" + clientConfiguredTimeThresholdMs); + } else { + log.info("Using batching (server settings) for this run."); + } + } + + /** + * @return the listener + */ + // added: Bug 50008 - allow BatchSampleSender to be subclassed + protected RemoteSampleListener getListener() { + return listener; + } + + /** + * @return the sampleStore + */ + // added: Bug 50008 - allow BatchSampleSender to be subclassed + protected List getSampleStore() { + return sampleStore; + } + + /** + * Checks if any sample events are still present in the sampleStore and + * sends them to the listener. Informs the listener of the testended. + * + * @param host + * the host that the test has ended on. + */ + @Override + public void testEnded(String host) { + log.info("Test Ended on " + host); + try { + if (sampleStore.size() != 0) { + listener.processBatch(sampleStore); + sampleStore.clear(); + } + listener.testEnded(host); + } catch (RemoteException err) { + log.error("testEnded(host)", err); + } + } + + /** + * Stores sample events untill either a time or sample threshold is + * breached. Both thresholds are reset if one fires. If only one threshold + * is set it becomes the only value checked against. When a threhold is + * breached the list of sample events is sent to a listener where the event + * are fired locally. + * + * @param e + * a Sample Event + */ + @Override + public void sampleOccurred(SampleEvent e) { + List clonedStore = null; + synchronized (sampleStore) { + sampleStore.add(e); + final int sampleCount = sampleStore.size(); + + boolean sendNow = false; + if (numSamplesThreshold != -1) { + if (sampleCount >= numSamplesThreshold) { + sendNow = true; + } + } + + long now = 0; + if (timeThresholdMs != -1) { + now = System.currentTimeMillis(); + // Checking for and creating initial timestamp to check against + if (batchSendTime == -1) { + this.batchSendTime = now + timeThresholdMs; + } + if (batchSendTime < now && sampleCount > 0) { + sendNow = true; + } + } + + if (sendNow){ + @SuppressWarnings("unchecked") // OK because sampleStore is of type ArrayList + final ArrayList clone = (ArrayList)((ArrayList)sampleStore).clone(); + clonedStore = clone; + sampleStore.clear(); + if (timeThresholdMs != -1) { + this.batchSendTime = now + timeThresholdMs; + } + } + } // synchronized(sampleStore) + + if (clonedStore != null){ + try { + log.debug("Firing sample"); + listener.processBatch(clonedStore); + clonedStore.clear(); + } catch (RemoteException err) { + log.error("sampleOccurred", err); + } + } + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * + * @return this + * @throws ObjectStreamException + * never + */ + private Object readResolve() throws ObjectStreamException{ + if (isClientConfigured()) { + numSamplesThreshold = clientConfiguredNumSamplesThreshold; + timeThresholdMs = clientConfiguredTimeThresholdMs; + } else { + numSamplesThreshold = NUM_SAMPLES_THRESHOLD; + timeThresholdMs = TIME_THRESHOLD_MS; + } + log.info("Using batching for this run." + + " Thresholds: num=" + numSamplesThreshold + + ", time=" + timeThresholdMs); + return this; + } +} diff --git a/src/core/org/apache/jmeter/samplers/Clearable.java b/src/core/org/apache/jmeter/samplers/Clearable.java new file mode 100644 index 00000000000..d50e44b1e40 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/Clearable.java @@ -0,0 +1,34 @@ +/* + * 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.samplers; + +/** + * Identifies an object which supports the clearing of run-time data + * using the clearData() method. + * + * Intended for implementation by Listeners. + */ +public interface Clearable { + /** + * Clears the current data of the object. + */ + void clearData(); + // N.B. originally called clear() + // @see also JMeterGUIComponent +} diff --git a/src/core/org/apache/jmeter/samplers/DataStrippingSampleSender.java b/src/core/org/apache/jmeter/samplers/DataStrippingSampleSender.java new file mode 100644 index 00000000000..34012594002 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/DataStrippingSampleSender.java @@ -0,0 +1,119 @@ +/* + * 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.samplers; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.rmi.RemoteException; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The standard remote sample reporting should be more friendly to the main purpose of + * remote testing - which is scalability. To increase scalability, this class strips out the + * response data before sending. + * + * + */ +public class DataStrippingSampleSender extends AbstractSampleSender implements Serializable { + + private static final long serialVersionUID = -5556040298982085715L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final RemoteSampleListener listener; + private final SampleSender decoratedSender; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public DataStrippingSampleSender(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + listener = null; + decoratedSender = null; + } + + DataStrippingSampleSender(RemoteSampleListener listener) { + this.listener = listener; + decoratedSender = null; + log.info("Using DataStrippingSampleSender for this run"); + } + + DataStrippingSampleSender(SampleSender decorate) + { + this.decoratedSender = decorate; + this.listener = null; + log.info("Using DataStrippingSampleSender for this run"); + } + + @Override + public void testEnded(String host) { + log.info("Test Ended on " + host); + if(decoratedSender != null) decoratedSender.testEnded(host); + } + + @Override + public void sampleOccurred(SampleEvent event) { + //Strip the response data before writing, but only for a successful request. + SampleResult result = event.getResult(); + if(result.isSuccessful()) { + // Compute bytes before stripping + stripResponse(result); + // see Bug 57449 + for (SampleResult subResult : result.getSubResults()) { + stripResponse(subResult); + } + } + if(decoratedSender == null) + { + try { + listener.sampleOccurred(event); + } catch (RemoteException e) { + log.error("Error sending sample result over network ",e); + } + } + else + { + decoratedSender.sampleOccurred(event); + } + } + + /** + * Strip response but fill in bytes field. + * @param result {@link SampleResult} + */ + private final void stripResponse(SampleResult result) { + result.setBytes(result.getBytes()); + result.setResponseData(SampleResult.EMPTY_BA); + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * + * @return this + * @throws ObjectStreamException + * never + */ + private Object readResolve() throws ObjectStreamException{ + log.info("Using DataStrippingSampleSender for this run"); + return this; + } +} diff --git a/src/core/org/apache/jmeter/samplers/DiskStoreSampleSender.java b/src/core/org/apache/jmeter/samplers/DiskStoreSampleSender.java new file mode 100644 index 00000000000..24f836bb29e --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/DiskStoreSampleSender.java @@ -0,0 +1,175 @@ +/* + * 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.samplers; + +import org.apache.log.Logger; +import org.apache.commons.io.IOUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; + +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamException; +import java.io.OutputStream; +import java.io.Serializable; +import java.rmi.RemoteException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Version of HoldSampleSender that stores the samples on disk as a serialised stream. + */ + +public class DiskStoreSampleSender extends AbstractSampleSender implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 252L; + + private final RemoteSampleListener listener; + + private transient volatile ObjectOutputStream oos; + private transient volatile File temporaryFile; + private transient volatile ExecutorService singleExecutor; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public DiskStoreSampleSender(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + listener = null; + } + + DiskStoreSampleSender(RemoteSampleListener listener) { + this.listener = listener; + log.info("Using DiskStoreSampleSender for this test run"); // client log file + } + + @Override + public void testEnded(String host) { + log.info("Test Ended on " + host); + singleExecutor.submit(new Runnable(){ + @Override + public void run() { + try { + oos.close(); // ensure output is flushed + } catch (IOException e) { + log.error("Failed to close data file ", e); + } + }}); + singleExecutor.shutdown(); // finish processing samples + try { + if (!singleExecutor.awaitTermination(3, TimeUnit.SECONDS)) { + log.error("Executor did not terminate in a timely fashion"); + } + } catch (InterruptedException e1) { + log.error("Executor did not terminate in a timely fashion", e1); + } + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new FileInputStream(temporaryFile)); + Object obj = null; + while((obj = ois.readObject()) != null) { + if (obj instanceof SampleEvent) { + try { + listener.sampleOccurred((SampleEvent) obj); + } catch (RemoteException err) { + if (err.getCause() instanceof java.net.ConnectException){ + throw new JMeterError("Could not return sample",err); + } + log.error("returning sample", err); + } + } else { + log.error("Unexpected object type found in data file "+obj.getClass().getName()); + } + } + } catch (EOFException err) { + // expected + } catch (IOException err) { + log.error("returning sample", err); + } catch (ClassNotFoundException err) { + log.error("returning sample", err); + } finally { + try { + listener.testEnded(host); + } catch (RemoteException e) { + log.error("returning sample", e); + } + IOUtils.closeQuietly(ois); + if(!temporaryFile.delete()) { + log.warn("Could not delete file:"+temporaryFile.getAbsolutePath()); + } + } + } + + @Override + public void sampleOccurred(final SampleEvent e) { + // sampleOccurred is called from multiple threads; not safe to write from multiple threads. + // also decouples the file IO from sample generation + singleExecutor.submit(new Runnable() { + @Override + public void run() { + try { + oos.writeObject(e); + } catch (IOException err) { + log.error("sampleOccurred", err); + } + } + + } + ); + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * + * @return this + * @throws ObjectStreamException + * never + */ + // TODO should errors be thrown back through RMI? + private Object readResolve() throws ObjectStreamException{ + log.info("Using DiskStoreSampleSender for this test run"); // server log file + singleExecutor = Executors.newSingleThreadExecutor(); + try { + temporaryFile = File.createTempFile("SerialisedSampleSender", ".ser"); + temporaryFile.deleteOnExit(); + singleExecutor.submit(new Runnable(){ + @Override + public void run() { + OutputStream anOutputStream; + try { + anOutputStream = new FileOutputStream(temporaryFile); + oos = new ObjectOutputStream(anOutputStream); + } catch (IOException e) { + log.error("Failed to create output Stream", e); + } + }}); + } catch (IOException e) { + log.error("Failed to create output file", e); + } + return this; + } +} diff --git a/src/core/org/apache/jmeter/samplers/Entry.java b/src/core/org/apache/jmeter/samplers/Entry.java new file mode 100644 index 00000000000..979c4900245 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/Entry.java @@ -0,0 +1,96 @@ +/* + * 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.samplers; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.config.ConfigElement; + +// TODO - not used at present - could perhaps be removed +public class Entry { + + private Map, ConfigElement> configSet; + + // Set clonedSet; + private Class sampler; + + private List assertions; + + public Entry() { + configSet = new HashMap, ConfigElement>(); + // clonedSet = new HashSet(); + assertions = new LinkedList(); + } + + public void addAssertion(Assertion assertion) { + assertions.add(assertion); + } + + public List getAssertions() { + return assertions; + } + + public void setSamplerClass(Class samplerClass) { + this.sampler = samplerClass; + } + + public Class getSamplerClass() { + return this.sampler; + } + + public ConfigElement getConfigElement(Class configClass) { + return configSet.get(configClass); + } + + public void addConfigElement(ConfigElement config) { + addConfigElement(config, config.getClass()); + } + + /** + * Add a config element as a specific class. Usually this is done to add a + * subclass as one of it's parent classes. + * + * @param config + * the {@link ConfigElement} to be added + * @param asClass + * the {@link Class} under which the {@link ConfigElement} should + * be registered + */ + public void addConfigElement(ConfigElement config, Class asClass) { + if (config != null) { + ConfigElement current = configSet.get(asClass); + if (current == null) { + configSet.put(asClass, cloneIfNecessary(config)); + } else { + current.addConfigElement(config); + } + } + } + + private ConfigElement cloneIfNecessary(ConfigElement config) { + if (config.expectsModification()) { + return config; + } + return (ConfigElement) config.clone(); + } +} diff --git a/src/core/org/apache/jmeter/samplers/HoldSampleSender.java b/src/core/org/apache/jmeter/samplers/HoldSampleSender.java new file mode 100644 index 00000000000..96026c81b5b --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/HoldSampleSender.java @@ -0,0 +1,96 @@ +/* + * 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.samplers; + +import org.apache.log.Logger; +import org.apache.jorphan.logging.LoggingManager; + +import java.util.List; +import java.util.ArrayList; +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** + * Lars-Erik Helander provided the idea (and original implementation) for the + * caching functionality (sampleStore). + */ + +public class HoldSampleSender extends AbstractSampleSender implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final List sampleStore = new ArrayList(); + + private final RemoteSampleListener listener; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public HoldSampleSender(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + listener = null; + } + + HoldSampleSender(RemoteSampleListener listener) { + this.listener = listener; + log.warn("Using HoldSampleSender for this test run, ensure you have configured enough memory (-Xmx) for your test"); // client + } + + @Override + public void testEnded(String host) { + log.info("Test Ended on " + host); + try { + for (SampleEvent se : sampleStore) { + listener.sampleOccurred(se); + } + listener.testEnded(host); + sampleStore.clear(); + } catch (Throwable ex) { + log.error("testEnded(host)", ex); + if (ex instanceof Error){ + throw (Error) ex; + } + if (ex instanceof RuntimeException){ + throw (RuntimeException) ex; + } + } + + } + + @Override + public void sampleOccurred(SampleEvent e) { + synchronized (sampleStore) { + sampleStore.add(e); + } + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * + * @return this + * @throws ObjectStreamException + * never + */ + private Object readResolve() throws ObjectStreamException{ + log.warn("Using HoldSampleSender for this test run, ensure you have configured enough memory (-Xmx) for your test"); // server + return this; + } +} diff --git a/src/core/org/apache/jmeter/samplers/Interruptible.java b/src/core/org/apache/jmeter/samplers/Interruptible.java new file mode 100644 index 00000000000..0c7f0194396 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/Interruptible.java @@ -0,0 +1,34 @@ +/* + * 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.samplers; + +/** + * Samplers which are able to interrupt a potentially long-running operation should + * implement this interface. + * + */ +public interface Interruptible { + + /** + * Interrupt the current operation if possible. + * + * @return true if there was an operation to interrupt. + */ + boolean interrupt(); +} diff --git a/src/core/org/apache/jmeter/samplers/RemoteListenerWrapper.java b/src/core/org/apache/jmeter/samplers/RemoteListenerWrapper.java new file mode 100644 index 00000000000..d8bdea79cd2 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/RemoteListenerWrapper.java @@ -0,0 +1,134 @@ +/* + * 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.samplers; + +import java.io.Serializable; +import java.rmi.RemoteException; + +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * + * Lars-Erik Helander provided the idea (and original implementation) for the + * caching functionality (sampleStore). + * + * @version $Revision$ + */ +public class RemoteListenerWrapper extends AbstractTestElement implements SampleListener, TestStateListener, Serializable, + NoThreadClone { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final RemoteSampleListener listener; + + private final SampleSender sender; + + public RemoteListenerWrapper(RemoteSampleListener l) { + listener = l; + // Get appropriate sender class governed by the behaviour set in the JMeter property + sender = SampleSenderFactory.getInstance(listener); + } + + public RemoteListenerWrapper() // TODO: not used - make private? + { + listener = null; + sender = null; + } + + @Override + public void testStarted() { + log.debug("Test Started()"); + try { + listener.testStarted(); + } catch (Throwable ex) { + log.warn("testStarted()", ex); + if (ex instanceof Error){ + throw (Error) ex; + } + if (ex instanceof RuntimeException){ + throw (RuntimeException) ex; + } + } + + } + + @Override + public void testEnded() { + sender.testEnded(); + } + + @Override + public void testStarted(String host) { + log.debug("Test Started on " + host); + try { + listener.testStarted(host); + } catch (Throwable ex) { + log.error("testStarted(host)", ex); + if (ex instanceof Error){ + throw (Error) ex; + } + if (ex instanceof RuntimeException){ + throw (RuntimeException) ex; + } + } + } + + @Override + public void testEnded(String host) { + sender.testEnded(host); + } + + @Override + public void sampleOccurred(SampleEvent e) { + sender.sampleOccurred(e); + } + + // Note that sampleStarted() and sampleStopped() is not made to appear + // in synch with sampleOccured() when replaying held samples. + // For now this is not critical since sampleStarted() and sampleStopped() + // is not used, but it may become an issue in the future. Then these + // events must also be stored so that replay of all events may occur and + // in the right order. Each stored event must then be tagged with something + // that lets you distinguish between occured, started and ended. + + @Override + public void sampleStarted(SampleEvent e) { + log.debug("Sample started"); + try { + listener.sampleStarted(e); + } catch (RemoteException err) { + log.error("sampleStarted", err); + } + } + + @Override + public void sampleStopped(SampleEvent e) { + log.debug("Sample stopped"); + try { + listener.sampleStopped(e); + } catch (RemoteException err) { + log.error("sampleStopped", err); + } + } +} diff --git a/src/core/org/apache/jmeter/samplers/RemoteSampleListener.java b/src/core/org/apache/jmeter/samplers/RemoteSampleListener.java new file mode 100644 index 00000000000..95a3d513230 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/RemoteSampleListener.java @@ -0,0 +1,82 @@ +/* + * 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.samplers; + +import java.rmi.RemoteException; +import java.util.List; + +/** + * Allows notification on events occurring during the sampling process. + * Specifically, when sampling is started, when a specific sample is obtained, + * and when sampling is stopped. + * + * @version $Revision$ + */ +public interface RemoteSampleListener extends java.rmi.Remote { + void testStarted() throws RemoteException; + + void testStarted(String host) throws RemoteException; + + void testEnded() throws RemoteException; + + void testEnded(String host) throws RemoteException; + + // Not currently needed by any Remoteable classes + // Anyway, would probably be too expensive in terms of network traffic + // + // void testIterationStart(LoopIterationEvent event); + + /** + * This method is called remotely and fires a list of samples events + * received locally. The function is to reduce network load when using + * remote testing. + * + * @param samples + * the list of sample events to be fired locally. + * @throws RemoteException when calling the remote method fails + */ + void processBatch(List samples) throws RemoteException; + + /** + * A sample has started and stopped. + * + * @param e + * the event with data about the completed sample + * @throws RemoteException when calling the remote method fails + */ + void sampleOccurred(SampleEvent e) throws RemoteException; + + /** + * A sample has started. + * + * @param e + * the event with data about the started sample + * @throws RemoteException when calling the remote method fails + */ + void sampleStarted(SampleEvent e) throws RemoteException; + + /** + * A sample has stopped. + * + * @param e + * the event with data about the stopped sample + * @throws RemoteException when calling the remote method fails + */ + void sampleStopped(SampleEvent e) throws RemoteException; +} diff --git a/src/core/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java b/src/core/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java new file mode 100644 index 00000000000..0b290318248 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java @@ -0,0 +1,127 @@ +/* + * 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.samplers; + +import java.rmi.RemoteException; +import java.util.List; + +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implementation of remote sampler listener, also supports TestStateListener + */ +public class RemoteSampleListenerImpl extends java.rmi.server.UnicastRemoteObject + implements RemoteSampleListener, SampleListener, TestStateListener { + + private static final long serialVersionUID = 240L; + + private final TestStateListener testListener; + + private final SampleListener sampleListener; + + private static final int DEFAULT_LOCAL_PORT = + JMeterUtils.getPropDefault("client.rmi.localport", 0); // $NON-NLS-1$ + + public RemoteSampleListenerImpl(Object listener) throws RemoteException { + super(DEFAULT_LOCAL_PORT); + if (listener instanceof TestStateListener) { + testListener = (TestStateListener) listener; + } else { + testListener = null; + } + if (listener instanceof SampleListener) { + sampleListener = (SampleListener) listener; + } else { + sampleListener = null; + } + } + + @Override + public void testStarted() { + if (testListener != null) { + testListener.testStarted(); + } + } + + @Override + public void testStarted(String host) { + if (testListener != null) { + testListener.testStarted(host); + } + } + + @Override + public void testEnded() { + if (testListener != null) { + testListener.testEnded(); + } + } + + @Override + public void testEnded(String host) { + if (testListener != null) { + testListener.testEnded(host); + } + } + + /** + * This method is called remotely and fires a list of samples events + * received locally. The function is to reduce network load when using + * remote testing. + * + * @param samples + * the list of sample events to be fired locally + */ + @Override + public void processBatch(List samples) { + if (samples != null && sampleListener != null) { + for (SampleEvent e : samples) { + sampleListener.sampleOccurred(e); + } + } + } + + @Override + public void sampleOccurred(SampleEvent e) { + if (sampleListener != null) { + sampleListener.sampleOccurred(e); + } + } + + /** + * A sample has started. + */ + @Override + public void sampleStarted(SampleEvent e) { + if (sampleListener != null) { + sampleListener.sampleStarted(e); + } + } + + /** + * A sample has stopped. + */ + @Override + public void sampleStopped(SampleEvent e) { + if (sampleListener != null) { + sampleListener.sampleStopped(e); + } + } +} diff --git a/src/core/org/apache/jmeter/samplers/RemoteSampleListenerWrapper.java b/src/core/org/apache/jmeter/samplers/RemoteSampleListenerWrapper.java new file mode 100644 index 00000000000..e4205735c5e --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/RemoteSampleListenerWrapper.java @@ -0,0 +1,74 @@ +/* + * 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.samplers; + +import java.io.Serializable; +import java.rmi.RemoteException; + +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * @version $Revision$ + */ + +public class RemoteSampleListenerWrapper extends AbstractTestElement implements SampleListener, Serializable, + NoThreadClone { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private RemoteSampleListener listener; + + public RemoteSampleListenerWrapper(RemoteSampleListener l) { + listener = l; + } + + public RemoteSampleListenerWrapper() { + } + + @Override + public void sampleOccurred(SampleEvent e) { + try { + listener.sampleOccurred(e); + } catch (RemoteException err) { + log.error("", err); // $NON-NLS-1$ + } + } + + @Override + public void sampleStarted(SampleEvent e) { + try { + listener.sampleStarted(e); + } catch (RemoteException err) { + log.error("", err); // $NON-NLS-1$ + } + } + + @Override + public void sampleStopped(SampleEvent e) { + try { + listener.sampleStopped(e); + } catch (RemoteException err) { + log.error("", err); // $NON-NLS-1$ + } + } +} diff --git a/src/core/org/apache/jmeter/samplers/RemoteTestListenerWrapper.java b/src/core/org/apache/jmeter/samplers/RemoteTestListenerWrapper.java new file mode 100644 index 00000000000..d6e43c5fbe0 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/RemoteTestListenerWrapper.java @@ -0,0 +1,85 @@ +/* + * 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.samplers; + +import java.io.Serializable; + +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * @version $Revision$ + */ +public class RemoteTestListenerWrapper extends AbstractTestElement implements TestStateListener, Serializable, NoThreadClone { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final RemoteSampleListener listener; + + public RemoteTestListenerWrapper() { + log.warn("Only intended for use in testing"); + listener = null; + } + + public RemoteTestListenerWrapper(RemoteSampleListener l) { + listener = l; + } + + @Override + public void testStarted() { + try { + listener.testStarted(); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + + } + + @Override + public void testEnded() { + try { + listener.testEnded(); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + } + + @Override + public void testStarted(String host) { + try { + listener.testStarted(host); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + } + + @Override + public void testEnded(String host) { + try { + listener.testEnded(host); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + } + +} diff --git a/src/core/org/apache/jmeter/samplers/Remoteable.java b/src/core/org/apache/jmeter/samplers/Remoteable.java new file mode 100644 index 00000000000..bced763c3b2 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/Remoteable.java @@ -0,0 +1,26 @@ +/* + * 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.samplers; + +/** + * Marker interface used by ConvertListeners to determine which test elements to wrap + * so that the results are processed by the client rather than the server + */ +public interface Remoteable { +} diff --git a/src/core/org/apache/jmeter/samplers/SampleEvent.java b/src/core/org/apache/jmeter/samplers/SampleEvent.java new file mode 100644 index 00000000000..b00b9a3154f --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/SampleEvent.java @@ -0,0 +1,229 @@ +/* + * 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.samplers; + +import java.io.Serializable; +import java.util.Arrays; + +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +/** + * Packages information regarding the target of a sample event, such as the + * result from that event and the thread group it ran in. + */ +public class SampleEvent implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + /** The property {@value} is used to define additional variables to be saved */ + public static final String SAMPLE_VARIABLES = "sample_variables"; // $NON-NLS-1$ + + public static final String HOSTNAME; + + // List of variable names to be saved in JTL files + private static volatile String[] variableNames = new String[0]; + + // The values. Entries may be null, but there will be the correct number. + private final String[] values; + + // The hostname cannot change during a run, so safe to cache it just once + static { + HOSTNAME=JMeterUtils.getLocalHostName(); + initSampleVariables(); + } + + /** + * Set up the additional variable names to be saved + * from the value in the {@link #SAMPLE_VARIABLES} property + */ + public static void initSampleVariables() { + String vars = JMeterUtils.getProperty(SAMPLE_VARIABLES); + variableNames=vars != null ? vars.split(",") : new String[0]; + log.info("List of sample_variables: " + Arrays.toString(variableNames)); + } + + private final SampleResult result; + + private final String threadGroup; // TODO appears to duplicate the threadName field in SampleResult + + private final String hostname; + + private final boolean isTransactionSampleEvent; + + /** + * Constructor used for Unit tests only. Uses null for the + * associated {@link SampleResult} and the threadGroup-name. + */ + public SampleEvent() { + this(null, null); + } + + /** + * Creates SampleEvent without saving any variables. + *

+ * Use by {@link org.apache.jmeter.protocol.http.proxy.ProxyControl + * ProxyControl} and {@link StatisticalSampleSender}. + * + * @param result + * The SampleResult to be associated with this event + * @param threadGroup + * The name of the thread, the {@link SampleResult} was recorded + */ + public SampleEvent(SampleResult result, String threadGroup) { + this(result, threadGroup, HOSTNAME, false); + } + + /** + * Constructor used for normal samples, saves variable values if any are + * defined. + * + * @param result + * The SampleResult to be associated with this event + * @param threadGroup + * The name of the thread, the {@link SampleResult} was recorded + * @param jmvars + * the {@link JMeterVariables} of the thread, the + * {@link SampleResult} was recorded + */ + public SampleEvent(SampleResult result, String threadGroup, JMeterVariables jmvars) { + this(result, threadGroup, jmvars, false); + } + + /** + * Only intended for use when loading results from a file. + * + * @param result + * The SampleResult to be associated with this event + * @param threadGroup + * The name of the thread, the {@link SampleResult} was recorded + * @param hostname + * The name of the host, for which the {@link SampleResult} was + * recorded + */ + public SampleEvent(SampleResult result, String threadGroup, String hostname) { + this(result, threadGroup, hostname, false); + } + + private SampleEvent(SampleResult result, String threadGroup, String hostname, boolean isTransactionSampleEvent) { + this.result = result; + this.threadGroup = threadGroup; + this.hostname = hostname; + this.values = new String[variableNames.length]; + this.isTransactionSampleEvent = isTransactionSampleEvent; + } + + /** + * @param result + * The SampleResult to be associated with this event + * @param threadGroup + * The name of the thread, the {@link SampleResult} was recorded + * @param jmvars + * the {@link JMeterVariables} of the thread, the + * {@link SampleResult} was recorded + * @param isTransactionSampleEvent + * Flag whether this event is an transaction sample event + */ + public SampleEvent(SampleResult result, String threadGroup, JMeterVariables jmvars, boolean isTransactionSampleEvent) { + this(result, threadGroup, HOSTNAME, isTransactionSampleEvent); + saveVars(jmvars); + } + + private void saveVars(JMeterVariables vars){ + for(int i = 0; i < variableNames.length; i++){ + values[i] = vars.get(variableNames[i]); + } + } + + /** + * Get the number of defined variables + * + * @return the number of variables defined + */ + public static int getVarCount(){ + return variableNames.length; + } + + /** + * Get the nth variable name (zero-based) + * + * @param i + * specifies which variable name should be returned (zero-based) + * @return the variable name of the nth variable + */ + public static String getVarName(int i){ + return variableNames[i]; + } + + /** + * Get the nth variable value (zero-based) + * + * @param i + * specifies which variable value should be returned (zero-based) + * @return the value of the nth variable + * @throws JMeterError + * when an invalid index i was given + */ + public String getVarValue(int i){ + try { + return values[i]; + } catch (ArrayIndexOutOfBoundsException e) { + throw new JMeterError("Check the sample_variable settings!", e); + } + } + + /** + * Get the {@link SampleResult} associated with this event + * + * @return the associated {@link SampleResult} + */ + public SampleResult getResult() { + return result; + } + + /** + * Get the name of the thread group for which this event was recorded + * + * @return the name of the thread group + */ + public String getThreadGroup() { + return threadGroup; + } + + /** + * Get the name of the host for which this event was recorded + * + * @return the name of the host + */ + public String getHostname() { + return hostname; + } + + /** + * @return the isTransactionSampleEvent + */ + public boolean isTransactionSampleEvent() { + return isTransactionSampleEvent; + } + +} diff --git a/src/core/org/apache/jmeter/samplers/SampleListener.java b/src/core/org/apache/jmeter/samplers/SampleListener.java new file mode 100644 index 00000000000..01b263a8dc1 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/SampleListener.java @@ -0,0 +1,52 @@ +/* + * 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.samplers; + +/** + * Allows notification on events occurring during the sampling process. + * Specifically, when sampling is started, when a specific sample is obtained, + * and when sampling is stopped. + * + * @version $Revision$ + */ +public interface SampleListener { + /** + * A sample has started and stopped. + * + * @param e + * the {@link SampleEvent} that has occurred + */ + void sampleOccurred(SampleEvent e); + + /** + * A sample has started. + * + * @param e + * the {@link SampleEvent} that has started + */ + void sampleStarted(SampleEvent e); + + /** + * A sample has stopped. + * + * @param e + * the {@link SampleEvent} that has stopped + */ + void sampleStopped(SampleEvent e); +} diff --git a/src/core/org/apache/jmeter/samplers/SampleResult.java b/src/core/org/apache/jmeter/samplers/SampleResult.java new file mode 100644 index 00000000000..74f84cecb57 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/SampleResult.java @@ -0,0 +1,1415 @@ +/* + * 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.samplers; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +// For unit tests, @see TestSampleResult + +/** + * This is a nice packaging for the various information returned from taking a + * sample of an entry. + * + */ +public class SampleResult implements Serializable, Cloneable { + + private static final long serialVersionUID = 241L; + + // Needs to be accessible from Test code + static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The default encoding to be used if not overridden. + * The value is ISO-8859-1. + */ + public static final String DEFAULT_HTTP_ENCODING = "ISO-8859-1"; // $NON-NLS-1$ + + // Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries + // However the suggested System.getProperty("file.encoding") is Cp1252 on + // Windows + // So use a new property with the original value as default + // needs to be accessible from test code + /** + * The default encoding to be used to decode the responseData byte array. + * The value is defined by the property "sampleresult.default.encoding" + * with a default of DEFAULT_HTTP_ENCODING if that is not defined. + */ + protected static final String DEFAULT_ENCODING + = JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$ + DEFAULT_HTTP_ENCODING); + + /* The default used by {@link #setResponseData(String, String)} */ + private static final String DEFAULT_CHARSET = Charset.defaultCharset().name(); + + /** + * Data type value indicating that the response data is text. + * + * @see #getDataType + * @see #setDataType(java.lang.String) + */ + public static final String TEXT = "text"; // $NON-NLS-1$ + + /** + * Data type value indicating that the response data is binary. + * + * @see #getDataType + * @see #setDataType(java.lang.String) + */ + public static final String BINARY = "bin"; // $NON-NLS-1$ + + /** empty array which can be returned instead of null */ + public static final byte[] EMPTY_BA = new byte[0]; + + private static final SampleResult[] EMPTY_SR = new SampleResult[0]; + + private static final AssertionResult[] EMPTY_AR = new AssertionResult[0]; + + private static final boolean GETBYTES_BODY_REALSIZE = + JMeterUtils.getPropDefault("sampleresult.getbytes.body_real_size", true); // $NON-NLS-1$ + + private static final boolean GETBYTES_HEADERS_SIZE = + JMeterUtils.getPropDefault("sampleresult.getbytes.headers_size", true); // $NON-NLS-1$ + + private static final boolean GETBYTES_NETWORK_SIZE = + GETBYTES_HEADERS_SIZE && GETBYTES_BODY_REALSIZE ? true : false; + + private SampleSaveConfiguration saveConfig; + + private SampleResult parent = null; + + /** + * @param propertiesToSave + * The propertiesToSave to set. + */ + public void setSaveConfig(SampleSaveConfiguration propertiesToSave) { + this.saveConfig = propertiesToSave; + } + + public SampleSaveConfiguration getSaveConfig() { + return saveConfig; + } + + private byte[] responseData = EMPTY_BA; + + private String responseCode = "";// Never return null + + private String label = "";// Never return null + + /** Filename used by ResultSaver */ + private String resultFileName = ""; + + /** The data used by the sampler */ + private String samplerData; + + private String threadName = ""; // Never return null + + private String responseMessage = ""; + + private String responseHeaders = ""; // Never return null + + private String contentType = ""; // e.g. text/html; charset=utf-8 + + private String requestHeaders = ""; + + // TODO timeStamp == 0 means either not yet initialised or no stamp available (e.g. when loading a results file) + /** the time stamp - can be start or end */ + private long timeStamp = 0; + + private long startTime = 0; + + private long endTime = 0; + + private long idleTime = 0;// Allow for non-sample time + + /** Start of pause (if any) */ + private long pauseTime = 0; + + private List assertionResults; + + private List subResults; + + private String dataType=""; // Don't return null if not set + + private boolean success; + + //@GuardedBy("this"") + /** files that this sample has been saved in */ + /** In Non GUI mode and when best config is used, size never exceeds 1, + * but as a compromise set it to 3 + */ + private final Set files = new HashSet(3); + + private String dataEncoding;// (is this really the character set?) e.g. + // ISO-8895-1, UTF-8 + + /** elapsed time */ + private long elapsedTime = 0; + + /** time to first response */ + private long latency = 0; + + /** + * time to end connecting + */ + private long connectTime = 0; + + /** Should thread start next iteration ? */ + private boolean startNextThreadLoop = false; + + /** Should thread terminate? */ + private boolean stopThread = false; + + /** Should test terminate? */ + private boolean stopTest = false; + + /** Should test terminate abruptly? */ + private boolean stopTestNow = false; + + /** Is the sampler acting as a monitor? */ + private boolean isMonitor = false; + + private int sampleCount = 1; + + private int bytes = 0; // Allows override of sample size in case sampler does not want to store all the data + + private int headersSize = 0; + + private int bodySize = 0; + + /** Currently active threads in this thread group */ + private volatile int groupThreads = 0; + + /** Currently active threads in all thread groups */ + private volatile int allThreads = 0; + + // TODO do contentType and/or dataEncoding belong in HTTPSampleResult instead? + + private static final boolean startTimeStamp + = JMeterUtils.getPropDefault("sampleresult.timestamp.start", false); // $NON-NLS-1$ + + // Allow read-only access from test code + static final boolean USENANOTIME + = JMeterUtils.getPropDefault("sampleresult.useNanoTime", true); // $NON-NLS-1$ + + // How long between checks of nanotime; default 5000ms; set to <=0 to disable the thread + private static final long NANOTHREAD_SLEEP = + JMeterUtils.getPropDefault("sampleresult.nanoThreadSleep", 5000); // $NON-NLS-1$; + + static { + if (startTimeStamp) { + log.info("Note: Sample TimeStamps are START times"); + } else { + log.info("Note: Sample TimeStamps are END times"); + } + log.info("sampleresult.default.encoding is set to " + DEFAULT_ENCODING); + log.info("sampleresult.useNanoTime="+USENANOTIME); + log.info("sampleresult.nanoThreadSleep="+NANOTHREAD_SLEEP); + + if (USENANOTIME && NANOTHREAD_SLEEP > 0) { + // Make sure we start with a reasonable value + NanoOffset.nanoOffset = System.currentTimeMillis() - SampleResult.sampleNsClockInMs(); + NanoOffset nanoOffset = new NanoOffset(); + nanoOffset.setDaemon(true); + nanoOffset.setName("NanoOffset"); + nanoOffset.start(); + } + } + + + private final long nanoTimeOffset; + + // Allow testcode access to the settings + final boolean useNanoTime; + + final long nanoThreadSleep; + + /** + * Cache for responseData as string to avoid multiple computations + */ + private volatile transient String responseDataAsString; + + private long initOffset(){ + if (useNanoTime){ + return nanoThreadSleep > 0 ? NanoOffset.getNanoOffset() : System.currentTimeMillis() - sampleNsClockInMs(); + } else { + return Long.MIN_VALUE; + } + } + + public SampleResult() { + this(USENANOTIME, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime setting + SampleResult(boolean nanoTime) { + this(nanoTime, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime and nanoThreadSleep settings + SampleResult(boolean nanoTime, long nanoThreadSleep) { + this.elapsedTime = 0; + this.useNanoTime = nanoTime; + this.nanoThreadSleep = nanoThreadSleep; + this.nanoTimeOffset = initOffset(); + } + + /** + * Copy constructor. + * + * @param res existing sample result + */ + public SampleResult(SampleResult res) { + this(); + allThreads = res.allThreads;//OK + assertionResults = res.assertionResults;// TODO ?? + bytes = res.bytes; + headersSize = res.headersSize; + bodySize = res.bodySize; + contentType = res.contentType;//OK + dataEncoding = res.dataEncoding;//OK + dataType = res.dataType;//OK + endTime = res.endTime;//OK + // files is created automatically, and applies per instance + groupThreads = res.groupThreads;//OK + idleTime = res.idleTime; + isMonitor = res.isMonitor; + label = res.label;//OK + latency = res.latency; + connectTime = res.connectTime; + location = res.location;//OK + parent = res.parent; // TODO ?? + pauseTime = res.pauseTime; + requestHeaders = res.requestHeaders;//OK + responseCode = res.responseCode;//OK + responseData = res.responseData;//OK + responseDataAsString = null; + responseHeaders = res.responseHeaders;//OK + responseMessage = res.responseMessage;//OK + // Don't copy this; it is per instance resultFileName = res.resultFileName; + sampleCount = res.sampleCount; + samplerData = res.samplerData; + saveConfig = res.saveConfig; + startTime = res.startTime;//OK + stopTest = res.stopTest; + stopTestNow = res.stopTestNow; + stopThread = res.stopThread; + startNextThreadLoop = res.startNextThreadLoop; + subResults = res.subResults; // TODO ?? + success = res.success;//OK + threadName = res.threadName;//OK + elapsedTime = res.elapsedTime; + timeStamp = res.timeStamp; + } + + public boolean isStampedAtStart() { + return startTimeStamp; + } + + /** + * Create a sample with a specific elapsed time but don't allow the times to + * be changed later + * + * (only used by HTTPSampleResult) + * + * @param elapsed + * time + * @param atend + * create the sample finishing now, else starting now + */ + protected SampleResult(long elapsed, boolean atend) { + this(); + long now = currentTimeInMillis(); + if (atend) { + setTimes(now - elapsed, now); + } else { + setTimes(now, now + elapsed); + } + } + + /** + * Create a sample with specific start and end times for test purposes, but + * don't allow the times to be changed later + * + * (used by StatVisualizerModel.Test) + * + * @param start + * start time in milliseconds since unix epoch + * @param end + * end time in milliseconds since unix epoch + * @return sample with given start and end time + */ + public static SampleResult createTestSample(long start, long end) { + SampleResult res = new SampleResult(); + res.setStartTime(start); + res.setEndTime(end); + return res; + } + + /** + * Create a sample with a specific elapsed time for test purposes, but don't + * allow the times to be changed later + * + * @param elapsed + * - desired elapsed time in milliseconds + * @return sample that starts 'now' and ends elapsed milliseconds later + */ + public static SampleResult createTestSample(long elapsed) { + long now = System.currentTimeMillis(); + return createTestSample(now, now + elapsed); + } + + /** + * Allow users to create a sample with specific timestamp and elapsed times + * for cloning purposes, but don't allow the times to be changed later + * + * Currently used by OldSaveService, CSVSaveService and + * StatisticalSampleResult + * + * @param stamp + * this may be a start time or an end time (both in + * milliseconds) + * @param elapsed + * time in milliseconds + */ + public SampleResult(long stamp, long elapsed) { + this(); + stampAndTime(stamp, elapsed); + } + + private static long sampleNsClockInMs() { + return System.nanoTime() / 1000000; + } + + /** + * Helper method to get 1 ms resolution timing. + * + * @return the current time in milliseconds + * @throws RuntimeException + * when useNanoTime is true but + * nanoTimeOffset is not set + */ + public long currentTimeInMillis() { + if (useNanoTime){ + if (nanoTimeOffset == Long.MIN_VALUE){ + throw new RuntimeException("Invalid call; nanoTimeOffset as not been set"); + } + return sampleNsClockInMs() + nanoTimeOffset; + } + return System.currentTimeMillis(); + } + + // Helper method to maintain timestamp relationships + private void stampAndTime(long stamp, long elapsed) { + if (startTimeStamp) { + startTime = stamp; + endTime = stamp + elapsed; + } else { + startTime = stamp - elapsed; + endTime = stamp; + } + timeStamp = stamp; + elapsedTime = elapsed; + } + + /** + * For use by SaveService only. + * + * @param stamp + * this may be a start time or an end time (both in milliseconds) + * @param elapsed + * time in milliseconds + * @throws RuntimeException + * when startTime or endTime has been + * set already + */ + public void setStampAndTime(long stamp, long elapsed) { + if (startTime != 0 || endTime != 0){ + throw new RuntimeException("Calling setStampAndTime() after start/end times have been set"); + } + stampAndTime(stamp, elapsed); + } + + /** + * Set the "marked" flag to show that the result has been written to the file. + * + * @param filename the name of the file + * @return true if the result was previously marked + */ + public synchronized boolean markFile(String filename) { + return !files.add(filename); + } + + public String getResponseCode() { + return responseCode; + } + + private static final String OK_CODE = Integer.toString(HttpURLConnection.HTTP_OK); + private static final String OK_MSG = "OK"; // $NON-NLS-1$ + + /** + * Set response code to OK, i.e. "200" + * + */ + public void setResponseCodeOK(){ + responseCode=OK_CODE; + } + + public void setResponseCode(String code) { + responseCode = code; + } + + public boolean isResponseCodeOK(){ + return responseCode.equals(OK_CODE); + } + public String getResponseMessage() { + return responseMessage; + } + + public void setResponseMessage(String msg) { + responseMessage = msg; + } + + public void setResponseMessageOK() { + responseMessage = OK_MSG; + } + + /** + * Set result statuses OK - shorthand method to set: + *

    + *
  • ResponseCode
  • + *
  • ResponseMessage
  • + *
  • Successful status
  • + *
+ */ + public void setResponseOK(){ + setResponseCodeOK(); + setResponseMessageOK(); + setSuccessful(true); + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + /** + * Get the sample timestamp, which may be either the start time or the end time. + * + * @see #getStartTime() + * @see #getEndTime() + * + * @return timeStamp in milliseconds + */ + public long getTimeStamp() { + return timeStamp; + } + + public String getSampleLabel() { + return label; + } + + /** + * Get the sample label for use in summary reports etc. + * + * @param includeGroup whether to include the thread group name + * @return the label + */ + public String getSampleLabel(boolean includeGroup) { + if (includeGroup) { + StringBuilder sb = new StringBuilder(threadName.substring(0,threadName.lastIndexOf(' '))); //$NON-NLS-1$ + return sb.append(":").append(label).toString(); //$NON-NLS-1$ + } + return label; + } + + public void setSampleLabel(String label) { + this.label = label; + } + + public void addAssertionResult(AssertionResult assertResult) { + if (assertionResults == null) { + assertionResults = new ArrayList(); + } + assertionResults.add(assertResult); + } + + /** + * Gets the assertion results associated with this sample. + * + * @return an array containing the assertion results for this sample. + * Returns empty array if there are no assertion results. + */ + public AssertionResult[] getAssertionResults() { + if (assertionResults == null) { + return EMPTY_AR; + } + return assertionResults.toArray(new AssertionResult[assertionResults.size()]); + } + + /** + * Add a subresult and adjust the parent byte count and end-time. + * + * @param subResult + * the {@link SampleResult} to be added + */ + public void addSubResult(SampleResult subResult) { + if(subResult == null) { + // see https://bz.apache.org/bugzilla/show_bug.cgi?id=54778 + return; + } + String tn = getThreadName(); + if (tn.length()==0) { + tn=Thread.currentThread().getName();//TODO do this more efficiently + this.setThreadName(tn); + } + subResult.setThreadName(tn); // TODO is this really necessary? + + // Extend the time to the end of the added sample + setEndTime(Math.max(getEndTime(), subResult.getEndTime() + nanoTimeOffset - subResult.nanoTimeOffset)); // Bug 51855 + // Include the byte count for the added sample + setBytes(getBytes() + subResult.getBytes()); + setHeadersSize(getHeadersSize() + subResult.getHeadersSize()); + setBodySize(getBodySize() + subResult.getBodySize()); + addRawSubResult(subResult); + } + + /** + * Add a subresult to the collection without updating any parent fields. + * + * @param subResult + * the {@link SampleResult} to be added + */ + public void addRawSubResult(SampleResult subResult){ + storeSubResult(subResult); + } + + /** + * Add a subresult read from a results file. + *

+ * As for {@link SampleResult#addSubResult(SampleResult) + * addSubResult(SampleResult)}, except that the fields don't need to be + * accumulated + * + * @param subResult + * the {@link SampleResult} to be added + */ + public void storeSubResult(SampleResult subResult) { + if (subResults == null) { + subResults = new ArrayList(); + } + subResults.add(subResult); + subResult.setParent(this); + } + + /** + * Gets the subresults associated with this sample. + * + * @return an array containing the subresults for this sample. Returns an + * empty array if there are no subresults. + */ + public SampleResult[] getSubResults() { + if (subResults == null) { + return EMPTY_SR; + } + return subResults.toArray(new SampleResult[subResults.size()]); + } + + /** + * Sets the responseData attribute of the SampleResult object. + * + * If the parameter is null, then the responseData is set to an empty byte array. + * This ensures that getResponseData() can never be null. + * + * @param response + * the new responseData value + */ + public void setResponseData(byte[] response) { + responseDataAsString = null; + responseData = response == null ? EMPTY_BA : response; + } + + /** + * Sets the responseData attribute of the SampleResult object. + * Should only be called after setting the dataEncoding (if necessary) + * + * @param response + * the new responseData value (String) + * + * @deprecated - only intended for use from BeanShell code + */ + @Deprecated + public void setResponseData(String response) { + responseDataAsString = null; + try { + responseData = response.getBytes(getDataEncodingWithDefault()); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string, using default encoding. "+e.getLocalizedMessage()); + responseData = response.getBytes(); // N.B. default charset is used deliberately here + } + } + + /** + * Sets the encoding and responseData attributes of the SampleResult object. + * + * @param response the new responseData value (String) + * @param encoding the encoding to set and then use (if null, use platform default) + * + */ + public void setResponseData(final String response, final String encoding) { + responseDataAsString = null; + String encodeUsing = encoding != null? encoding : DEFAULT_CHARSET; + try { + responseData = response.getBytes(encodeUsing); + setDataEncoding(encodeUsing); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string using '"+encodeUsing+ + "', using default encoding: "+DEFAULT_CHARSET,e); + responseData = response.getBytes(); // N.B. default charset is used deliberately here + setDataEncoding(DEFAULT_CHARSET); + } + } + + /** + * Gets the responseData attribute of the SampleResult object. + *

+ * Note that some samplers may not store all the data, in which case + * getResponseData().length will be incorrect. + * + * Instead, always use {@link #getBytes()} to obtain the sample result byte count. + *

+ * @return the responseData value (cannot be null) + */ + public byte[] getResponseData() { + return responseData; + } + + /** + * Gets the responseData of the SampleResult object as a String + * + * @return the responseData value as a String, converted according to the encoding + */ + public String getResponseDataAsString() { + try { + if(responseDataAsString == null) { + responseDataAsString= new String(responseData,getDataEncodingWithDefault()); + } + return responseDataAsString; + } catch (UnsupportedEncodingException e) { + log.warn("Using platform default as "+getDataEncodingWithDefault()+" caused "+e); + return new String(responseData); // N.B. default charset is used deliberately here + } + } + + public void setSamplerData(String s) { + samplerData = s; + } + + public String getSamplerData() { + return samplerData; + } + + /** + * Get the time it took this sample to occur. + * + * @return elapsed time in milliseonds + * + */ + public long getTime() { + return elapsedTime; + } + + public boolean isSuccessful() { + return success; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + + public String getDataType() { + return dataType; + } + /** + * Extract and save the DataEncoding and DataType from the parameter provided. + * Does not save the full content Type. + * @see #setContentType(String) which should be used to save the full content-type string + * + * @param ct - content type (may be null) + */ + public void setEncodingAndType(String ct){ + if (ct != null) { + // Extract charset and store as DataEncoding + // N.B. The meta tag: + // + // is now processed by HTTPSampleResult#getDataEncodingWithDefault + final String CS_PFX = "charset="; // $NON-NLS-1$ + int cset = ct.toLowerCase(java.util.Locale.ENGLISH).indexOf(CS_PFX); + if (cset >= 0) { + String charSet = ct.substring(cset + CS_PFX.length()); + // handle: ContentType: text/plain; charset=ISO-8859-1; format=flowed + int semiColon = charSet.indexOf(';'); + if (semiColon >= 0) { + charSet=charSet.substring(0, semiColon); + } + // Check for quoted string + if (charSet.startsWith("\"")||charSet.startsWith("\'")){ // $NON-NLS-1$ + setDataEncoding(charSet.substring(1, charSet.length()-1)); // remove quotes + } else { + setDataEncoding(charSet); + } + } + if (isBinaryType(ct)) { + setDataType(BINARY); + } else { + setDataType(TEXT); + } + } + } + + // List of types that are known to be binary + private static final String[] BINARY_TYPES = { + "image/", //$NON-NLS-1$ + "audio/", //$NON-NLS-1$ + "video/", //$NON-NLS-1$ + }; + + // List of types that are known to be ascii, although they may appear to be binary + private static final String[] NON_BINARY_TYPES = { + "audio/x-mpegurl", //$NON-NLS-1$ (HLS Media Manifest) + "video/f4m" //$NON-NLS-1$ (Flash Media Manifest) + }; + + /* + * Determine if content-type is known to be binary, i.e. not displayable as text. + * + * @param ct content type + * @return true if content-type is of type binary. + */ + private static boolean isBinaryType(String ct){ + for (String entry : NON_BINARY_TYPES){ + if (ct.startsWith(entry)){ + return false; + } + } + for (int i = 0; i < BINARY_TYPES.length; i++){ + if (ct.startsWith(BINARY_TYPES[i])){ + return true; + } + } + return false; + } + + /** + * Sets the successful attribute of the SampleResult object. + * + * @param success + * the new successful value + */ + public void setSuccessful(boolean success) { + this.success = success; + } + + /** + * Returns the display name. + * + * @return display name of this sample result + */ + @Override + public String toString() { + return getSampleLabel(); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @return the value of the dataEncoding or DEFAULT_ENCODING + */ + public String getDataEncodingWithDefault() { + return getDataEncodingWithDefault(DEFAULT_ENCODING); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @param defaultEncoding the default to be applied + * @return the value of the dataEncoding or the provided default + */ + protected String getDataEncodingWithDefault(String defaultEncoding) { + if (dataEncoding != null && dataEncoding.length() > 0) { + return dataEncoding; + } + return defaultEncoding; + } + + /** + * Returns the dataEncoding. May be null or the empty String. + * @return the value of the dataEncoding + */ + public String getDataEncodingNoDefault() { + return dataEncoding; + } + + /** + * Sets the dataEncoding. + * + * @param dataEncoding + * the dataEncoding to set, e.g. ISO-8895-1, UTF-8 + */ + public void setDataEncoding(String dataEncoding) { + this.dataEncoding = dataEncoding; + } + + /** + * @return whether to stop the test + */ + public boolean isStopTest() { + return stopTest; + } + + /** + * @return whether to stop the test now + */ + public boolean isStopTestNow() { + return stopTestNow; + } + + /** + * @return whether to stop this thread + */ + public boolean isStopThread() { + return stopThread; + } + + public void setStopTest(boolean b) { + stopTest = b; + } + + public void setStopTestNow(boolean b) { + stopTestNow = b; + } + + public void setStopThread(boolean b) { + stopThread = b; + } + + /** + * @return the request headers + */ + public String getRequestHeaders() { + return requestHeaders; + } + + /** + * @return the response headers + */ + public String getResponseHeaders() { + return responseHeaders; + } + + /** + * @param string - + * request headers + */ + public void setRequestHeaders(String string) { + requestHeaders = string; + } + + /** + * @param string - + * response headers + */ + public void setResponseHeaders(String string) { + responseHeaders = string; + } + + /** + * @return the full content type - e.g. text/html [;charset=utf-8 ] + */ + public String getContentType() { + return contentType; + } + + /** + * Get the media type from the Content Type + * @return the media type - e.g. text/html (without charset, if any) + */ + public String getMediaType() { + return JOrphanUtils.trim(contentType," ;").toLowerCase(java.util.Locale.ENGLISH); + } + + /** + * Stores the content-type string, e.g. text/xml; charset=utf-8 + * @see #setEncodingAndType(String) which can be used to extract the charset. + * + * @param string the content-type to be set + */ + public void setContentType(String string) { + contentType = string; + } + + /** + * @return idleTime + */ + public long getIdleTime() { + return idleTime; + } + + /** + * @return the end time + */ + public long getEndTime() { + return endTime; + } + + /** + * @return the start time + */ + public long getStartTime() { + return startTime; + } + + /* + * Helper methods N.B. setStartTime must be called before setEndTime + * + * setStartTime is used by HTTPSampleResult to clone the parent sampler and + * allow the original start time to be kept + */ + protected final void setStartTime(long start) { + startTime = start; + if (startTimeStamp) { + timeStamp = startTime; + } + } + + public void setEndTime(long end) { + endTime = end; + if (!startTimeStamp) { + timeStamp = endTime; + } + if (startTime == 0) { + log.error("setEndTime must be called after setStartTime", new Throwable("Invalid call sequence")); + // TODO should this throw an error? + } else { + elapsedTime = endTime - startTime - idleTime; + } + } + + /** + * Set idle time pause. + * For use by SampleResultConverter/CSVSaveService. + * @param idle long + */ + public void setIdleTime(long idle) { + idleTime = idle; + } + + private void setTimes(long start, long end) { + setStartTime(start); + setEndTime(end); + } + + /** + * Record the start time of a sample + * + */ + public void sampleStart() { + if (startTime == 0) { + setStartTime(currentTimeInMillis()); + } else { + log.error("sampleStart called twice", new Throwable("Invalid call sequence")); + } + } + + /** + * Record the end time of a sample and calculate the elapsed time + * + */ + public void sampleEnd() { + if (endTime == 0) { + setEndTime(currentTimeInMillis()); + } else { + log.error("sampleEnd called twice", new Throwable("Invalid call sequence")); + } + } + + /** + * Pause a sample + * + */ + public void samplePause() { + if (pauseTime != 0) { + log.error("samplePause called twice", new Throwable("Invalid call sequence")); + } + pauseTime = currentTimeInMillis(); + } + + /** + * Resume a sample + * + */ + public void sampleResume() { + if (pauseTime == 0) { + log.error("sampleResume without samplePause", new Throwable("Invalid call sequence")); + } + idleTime += currentTimeInMillis() - pauseTime; + pauseTime = 0; + } + + /** + * When a Sampler is working as a monitor + * + * @param monitor + * flag whether this sampler is working as a monitor + */ + public void setMonitor(boolean monitor) { + isMonitor = monitor; + } + + /** + * If the sampler is a monitor, method will return true. + * + * @return true if the sampler is a monitor + */ + public boolean isMonitor() { + return isMonitor; + } + + /** + * The statistical sample sender aggregates several samples to save on + * transmission costs. + * + * @param count number of samples represented by this instance + */ + public void setSampleCount(int count) { + sampleCount = count; + } + + /** + * return the sample count. by default, the value is 1. + * + * @return the sample count + */ + public int getSampleCount() { + return sampleCount; + } + + /** + * Returns the count of errors. + * + * @return 0 - or 1 if the sample failed + * + * TODO do we need allow for nested samples? + */ + public int getErrorCount(){ + return success ? 0 : 1; + } + + public void setErrorCount(int i){// for reading from CSV files + // ignored currently + } + + /* + * TODO: error counting needs to be sorted out. + * + * At present the Statistical Sampler tracks errors separately + * It would make sense to move the error count here, but this would + * mean lots of changes. + * It's also tricky maintaining the count - it can't just be incremented/decremented + * when the success flag is set as this may be done multiple times. + * The work-round for now is to do the work in the StatisticalSampleResult, + * which overrides this method. + * Note that some JMS samplers also create samples with > 1 sample count + * Also the Transaction Controller probably needs to be changed to do + * proper sample and error accounting. + * The purpose of this work-round is to allow at least minimal support for + * errors in remote statistical batch mode. + * + */ + /** + * In the event the sampler does want to pass back the actual contents, we + * still want to calculate the throughput. The bytes are the bytes of the + * response data. + * + * @param length + * the number of bytes of the response data for this sample + */ + public void setBytes(int length) { + bytes = length; + } + + /** + * return the bytes returned by the response. + * + * @return byte count + */ + public int getBytes() { + if (GETBYTES_NETWORK_SIZE) { + int tmpSum = this.getHeadersSize() + this.getBodySize(); + return tmpSum == 0 ? bytes : tmpSum; + } else if (GETBYTES_HEADERS_SIZE) { + return this.getHeadersSize(); + } else if (GETBYTES_BODY_REALSIZE) { + return this.getBodySize(); + } + return bytes == 0 ? responseData.length : bytes; + } + + /** + * @return Returns the latency. + */ + public long getLatency() { + return latency; + } + + /** + * Set the time to the first response + * + */ + public void latencyEnd() { + latency = currentTimeInMillis() - startTime - idleTime; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param latency + * The latency to set. + */ + public void setLatency(long latency) { + this.latency = latency; + } + + /** + * @return Returns the connect time. + */ + public long getConnectTime() { + return connectTime; + } + + /** + * Set the time to the end of connecting + */ + public void connectEnd() { + connectTime = currentTimeInMillis() - startTime - idleTime; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param time The connect time to set. + */ + public void setConnectTime(long time) { + this.connectTime = time; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param timeStamp + * The timeStamp to set. + */ + public void setTimeStamp(long timeStamp) { + this.timeStamp = timeStamp; + } + + private URL location; + + public void setURL(URL location) { + this.location = location; + } + + public URL getURL() { + return location; + } + + /** + * Get a String representation of the URL (if defined). + * + * @return ExternalForm of URL, or empty string if url is null + */ + public String getUrlAsString() { + return location == null ? "" : location.toExternalForm(); + } + + /** + * @return Returns the parent. + */ + public SampleResult getParent() { + return parent; + } + + /** + * @param parent + * The parent to set. + */ + public void setParent(SampleResult parent) { + this.parent = parent; + } + + public String getResultFileName() { + return resultFileName; + } + + public void setResultFileName(String resultFileName) { + this.resultFileName = resultFileName; + } + + public int getGroupThreads() { + return groupThreads; + } + + public void setGroupThreads(int n) { + this.groupThreads = n; + } + + public int getAllThreads() { + return allThreads; + } + + public void setAllThreads(int n) { + this.allThreads = n; + } + + // Bug 47394 + /** + * Allow custom SampleSenders to drop unwanted assertionResults + */ + public void removeAssertionResults() { + this.assertionResults = null; + } + + /** + * Allow custom SampleSenders to drop unwanted subResults + */ + public void removeSubResults() { + this.subResults = null; + } + + /** + * Set the headers size in bytes + * + * @param size + * the number of bytes of the header + */ + public void setHeadersSize(int size) { + this.headersSize = size; + } + + /** + * Get the headers size in bytes + * + * @return the headers size + */ + public int getHeadersSize() { + return headersSize; + } + + /** + * @return the body size in bytes + */ + public int getBodySize() { + return bodySize == 0 ? responseData.length : bodySize; + } + + /** + * @param bodySize the body size to set + */ + public void setBodySize(int bodySize) { + this.bodySize = bodySize; + } + + private static class NanoOffset extends Thread { + + private static volatile long nanoOffset; + + static long getNanoOffset() { + return nanoOffset; + } + + @Override + public void run() { + // Wait longer than a clock pulse (generally 10-15ms) + getOffset(30L); // Catch an early clock pulse to reduce slop. + while(true) { + getOffset(NANOTHREAD_SLEEP); // Can now afford to wait a bit longer between checks + } + + } + + private void getOffset(long wait) { + try { + TimeUnit.MILLISECONDS.sleep(wait); + long clock = System.currentTimeMillis(); + long nano = SampleResult.sampleNsClockInMs(); + nanoOffset = clock - nano; + } catch (InterruptedException ignore) { + // ignored + } + } + + } + + /** + * @return the startNextThreadLoop + */ + public boolean isStartNextThreadLoop() { + return startNextThreadLoop; + } + + /** + * @param startNextThreadLoop the startNextLoop to set + */ + public void setStartNextThreadLoop(boolean startNextThreadLoop) { + this.startNextThreadLoop = startNextThreadLoop; + } + + /** + * Clean up cached data + */ + public void cleanAfterSample() { + this.responseDataAsString = null; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException("This should not happen"); + } + } +} diff --git a/src/core/org/apache/jmeter/samplers/SampleSaveConfiguration.java b/src/core/org/apache/jmeter/samplers/SampleSaveConfiguration.java new file mode 100644 index 00000000000..1b0a538bc39 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/SampleSaveConfiguration.java @@ -0,0 +1,855 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 7, 2004 + */ +package org.apache.jmeter.samplers; + +import java.io.Serializable; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Properties; + +import org.apache.commons.lang3.CharUtils; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +/* + * N.B. to add a new field, remember the following + * - static _xyz + * - instance xyz=_xyz + * - clone s.xyz = xyz (perhaps) + * - setXyz(boolean) + * - saveXyz() + * - update SampleSaveConfigurationConverter to add new fields to marshall() and shouldSerialiseMember() + * - update SampleResultConverter and/or HTTPSampleConverter + * - update CSVSaveService: CSV_XXXX, makeResultFromDelimitedString, printableFieldNamesToString, static{} + * - update messages.properties to add save_xyz entry + * - update jmeter.properties to add new property + * - update listeners.xml to add new property, CSV and XML names etc. + * - take screenshot sample_result_config.png + * - update listeners.xml and component_reference.xml with new dimensions (might not change) + * + */ +/** + * Holds details of which sample attributes to save. + * + * The pop-up dialogue for this is created by the class SavePropertyDialog, which assumes: + *

+ * For each field XXX + *

    + *
  • methods have the signature "boolean saveXXX()"
  • + *
  • a corresponding "void setXXX(boolean)" method
  • + *
  • messages.properties contains the key save_XXX
  • + *
+ */ +public class SampleSaveConfiguration implements Cloneable, Serializable { + private static final long serialVersionUID = 7L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // --------------------------------------------------------------------- + // PROPERTY FILE CONSTANTS + // --------------------------------------------------------------------- + + /** Indicates that the results file should be in XML format. * */ + private static final String XML = "xml"; // $NON_NLS-1$ + + /** Indicates that the results file should be in CSV format. * */ + private static final String CSV = "csv"; // $NON_NLS-1$ + + /** Indicates that the results should be stored in a database. * */ + //NOTUSED private static final String DATABASE = "db"; // $NON_NLS-1$ + + /** A properties file indicator for true. * */ + private static final String TRUE = "true"; // $NON_NLS-1$ + + /** A properties file indicator for false. * */ + private static final String FALSE = "false"; // $NON_NLS-1$ + + /** A properties file indicator for milliseconds. * */ + private static final String MILLISECONDS = "ms"; // $NON_NLS-1$ + + /** A properties file indicator for none. * */ + private static final String NONE = "none"; // $NON_NLS-1$ + + /** A properties file indicator for the first of a series. * */ + private static final String FIRST = "first"; // $NON_NLS-1$ + + /** A properties file indicator for all of a series. * */ + private static final String ALL = "all"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating which assertion results should be + * saved. + **************************************************************************/ + private static final String ASSERTION_RESULTS_FAILURE_MESSAGE_PROP = + "jmeter.save.saveservice.assertion_results_failure_message"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating which assertion results should be + * saved. + **************************************************************************/ + private static final String ASSERTION_RESULTS_PROP = "jmeter.save.saveservice.assertion_results"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating which delimiter should be used when + * saving in a delimited values format. + **************************************************************************/ + private static final String DEFAULT_DELIMITER_PROP = "jmeter.save.saveservice.default_delimiter"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating which format should be used when + * saving the results, e.g., xml or csv. + **************************************************************************/ + private static final String OUTPUT_FORMAT_PROP = "jmeter.save.saveservice.output_format"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether field names should be printed + * to a delimited file. + **************************************************************************/ + private static final String PRINT_FIELD_NAMES_PROP = "jmeter.save.saveservice.print_field_names"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the data type should be + * saved. + **************************************************************************/ + private static final String SAVE_DATA_TYPE_PROP = "jmeter.save.saveservice.data_type"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the label should be saved. + **************************************************************************/ + private static final String SAVE_LABEL_PROP = "jmeter.save.saveservice.label"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the response code should be + * saved. + **************************************************************************/ + private static final String SAVE_RESPONSE_CODE_PROP = "jmeter.save.saveservice.response_code"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the response data should be + * saved. + **************************************************************************/ + private static final String SAVE_RESPONSE_DATA_PROP = "jmeter.save.saveservice.response_data"; // $NON_NLS-1$ + + private static final String SAVE_RESPONSE_DATA_ON_ERROR_PROP = "jmeter.save.saveservice.response_data.on_error"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the response message should + * be saved. + **************************************************************************/ + private static final String SAVE_RESPONSE_MESSAGE_PROP = "jmeter.save.saveservice.response_message"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the success indicator should + * be saved. + **************************************************************************/ + private static final String SAVE_SUCCESSFUL_PROP = "jmeter.save.saveservice.successful"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the thread name should be + * saved. + **************************************************************************/ + private static final String SAVE_THREAD_NAME_PROP = "jmeter.save.saveservice.thread_name"; // $NON_NLS-1$ + + // Save bytes read + private static final String SAVE_BYTES_PROP = "jmeter.save.saveservice.bytes"; // $NON_NLS-1$ + + // Save URL + private static final String SAVE_URL_PROP = "jmeter.save.saveservice.url"; // $NON_NLS-1$ + + // Save fileName for ResultSaver + private static final String SAVE_FILENAME_PROP = "jmeter.save.saveservice.filename"; // $NON_NLS-1$ + + // Save hostname for ResultSaver + private static final String SAVE_HOSTNAME_PROP = "jmeter.save.saveservice.hostname"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the time should be saved. + **************************************************************************/ + private static final String SAVE_TIME_PROP = "jmeter.save.saveservice.time"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property giving the format of the time stamp + **************************************************************************/ + private static final String TIME_STAMP_FORMAT_PROP = "jmeter.save.saveservice.timestamp_format"; // $NON_NLS-1$ + + private static final String SUBRESULTS_PROP = "jmeter.save.saveservice.subresults"; // $NON_NLS-1$ + private static final String ASSERTIONS_PROP = "jmeter.save.saveservice.assertions"; // $NON_NLS-1$ + private static final String LATENCY_PROP = "jmeter.save.saveservice.latency"; // $NON_NLS-1$ + private static final String CONNECT_TIME_PROP = "jmeter.save.saveservice.connect_time"; // $NON_NLS-1$ + private static final String SAMPLERDATA_PROP = "jmeter.save.saveservice.samplerData"; // $NON_NLS-1$ + private static final String RESPONSEHEADERS_PROP = "jmeter.save.saveservice.responseHeaders"; // $NON_NLS-1$ + private static final String REQUESTHEADERS_PROP = "jmeter.save.saveservice.requestHeaders"; // $NON_NLS-1$ + private static final String ENCODING_PROP = "jmeter.save.saveservice.encoding"; // $NON_NLS-1$ + + + // optional processing instruction for line 2; e.g. + // + private static final String XML_PI = "jmeter.save.saveservice.xml_pi"; // $NON_NLS-1$ + + private static final String SAVE_THREAD_COUNTS = "jmeter.save.saveservice.thread_counts"; // $NON_NLS-1$ + + private static final String SAVE_SAMPLE_COUNT = "jmeter.save.saveservice.sample_count"; // $NON_NLS-1$ + + private static final String SAVE_IDLE_TIME = "jmeter.save.saveservice.idle_time"; // $NON_NLS-1$ + // N.B. Remember to update the equals and hashCode methods when adding new variables. + + // Initialise values from properties + private boolean time = _time, latency = _latency, connectTime=_connectTime, timestamp = _timestamp, success = _success, label = _label, + code = _code, message = _message, threadName = _threadName, dataType = _dataType, encoding = _encoding, + assertions = _assertions, subresults = _subresults, responseData = _responseData, + samplerData = _samplerData, xml = _xml, fieldNames = _fieldNames, responseHeaders = _responseHeaders, + requestHeaders = _requestHeaders, responseDataOnError = _responseDataOnError; + + private boolean saveAssertionResultsFailureMessage = _saveAssertionResultsFailureMessage; + + private boolean url = _url, bytes = _bytes , fileName = _fileName; + + private boolean hostname = _hostname; + + private boolean threadCounts = _threadCounts; + + private boolean sampleCount = _sampleCount; + + private boolean idleTime = _idleTime; + + // Does not appear to be used (yet) + private int assertionsResultsToSave = _assertionsResultsToSave; + + + // Don't save this, as it is derived from the time format + private boolean printMilliseconds = _printMilliseconds; + + /** A formatter for the time stamp. */ + private transient DateFormat formatter = _formatter; + /* Make transient as we don't want to save the SimpleDataFormat class + * Also, there's currently no way to change the value via the GUI, so changing it + * later means editting the JMX, or recreating the Listener. + */ + + // Defaults from properties: + private static final boolean _time, _timestamp, _success, _label, _code, _message, _threadName, _xml, + _responseData, _dataType, _encoding, _assertions, _latency, _connectTime, _subresults, _samplerData, _fieldNames, + _responseHeaders, _requestHeaders; + + private static final boolean _responseDataOnError; + + private static final boolean _saveAssertionResultsFailureMessage; + + private static final String _timeStampFormat; + + private static final int _assertionsResultsToSave; + + // TODO turn into method? + public static final int SAVE_NO_ASSERTIONS = 0; + + public static final int SAVE_FIRST_ASSERTION = SAVE_NO_ASSERTIONS + 1; + + public static final int SAVE_ALL_ASSERTIONS = SAVE_FIRST_ASSERTION + 1; + + private static final boolean _printMilliseconds; + + private static final boolean _bytes; + + private static final boolean _url; + + private static final boolean _fileName; + + private static final boolean _hostname; + + private static final boolean _threadCounts; + + private static final boolean _sampleCount; + + private static final DateFormat _formatter; + + /** + * The string used to separate fields when stored to disk, for example, the + * comma for CSV files. + */ + private static final String _delimiter; + + private static final boolean _idleTime; + + private static final String DEFAULT_DELIMITER = ","; // $NON_NLS-1$ + + /** + * Read in the properties having to do with saving from a properties file. + */ + static { + Properties props = JMeterUtils.getJMeterProperties(); + + _subresults = TRUE.equalsIgnoreCase(props.getProperty(SUBRESULTS_PROP, TRUE)); + _assertions = TRUE.equalsIgnoreCase(props.getProperty(ASSERTIONS_PROP, TRUE)); + _latency = TRUE.equalsIgnoreCase(props.getProperty(LATENCY_PROP, TRUE)); + _connectTime = TRUE.equalsIgnoreCase(props.getProperty(CONNECT_TIME_PROP, FALSE)); + _samplerData = TRUE.equalsIgnoreCase(props.getProperty(SAMPLERDATA_PROP, FALSE)); + _responseHeaders = TRUE.equalsIgnoreCase(props.getProperty(RESPONSEHEADERS_PROP, FALSE)); + _requestHeaders = TRUE.equalsIgnoreCase(props.getProperty(REQUESTHEADERS_PROP, FALSE)); + _encoding = TRUE.equalsIgnoreCase(props.getProperty(ENCODING_PROP, FALSE)); + + String dlm = props.getProperty(DEFAULT_DELIMITER_PROP, DEFAULT_DELIMITER); + if (dlm.equals("\\t")) {// Make it easier to enter a tab (can use \ but that is awkward) + dlm="\t"; + } + + if (dlm.length() != 1){ + throw new JMeterError("Delimiter '"+dlm+"' must be of length 1."); + } + char ch = dlm.charAt(0); + + if (CharUtils.isAsciiAlphanumeric(ch) || ch == CSVSaveService.QUOTING_CHAR){ + throw new JMeterError("Delimiter '"+ch+"' must not be alphanumeric or "+CSVSaveService.QUOTING_CHAR+"."); + } + + if (ch != '\t' && !CharUtils.isAsciiPrintable(ch)){ + throw new JMeterError("Delimiter (code "+(int)ch+") must be printable."); + } + + _delimiter = dlm; + + _fieldNames = TRUE.equalsIgnoreCase(props.getProperty(PRINT_FIELD_NAMES_PROP, FALSE)); + + _dataType = TRUE.equalsIgnoreCase(props.getProperty(SAVE_DATA_TYPE_PROP, TRUE)); + + _label = TRUE.equalsIgnoreCase(props.getProperty(SAVE_LABEL_PROP, TRUE)); + + _code = TRUE.equalsIgnoreCase(props.getProperty(SAVE_RESPONSE_CODE_PROP, TRUE)); + + _responseData = TRUE.equalsIgnoreCase(props.getProperty(SAVE_RESPONSE_DATA_PROP, FALSE)); + + _responseDataOnError = TRUE.equalsIgnoreCase(props.getProperty(SAVE_RESPONSE_DATA_ON_ERROR_PROP, FALSE)); + + _message = TRUE.equalsIgnoreCase(props.getProperty(SAVE_RESPONSE_MESSAGE_PROP, TRUE)); + + _success = TRUE.equalsIgnoreCase(props.getProperty(SAVE_SUCCESSFUL_PROP, TRUE)); + + _threadName = TRUE.equalsIgnoreCase(props.getProperty(SAVE_THREAD_NAME_PROP, TRUE)); + + _bytes = TRUE.equalsIgnoreCase(props.getProperty(SAVE_BYTES_PROP, TRUE)); + + _url = TRUE.equalsIgnoreCase(props.getProperty(SAVE_URL_PROP, FALSE)); + + _fileName = TRUE.equalsIgnoreCase(props.getProperty(SAVE_FILENAME_PROP, FALSE)); + + _hostname = TRUE.equalsIgnoreCase(props.getProperty(SAVE_HOSTNAME_PROP, FALSE)); + + _time = TRUE.equalsIgnoreCase(props.getProperty(SAVE_TIME_PROP, TRUE)); + + _timeStampFormat = props.getProperty(TIME_STAMP_FORMAT_PROP, MILLISECONDS); + + _printMilliseconds = MILLISECONDS.equalsIgnoreCase(_timeStampFormat); + + // Prepare for a pretty date + if (!_printMilliseconds && !NONE.equalsIgnoreCase(_timeStampFormat) && (_timeStampFormat != null)) { + _formatter = new SimpleDateFormat(_timeStampFormat); + } else { + _formatter = null; + } + + _timestamp = !NONE.equalsIgnoreCase(_timeStampFormat);// reversed compare allows for null + + _saveAssertionResultsFailureMessage = TRUE.equalsIgnoreCase(props.getProperty( + ASSERTION_RESULTS_FAILURE_MESSAGE_PROP, FALSE)); + + String whichAssertionResults = props.getProperty(ASSERTION_RESULTS_PROP, NONE); + if (NONE.equals(whichAssertionResults)) { + _assertionsResultsToSave = SAVE_NO_ASSERTIONS; + } else if (FIRST.equals(whichAssertionResults)) { + _assertionsResultsToSave = SAVE_FIRST_ASSERTION; + } else if (ALL.equals(whichAssertionResults)) { + _assertionsResultsToSave = SAVE_ALL_ASSERTIONS; + } else { + _assertionsResultsToSave = 0; + } + + String howToSave = props.getProperty(OUTPUT_FORMAT_PROP, CSV); + + if (XML.equals(howToSave)) { + _xml = true; + } else { + if (!CSV.equals(howToSave)) { + log.warn(OUTPUT_FORMAT_PROP + " has unexepected value: '" + howToSave + "' - assuming 'csv' format"); + } + _xml = false; + } + + _threadCounts=TRUE.equalsIgnoreCase(props.getProperty(SAVE_THREAD_COUNTS, TRUE)); + + _sampleCount=TRUE.equalsIgnoreCase(props.getProperty(SAVE_SAMPLE_COUNT, FALSE)); + + _idleTime=TRUE.equalsIgnoreCase(props.getProperty(SAVE_IDLE_TIME, FALSE)); + } + + // Don't save this, as not settable via GUI + private String delimiter = _delimiter; + + // Don't save this - only needed for processing CSV headers currently + private transient int varCount = 0; + + private static final SampleSaveConfiguration _static = new SampleSaveConfiguration(); + + public int getVarCount() { // Only for use by CSVSaveService + return varCount; + } + + public void setVarCount(int varCount) { // Only for use by CSVSaveService + this.varCount = varCount; + } + + // Give access to initial configuration + public static SampleSaveConfiguration staticConfig() { + return _static; + } + + public SampleSaveConfiguration() { + } + + /** + * Alternate constructor for use by OldSaveService + * + * @param value initial setting for boolean fields used in Config dialogue + */ + public SampleSaveConfiguration(boolean value) { + assertions = value; + bytes = value; + code = value; + dataType = value; + encoding = value; + fieldNames = value; + fileName = value; + hostname = value; + label = value; + latency = value; + connectTime = value; + message = value; + printMilliseconds = _printMilliseconds;//is derived from properties only + requestHeaders = value; + responseData = value; + responseDataOnError = value; + responseHeaders = value; + samplerData = value; + saveAssertionResultsFailureMessage = value; + subresults = value; + success = value; + threadCounts = value; + sampleCount = value; + threadName = value; + time = value; + timestamp = value; + url = value; + xml = value; + } + + private Object readResolve(){ + formatter = _formatter; + return this; + } + + @Override + public Object clone() { + try { + SampleSaveConfiguration clone = (SampleSaveConfiguration)super.clone(); + if(this.formatter != null) { + clone.formatter = (SimpleDateFormat)this.formatter.clone(); + } + return clone; + } + catch(CloneNotSupportedException e) { + throw new RuntimeException("Should not happen",e); + } + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if((obj == null) || (obj.getClass() != this.getClass())) { + return false; + } + // We know we are comparing to another SampleSaveConfiguration + SampleSaveConfiguration s = (SampleSaveConfiguration)obj; + boolean primitiveValues = s.time == time && + s.latency == latency && + s.connectTime == connectTime && + s.timestamp == timestamp && + s.success == success && + s.label == label && + s.code == code && + s.message == message && + s.threadName == threadName && + s.dataType == dataType && + s.encoding == encoding && + s.assertions == assertions && + s.subresults == subresults && + s.responseData == responseData && + s.samplerData == samplerData && + s.xml == xml && + s.fieldNames == fieldNames && + s.responseHeaders == responseHeaders && + s.requestHeaders == requestHeaders && + s.assertionsResultsToSave == assertionsResultsToSave && + s.saveAssertionResultsFailureMessage == saveAssertionResultsFailureMessage && + s.printMilliseconds == printMilliseconds && + s.responseDataOnError == responseDataOnError && + s.url == url && + s.bytes == bytes && + s.fileName == fileName && + s.hostname == hostname && + s.sampleCount == sampleCount && + s.idleTime == idleTime && + s.threadCounts == threadCounts; + + boolean stringValues = false; + if(primitiveValues) { + stringValues = s.delimiter == delimiter || (delimiter != null && delimiter.equals(s.delimiter)); + } + boolean complexValues = false; + if(primitiveValues && stringValues) { + complexValues = s.formatter == formatter || (formatter != null && formatter.equals(s.formatter)); + } + + return primitiveValues && stringValues && complexValues; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + (time ? 1 : 0); + hash = 31 * hash + (latency ? 1 : 0); + hash = 31 * hash + (connectTime ? 1 : 0); + hash = 31 * hash + (timestamp ? 1 : 0); + hash = 31 * hash + (success ? 1 : 0); + hash = 31 * hash + (label ? 1 : 0); + hash = 31 * hash + (code ? 1 : 0); + hash = 31 * hash + (message ? 1 : 0); + hash = 31 * hash + (threadName ? 1 : 0); + hash = 31 * hash + (dataType ? 1 : 0); + hash = 31 * hash + (encoding ? 1 : 0); + hash = 31 * hash + (assertions ? 1 : 0); + hash = 31 * hash + (subresults ? 1 : 0); + hash = 31 * hash + (responseData ? 1 : 0); + hash = 31 * hash + (samplerData ? 1 : 0); + hash = 31 * hash + (xml ? 1 : 0); + hash = 31 * hash + (fieldNames ? 1 : 0); + hash = 31 * hash + (responseHeaders ? 1 : 0); + hash = 31 * hash + (requestHeaders ? 1 : 0); + hash = 31 * hash + assertionsResultsToSave; + hash = 31 * hash + (saveAssertionResultsFailureMessage ? 1 : 0); + hash = 31 * hash + (printMilliseconds ? 1 : 0); + hash = 31 * hash + (responseDataOnError ? 1 : 0); + hash = 31 * hash + (url ? 1 : 0); + hash = 31 * hash + (bytes ? 1 : 0); + hash = 31 * hash + (fileName ? 1 : 0); + hash = 31 * hash + (hostname ? 1 : 0); + hash = 31 * hash + (threadCounts ? 1 : 0); + hash = 31 * hash + (delimiter != null ? delimiter.hashCode() : 0); + hash = 31 * hash + (formatter != null ? formatter.hashCode() : 0); + hash = 31 * hash + (sampleCount ? 1 : 0); + hash = 31 * hash + (idleTime ? 1 : 0); + + return hash; + } + + ///////////////////// Start of standard save/set access methods ///////////////////// + + public boolean saveResponseHeaders() { + return responseHeaders; + } + + public void setResponseHeaders(boolean r) { + responseHeaders = r; + } + + public boolean saveRequestHeaders() { + return requestHeaders; + } + + public void setRequestHeaders(boolean r) { + requestHeaders = r; + } + + public boolean saveAssertions() { + return assertions; + } + + public void setAssertions(boolean assertions) { + this.assertions = assertions; + } + + public boolean saveCode() { + return code; + } + + public void setCode(boolean code) { + this.code = code; + } + + public boolean saveDataType() { + return dataType; + } + + public void setDataType(boolean dataType) { + this.dataType = dataType; + } + + public boolean saveEncoding() { + return encoding; + } + + public void setEncoding(boolean encoding) { + this.encoding = encoding; + } + + public boolean saveLabel() { + return label; + } + + public void setLabel(boolean label) { + this.label = label; + } + + public boolean saveLatency() { + return latency; + } + + public void setLatency(boolean latency) { + this.latency = latency; + } + + public boolean saveConnectTime() { + return connectTime; + } + + public void setConnectTime(boolean connectTime) { + this.connectTime = connectTime; + } + + public boolean saveMessage() { + return message; + } + + public void setMessage(boolean message) { + this.message = message; + } + + public boolean saveResponseData(SampleResult res) { + return responseData || TestPlan.getFunctionalMode() || (responseDataOnError && !res.isSuccessful()); + } + + public boolean saveResponseData() + { + return responseData; + } + + public void setResponseData(boolean responseData) { + this.responseData = responseData; + } + + public boolean saveSamplerData(SampleResult res) { + return samplerData || TestPlan.getFunctionalMode() // as per 2.0 branch + || (responseDataOnError && !res.isSuccessful()); + } + + public boolean saveSamplerData() + { + return samplerData; + } + + public void setSamplerData(boolean samplerData) { + this.samplerData = samplerData; + } + + public boolean saveSubresults() { + return subresults; + } + + public void setSubresults(boolean subresults) { + this.subresults = subresults; + } + + public boolean saveSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean saveThreadName() { + return threadName; + } + + public void setThreadName(boolean threadName) { + this.threadName = threadName; + } + + public boolean saveTime() { + return time; + } + + public void setTime(boolean time) { + this.time = time; + } + + public boolean saveTimestamp() { + return timestamp; + } + + public void setTimestamp(boolean timestamp) { + this.timestamp = timestamp; + } + + public boolean saveAsXml() { + return xml; + } + + public void setAsXml(boolean xml) { + this.xml = xml; + } + + public boolean saveFieldNames() { + return fieldNames; + } + + public void setFieldNames(boolean printFieldNames) { + this.fieldNames = printFieldNames; + } + + public boolean saveUrl() { + return url; + } + + public void setUrl(boolean save) { + this.url = save; + } + + public boolean saveBytes() { + return bytes; + } + + public void setBytes(boolean save) { + this.bytes = save; + } + + public boolean saveFileName() { + return fileName; + } + + public void setFileName(boolean save) { + this.fileName = save; + } + + public boolean saveAssertionResultsFailureMessage() { + return saveAssertionResultsFailureMessage; + } + + public void setAssertionResultsFailureMessage(boolean b) { + saveAssertionResultsFailureMessage = b; + } + + public boolean saveThreadCounts() { + return threadCounts; + } + + public void setThreadCounts(boolean save) { + this.threadCounts = save; + } + + public boolean saveSampleCount() { + return sampleCount; + } + + public void setSampleCount(boolean save) { + this.sampleCount = save; + } + + ///////////////// End of standard field accessors ///////////////////// + + /** + * Only intended for use by OldSaveService (and test cases) + * + * @param fmt + * format of the date to be saved. If null + * milliseconds since epoch will be printed + */ + public void setFormatter(DateFormat fmt){ + printMilliseconds = (fmt == null); // maintain relationship + formatter = fmt; + } + + public boolean printMilliseconds() { + return printMilliseconds; + } + + public DateFormat formatter() { + return formatter; + } + + public int assertionsResultsToSave() { + return assertionsResultsToSave; + } + + public String getDelimiter() { + return delimiter; + } + + public String getXmlPi() { + return JMeterUtils.getJMeterProperties().getProperty(XML_PI, ""); // Defaults to empty; + } + + // Used by old Save service + public void setDelimiter(String delim) { + delimiter=delim; + } + + // Used by SampleSaveConfigurationConverter.unmarshall() + public void setDefaultDelimiter() { + delimiter=_delimiter; + } + + // Used by SampleSaveConfigurationConverter.unmarshall() + public void setDefaultTimeStampFormat() { + printMilliseconds=_printMilliseconds; + formatter=_formatter; + } + + public boolean saveHostname(){ + return hostname; + } + + public void setHostname(boolean save){ + hostname = save; + } + + public boolean saveIdleTime() { + return idleTime; + } + + public void setIdleTime(boolean save) { + idleTime = save; + } +} diff --git a/src/core/org/apache/jmeter/samplers/SampleSender.java b/src/core/org/apache/jmeter/samplers/SampleSender.java new file mode 100644 index 00000000000..cfd345de139 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/SampleSender.java @@ -0,0 +1,50 @@ +/* + * 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.samplers; + +/** + * This interface is used to define the methods that need to be intercepted + * by the SampleSender wrapper classes processed by the RemoteListenerWrapper. + */ +public interface SampleSender { + /** + * The test ended (probably not used; client-server mode needs a host) + */ + void testEnded(); + + /** + * The test ended. + * + * This will be called from the engine thread. + * + * @param host + * the host that the test ended on. + */ + void testEnded(String host); + + /** + * A sample occurred. + * + * This method will be called from the sampler thread. + * + * @param e + * a Sample Event + */ + void sampleOccurred(SampleEvent e); +} diff --git a/src/core/org/apache/jmeter/samplers/SampleSenderFactory.java b/src/core/org/apache/jmeter/samplers/SampleSenderFactory.java new file mode 100644 index 00000000000..b5b4e7d8ddd --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/SampleSenderFactory.java @@ -0,0 +1,110 @@ +/* + * 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.samplers; + +import java.lang.reflect.Constructor; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class SampleSenderFactory { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String MODE_STANDARD = "Standard"; // $NON-NLS-1$ + + private static final String MODE_HOLD = "Hold"; // $NON-NLS-1$ + + private static final String MODE_BATCH = "Batch"; // $NON-NLS-1$ + + private static final String MODE_STATISTICAL = "Statistical"; // $NON-NLS-1$ + + private static final String MODE_STRIPPED = "Stripped"; // $NON-NLS-1$ + + private static final String MODE_STRIPPED_BATCH = "StrippedBatch"; // $NON-NLS-1$ + + private static final String MODE_ASYNCH = "Asynch"; // $NON-NLS-1$ + + private static final String MODE_STRIPPED_ASYNCH = "StrippedAsynch"; // $NON-NLS-1$ + + private static final String MODE_DISKSTORE = "DiskStore"; // $NON-NLS-1$ + + private static final String MODE_STRIPPED_DISKSTORE = "StrippedDiskStore"; // $NON-NLS-1$ + + /** + * Checks for the Jmeter property mode and returns the required class. + * + * @param listener + * @return the appropriate class. Standard Jmeter functionality, + * hold_samples until end of test or batch samples. + */ + static SampleSender getInstance(RemoteSampleListener listener) { + // Support original property name + final boolean holdSamples = JMeterUtils.getPropDefault("hold_samples", false); // $NON-NLS-1$ + + // Extended property name + final String type = JMeterUtils.getPropDefault("mode", MODE_STRIPPED_BATCH); // $NON-NLS-1$ + + if (holdSamples || type.equalsIgnoreCase(MODE_HOLD)) { + if(holdSamples) { + log.warn("Property hold_samples is deprecated and will be removed in upcomping version, use mode="+MODE_HOLD +" instead"); + } + HoldSampleSender h = new HoldSampleSender(listener); + return h; + } else if (type.equalsIgnoreCase(MODE_BATCH)) { + BatchSampleSender b = new BatchSampleSender(listener); + return b; + } else if(type.equalsIgnoreCase(MODE_STRIPPED_BATCH)) { + return new DataStrippingSampleSender(new BatchSampleSender(listener)); + } else if (type.equalsIgnoreCase(MODE_STATISTICAL)) { + StatisticalSampleSender s = new StatisticalSampleSender(listener); + return s; + } else if (type.equalsIgnoreCase(MODE_STANDARD)) { + StandardSampleSender s = new StandardSampleSender(listener); + return s; + } else if(type.equalsIgnoreCase(MODE_STRIPPED)){ + return new DataStrippingSampleSender(listener); + } else if(type.equalsIgnoreCase(MODE_ASYNCH)){ + return new AsynchSampleSender(listener); + } else if(type.equalsIgnoreCase(MODE_STRIPPED_ASYNCH)) { + return new DataStrippingSampleSender(new AsynchSampleSender(listener)); + } else if(type.equalsIgnoreCase(MODE_DISKSTORE)){ + return new DiskStoreSampleSender(listener); + } else if(type.equalsIgnoreCase(MODE_STRIPPED_DISKSTORE)){ + return new DataStrippingSampleSender(new DiskStoreSampleSender(listener)); + } else { + // should be a user provided class name + SampleSender s = null; + try { + Class clazz = Class.forName(type); + Constructor cons = clazz.getConstructor(new Class[] {RemoteSampleListener.class}); + s = (SampleSender) cons.newInstance(new Object [] {listener}); + } catch (Exception e) { + // houston we have a problem !! + log.error("Unable to create a sample sender from class:'"+type+"', search for mode property in jmeter.properties for correct configuration options"); + throw new IllegalArgumentException("Unable to create a sample sender from mode or class:'" + +type+"', search for mode property in jmeter.properties for correct configuration options, message:"+e.getMessage(), e); + } + + return s; + } + + } +} diff --git a/src/core/org/apache/jmeter/samplers/Sampler.java b/src/core/org/apache/jmeter/samplers/Sampler.java new file mode 100644 index 00000000000..99a425d034e --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/Sampler.java @@ -0,0 +1,41 @@ +/* + * 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.samplers; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.TestElement; + +/** + * Classes which are able to generate information about an entry should + * implement this interface. + * + * @version $Revision$ + */ +public interface Sampler extends Serializable, TestElement { + /** + * Obtains statistics about the given Entry, and packages the information + * into a SampleResult. + * + * @param e + * the Entry (TODO seems to be unused) + * @return information about the sample + */ + SampleResult sample(Entry e); +} diff --git a/src/core/org/apache/jmeter/samplers/StandardSampleSender.java b/src/core/org/apache/jmeter/samplers/StandardSampleSender.java new file mode 100644 index 00000000000..3af8cd9252b --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/StandardSampleSender.java @@ -0,0 +1,87 @@ +/* + * 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.samplers; + +import org.apache.log.Logger; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; + +import java.rmi.RemoteException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** + * Default behaviour for remote testing. + */ + +public class StandardSampleSender extends AbstractSampleSender implements Serializable { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final RemoteSampleListener listener; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public StandardSampleSender(){ + this.listener = null; + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + + StandardSampleSender(RemoteSampleListener listener) { + this.listener = listener; + log.info("Using StandardSampleSender for this test run"); + } + + @Override + public void testEnded(String host) { + log.info("Test Ended on " + host); + try { + listener.testEnded(host); + } catch (RemoteException ex) { + log.warn("testEnded(host)"+ex); + } + } + + @Override + public void sampleOccurred(SampleEvent e) { + try { + listener.sampleOccurred(e); + } catch (RemoteException err) { + if (err.getCause() instanceof java.net.ConnectException){ + throw new JMeterError("Could not return sample",err); + } + log.error("sampleOccurred", err); + } + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * + * @return this + * @throws ObjectStreamException + * never + */ + private Object readResolve() throws ObjectStreamException{ + log.info("Using StandardSampleSender for this test run"); + return this; + } +} diff --git a/src/core/org/apache/jmeter/samplers/StatisticalSampleResult.java b/src/core/org/apache/jmeter/samplers/StatisticalSampleResult.java new file mode 100644 index 00000000000..dc7da6b3a24 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/StatisticalSampleResult.java @@ -0,0 +1,149 @@ +/* + * 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.samplers; + +import java.io.Serializable; + +/** + * Aggregates sample results for use by the Statistical remote batch mode. + * Samples are aggregated by the key defined by getKey(). + * TODO: merge error count into parent class? + */ +public class StatisticalSampleResult extends SampleResult implements + Serializable { + + private static final long serialVersionUID = 240L; + + private int errorCount; + + // Need to maintain our own elapsed timer to ensure more accurate aggregation + private long elapsed; + + public StatisticalSampleResult(){// May be called by XStream + } + + /** + * Allow OldSaveService to generate a suitable result when sample/error counts have been saved. + * + * @deprecated Needs to be replaced when multiple sample results are sorted out + * + * @param stamp this may be a start time or an end time (both in milliseconds) + * @param elapsed time in milliseconds + */ + @Deprecated + public StatisticalSampleResult(long stamp, long elapsed) { + super(stamp, elapsed); + this.elapsed = elapsed; + } + + /** + * Create a statistical sample result from an ordinary sample result. + * + * @param res the sample result + */ + public StatisticalSampleResult(SampleResult res) { + // Copy data that is shared between samples (i.e. the key items): + setSampleLabel(res.getSampleLabel()); + + setThreadName(res.getThreadName()); + + setSuccessful(true); // Assume result is OK + setSampleCount(0); // because we add the sample count in later + elapsed = 0; + } + + /** + * Create a statistical sample result from an ordinary sample result. + * + * @param res the sample result + * @param unused no longer used + * @deprecated no longer necessary; use {@link #StatisticalSampleResult(SampleResult)} instead + */ + @Deprecated + public StatisticalSampleResult(SampleResult res, boolean unused) { + this(res); + } + + public void add(SampleResult res) { + // Add Sample Counter + setSampleCount(getSampleCount() + res.getSampleCount()); + + setBytes(getBytes() + res.getBytes()); + + // Add Error Counter + if (!res.isSuccessful()) { + errorCount++; + this.setSuccessful(false); + } + + // Set start/end times + if (getStartTime()==0){ // Bug 40954 - ensure start time gets started! + this.setStartTime(res.getStartTime()); + } else { + this.setStartTime(Math.min(getStartTime(), res.getStartTime())); + } + this.setEndTime(Math.max(getEndTime(), res.getEndTime())); + + setLatency(getLatency()+ res.getLatency()); + setConnectTime(getConnectTime()+ res.getConnectTime()); + + elapsed += res.getTime(); + } + + @Override + public long getTime() { + return elapsed; + } + + @Override + public long getTimeStamp() { + return getEndTime(); + } + + @Override + public int getErrorCount() {// Overrides SampleResult + return errorCount; + } + + @Override + public void setErrorCount(int e) {// for reading CSV files + errorCount = e; + } + + /** + * Generates the key to be used for aggregating samples as follows:
+ * sampleLabel "-" [threadName|threadGroup] + *

+ * N.B. the key should agree with the fixed items that are saved in the sample. + * + * @param event sample event whose key is to be calculated + * @param keyOnThreadName true if key should use thread name, otherwise use thread group + * @return the key to use for aggregating samples + */ + public static String getKey(SampleEvent event, boolean keyOnThreadName) { + StringBuilder sb = new StringBuilder(80); + sb.append(event.getResult().getSampleLabel()); + if (keyOnThreadName){ + sb.append('-').append(event.getResult().getThreadName()); + } else { + sb.append('-').append(event.getThreadGroup()); + } + return sb.toString(); + } +} diff --git a/src/core/org/apache/jmeter/samplers/StatisticalSampleSender.java b/src/core/org/apache/jmeter/samplers/StatisticalSampleSender.java new file mode 100644 index 00000000000..f3153e92e14 --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/StatisticalSampleSender.java @@ -0,0 +1,226 @@ +/* + * 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.samplers; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implements batch reporting for remote testing. + * + */ +public class StatisticalSampleSender extends AbstractSampleSender implements Serializable { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int DEFAULT_NUM_SAMPLE_THRESHOLD = 100; + + private static final long DEFAULT_TIME_THRESHOLD = 60000L; + + // Static fields are set by the server when the class is constructed + + private static final int NUM_SAMPLES_THRESHOLD = JMeterUtils.getPropDefault( + "num_sample_threshold", DEFAULT_NUM_SAMPLE_THRESHOLD); + + private static final long TIME_THRESHOLD_MS = JMeterUtils.getPropDefault("time_threshold", + DEFAULT_TIME_THRESHOLD); + + // should the samples be aggregated on thread name or thread group (default) ? + private static boolean KEY_ON_THREADNAME = JMeterUtils.getPropDefault("key_on_threadname", false); + + // Instance fields are constructed by the client when the instance is create in the test plan + // and the field values are then transferred to the server copy by RMI serialisation/deserialisation + + private final int clientConfiguredNumSamplesThreshold = JMeterUtils.getPropDefault( + "num_sample_threshold", DEFAULT_NUM_SAMPLE_THRESHOLD); + + private final long clientConfiguredTimeThresholdMs = JMeterUtils.getPropDefault("time_threshold", + DEFAULT_TIME_THRESHOLD); + + // should the samples be aggregated on thread name or thread group (default) ? + private final boolean clientConfiguredKeyOnThreadName = JMeterUtils.getPropDefault("key_on_threadname", false); + + private final RemoteSampleListener listener; + + private final List sampleStore = new ArrayList(); + + //@GuardedBy("sampleStore") TODO perhaps use ConcurrentHashMap ? + private final Map sampleTable = new HashMap(); + + // Settings; readResolve sets these from the server/client values as appropriate + // TODO would be nice to make these final; not 100% sure volatile is needed as not changed after creation + private transient volatile int numSamplesThreshold; + + private transient volatile long timeThresholdMs; + + private transient volatile boolean keyOnThreadName; + + + // variables maintained by server code + // @GuardedBy("sampleStore") + private transient int sampleCount; // maintain separate count of samples for speed + + private transient long batchSendTime = -1; // @GuardedBy("sampleStore") + + /** + * @deprecated only for use by test code + */ + @Deprecated + public StatisticalSampleSender(){ + this(null); + log.warn("Constructor only intended for use in testing"); + } + + /** + * Constructor, only called by client code. + * + * @param listener that the List of sample events will be sent to. + */ + StatisticalSampleSender(RemoteSampleListener listener) { + this.listener = listener; + if (isClientConfigured()) { + log.info("Using StatisticalSampleSender (client settings) for this run." + + " Thresholds: num=" + clientConfiguredNumSamplesThreshold + + ", time=" + clientConfiguredTimeThresholdMs + + ". Key uses ThreadName: " + clientConfiguredKeyOnThreadName); + } else { + log.info("Using StatisticalSampleSender (server settings) for this run."); + } + } + + /** + * Checks if any sample events are still present in the sampleStore and + * sends them to the listener. Informs the listener that the test ended. + * + * @param host the hostname that the test has ended on. + */ + @Override + public void testEnded(String host) { + log.info("Test Ended on " + host); + try { + if (sampleStore.size() != 0) { + sendBatch(); + } + listener.testEnded(host); + } catch (RemoteException err) { + log.warn("testEnded(hostname)", err); + } + } + + /** + * Stores sample events until either a time or sample threshold is + * breached. Both thresholds are reset if one fires. If only one threshold + * is set it becomes the only value checked against. When a threshold is + * breached the list of sample events is sent to a listener where the event + * are fired locally. + * + * @param e a Sample Event + */ + @Override + public void sampleOccurred(SampleEvent e) { + synchronized (sampleStore) { + // Locate the statistical sample collector + String key = StatisticalSampleResult.getKey(e, keyOnThreadName); + StatisticalSampleResult statResult = sampleTable.get(key); + if (statResult == null) { + statResult = new StatisticalSampleResult(e.getResult()); + // store the new statistical result collector + sampleTable.put(key, statResult); + // add a new wrapper sampleevent + sampleStore + .add(new SampleEvent(statResult, e.getThreadGroup())); + } + statResult.add(e.getResult()); + sampleCount++; + boolean sendNow = false; + if (numSamplesThreshold != -1) { + if (sampleCount >= numSamplesThreshold) { + sendNow = true; + } + } + + long now = 0; + if (timeThresholdMs != -1) { + now = System.currentTimeMillis(); + // Checking for and creating initial timestamp to check against + if (batchSendTime == -1) { + this.batchSendTime = now + timeThresholdMs; + } + if (batchSendTime < now) { + sendNow = true; + } + } + if (sendNow) { + try { + if (log.isDebugEnabled()) { + log.debug("Firing sample"); + } + sendBatch(); + if (timeThresholdMs != -1) { + this.batchSendTime = now + timeThresholdMs; + } + } catch (RemoteException err) { + log.warn("sampleOccurred", err); + } + } + } // synchronized(sampleStore) + } + + private void sendBatch() throws RemoteException { + if (sampleStore.size() > 0) { + listener.processBatch(sampleStore); + sampleStore.clear(); + sampleTable.clear(); + sampleCount = 0; + } + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * @return this + * @throws ObjectStreamException never + */ + private Object readResolve() throws ObjectStreamException{ + if (isClientConfigured()) { + numSamplesThreshold = clientConfiguredNumSamplesThreshold; + timeThresholdMs = clientConfiguredTimeThresholdMs; + keyOnThreadName = clientConfiguredKeyOnThreadName; + } else { + numSamplesThreshold = NUM_SAMPLES_THRESHOLD; + timeThresholdMs = TIME_THRESHOLD_MS; + keyOnThreadName = KEY_ON_THREADNAME; + } + log.info("Using StatisticalSampleSender for this run." + + (isClientConfigured() ? " Client config: " : " Server config: ") + + " Thresholds: num=" + numSamplesThreshold + + ", time=" + timeThresholdMs + + ". Key uses ThreadName: " + keyOnThreadName); + return this; + } +} diff --git a/src/core/org/apache/jmeter/samplers/gui/AbstractSamplerGui.java b/src/core/org/apache/jmeter/samplers/gui/AbstractSamplerGui.java new file mode 100644 index 00000000000..b61ea31d48a --- /dev/null +++ b/src/core/org/apache/jmeter/samplers/gui/AbstractSamplerGui.java @@ -0,0 +1,65 @@ +/* + * 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.samplers.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage samplers. + * + */ +public abstract class AbstractSamplerGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most sampler + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + @Override + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultSamplerMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#SAMPLERS}, which is + * appropriate for most sampler components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.SAMPLERS }); + } +} diff --git a/src/core/org/apache/jmeter/save/CSVSaveService.java b/src/core/org/apache/jmeter/save/CSVSaveService.java new file mode 100644 index 00000000000..13f32b67574 --- /dev/null +++ b/src/core/org/apache/jmeter/save/CSVSaveService.java @@ -0,0 +1,1157 @@ +/* + * 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.save; + +import java.io.BufferedReader; +import java.io.CharArrayWriter; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.swing.table.DefaultTableModel; + +import org.apache.commons.collections.map.LinkedMap; +import org.apache.commons.lang3.CharUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.samplers.StatisticalSampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * This class provides a means for saving/reading test results as CSV files. + */ +// For unit tests, @see TestCSVSaveService +public final class CSVSaveService { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // --------------------------------------------------------------------- + // XML RESULT FILE CONSTANTS AND FIELD NAME CONSTANTS + // --------------------------------------------------------------------- + + private static final String DATA_TYPE = "dataType"; // $NON-NLS-1$ + private static final String FAILURE_MESSAGE = "failureMessage"; // $NON-NLS-1$ + private static final String LABEL = "label"; // $NON-NLS-1$ + private static final String RESPONSE_CODE = "responseCode"; // $NON-NLS-1$ + private static final String RESPONSE_MESSAGE = "responseMessage"; // $NON-NLS-1$ + private static final String SUCCESSFUL = "success"; // $NON-NLS-1$ + private static final String THREAD_NAME = "threadName"; // $NON-NLS-1$ + private static final String TIME_STAMP = "timeStamp"; // $NON-NLS-1$ + + // --------------------------------------------------------------------- + // ADDITIONAL CSV RESULT FILE CONSTANTS AND FIELD NAME CONSTANTS + // --------------------------------------------------------------------- + + private static final String CSV_ELAPSED = "elapsed"; // $NON-NLS-1$ + private static final String CSV_BYTES = "bytes"; // $NON-NLS-1$ + private static final String CSV_THREAD_COUNT1 = "grpThreads"; // $NON-NLS-1$ + private static final String CSV_THREAD_COUNT2 = "allThreads"; // $NON-NLS-1$ + private static final String CSV_SAMPLE_COUNT = "SampleCount"; // $NON-NLS-1$ + private static final String CSV_ERROR_COUNT = "ErrorCount"; // $NON-NLS-1$ + private static final String CSV_URL = "URL"; // $NON-NLS-1$ + private static final String CSV_FILENAME = "Filename"; // $NON-NLS-1$ + private static final String CSV_LATENCY = "Latency"; // $NON-NLS-1$ + private static final String CSV_CONNECT_TIME = "Connect"; // $NON-NLS-1$ + private static final String CSV_ENCODING = "Encoding"; // $NON-NLS-1$ + private static final String CSV_HOSTNAME = "Hostname"; // $NON-NLS-1$ + private static final String CSV_IDLETIME = "IdleTime"; // $NON-NLS-1$ + + // Used to enclose variable name labels, to distinguish from any of the + // above labels + private static final String VARIABLE_NAME_QUOTE_CHAR = "\""; // $NON-NLS-1$ + + // Initial config from properties + static private final SampleSaveConfiguration _saveConfig = SampleSaveConfiguration + .staticConfig(); + + // Date formats to try if the time format does not parse as milliseconds + private static final String DATE_FORMAT_STRINGS[] = { + "yyyy/MM/dd HH:mm:ss.SSS", // $NON-NLS-1$ + "yyyy/MM/dd HH:mm:ss", // $NON-NLS-1$ + "yyyy-MM-dd HH:mm:ss.SSS", // $NON-NLS-1$ + "yyyy-MM-dd HH:mm:ss", // $NON-NLS-1$ + + "MM/dd/yy HH:mm:ss" // $NON-NLS-1$ (for compatibility, this is the original default) + }; + + private static final String LINE_SEP = System.getProperty("line.separator"); // $NON-NLS-1$ + + /** + * Private constructor to prevent instantiation. + */ + private CSVSaveService() { + } + + /** + * Read Samples from a file; handles quoted strings. + * + * @param filename + * input file + * @param visualizer + * where to send the results + * @param resultCollector + * the parent collector + * @throws IOException + * when the file referenced by filename can't be + * read correctly + */ + public static void processSamples(String filename, Visualizer visualizer, + ResultCollector resultCollector) throws IOException { + BufferedReader dataReader = null; + final boolean errorsOnly = resultCollector.isErrorLogging(); + final boolean successOnly = resultCollector.isSuccessOnlyLogging(); + try { + dataReader = new BufferedReader(new InputStreamReader( + new FileInputStream(filename), SaveService.getFileEncoding("UTF-8"))); + dataReader.mark(400);// Enough to read the header column names + // Get the first line, and see if it is the header + String line = dataReader.readLine(); + if (line == null) { + throw new IOException(filename + ": unable to read header line"); + } + long lineNumber = 1; + SampleSaveConfiguration saveConfig = CSVSaveService + .getSampleSaveConfiguration(line, filename); + if (saveConfig == null) {// not a valid header + log.info(filename + + " does not appear to have a valid header. Using default configuration."); + saveConfig = (SampleSaveConfiguration) resultCollector + .getSaveConfig().clone(); // may change the format later + dataReader.reset(); // restart from beginning + lineNumber = 0; + } + String[] parts; + final char delim = saveConfig.getDelimiter().charAt(0); + // TODO: does it matter that an empty line will terminate the loop? + // CSV output files should never contain empty lines, so probably + // not + // If so, then need to check whether the reader is at EOF + while ((parts = csvReadFile(dataReader, delim)).length != 0) { + lineNumber++; + SampleEvent event = CSVSaveService.makeResultFromDelimitedString(parts, saveConfig, lineNumber); + if (event != null) { + final SampleResult result = event.getResult(); + if (ResultCollector.isSampleWanted(result.isSuccessful(), + errorsOnly, successOnly)) { + visualizer.add(result); + } + } + } + } finally { + JOrphanUtils.closeQuietly(dataReader); + } + } + + /** + * Make a SampleResult given a set of tokens + * + * @param parts + * tokens parsed from the input + * @param saveConfig + * the save configuration (may be updated) + * @param lineNumber the line number (for error reporting) + * @return the sample result + * + * @throws JMeterError + */ + private static SampleEvent makeResultFromDelimitedString( + final String[] parts, + final SampleSaveConfiguration saveConfig, // may be updated + final long lineNumber) { + + SampleResult result = null; + String hostname = "";// $NON-NLS-1$ + long timeStamp = 0; + long elapsed = 0; + String text = null; + String field = null; // Save the name for error reporting + int i = 0; + try { + if (saveConfig.saveTimestamp()) { + field = TIME_STAMP; + text = parts[i++]; + if (saveConfig.printMilliseconds()) { + try { + timeStamp = Long.parseLong(text); // see if this works + } catch (NumberFormatException e) { // it did not, let's try some other formats + log.warn(e.toString()); + boolean foundMatch = false; + for(String fmt : DATE_FORMAT_STRINGS) { + SimpleDateFormat dateFormat = new SimpleDateFormat(fmt); + dateFormat.setLenient(false); + try { + Date stamp = dateFormat.parse(text); + timeStamp = stamp.getTime(); + // method is only ever called from one thread at a time + // so it's OK to use a static DateFormat + log.warn("Setting date format to: " + fmt); + saveConfig.setFormatter(dateFormat); + foundMatch = true; + break; + } catch (ParseException e1) { + log.info(text+" did not match "+fmt); + } + } + if (!foundMatch) { + throw new ParseException("No date-time format found matching "+text,-1); + } + } + } else if (saveConfig.formatter() != null) { + Date stamp = saveConfig.formatter().parse(text); + timeStamp = stamp.getTime(); + } else { // can this happen? + final String msg = "Unknown timestamp format"; + log.warn(msg); + throw new JMeterError(msg); + } + } + + if (saveConfig.saveTime()) { + field = CSV_ELAPSED; + text = parts[i++]; + elapsed = Long.parseLong(text); + } + + if (saveConfig.saveSampleCount()) { + result = new StatisticalSampleResult(timeStamp, elapsed); + } else { + result = new SampleResult(timeStamp, elapsed); + } + + if (saveConfig.saveLabel()) { + field = LABEL; + text = parts[i++]; + result.setSampleLabel(text); + } + if (saveConfig.saveCode()) { + field = RESPONSE_CODE; + text = parts[i++]; + result.setResponseCode(text); + } + + if (saveConfig.saveMessage()) { + field = RESPONSE_MESSAGE; + text = parts[i++]; + result.setResponseMessage(text); + } + + if (saveConfig.saveThreadName()) { + field = THREAD_NAME; + text = parts[i++]; + result.setThreadName(text); + } + + if (saveConfig.saveDataType()) { + field = DATA_TYPE; + text = parts[i++]; + result.setDataType(text); + } + + if (saveConfig.saveSuccess()) { + field = SUCCESSFUL; + text = parts[i++]; + result.setSuccessful(Boolean.valueOf(text).booleanValue()); + } + + if (saveConfig.saveAssertionResultsFailureMessage()) { + i++; + // TODO - should this be restored? + } + + if (saveConfig.saveBytes()) { + field = CSV_BYTES; + text = parts[i++]; + result.setBytes(Integer.parseInt(text)); + } + + if (saveConfig.saveThreadCounts()) { + field = CSV_THREAD_COUNT1; + text = parts[i++]; + result.setGroupThreads(Integer.parseInt(text)); + + field = CSV_THREAD_COUNT2; + text = parts[i++]; + result.setAllThreads(Integer.parseInt(text)); + } + + if (saveConfig.saveUrl()) { + i++; + // TODO: should this be restored? + } + + if (saveConfig.saveFileName()) { + field = CSV_FILENAME; + text = parts[i++]; + result.setResultFileName(text); + } + if (saveConfig.saveLatency()) { + field = CSV_LATENCY; + text = parts[i++]; + result.setLatency(Long.parseLong(text)); + } + + if (saveConfig.saveEncoding()) { + field = CSV_ENCODING; + text = parts[i++]; + result.setEncodingAndType(text); + } + + if (saveConfig.saveSampleCount()) { + field = CSV_SAMPLE_COUNT; + text = parts[i++]; + result.setSampleCount(Integer.parseInt(text)); + field = CSV_ERROR_COUNT; + text = parts[i++]; + result.setErrorCount(Integer.parseInt(text)); + } + + if (saveConfig.saveHostname()) { + field = CSV_HOSTNAME; + hostname = parts[i++]; + } + + if (saveConfig.saveIdleTime()) { + field = CSV_IDLETIME; + text = parts[i++]; + result.setIdleTime(Long.parseLong(text)); + } + if (saveConfig.saveConnectTime()) { + field = CSV_CONNECT_TIME; + text = parts[i++]; + result.setConnectTime(Long.parseLong(text)); + } + + if (i + saveConfig.getVarCount() < parts.length) { + log.warn("Line: " + lineNumber + ". Found " + parts.length + + " fields, expected " + i + + ". Extra fields have been ignored."); + } + + } catch (NumberFormatException e) { + log.warn("Error parsing field '" + field + "' at line " + + lineNumber + " " + e); + throw new JMeterError(e); + } catch (ParseException e) { + log.warn("Error parsing field '" + field + "' at line " + + lineNumber + " " + e); + throw new JMeterError(e); + } catch (ArrayIndexOutOfBoundsException e) { + log.warn("Insufficient columns to parse field '" + field + + "' at line " + lineNumber); + throw new JMeterError(e); + } + return new SampleEvent(result, "", hostname); + } + + /** + * Generates the field names for the output file + * + * @return the field names as a string + */ + public static String printableFieldNamesToString() { + return printableFieldNamesToString(_saveConfig); + } + + /** + * Generates the field names for the output file + * + * @param saveConfig + * the configuration of what is to be saved + * @return the field names as a string + */ + public static String printableFieldNamesToString( + SampleSaveConfiguration saveConfig) { + StringBuilder text = new StringBuilder(); + String delim = saveConfig.getDelimiter(); + + if (saveConfig.saveTimestamp()) { + text.append(TIME_STAMP); + text.append(delim); + } + + if (saveConfig.saveTime()) { + text.append(CSV_ELAPSED); + text.append(delim); + } + + if (saveConfig.saveLabel()) { + text.append(LABEL); + text.append(delim); + } + + if (saveConfig.saveCode()) { + text.append(RESPONSE_CODE); + text.append(delim); + } + + if (saveConfig.saveMessage()) { + text.append(RESPONSE_MESSAGE); + text.append(delim); + } + + if (saveConfig.saveThreadName()) { + text.append(THREAD_NAME); + text.append(delim); + } + + if (saveConfig.saveDataType()) { + text.append(DATA_TYPE); + text.append(delim); + } + + if (saveConfig.saveSuccess()) { + text.append(SUCCESSFUL); + text.append(delim); + } + + if (saveConfig.saveAssertionResultsFailureMessage()) { + text.append(FAILURE_MESSAGE); + text.append(delim); + } + + if (saveConfig.saveBytes()) { + text.append(CSV_BYTES); + text.append(delim); + } + + if (saveConfig.saveThreadCounts()) { + text.append(CSV_THREAD_COUNT1); + text.append(delim); + text.append(CSV_THREAD_COUNT2); + text.append(delim); + } + + if (saveConfig.saveUrl()) { + text.append(CSV_URL); + text.append(delim); + } + + if (saveConfig.saveFileName()) { + text.append(CSV_FILENAME); + text.append(delim); + } + + if (saveConfig.saveLatency()) { + text.append(CSV_LATENCY); + text.append(delim); + } + + if (saveConfig.saveEncoding()) { + text.append(CSV_ENCODING); + text.append(delim); + } + + if (saveConfig.saveSampleCount()) { + text.append(CSV_SAMPLE_COUNT); + text.append(delim); + text.append(CSV_ERROR_COUNT); + text.append(delim); + } + + if (saveConfig.saveHostname()) { + text.append(CSV_HOSTNAME); + text.append(delim); + } + + if (saveConfig.saveIdleTime()) { + text.append(CSV_IDLETIME); + text.append(delim); + } + + if (saveConfig.saveConnectTime()) { + text.append(CSV_CONNECT_TIME); + text.append(delim); + } + + for (int i = 0; i < SampleEvent.getVarCount(); i++) { + text.append(VARIABLE_NAME_QUOTE_CHAR); + text.append(SampleEvent.getVarName(i)); + text.append(VARIABLE_NAME_QUOTE_CHAR); + text.append(delim); + } + + String resultString = null; + int size = text.length(); + int delSize = delim.length(); + + // Strip off the trailing delimiter + if (size >= delSize) { + resultString = text.substring(0, size - delSize); + } else { + resultString = text.toString(); + } + return resultString; + } + + // Map header names to set() methods + private static final LinkedMap headerLabelMethods = new LinkedMap(); + + // These entries must be in the same order as columns are saved/restored. + + static { + headerLabelMethods.put(TIME_STAMP, new Functor("setTimestamp")); + headerLabelMethods.put(CSV_ELAPSED, new Functor("setTime")); + headerLabelMethods.put(LABEL, new Functor("setLabel")); + headerLabelMethods.put(RESPONSE_CODE, new Functor("setCode")); + headerLabelMethods.put(RESPONSE_MESSAGE, new Functor("setMessage")); + headerLabelMethods.put(THREAD_NAME, new Functor("setThreadName")); + headerLabelMethods.put(DATA_TYPE, new Functor("setDataType")); + headerLabelMethods.put(SUCCESSFUL, new Functor("setSuccess")); + headerLabelMethods.put(FAILURE_MESSAGE, new Functor( + "setAssertionResultsFailureMessage")); + headerLabelMethods.put(CSV_BYTES, new Functor("setBytes")); + // Both these are needed in the list even though they set the same + // variable + headerLabelMethods.put(CSV_THREAD_COUNT1, + new Functor("setThreadCounts")); + headerLabelMethods.put(CSV_THREAD_COUNT2, + new Functor("setThreadCounts")); + headerLabelMethods.put(CSV_URL, new Functor("setUrl")); + headerLabelMethods.put(CSV_FILENAME, new Functor("setFileName")); + headerLabelMethods.put(CSV_LATENCY, new Functor("setLatency")); + headerLabelMethods.put(CSV_ENCODING, new Functor("setEncoding")); + // Both these are needed in the list even though they set the same + // variable + headerLabelMethods.put(CSV_SAMPLE_COUNT, new Functor("setSampleCount")); + headerLabelMethods.put(CSV_ERROR_COUNT, new Functor("setSampleCount")); + headerLabelMethods.put(CSV_HOSTNAME, new Functor("setHostname")); + headerLabelMethods.put(CSV_IDLETIME, new Functor("setIdleTime")); + headerLabelMethods.put(CSV_CONNECT_TIME, new Functor("setConnectTime")); + } + + /** + * Parse a CSV header line + * + * @param headerLine + * from CSV file + * @param filename + * name of file (for log message only) + * @return config corresponding to the header items found or null if not a + * header line + */ + public static SampleSaveConfiguration getSampleSaveConfiguration( + String headerLine, String filename) { + String[] parts = splitHeader(headerLine, _saveConfig.getDelimiter()); // Try + // default + // delimiter + + String delim = null; + + if (parts == null) { + Perl5Matcher matcher = JMeterUtils.getMatcher(); + PatternMatcherInput input = new PatternMatcherInput(headerLine); + Pattern pattern = JMeterUtils.getPatternCache() + // This assumes the header names are all single words with no spaces + // word followed by 0 or more repeats of (non-word char + word) + // where the non-word char (\2) is the same + // e.g. abc|def|ghi but not abd|def~ghi + .getPattern("\\w+((\\W)\\w+)?(\\2\\w+)*(\\2\"\\w+\")*", // $NON-NLS-1$ + // last entries may be quoted strings + Perl5Compiler.READ_ONLY_MASK); + if (matcher.matches(input, pattern)) { + delim = matcher.getMatch().group(2); + parts = splitHeader(headerLine, delim);// now validate the + // result + } + } + + if (parts == null) { + return null; // failed to recognise the header + } + + // We know the column names all exist, so create the config + SampleSaveConfiguration saveConfig = new SampleSaveConfiguration(false); + + int varCount = 0; + for (int i = 0; i < parts.length; i++) { + String label = parts[i]; + if (isVariableName(label)) { + varCount++; + } else { + Functor set = (Functor) headerLabelMethods.get(label); + set.invoke(saveConfig, new Boolean[] { Boolean.TRUE }); + } + } + + if (delim != null) { + log.warn("Default delimiter '" + _saveConfig.getDelimiter() + + "' did not work; using alternate '" + delim + + "' for reading " + filename); + saveConfig.setDelimiter(delim); + } + + saveConfig.setVarCount(varCount); + + return saveConfig; + } + + private static String[] splitHeader(String headerLine, String delim) { + String parts[] = headerLine.split("\\Q" + delim);// $NON-NLS-1$ + int previous = -1; + // Check if the line is a header + for (int i = 0; i < parts.length; i++) { + final String label = parts[i]; + // Check for Quoted variable names + if (isVariableName(label)) { + previous = Integer.MAX_VALUE; // they are always last + continue; + } + int current = headerLabelMethods.indexOf(label); + if (current == -1) { + return null; // unknown column name + } + if (current <= previous) { + log.warn("Column header number " + (i + 1) + " name " + label + + " is out of order."); + return null; // out of order + } + previous = current; + } + return parts; + } + + /** + * Check if the label is a variable name, i.e. is it enclosed in + * double-quotes? + * + * @param label + * column name from CSV file + * @return if the label is enclosed in double-quotes + */ + private static boolean isVariableName(final String label) { + return label.length() > 2 && label.startsWith(VARIABLE_NAME_QUOTE_CHAR) + && label.endsWith(VARIABLE_NAME_QUOTE_CHAR); + } + + /** + * Method will save aggregate statistics as CSV. For now I put it here. Not + * sure if it should go in the newer SaveService instead of here. if we ever + * decide to get rid of this class, we'll need to move this method to the + * new save service. + * + * @param data + * List of data rows + * @param writer + * output file + * @throws IOException + * when writing to writer fails + */ + public static void saveCSVStats(List data, FileWriter writer) + throws IOException { + saveCSVStats(data, writer, null); + } + + /** + * Method will save aggregate statistics as CSV. For now I put it here. Not + * sure if it should go in the newer SaveService instead of here. if we ever + * decide to get rid of this class, we'll need to move this method to the + * new save service. + * + * @param data + * List of data rows + * @param writer + * output file + * @param headers + * header names (if non-null) + * @throws IOException + * when writing to writer fails + */ + public static void saveCSVStats(List data, FileWriter writer, + String headers[]) throws IOException { + final char DELIM = ','; + final char SPECIALS[] = new char[] { DELIM, QUOTING_CHAR }; + if (headers != null) { + for (int i = 0; i < headers.length; i++) { + if (i > 0) { + writer.write(DELIM); + } + writer.write(quoteDelimiters(headers[i], SPECIALS)); + } + writer.write(LINE_SEP); + } + for (int idx = 0; idx < data.size(); idx++) { + List row = (List) data.get(idx); + for (int idy = 0; idy < row.size(); idy++) { + if (idy > 0) { + writer.write(DELIM); + } + Object item = row.get(idy); + writer.write(quoteDelimiters(String.valueOf(item), SPECIALS)); + } + writer.write(LINE_SEP); + } + } + + /** + * Method saves aggregate statistics (with header names) as CSV from a table + * model. Same as {@link #saveCSVStats(List, FileWriter, String[])} except + * that there is no need to create a List containing the data. + * + * @param model + * table model containing the data + * @param writer + * output file + * @throws IOException + * when writing to writer fails + */ + public static void saveCSVStats(DefaultTableModel model, FileWriter writer) + throws IOException { + saveCSVStats(model, writer, true); + } + + /** + * Method saves aggregate statistics as CSV from a table model. Same as + * {@link #saveCSVStats(List, FileWriter, String[])} except that there is no + * need to create a List containing the data. + * + * @param model + * table model containing the data + * @param writer + * output file + * @param saveHeaders + * whether or not to save headers + * @throws IOException + * when writing to writer fails + */ + public static void saveCSVStats(DefaultTableModel model, FileWriter writer, + boolean saveHeaders) throws IOException { + final char DELIM = ','; + final char SPECIALS[] = new char[] { DELIM, QUOTING_CHAR }; + final int columns = model.getColumnCount(); + final int rows = model.getRowCount(); + if (saveHeaders) { + for (int i = 0; i < columns; i++) { + if (i > 0) { + writer.write(DELIM); + } + writer.write(quoteDelimiters(model.getColumnName(i), SPECIALS)); + } + writer.write(LINE_SEP); + } + for (int row = 0; row < rows; row++) { + for (int column = 0; column < columns; column++) { + if (column > 0) { + writer.write(DELIM); + } + Object item = model.getValueAt(row, column); + writer.write(quoteDelimiters(String.valueOf(item), SPECIALS)); + } + writer.write(LINE_SEP); + } + } + + /** + * Convert a result into a string, where the fields of the result are + * separated by the default delimiter. + * + * @param event + * the sample event to be converted + * @return the separated value representation of the result + */ + public static String resultToDelimitedString(SampleEvent event) { + return resultToDelimitedString(event, event.getResult().getSaveConfig() + .getDelimiter()); + } + + /* + * Class to handle generating the delimited string. - adds the delimiter + * if not the first call - quotes any strings that require it + */ + static final class StringQuoter { + private final StringBuilder sb; + private final char[] specials; + private boolean addDelim; + + public StringQuoter(char delim) { + sb = new StringBuilder(150); + specials = new char[] { delim, QUOTING_CHAR, CharUtils.CR, + CharUtils.LF }; + addDelim = false; // Don't add delimiter first time round + } + + private void addDelim() { + if (addDelim) { + sb.append(specials[0]); + } else { + addDelim = true; + } + } + + // These methods handle parameters that could contain delimiters or + // quotes: + public void append(String s) { + addDelim(); + // if (s == null) return; + sb.append(quoteDelimiters(s, specials)); + } + + public void append(Object obj) { + append(String.valueOf(obj)); + } + + // These methods handle parameters that cannot contain delimiters or + // quotes + public void append(int i) { + addDelim(); + sb.append(i); + } + + public void append(long l) { + addDelim(); + sb.append(l); + } + + public void append(boolean b) { + addDelim(); + sb.append(b); + } + + @Override + public String toString() { + return sb.toString(); + } + } + + /** + * Convert a result into a string, where the fields of the result are + * separated by a specified String. + * + * @param event + * the sample event to be converted + * @param delimiter + * the separation string + * @return the separated value representation of the result + */ + public static String resultToDelimitedString(SampleEvent event, + final String delimiter) { + StringQuoter text = new StringQuoter(delimiter.charAt(0)); + + SampleResult sample = event.getResult(); + SampleSaveConfiguration saveConfig = sample.getSaveConfig(); + + if (saveConfig.saveTimestamp()) { + if (saveConfig.printMilliseconds()) { + text.append(sample.getTimeStamp()); + } else if (saveConfig.formatter() != null) { + String stamp = saveConfig.formatter().format( + new Date(sample.getTimeStamp())); + text.append(stamp); + } + } + + if (saveConfig.saveTime()) { + text.append(sample.getTime()); + } + + if (saveConfig.saveLabel()) { + text.append(sample.getSampleLabel()); + } + + if (saveConfig.saveCode()) { + text.append(sample.getResponseCode()); + } + + if (saveConfig.saveMessage()) { + text.append(sample.getResponseMessage()); + } + + if (saveConfig.saveThreadName()) { + text.append(sample.getThreadName()); + } + + if (saveConfig.saveDataType()) { + text.append(sample.getDataType()); + } + + if (saveConfig.saveSuccess()) { + text.append(sample.isSuccessful()); + } + + if (saveConfig.saveAssertionResultsFailureMessage()) { + String message = null; + AssertionResult[] results = sample.getAssertionResults(); + + if (results != null) { + // Find the first non-null message + for (int i = 0; i < results.length; i++) { + message = results[i].getFailureMessage(); + if (message != null) { + break; + } + } + } + + if (message != null) { + text.append(message); + } else { + text.append(""); // Need to append something so delimiter is + // added + } + } + + if (saveConfig.saveBytes()) { + text.append(sample.getBytes()); + } + + if (saveConfig.saveThreadCounts()) { + text.append(sample.getGroupThreads()); + text.append(sample.getAllThreads()); + } + if (saveConfig.saveUrl()) { + text.append(sample.getURL()); + } + + if (saveConfig.saveFileName()) { + text.append(sample.getResultFileName()); + } + + if (saveConfig.saveLatency()) { + text.append(sample.getLatency()); + } + + if (saveConfig.saveEncoding()) { + text.append(sample.getDataEncodingWithDefault()); + } + + if (saveConfig.saveSampleCount()) { + // Need both sample and error count to be any use + text.append(sample.getSampleCount()); + text.append(sample.getErrorCount()); + } + + if (saveConfig.saveHostname()) { + text.append(event.getHostname()); + } + + if (saveConfig.saveIdleTime()) { + text.append(event.getResult().getIdleTime()); + } + + if (saveConfig.saveConnectTime()) { + text.append(sample.getConnectTime()); + } + + for (int i = 0; i < SampleEvent.getVarCount(); i++) { + text.append(event.getVarValue(i)); + } + + return text.toString(); + } + + // =================================== CSV quote/unquote handling + // ============================== + + /* + * Private versions of what might eventually be part of Commons-CSV or + * Commons-Lang/Io... + */ + + /* + *

Returns a String value for a character-delimited column + * value enclosed in the quote character, if required.

+ * + *

If the value contains a special character, then the String value is + * returned enclosed in the quote character.

+ * + *

Any quote characters in the value are doubled up.

+ * + *

If the value does not contain any special characters, then the String + * value is returned unchanged.

+ * + *

N.B. The list of special characters includes the quote character. + *

+ * + * @param input the input column String, may be null (without enclosing + * delimiters) + * + * @param specialChars special characters; second one must be the quote + * character + * + * @return the input String, enclosed in quote characters if the value + * contains a special character, null for null string input + */ + private static String quoteDelimiters(String input, char[] specialChars) { + if (StringUtils.containsNone(input, specialChars)) { + return input; + } + StringBuilder buffer = new StringBuilder(input.length() + 10); + final char quote = specialChars[1]; + buffer.append(quote); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (c == quote) { + buffer.append(quote); // double the quote char + } + buffer.append(c); + } + buffer.append(quote); + return buffer.toString(); + } + + // State of the parser + private enum ParserState {INITIAL, PLAIN, QUOTED, EMBEDDEDQUOTE} + + public static final char QUOTING_CHAR = '"'; + + /** + * Reads from file and splits input into strings according to the delimiter, + * taking note of quoted strings. + *

+ * Handles DOS (CRLF), Unix (LF), and Mac (CR) line-endings equally. + *

+ * A blank line - or a quoted blank line - both return an array containing + * a single empty String. + * @param infile + * input file - must support mark(1) + * @param delim + * delimiter (e.g. comma) + * @return array of strings, will be empty if there is no data, i.e. if the input is at EOF. + * @throws IOException + * also for unexpected quote characters + */ + public static String[] csvReadFile(BufferedReader infile, char delim) + throws IOException { + int ch; + ParserState state = ParserState.INITIAL; + List list = new ArrayList(); + CharArrayWriter baos = new CharArrayWriter(200); + boolean push = false; + while (-1 != (ch = infile.read())) { + push = false; + switch (state) { + case INITIAL: + if (ch == QUOTING_CHAR) { + state = ParserState.QUOTED; + } else if (isDelimOrEOL(delim, ch)) { + push = true; + } else { + baos.write(ch); + state = ParserState.PLAIN; + } + break; + case PLAIN: + if (ch == QUOTING_CHAR) { + baos.write(ch); + throw new IOException( + "Cannot have quote-char in plain field:[" + + baos.toString() + "]"); + } else if (isDelimOrEOL(delim, ch)) { + push = true; + state = ParserState.INITIAL; + } else { + baos.write(ch); + } + break; + case QUOTED: + if (ch == QUOTING_CHAR) { + state = ParserState.EMBEDDEDQUOTE; + } else { + baos.write(ch); + } + break; + case EMBEDDEDQUOTE: + if (ch == QUOTING_CHAR) { + baos.write(QUOTING_CHAR); // doubled quote => quote + state = ParserState.QUOTED; + } else if (isDelimOrEOL(delim, ch)) { + push = true; + state = ParserState.INITIAL; + } else { + baos.write(QUOTING_CHAR); + throw new IOException( + "Cannot have single quote-char in quoted field:[" + + baos.toString() + "]"); + } + break; + } // switch(state) + if (push) { + if (ch == '\r') {// Remove following \n if present + infile.mark(1); + if (infile.read() != '\n') { + infile.reset(); // did not find \n, put the character + // back + } + } + String s = baos.toString(); + list.add(s); + baos.reset(); + } + if ((ch == '\n' || ch == '\r') && state != ParserState.QUOTED) { + break; + } + } // while not EOF + if (ch == -1) {// EOF (or end of string) so collect any remaining data + if (state == ParserState.QUOTED) { + throw new IOException("Missing trailing quote-char in quoted field:[\"" + + baos.toString() + "]"); + } + // Do we have some data, or a trailing empty field? + if (baos.size() > 0 // we have some data + || push // we've started a field + || state == ParserState.EMBEDDEDQUOTE // Just seen "" + ) { + list.add(baos.toString()); + } + } + return list.toArray(new String[list.size()]); + } + + private static boolean isDelimOrEOL(char delim, int ch) { + return ch == delim || ch == '\n' || ch == '\r'; + } + + /** + * Reads from String and splits into strings according to the delimiter, + * taking note of quoted strings. + * + * Handles DOS (CRLF), Unix (LF), and Mac (CR) line-endings equally. + * + * @param line + * input line - not {@code null} + * @param delim + * delimiter (e.g. comma) + * @return array of strings + * @throws IOException + * also for unexpected quote characters + */ + public static String[] csvSplitString(String line, char delim) + throws IOException { + return csvReadFile(new BufferedReader(new StringReader(line)), delim); + } +} diff --git a/src/core/org/apache/jmeter/save/ListenerResultWrapper.java b/src/core/org/apache/jmeter/save/ListenerResultWrapper.java new file mode 100644 index 00000000000..a69518cb868 --- /dev/null +++ b/src/core/org/apache/jmeter/save/ListenerResultWrapper.java @@ -0,0 +1,80 @@ +/* + * 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.save; + +import java.util.Collection; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * ListenerResultWrapper is for calculated results generated by listeners like + * aggregate listener and monitor listener. + */ +public class ListenerResultWrapper { + private String version = ""; + + private Collection calculatedResults; + + private long testStartTime; + + /** + * @return Returns the sampleResults. + */ + public Collection getSampleResults() { + return calculatedResults; + } + + /** + * @param results + * The sampleResults to set. + */ + public void setSampleResults(Collection results) { + this.calculatedResults = results; + } + + /** + * @return Returns the testStartTime. + */ + public long getTestStartTime() { + return testStartTime; + } + + /** + * @param testStartTime + * The testStartTime to set. + */ + public void setTestStartTime(long testStartTime) { + this.testStartTime = testStartTime; + } + + /** + * @return Returns the version. + */ + public String getVersion() { + return version; + } + + /** + * @param version + * The version to set. + */ + public void setVersion(String version) { + this.version = version; + } +} diff --git a/src/core/org/apache/jmeter/save/OldSaveService.java b/src/core/org/apache/jmeter/save/OldSaveService.java new file mode 100644 index 00000000000..76be92e2ed5 --- /dev/null +++ b/src/core/org/apache/jmeter/save/OldSaveService.java @@ -0,0 +1,530 @@ +/* + * 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.save; + +//import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +//import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Collection; +//import java.util.Date; +//import java.util.Iterator; +//import java.util.LinkedList; +//import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +//import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; +//import org.apache.avalon.framework.configuration.DefaultConfigurationSerializer; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.SampleResult; +//import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.MapProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.NameUpdater; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xml.sax.SAXException; + +/** + * This class restores the original Avalon XML format (not used by default). + * + * This may be removed in a future release. + */ +public final class OldSaveService { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // --------------------------------------------------------------------- + // XML RESULT FILE CONSTANTS AND FIELD NAME CONSTANTS + // --------------------------------------------------------------------- + + // Shared with TestElementSaver + static final String PRESERVE = "preserve"; // $NON-NLS-1$ + static final String XML_SPACE = "xml:space"; // $NON-NLS-1$ + + private static final String ASSERTION_RESULT_TAG_NAME = "assertionResult"; // $NON-NLS-1$ + private static final String BINARY = "binary"; // $NON-NLS-1$ + private static final String DATA_TYPE = "dataType"; // $NON-NLS-1$ + private static final String ERROR = "error"; // $NON-NLS-1$ + private static final String FAILURE = "failure"; // $NON-NLS-1$ + private static final String FAILURE_MESSAGE = "failureMessage"; // $NON-NLS-1$ + private static final String LABEL = "label"; // $NON-NLS-1$ + private static final String RESPONSE_CODE = "responseCode"; // $NON-NLS-1$ + private static final String RESPONSE_MESSAGE = "responseMessage"; // $NON-NLS-1$ + private static final String SAMPLE_RESULT_TAG_NAME = "sampleResult"; // $NON-NLS-1$ + private static final String SUCCESSFUL = "success"; // $NON-NLS-1$ + private static final String THREAD_NAME = "threadName"; // $NON-NLS-1$ + private static final String TIME = "time"; // $NON-NLS-1$ + private static final String TIME_STAMP = "timeStamp"; // $NON-NLS-1$ + + private static final DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); + + /** + * Private constructor to prevent instantiation. + */ + private OldSaveService() { + } + + +// public static void saveSubTree(HashTree subTree, OutputStream writer) throws IOException { +// Configuration config = getConfigsFromTree(subTree).get(0); +// DefaultConfigurationSerializer saver = new DefaultConfigurationSerializer(); +// +// saver.setIndent(true); +// try { +// saver.serialize(writer, config); +// } catch (SAXException e) { +// throw new IOException("SAX implementation problem"); +// } catch (ConfigurationException e) { +// throw new IOException("Problem using Avalon Configuration tools"); +// } +// } + + /** + * Read sampleResult from Avalon XML file. + * + * @param config Avalon configuration + * @return sample result + */ + // Probably no point in converting this to return a SampleEvent + private static SampleResult getSampleResult(Configuration config) { + SampleResult result = new SampleResult(config.getAttributeAsLong(TIME_STAMP, 0L), config.getAttributeAsLong( + TIME, 0L)); + + result.setThreadName(config.getAttribute(THREAD_NAME, "")); // $NON-NLS-1$ + result.setDataType(config.getAttribute(DATA_TYPE, "")); + result.setResponseCode(config.getAttribute(RESPONSE_CODE, "")); // $NON-NLS-1$ + result.setResponseMessage(config.getAttribute(RESPONSE_MESSAGE, "")); // $NON-NLS-1$ + result.setSuccessful(config.getAttributeAsBoolean(SUCCESSFUL, false)); + result.setSampleLabel(config.getAttribute(LABEL, "")); // $NON-NLS-1$ + result.setResponseData(getBinaryData(config.getChild(BINARY))); + Configuration[] subResults = config.getChildren(SAMPLE_RESULT_TAG_NAME); + + for (int i = 0; i < subResults.length; i++) { + result.storeSubResult(getSampleResult(subResults[i])); + } + Configuration[] assResults = config.getChildren(ASSERTION_RESULT_TAG_NAME); + + for (int i = 0; i < assResults.length; i++) { + result.addAssertionResult(getAssertionResult(assResults[i])); + } + + Configuration[] samplerData = config.getChildren("property"); // $NON-NLS-1$ + for (int i = 0; i < samplerData.length; i++) { + result.setSamplerData(samplerData[i].getValue("")); // $NON-NLS-1$ + } + return result; + } + +// private static List getConfigsFromTree(HashTree subTree) { +// Iterator iter = subTree.list().iterator(); +// List configs = new LinkedList(); +// +// while (iter.hasNext()) { +// TestElement item = iter.next(); +// DefaultConfiguration config = new DefaultConfiguration("node", "node"); // $NON-NLS-1$ // $NON-NLS-2$ +// +// config.addChild(getConfigForTestElement(null, item)); +// List configList = getConfigsFromTree(subTree.getTree(item)); +// Iterator iter2 = configList.iterator(); +// +// while (iter2.hasNext()) { +// config.addChild(iter2.next()); +// } +// configs.add(config); +// } +// return configs; +// } + +// private static Configuration getConfiguration(byte[] bin) { +// DefaultConfiguration config = new DefaultConfiguration(BINARY, "JMeter Save Service"); // $NON-NLS-1$ +// +// try { +// config.setValue(new String(bin, "UTF-8")); // $NON-NLS-1$ +// } catch (UnsupportedEncodingException e) { +// log.error("", e); // $NON-NLS-1$ +// } +// return config; +// } + + private static byte[] getBinaryData(Configuration config) { + if (config == null) { + return new byte[0]; + } + try { + return config.getValue("").getBytes("UTF-8"); // $NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + return new byte[0]; + } + } + + private static AssertionResult getAssertionResult(Configuration config) { + AssertionResult result = new AssertionResult(""); //TODO provide proper name? + result.setError(config.getAttributeAsBoolean(ERROR, false)); + result.setFailure(config.getAttributeAsBoolean(FAILURE, false)); + result.setFailureMessage(config.getAttribute(FAILURE_MESSAGE, "")); + return result; + } + +// private static Configuration getConfiguration(AssertionResult assResult) { +// DefaultConfiguration config = new DefaultConfiguration(ASSERTION_RESULT_TAG_NAME, "JMeter Save Service"); +// +// config.setAttribute(FAILURE_MESSAGE, assResult.getFailureMessage()); +// config.setAttribute(ERROR, "" + assResult.isError()); +// config.setAttribute(FAILURE, "" + assResult.isFailure()); +// return config; +// } + +// /** +// * This method determines the content of the result data that will be +// * stored for the Avalon XML format. +// * +// * @param result +// * the object containing all of the data that has been collected. +// * @param saveConfig +// * the configuration giving the data items to be saved. +// * N.B. It is rather out of date, as many fields are not saved. +// * However it is probably not worth updating, as no-one should be using the format. +// */ +// public static Configuration getConfiguration(SampleResult result, SampleSaveConfiguration saveConfig) { +// DefaultConfiguration config = new DefaultConfiguration(SAMPLE_RESULT_TAG_NAME, "JMeter Save Service"); // $NON-NLS-1$ +// +// if (saveConfig.saveTime()) { +// config.setAttribute(TIME, String.valueOf(result.getTime())); +// } +// if (saveConfig.saveLabel()) { +// config.setAttribute(LABEL, result.getSampleLabel()); +// } +// if (saveConfig.saveCode()) { +// config.setAttribute(RESPONSE_CODE, result.getResponseCode()); +// } +// if (saveConfig.saveMessage()) { +// config.setAttribute(RESPONSE_MESSAGE, result.getResponseMessage()); +// } +// if (saveConfig.saveThreadName()) { +// config.setAttribute(THREAD_NAME, result.getThreadName()); +// } +// if (saveConfig.saveDataType()) { +// config.setAttribute(DATA_TYPE, result.getDataType()); +// } +// +// if (saveConfig.printMilliseconds()) { +// config.setAttribute(TIME_STAMP, String.valueOf(result.getTimeStamp())); +// } else if (saveConfig.formatter() != null) { +// String stamp = saveConfig.formatter().format(new Date(result.getTimeStamp())); +// +// config.setAttribute(TIME_STAMP, stamp); +// } +// +// if (saveConfig.saveSuccess()) { +// config.setAttribute(SUCCESSFUL, Boolean.toString(result.isSuccessful())); +// } +// +// SampleResult[] subResults = result.getSubResults(); +// +// if (subResults != null) { +// for (int i = 0; i < subResults.length; i++) { +// config.addChild(getConfiguration(subResults[i], saveConfig)); +// } +// } +// +// AssertionResult[] assResults = result.getAssertionResults(); +// +// if (saveConfig.saveSamplerData(result)) { +// config.addChild(createConfigForString("samplerData", result.getSamplerData())); // $NON-NLS-1$ +// } +// if (saveConfig.saveAssertions() && assResults != null) { +// for (int i = 0; i < assResults.length; i++) { +// config.addChild(getConfiguration(assResults[i])); +// } +// } +// if (saveConfig.saveResponseData(result)) { +// config.addChild(getConfiguration(result.getResponseData())); +// } +// return config; +// } + +// private static Configuration getConfigForTestElement(String named, TestElement item) { +// TestElementSaver saver = new TestElementSaver(named); +// item.traverse(saver); +// Configuration config = saver.getConfiguration(); +// /* +// * DefaultConfiguration config = new DefaultConfiguration("testelement", +// * "testelement"); +// * +// * if (named != null) { config.setAttribute("name", named); } if +// * (item.getProperty(TestElement.TEST_CLASS) != null) { +// * config.setAttribute("class", (String) +// * item.getProperty(TestElement.TEST_CLASS)); } else { +// * config.setAttribute("class", item.getClass().getName()); } Iterator +// * iter = item.getPropertyNames().iterator(); +// * +// * while (iter.hasNext()) { String name = (String) iter.next(); Object +// * value = item.getProperty(name); +// * +// * if (value instanceof TestElement) { +// * config.addChild(getConfigForTestElement(name, (TestElement) value)); } +// * else if (value instanceof Collection) { +// * config.addChild(createConfigForCollection(name, (Collection) value)); } +// * else if (value != null) { config.addChild(createConfigForString(name, +// * value.toString())); } } +// */ +// return config; +// } + + +// private static Configuration createConfigForString(String name, String value) { +// if (value == null) { +// value = ""; +// } +// DefaultConfiguration config = new DefaultConfiguration("property", "property"); +// +// config.setAttribute("name", name); +// config.setValue(value); +// config.setAttribute(XML_SPACE, PRESERVE); +// return config; +// } + + // Called by SaveService.loadTree(InputStream reader) if XStream loading fails + public synchronized static HashTree loadSubTree(InputStream in) throws IOException { + try { + Configuration config = builder.build(in); + HashTree loadedTree = generateNode(config); + + return loadedTree; + } catch (ConfigurationException e) { + String message = "Problem loading using Avalon Configuration tools"; + log.error(message, e); + throw new IOException(message); + } catch (SAXException e) { + String message = "Problem with SAX implementation"; + log.error(message, e); + throw new IOException(message); + } + } + + private static TestElement createTestElement(Configuration config) throws ConfigurationException, + ClassNotFoundException, IllegalAccessException, InstantiationException { + TestElement element = null; + + String testClass = config.getAttribute("class"); // $NON-NLS-1$ + + String gui_class=""; // $NON-NLS-1$ + Configuration[] children = config.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i].getName().equals("property")) { // $NON-NLS-1$ + if (children[i].getAttribute("name").equals(TestElement.GUI_CLASS)){ // $NON-NLS-1$ + gui_class=children[i].getValue(); + } + } + } + + String newClass = NameUpdater.getCurrentTestName(testClass,gui_class); + + element = (TestElement) Class.forName(newClass).newInstance(); + + for (int i = 0; i < children.length; i++) { + if (children[i].getName().equals("property")) { // $NON-NLS-1$ + try { + JMeterProperty prop = createProperty(children[i], newClass); + if (prop!=null) { + element.setProperty(prop); + } + } catch (Exception ex) { + log.error("Problem loading property", ex); + element.setProperty(children[i].getAttribute("name"), ""); // $NON-NLS-1$ // $NON-NLS-2$ + } + } else if (children[i].getName().equals("testelement")) { // $NON-NLS-1$ + element.setProperty(new TestElementProperty(children[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createTestElement(children[i]))); + } else if (children[i].getName().equals("collection")) { // $NON-NLS-1$ + element.setProperty(new CollectionProperty(children[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createCollection(children[i], newClass))); + } else if (children[i].getName().equals("map")) { // $NON-NLS-1$ + element.setProperty(new MapProperty(children[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createMap(children[i],newClass))); + } + } + return element; + } + + private static Collection createCollection(Configuration config, String testClass) throws ConfigurationException, + ClassNotFoundException, IllegalAccessException, InstantiationException { + @SuppressWarnings("unchecked") // OK + Collection coll = (Collection) Class.forName(config.getAttribute("class")).newInstance(); // $NON-NLS-1$ + Configuration[] items = config.getChildren(); + + for (int i = 0; i < items.length; i++) { + if (items[i].getName().equals("property")) { // $NON-NLS-1$ + JMeterProperty prop = createProperty(items[i], testClass); + if (prop!=null) { + coll.add(prop); + } + } else if (items[i].getName().equals("testelement")) { // $NON-NLS-1$ + coll.add(new TestElementProperty(items[i].getAttribute("name", ""), createTestElement(items[i]))); // $NON-NLS-1$ // $NON-NLS-2$ + } else if (items[i].getName().equals("collection")) { // $NON-NLS-1$ + coll.add(new CollectionProperty(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createCollection(items[i], testClass))); + } else if (items[i].getName().equals("string")) { // $NON-NLS-1$ + JMeterProperty prop = createProperty(items[i], testClass); + if (prop!=null) { + coll.add(prop); + } + } else if (items[i].getName().equals("map")) { // $NON-NLS-1$ + coll.add(new MapProperty(items[i].getAttribute("name", ""), createMap(items[i], testClass))); // $NON-NLS-1$ // $NON-NLS-2$ + } + } + return coll; + } + + private static JMeterProperty createProperty(Configuration config, String testClass) throws IllegalAccessException, + ClassNotFoundException, InstantiationException { + String value = config.getValue(""); // $NON-NLS-1$ + String name = config.getAttribute("name", value); // $NON-NLS-1$ + String oname = name; + String type = config.getAttribute("propType", StringProperty.class.getName()); // $NON-NLS-1$ + + // Do upgrade translation: + name = NameUpdater.getCurrentName(name, testClass); + if (TestElement.GUI_CLASS.equals(name)) { + value = NameUpdater.getCurrentName(value); + } else if (TestElement.TEST_CLASS.equals(name)) { + value=testClass; // must always agree + } else { + value = NameUpdater.getCurrentName(value, name, testClass); + } + + // Delete any properties whose name converts to the empty string + if (oname.length() != 0 && name.length()==0) { + return null; + } + + // Create the property: + JMeterProperty prop = (JMeterProperty) Class.forName(type).newInstance(); + prop.setName(name); + prop.setObjectValue(value); + + return prop; + } + + private static Map createMap(Configuration config, String testClass) throws ConfigurationException, + ClassNotFoundException, IllegalAccessException, InstantiationException { + @SuppressWarnings("unchecked") // OK + Map map = (Map) Class.forName(config.getAttribute("class")).newInstance(); + Configuration[] items = config.getChildren(); + + for (int i = 0; i < items.length; i++) { + if (items[i].getName().equals("property")) { // $NON-NLS-1$ + JMeterProperty prop = createProperty(items[i], testClass); + if (prop!=null) { + map.put(prop.getName(), prop); + } + } else if (items[i].getName().equals("testelement")) { // $NON-NLS-1$ + map.put(items[i].getAttribute("name", ""), new TestElementProperty(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createTestElement(items[i]))); + } else if (items[i].getName().equals("collection")) { // $NON-NLS-1$ + map.put(items[i].getAttribute("name"), // $NON-NLS-1$ + new CollectionProperty(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createCollection(items[i], testClass))); + } else if (items[i].getName().equals("map")) { // $NON-NLS-1$ + map.put(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + new MapProperty(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createMap(items[i], testClass))); + } + } + return map; + } + + private static HashTree generateNode(Configuration config) { + TestElement element = null; + + try { + element = createTestElement(config.getChild("testelement")); // $NON-NLS-1$ + } catch (Exception e) { + log.error("Problem loading part of file", e); + return null; + } + HashTree subTree = new ListedHashTree(element); + Configuration[] subNodes = config.getChildren("node"); // $NON-NLS-1$ + + for (int i = 0; i < subNodes.length; i++) { + HashTree t = generateNode(subNodes[i]); + + if (t != null) { + subTree.add(element, t); + } + } + return subTree; + } + + // Called by ResultCollector#loadExistingFile() if XStream loading fails + public static void processSamples(String filename, Visualizer visualizer, ResultCollector rc) + throws SAXException, IOException, ConfigurationException + { + DefaultConfigurationBuilder cfgbuilder = new DefaultConfigurationBuilder(); + Configuration savedSamples = cfgbuilder.buildFromFile(filename); + Configuration[] samples = savedSamples.getChildren(); + final boolean errorsOnly = rc.isErrorLogging(); + final boolean successOnly = rc.isSuccessOnlyLogging(); + for (int i = 0; i < samples.length; i++) { + SampleResult result = OldSaveService.getSampleResult(samples[i]); + if (ResultCollector.isSampleWanted(result.isSuccessful(), errorsOnly, successOnly)) { + visualizer.add(result); + } + } + } + + // Called by ResultCollector#recordResult() +// public static String getSerializedSampleResult( +// SampleResult result, DefaultConfigurationSerializer slzr, SampleSaveConfiguration cfg) +// throws SAXException, IOException, +// ConfigurationException { +// ByteArrayOutputStream tempOut = new ByteArrayOutputStream(); +// +// slzr.serialize(tempOut, OldSaveService.getConfiguration(result, cfg)); +// String serVer = tempOut.toString(); +// String lineSep=System.getProperty("line.separator"); // $NON-NLS-1$ +// /* +// * Remove the prefix. +// * When using the x-jars (xakan etc) or Java 1.4, the serialised output has a +// * newline after the prefix. However, when using Java 1.5 without the x-jars, the output +// * has no newline at all. +// */ +// int index = serVer.indexOf(lineSep); // Is there a new-line? +// if (index > -1) {// Yes, assume it follows the prefix +// return serVer.substring(index); +// } +// if (serVer.startsWith("");// must exist // $NON-NLS-1$ +// return lineSep + serVer.substring(index+2);// +2 for ?> +// } +// return serVer; +// } +} diff --git a/src/core/org/apache/jmeter/save/SaveGraphicsService.java b/src/core/org/apache/jmeter/save/SaveGraphicsService.java new file mode 100644 index 00000000000..392f8c7d27f --- /dev/null +++ b/src/core/org/apache/jmeter/save/SaveGraphicsService.java @@ -0,0 +1,199 @@ +/* + * 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.save; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import javax.swing.JComponent; + +import org.apache.xmlgraphics.image.codec.png.PNGEncodeParam; +import org.apache.xmlgraphics.image.codec.png.PNGImageEncoder; +import org.apache.xmlgraphics.image.codec.tiff.TIFFEncodeParam; +import org.apache.xmlgraphics.image.codec.tiff.TIFFImageEncoder; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Class is responsible for taking a component and saving it as a JPEG, PNG or + * TIFF. The class is very simple. Thanks to Batik and the developers who worked + * so hard on it. + */ +public class SaveGraphicsService { + + public static final int PNG = 0; + + public static final int TIFF = 1; + + public static final String PNG_EXTENSION = ".png"; //$NON-NLS-1$ + + public static final String TIFF_EXTENSION = ".tif"; //$NON-NLS-1$ + + public static final String JPEG_EXTENSION = ".jpg"; //$NON-NLS-1$ + + /** + * + */ + public SaveGraphicsService() { + super(); + } + +/* + * This is not currently used by JMeter code. + * As it uses Sun-specific code (the only such in JMeter), it has been commented out for now. + */ +// /** +// * If someone wants to save a JPEG, use this method. There is a limitation +// * though. It uses gray scale instead of color due to artifacts with color +// * encoding. For some reason, it does not translate pure red and orange +// * correctly. To make the text readable, gray scale is used. +// * +// * @param filename +// * @param component +// */ +// public void saveUsingJPEGEncoder(String filename, JComponent component) { +// Dimension size = component.getSize(); +// // We use Gray scale, since color produces poor quality +// // this is an unfortunate result of the default codec +// // implementation. +// BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_USHORT_GRAY); +// Graphics2D grp = image.createGraphics(); +// component.paint(grp); +// +// File outfile = new File(filename + JPEG_EXTENSION); +// FileOutputStream fos = createFile(outfile); +// JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image); +// Float q = new Float(1.0); +// param.setQuality(q.floatValue(), true); +// JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(fos, param); +// +// try { +// encoder.encode(image); +// } catch (Exception e) { +// log.warn(e.toString()); +// } finally { +// JOrphanUtils.closeQuietly(fos); +// } +// } + + /** + * Method will save the JComponent as an image. The formats are PNG, and + * TIFF. + * + * @param filename + * name of the file to store the image into + * @param type + * of the image to be stored. Can be one of {@value #PNG} for PNG + * or {@value #TIFF} for TIFF + * @param component + * to draw the image on + */ + public void saveJComponent(String filename, int type, JComponent component) { + Dimension size = component.getSize(); + BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_BYTE_INDEXED); + Graphics2D grp = image.createGraphics(); + component.paint(grp); + + if (type == PNG) { + filename += PNG_EXTENSION; + this.savePNGWithBatik(filename, image); + } else if (type == TIFF) { + filename = filename + TIFF_EXTENSION; + this.saveTIFFWithBatik(filename, image); + } + } + + /** + * Use Batik to save a PNG of the graph + * + * @param filename + * name of the file to store the image into + * @param image + * to be stored + */ + public void savePNGWithBatik(String filename, BufferedImage image) { + File outfile = new File(filename); + OutputStream fos = createFile(outfile); + if (fos == null) { + return; + } + PNGEncodeParam param = PNGEncodeParam.getDefaultEncodeParam(image); + PNGImageEncoder encoder = new PNGImageEncoder(fos, param); + try { + encoder.encode(image); + } catch (IOException e) { + JMeterUtils.reportErrorToUser("PNGImageEncoder reported: "+e.getMessage(), "Problem creating image file"); + } finally { + JOrphanUtils.closeQuietly(fos); + } + } + + /** + * Use Batik to save a TIFF file of the graph + * + * @param filename + * name of the file to store the image into + * @param image + * to be stored + */ + public void saveTIFFWithBatik(String filename, BufferedImage image) { + File outfile = new File(filename); + OutputStream fos = createFile(outfile); + if (fos == null) { + return; + } + TIFFEncodeParam param = new TIFFEncodeParam(); + TIFFImageEncoder encoder = new TIFFImageEncoder(fos, param); + try { + encoder.encode(image); + } catch (IOException e) { + JMeterUtils.reportErrorToUser("TIFFImageEncoder reported: "+e.getMessage(), "Problem creating image file"); + // Yuck: TIFFImageEncoder uses Error to report runtime problems + } catch (Error e) { + JMeterUtils.reportErrorToUser("TIFFImageEncoder reported: "+e.getMessage(), "Problem creating image file"); + if (e.getClass() != Error.class){// rethrow other errors + throw e; + } + } finally { + JOrphanUtils.closeQuietly(fos); + } + } + + /** + * Create a new file for the graphics. Since the method creates a new file, + * we shouldn't get a FNFE. + * + * @param filename + * @return output stream created from the filename + */ + private FileOutputStream createFile(File filename) { + try { + return new FileOutputStream(filename); + } catch (FileNotFoundException e) { + JMeterUtils.reportErrorToUser("Could not create file: "+e.getMessage(), "Problem creating image file"); + return null; + } + } + +} diff --git a/src/core/org/apache/jmeter/save/SaveService.java b/src/core/org/apache/jmeter/save/SaveService.java new file mode 100644 index 00000000000..1c5f21b0bc5 --- /dev/null +++ b/src/core/org/apache/jmeter/save/SaveService.java @@ -0,0 +1,687 @@ +/* + * 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.save; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.jmeter.reporters.ResultCollectorHelper; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.NameUpdater; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.DataHolder; +import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; +import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; +import com.thoughtworks.xstream.io.xml.XppDriver; +import com.thoughtworks.xstream.mapper.CannotResolveClassException; +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.mapper.MapperWrapper; + +/** + * Handles setting up XStream serialisation. + * The class reads alias definitions from saveservice.properties. + * + */ +public class SaveService { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Names of DataHolder entries for JTL processing + public static final String SAMPLE_EVENT_OBJECT = "SampleEvent"; // $NON-NLS-1$ + public static final String RESULTCOLLECTOR_HELPER_OBJECT = "ResultCollectorHelper"; // $NON-NLS-1$ + + // Names of DataHolder entries for JMX processing + public static final String TEST_CLASS_NAME = "TestClassName"; // $NON-NLS-1$ + + private static final class XStreamWrapper extends XStream { + private XStreamWrapper(ReflectionProvider reflectionProvider) { + super(reflectionProvider); + } + + // Override wrapMapper in order to insert the Wrapper in the chain + @Override + protected MapperWrapper wrapMapper(MapperWrapper next) { + // Provide our own aliasing using strings rather than classes + return new MapperWrapper(next){ + // Translate alias to classname and then delegate to wrapped class + @Override + public Class realClass(String alias) { + String fullName = aliasToClass(alias); + if (fullName != null) { + fullName = NameUpdater.getCurrentName(fullName); + } + return super.realClass(fullName == null ? alias : fullName); + } + // Translate to alias and then delegate to wrapped class + @Override + public String serializedClass(@SuppressWarnings("rawtypes") // superclass does not use types + Class type) { + if (type == null) { + return super.serializedClass(null); // was type, but that caused FindBugs warning + } + String alias = classToAlias(type.getName()); + return alias == null ? super.serializedClass(type) : alias ; + } + }; + } + } + + private static final XStream JMXSAVER = new XStreamWrapper(new PureJavaReflectionProvider()); + private static final XStream JTLSAVER = new XStreamWrapper(new PureJavaReflectionProvider()); + static { + JTLSAVER.setMode(XStream.NO_REFERENCES); // This is needed to stop XStream keeping copies of each class + } + + // The XML header, with placeholder for encoding, since that is controlled by property + private static final String XML_HEADER = "\"?>"; // $NON-NLS-1$ + + // Default file name + private static final String SAVESERVICE_PROPERTIES_FILE = "/bin/saveservice.properties"; // $NON-NLS-1$ + + // Property name used to define file name + private static final String SAVESERVICE_PROPERTIES = "saveservice_properties"; // $NON-NLS-1$ + + // Define file format property names + private static final String FILE_FORMAT = "file_format"; // $NON-NLS-1$ + private static final String FILE_FORMAT_TESTPLAN = "file_format.testplan"; // $NON-NLS-1$ + private static final String FILE_FORMAT_TESTLOG = "file_format.testlog"; // $NON-NLS-1$ + + // Define file format versions + private static final String VERSION_2_2 = "2.2"; // $NON-NLS-1$ + + // Default to overall format, and then to version 2.2 + public static final String TESTPLAN_FORMAT + = JMeterUtils.getPropDefault(FILE_FORMAT_TESTPLAN + , JMeterUtils.getPropDefault(FILE_FORMAT, VERSION_2_2)); + + public static final String TESTLOG_FORMAT + = JMeterUtils.getPropDefault(FILE_FORMAT_TESTLOG + , JMeterUtils.getPropDefault(FILE_FORMAT, VERSION_2_2)); + + private static boolean validateFormat(String format){ + if ("2.2".equals(format)) return true; + if ("2.1".equals(format)) return true; + return false; + } + + static{ + if (!validateFormat(TESTPLAN_FORMAT)){ + log.error("Invalid test plan format: "+TESTPLAN_FORMAT); + } + if (!validateFormat(TESTLOG_FORMAT)){ + log.error("Invalid test log format: "+TESTLOG_FORMAT); + } + } + + /** New XStream format - more compressed class names */ + public static final boolean IS_TESTPLAN_FORMAT_22 + = VERSION_2_2.equals(TESTPLAN_FORMAT); + + // Holds the mappings from the saveservice properties file + // Key: alias Entry: full class name + // There may be multiple aliases which map to the same class + private static final Properties aliasToClass = new Properties(); + + // Holds the reverse mappings + // Key: full class name Entry: primary alias + private static final Properties classToAlias = new Properties(); + + // Version information for test plan header + // This is written to JMX files by ScriptWrapperConverter + // Also to JTL files by ResultCollector + private static final String VERSION = "1.2"; // $NON-NLS-1$ + + // This is written to JMX files by ScriptWrapperConverter + private static String propertiesVersion = "";// read from properties file; written to JMX files + + // Must match _version property value in saveservice.properties + // used to ensure saveservice.properties and SaveService are updated simultaneously + private static final String PROPVERSION = "2.8";// Expected version $NON-NLS-1$ + + // Internal information only + private static String fileVersion = ""; // read from saveservice.properties file// $NON-NLS-1$ + // Must match Revision id value in saveservice.properties, + // used to ensure saveservice.properties and SaveService are updated simultaneously + private static final String FILEVERSION = "1656252"; // Expected value $NON-NLS-1$ + private static String fileEncoding = ""; // read from properties file// $NON-NLS-1$ + + static { + log.info("Testplan (JMX) version: "+TESTPLAN_FORMAT+". Testlog (JTL) version: "+TESTLOG_FORMAT); + initProps(); + checkVersions(); + } + + // Helper method to simplify alias creation from properties + private static void makeAlias(String aliasList, String clazz) { + String aliases[]=aliasList.split(","); // Can have multiple aliases for same target classname + String alias=aliases[0]; + for (String a : aliases){ + Object old = aliasToClass.setProperty(a,clazz); + if (old != null){ + log.error("Duplicate class detected for "+alias+": "+clazz+" & "+old); + } + } + Object oldval=classToAlias.setProperty(clazz,alias); + if (oldval != null) { + log.error("Duplicate alias detected for "+clazz+": "+alias+" & "+oldval); + } + } + + public static Properties loadProperties() throws IOException{ + Properties nameMap = new Properties(); + FileInputStream fis = null; + try { + fis = new FileInputStream(JMeterUtils.getJMeterHome() + + JMeterUtils.getPropDefault(SAVESERVICE_PROPERTIES, SAVESERVICE_PROPERTIES_FILE)); + nameMap.load(fis); + } finally { + JOrphanUtils.closeQuietly(fis); + } + return nameMap; + } + private static void initProps() { + // Load the alias properties + try { + Properties nameMap = loadProperties(); + // now create the aliases + for (Map.Entry me : nameMap.entrySet()) { + String key = (String) me.getKey(); + String val = (String) me.getValue(); + if (!key.startsWith("_")) { // $NON-NLS-1$ + makeAlias(key, val); + } else { + // process special keys + if (key.equalsIgnoreCase("_version")) { // $NON-NLS-1$ + propertiesVersion = val; + log.info("Using SaveService properties version " + propertiesVersion); + } else if (key.equalsIgnoreCase("_file_version")) { // $NON-NLS-1$ + fileVersion = extractVersion(val); + log.info("Using SaveService properties file version " + fileVersion); + } else if (key.equalsIgnoreCase("_file_encoding")) { // $NON-NLS-1$ + fileEncoding = val; + log.info("Using SaveService properties file encoding " + fileEncoding); + } else { + key = key.substring(1);// Remove the leading "_" + try { + final String trimmedValue = val.trim(); + if (trimmedValue.equals("collection") // $NON-NLS-1$ + || trimmedValue.equals("mapping")) { // $NON-NLS-1$ + registerConverter(key, JMXSAVER, true); + registerConverter(key, JTLSAVER, true); + } else { + registerConverter(key, JMXSAVER, false); + registerConverter(key, JTLSAVER, false); + } + } catch (IllegalAccessException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (InstantiationException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (ClassNotFoundException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (IllegalArgumentException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (SecurityException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (InvocationTargetException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (NoSuchMethodException e1) { + log.warn("Can't register a converter: " + key, e1); + } + } + } + } + } catch (IOException e) { + log.fatalError("Bad saveservice properties file", e); + throw new JMeterError("JMeter requires the saveservice properties file to continue"); + } + } + + /** + * Register converter. + * @param key + * @param jmxsaver + * @param useMapper + * + * @throws InstantiationException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws NoSuchMethodException + * @throws ClassNotFoundException + */ + private static void registerConverter(String key, XStream jmxsaver, boolean useMapper) + throws InstantiationException, IllegalAccessException, + InvocationTargetException, NoSuchMethodException, + ClassNotFoundException { + if (useMapper){ + jmxsaver.registerConverter((Converter) Class.forName(key).getConstructor( + new Class[] { Mapper.class }).newInstance( + new Object[] { jmxsaver.getMapper() })); + } else { + jmxsaver.registerConverter((Converter) Class.forName(key).newInstance()); + } + } + + // For converters to use + public static String aliasToClass(String s){ + String r = aliasToClass.getProperty(s); + return r == null ? s : r; + } + + // For converters to use + public static String classToAlias(String s){ + String r = classToAlias.getProperty(s); + return r == null ? s : r; + } + + // Called by Save function + public static void saveTree(HashTree tree, OutputStream out) throws IOException { + // Get the OutputWriter to use + OutputStreamWriter outputStreamWriter = getOutputStreamWriter(out); + writeXmlHeader(outputStreamWriter); + // Use deprecated method, to avoid duplicating code + ScriptWrapper wrapper = new ScriptWrapper(); + wrapper.testPlan = tree; + JMXSAVER.toXML(wrapper, outputStreamWriter); + outputStreamWriter.write('\n');// Ensure terminated properly + outputStreamWriter.close(); + } + + // Used by Test code + public static void saveElement(Object el, OutputStream out) throws IOException { + // Get the OutputWriter to use + OutputStreamWriter outputStreamWriter = getOutputStreamWriter(out); + writeXmlHeader(outputStreamWriter); + // Use deprecated method, to avoid duplicating code + JMXSAVER.toXML(el, outputStreamWriter); + outputStreamWriter.close(); + } + + // Used by Test code + public static Object loadElement(InputStream in) throws IOException { + // Get the InputReader to use + InputStreamReader inputStreamReader = getInputStreamReader(in); + // Use deprecated method, to avoid duplicating code + Object element = JMXSAVER.fromXML(inputStreamReader); + inputStreamReader.close(); + return element; + } + + /** + * Save a sampleResult to an XML output file using XStream. + * + * @param evt sampleResult wrapped in a sampleEvent + * @param writer output stream which must be created using {@link #getFileEncoding(String)} + * @throws IOException when writing data to output fails + */ + // Used by ResultCollector.sampleOccurred(SampleEvent event) + public synchronized static void saveSampleResult(SampleEvent evt, Writer writer) throws IOException { + DataHolder dh = JTLSAVER.newDataHolder(); + dh.put(SAMPLE_EVENT_OBJECT, evt); + // This is effectively the same as saver.toXML(Object, Writer) except we get to provide the DataHolder + // Don't know why there is no method for this in the XStream class + try { + JTLSAVER.marshal(evt.getResult(), new XppDriver().createWriter(writer), dh); + } catch(RuntimeException e) { + throw new IllegalArgumentException("Failed marshalling:"+(evt.getResult() != null ? showDebuggingInfo(evt.getResult()) : "null"), e); + } + writer.write('\n'); + } + + /** + * + * @param result SampleResult + * @return String debugging information + */ + private static String showDebuggingInfo(SampleResult result) { + try { + return "class:"+result.getClass()+",content:"+ToStringBuilder.reflectionToString(result); + } catch(Exception e) { + return "Exception occured creating debug from event, message:"+e.getMessage(); + } + } + + /** + * @param elem test element + * @param writer output stream which must be created using {@link #getFileEncoding(String)} + * @throws IOException when writing data to output fails + */ + // Used by ResultCollector#recordStats() + public synchronized static void saveTestElement(TestElement elem, Writer writer) throws IOException { + JMXSAVER.toXML(elem, writer); // TODO should this be JTLSAVER? Only seems to be called by MonitorHealthVisualzer + writer.write('\n'); + } + + private static boolean versionsOK = true; + + // Extract version digits from String of the form #Revision: n.mm # + // (where # is actually $ above) + private static final String REVPFX = "$Revision: "; + private static final String REVSFX = " $"; // $NON-NLS-1$ + + private static String extractVersion(String rev) { + if (rev.length() > REVPFX.length() + REVSFX.length()) { + return rev.substring(REVPFX.length(), rev.length() - REVSFX.length()); + } + return rev; + } + +// private static void checkVersion(Class clazz, String expected) { +// +// String actual = "*NONE*"; // $NON-NLS-1$ +// try { +// actual = (String) clazz.getMethod("getVersion", null).invoke(null, null); +// actual = extractVersion(actual); +// } catch (Exception ignored) { +// // Not needed +// } +// if (0 != actual.compareTo(expected)) { +// versionsOK = false; +// log.warn("Version mismatch: expected '" + expected + "' found '" + actual + "' in " + clazz.getName()); +// } +// } + + // Routines for TestSaveService + static boolean checkPropertyVersion(){ + return SaveService.PROPVERSION.equals(SaveService.propertiesVersion); + } + + static boolean checkFileVersion(){ + return SaveService.FILEVERSION.equals(SaveService.fileVersion); + } + + // Allow test code to check for spurious class references + static List checkClasses(){ + final ClassLoader classLoader = SaveService.class.getClassLoader(); + List missingClasses = new ArrayList(); + //boolean OK = true; + for (Object clazz : classToAlias.keySet()) { + String name = (String) clazz; + if (!NameUpdater.isMapped(name)) {// don't bother checking class is present if it is to be updated + try { + Class.forName(name, false, classLoader); + } catch (ClassNotFoundException e) { + log.error("Unexpected entry in saveservice.properties; class does not exist and is not upgraded: "+name); + missingClasses.add(name); + } + } + } + return missingClasses; + } + + static boolean checkVersions() { + versionsOK = true; + // Disable converter version checks as they are more of a nuisance than helpful +// checkVersion(BooleanPropertyConverter.class, "493779"); // $NON-NLS-1$ +// checkVersion(HashTreeConverter.class, "514283"); // $NON-NLS-1$ +// checkVersion(IntegerPropertyConverter.class, "493779"); // $NON-NLS-1$ +// checkVersion(LongPropertyConverter.class, "493779"); // $NON-NLS-1$ +// checkVersion(MultiPropertyConverter.class, "514283"); // $NON-NLS-1$ +// checkVersion(SampleResultConverter.class, "571992"); // $NON-NLS-1$ +// +// // Not built until later, so need to use this method: +// try { +// checkVersion( +// Class.forName("org.apache.jmeter.protocol.http.util.HTTPResultConverter"), // $NON-NLS-1$ +// "514283"); // $NON-NLS-1$ +// } catch (ClassNotFoundException e) { +// versionsOK = false; +// log.warn(e.getLocalizedMessage()); +// } +// checkVersion(StringPropertyConverter.class, "493779"); // $NON-NLS-1$ +// checkVersion(TestElementConverter.class, "549987"); // $NON-NLS-1$ +// checkVersion(TestElementPropertyConverter.class, "549987"); // $NON-NLS-1$ +// checkVersion(ScriptWrapperConverter.class, "514283"); // $NON-NLS-1$ +// checkVersion(TestResultWrapperConverter.class, "514283"); // $NON-NLS-1$ +// checkVersion(SampleSaveConfigurationConverter.class,"549936"); // $NON-NLS-1$ + + if (!PROPVERSION.equalsIgnoreCase(propertiesVersion)) { + log.warn("Bad _version - expected " + PROPVERSION + ", found " + propertiesVersion + "."); + } +// if (!FILEVERSION.equalsIgnoreCase(fileVersion)) { +// log.warn("Bad _file_version - expected " + FILEVERSION + ", found " + fileVersion +"."); +// } + if (versionsOK) { + log.info("All converter versions present and correct"); + } + return versionsOK; + } + + /** + * Read results from JTL file. + * + * @param reader of the file + * @param resultCollectorHelper helper class to enable TestResultWrapperConverter to deliver the samples + * @throws IOException if an I/O error occurs + */ + public static void loadTestResults(InputStream reader, ResultCollectorHelper resultCollectorHelper) throws IOException { + // Get the InputReader to use + InputStreamReader inputStreamReader = getInputStreamReader(reader); + DataHolder dh = JTLSAVER.newDataHolder(); + dh.put(RESULTCOLLECTOR_HELPER_OBJECT, resultCollectorHelper); // Allow TestResultWrapper to feed back the samples + // This is effectively the same as saver.fromXML(InputStream) except we get to provide the DataHolder + // Don't know why there is no method for this in the XStream class + JTLSAVER.unmarshal(new XppDriver().createReader(reader), null, dh); + inputStreamReader.close(); + } + + /** + * Load a Test tree (JMX file) + * @param reader the JMX file as an {@link InputStream} + * @return the loaded tree or null if an error occurs + * @throws IOException if there is a problem reading the file or processing it + * @deprecated use {@link SaveService}{@link #loadTree(File)} + */ + public static HashTree loadTree(InputStream reader) throws IOException { + try { + return readTree(reader, null); + } catch(IllegalArgumentException e) { + log.error("Problem loading XML, message:"+e.getMessage(), e); + return null; + } finally { + JOrphanUtils.closeQuietly(reader); + } + } + + /** + * Load a Test tree (JMX file) + * @param file the JMX file + * @return the loaded tree + * @throws IOException if there is a problem reading the file or processing it + */ + public static HashTree loadTree(File file) throws IOException { + log.info("Loading file: " + file); + InputStream reader = null; + try { + reader = new FileInputStream(file); + return readTree(reader, file); + } finally { + JOrphanUtils.closeQuietly(reader); + } + } + + /** + * + * @param reader {@link InputStream} + * @param file the JMX file used only for debug, can be null + * @return the loaded tree + * @throws IOException if there is a problem reading the file or processing it + */ + private static final HashTree readTree(InputStream reader, File file) throws IOException { + if (!reader.markSupported()) { + reader = new BufferedInputStream(reader); + } + reader.mark(Integer.MAX_VALUE); + ScriptWrapper wrapper = null; + try { + // Get the InputReader to use + InputStreamReader inputStreamReader = getInputStreamReader(reader); + wrapper = (ScriptWrapper) JMXSAVER.fromXML(inputStreamReader); + inputStreamReader.close(); + if (wrapper == null){ + log.error("Problem loading XML: see above."); + return null; + } + return wrapper.testPlan; + } catch (CannotResolveClassException e) { + // FIXME We switching to JAVA7, use Multi-Catch Exceptions + if (e.getMessage().startsWith("node")) { + log.info("Problem loading XML, trying Avalon format"); + reader.reset(); + return OldSaveService.loadSubTree(reader); + } + if(file != null) { + throw new IllegalArgumentException("Problem loading XML from:'"+file.getAbsolutePath()+"', cannot determine class for element: " + e, e); + } else { + throw new IllegalArgumentException("Problem loading XML, cannot determine class for element: " + e, e); + } + } catch (NoClassDefFoundError e) { + if(file != null) { + throw new IllegalArgumentException("Problem loading XML from:'"+file.getAbsolutePath()+"', missing class "+e , e); + } else { + throw new IllegalArgumentException("Problem loading XML, missing class "+e , e); + } + } catch (ConversionException e) { + if(file != null) { + throw new IllegalArgumentException("Problem loading XML from:'"+file.getAbsolutePath()+"', conversion error "+e , e); + } else { + throw new IllegalArgumentException("Problem loading XML, conversion error "+e , e); + } + } + + } + private static InputStreamReader getInputStreamReader(InputStream inStream) { + // Check if we have a encoding to use from properties + Charset charset = getFileEncodingCharset(); + if(charset != null) { + return new InputStreamReader(inStream, charset); + } + else { + // We use the default character set encoding of the JRE + return new InputStreamReader(inStream); + } + } + + private static OutputStreamWriter getOutputStreamWriter(OutputStream outStream) { + // Check if we have a encoding to use from properties + Charset charset = getFileEncodingCharset(); + if(charset != null) { + return new OutputStreamWriter(outStream, charset); + } + else { + // We use the default character set encoding of the JRE + return new OutputStreamWriter(outStream); + } + } + + /** + * Returns the file Encoding specified in saveservice.properties or the default + * @param dflt value to return if file encoding was not provided + * + * @return file encoding or default + */ + // Used by ResultCollector when creating output files + public static String getFileEncoding(String dflt){ + if(fileEncoding != null && fileEncoding.length() > 0) { + return fileEncoding; + } + else { + return dflt; + } + } + + private static Charset getFileEncodingCharset() { + // Check if we have a encoding to use from properties + if(fileEncoding != null && fileEncoding.length() > 0) { + return Charset.forName(fileEncoding); + } + else { + // We use the default character set encoding of the JRE + return null; + } + } + + private static void writeXmlHeader(OutputStreamWriter writer) throws IOException { + // Write XML header if we have the charset to use for encoding + Charset charset = getFileEncodingCharset(); + if(charset != null) { + // We do not use getEncoding method of Writer, since that returns + // the historical name + String header = XML_HEADER.replaceAll("", charset.name()); + writer.write(header); + writer.write('\n'); + } + } + +// Normal output +// ---- Debugging information ---- +// required-type : org.apache.jorphan.collections.ListedHashTree +// cause-message : WebServiceSampler : WebServiceSampler +// class : org.apache.jmeter.save.ScriptWrapper +// message : WebServiceSampler : WebServiceSampler +// line number : 929 +// path : /jmeterTestPlan/hashTree/hashTree/hashTree[4]/hashTree[5]/WebServiceSampler +// cause-exception : com.thoughtworks.xstream.alias.CannotResolveClassException +// ------------------------------- + + /** + * Simplify getMessage() output from XStream ConversionException + * @param ce - ConversionException to analyse + * @return string with details of error + */ + public static String CEtoString(ConversionException ce){ + String msg = + "XStream ConversionException at line: " + ce.get("line number") + + "\n" + ce.get("message") + + "\nPerhaps a missing jar? See log file."; + return msg; + } + + public static String getPropertiesVersion() { + return propertiesVersion; + } + + public static String getVERSION() { + return VERSION; + } +} diff --git a/src/core/org/apache/jmeter/save/ScriptWrapper.java b/src/core/org/apache/jmeter/save/ScriptWrapper.java new file mode 100644 index 00000000000..96922628c7a --- /dev/null +++ b/src/core/org/apache/jmeter/save/ScriptWrapper.java @@ -0,0 +1,28 @@ +/* + * 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.save; + +import org.apache.jorphan.collections.HashTree; + +class ScriptWrapper { + // Used by ScriptWrapperConverter + String version = ""; + + HashTree testPlan; +} diff --git a/src/core/org/apache/jmeter/save/ScriptWrapperConverter.java b/src/core/org/apache/jmeter/save/ScriptWrapperConverter.java new file mode 100644 index 00000000000..6a0061c724c --- /dev/null +++ b/src/core/org/apache/jmeter/save/ScriptWrapperConverter.java @@ -0,0 +1,129 @@ +/* + * 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.save; + +import org.apache.jmeter.save.converters.ConversionHelp; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * Handles XStream conversion of Test Scripts + * + */ +public class ScriptWrapperConverter implements Converter { + + private static final String ATT_PROPERTIES = "properties"; // $NON-NLS-1$ + private static final String ATT_VERSION = "version"; // $NON-NLS-1$ + private static final String ATT_JMETER = "jmeter"; // $NON-NLS-1$ + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of the converter + */ + public static String getVersion() { + return "$Revision$"; // $NON-NLS-1$ + } + + private final Mapper classMapper; + + public ScriptWrapperConverter(Mapper classMapper) { + this.classMapper = classMapper; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass is not typed + return arg0.equals(ScriptWrapper.class); + } + + /** + * {@inheritDoc} + */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + ScriptWrapper wrap = (ScriptWrapper) arg0; + String version = SaveService.getVERSION(); + ConversionHelp.setOutVersion(version);// Ensure output follows version + writer.addAttribute(ATT_VERSION, version); + writer.addAttribute(ATT_PROPERTIES, SaveService.getPropertiesVersion()); + writer.addAttribute(ATT_JMETER, JMeterUtils.getJMeterVersion()); + writer.startNode(classMapper.serializedClass(wrap.testPlan.getClass())); + context.convertAnother(wrap.testPlan); + writer.endNode(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + ScriptWrapper wrap = new ScriptWrapper(); + wrap.version = reader.getAttribute(ATT_VERSION); + ConversionHelp.setInVersion(wrap.version);// Make sure decoding + // follows input file + reader.moveDown(); + // Catch errors and rethrow as ConversionException so we get location details + try { + wrap.testPlan = (HashTree) context.convertAnother(wrap, getNextType(reader)); + } catch (NoClassDefFoundError e) { + throw createConversionException(e); + } catch (Exception e) { + throw createConversionException(e); + } + return wrap; + } + + private ConversionException createConversionException(Throwable e) { + final ConversionException conversionException = new ConversionException(e); + StackTraceElement[] ste = e.getStackTrace(); + if (ste!=null){ + for(StackTraceElement top : ste){ + String className=top.getClassName(); + if (className.startsWith("org.apache.jmeter.")){ + conversionException.add("first-jmeter-class", top.toString()); + break; + } + } + } + return conversionException; + } + + protected Class getNextType(HierarchicalStreamReader reader) { + String classAttribute = reader.getAttribute(ConversionHelp.ATT_CLASS); + Class type; + if (classAttribute == null) { + type = classMapper.realClass(reader.getNodeName()); + } else { + type = classMapper.realClass(classAttribute); + } + return type; + } +} diff --git a/src/core/org/apache/jmeter/save/TestResultWrapper.java b/src/core/org/apache/jmeter/save/TestResultWrapper.java new file mode 100644 index 00000000000..6d2bda02704 --- /dev/null +++ b/src/core/org/apache/jmeter/save/TestResultWrapper.java @@ -0,0 +1,79 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 6, 2004 + */ +package org.apache.jmeter.save; + +import java.util.Collection; + +import org.apache.jmeter.samplers.SampleResult; + +public class TestResultWrapper { + private String version = ""; + + private Collection sampleResults; + + private long testStartTime; + + /** + * @return Returns the sampleResults. + */ + public Collection getSampleResults() { + return sampleResults; + } + + /** + * @param sampleResults + * The sampleResults to set. + */ + public void setSampleResults(Collection sampleResults) { + this.sampleResults = sampleResults; + } + + /** + * @return Returns the testStartTime. + */ + public long getTestStartTime() { + return testStartTime; + } + + /** + * @param testStartTime + * The testStartTime to set. + */ + public void setTestStartTime(long testStartTime) { + this.testStartTime = testStartTime; + } + + /** + * @return Returns the version. + */ + public String getVersion() { + return version; + } + + /** + * @param version + * The version to set. + */ + public void setVersion(String version) { + this.version = version; + } +} diff --git a/src/core/org/apache/jmeter/save/converters/BooleanPropertyConverter.java b/src/core/org/apache/jmeter/save/converters/BooleanPropertyConverter.java new file mode 100644 index 00000000000..1d37963a928 --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/BooleanPropertyConverter.java @@ -0,0 +1,66 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.BooleanProperty; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class BooleanPropertyConverter implements Converter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) {// superclass does not use types + return BooleanProperty.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext arg2) { + BooleanProperty prop = (BooleanProperty) obj; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + writer.setValue(prop.getStringValue()); + + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final String name = ConversionHelp.getPropertyName(reader, context); + if (name == null) { + return null; + } + BooleanProperty prop = new BooleanProperty(name, Boolean.valueOf(reader.getValue()).booleanValue()); + return prop; + } +} diff --git a/src/core/org/apache/jmeter/save/converters/ConversionHelp.java b/src/core/org/apache/jmeter/save/converters/ConversionHelp.java new file mode 100644 index 00000000000..a8a3e7b7dd3 --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/ConversionHelp.java @@ -0,0 +1,326 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 27, 2004 + */ +package org.apache.jmeter.save.converters; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.NameUpdater; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * Utility conversion routines for use with XStream + * + */ +public class ConversionHelp { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String CHAR_SET = "UTF-8"; //$NON-NLS-1$ + + // Attributes for TestElement and TestElementProperty + // Must all be unique + public static final String ATT_CLASS = "class"; //$NON-NLS-1$ + // Also used by PropertyConverter classes + public static final String ATT_NAME = "name"; // $NON-NLS-1$ + public static final String ATT_ELEMENT_TYPE = "elementType"; // $NON-NLS-1$ + + private static final String ATT_TE_ENABLED = "enabled"; //$NON-NLS-1$ + private static final String ATT_TE_TESTCLASS = "testclass"; //$NON-NLS-1$ + static final String ATT_TE_GUICLASS = "guiclass"; //$NON-NLS-1$ + private static final String ATT_TE_NAME = "testname"; //$NON-NLS-1$ + + + /* + * These must be set before reading/writing the XML. Rather a hack, but + * saves changing all the method calls to include an extra variable. + * + * AFAIK the variables should only be accessed from one thread, so no need to synchronize. + */ + private static String inVersion; + + private static String outVersion = "1.1"; // Default for writing//$NON-NLS-1$ + + public static void setInVersion(String v) { + inVersion = v; + } + + public static void setOutVersion(String v) { + outVersion = v; + } + + /** + * Encode a string (if necessary) for output to a JTL file. + * Strings are only encoded if the output version is 1.0, + * but nulls are always converted to the empty string. + * + * @param p string to encode + * @return encoded string (will never be null) + */ + public static String encode(String p) { + if (p == null) {// Nulls cannot be written using PrettyPrintWriter - they cause an NPE + return ""; // $NON-NLS-1$ + } + // Only encode strings if outVersion = 1.0 + if (!"1.0".equals(outVersion)) {//$NON-NLS-1$ + return p; + } + try { + String p1 = URLEncoder.encode(p, CHAR_SET); + return p1; + } catch (UnsupportedEncodingException e) { + log.warn("System doesn't support " + CHAR_SET, e); + return p; + } + } + + /** + * Decode a string if {@link #inVersion} equals 1.0 + * + * @param p + * the string to be decoded + * @return the newly decoded string + */ + public static String decode(String p) { + if (!"1.0".equals(inVersion)) {//$NON-NLS-1$ + return p; + } + // Only decode strings if inVersion = 1.0 + if (p == null) { + return null; + } + try { + return URLDecoder.decode(p, CHAR_SET); + } catch (UnsupportedEncodingException e) { + log.warn("System doesn't support " + CHAR_SET, e); + return p; + } + } + + /** + * Embed an array of bytes as a string with encoding in a + * xml-cdata section + * + * @param chars + * bytes to be encoded and embedded + * @param encoding + * the encoding to be used + * @return the encoded string embedded in a xml-cdata section + * @throws UnsupportedEncodingException + * when the bytes can not be encoded using encoding + */ + public static String cdata(byte[] chars, String encoding) throws UnsupportedEncodingException { + StringBuilder buf = new StringBuilder(""); + return buf.toString(); + } + + /** + * Names of properties that are handled specially + */ + private static final Map propertyToAttribute=new HashMap(); + + private static void mapentry(String prop, String att){ + propertyToAttribute.put(prop,att); + } + + static{ + mapentry(TestElement.NAME,ATT_TE_NAME); + mapentry(TestElement.GUI_CLASS,ATT_TE_GUICLASS);//$NON-NLS-1$ + mapentry(TestElement.TEST_CLASS,ATT_TE_TESTCLASS);//$NON-NLS-1$ + mapentry(TestElement.ENABLED,ATT_TE_ENABLED); + } + + private static void saveClass(TestElement el, HierarchicalStreamWriter writer, String prop){ + String clazz=el.getPropertyAsString(prop); + if (clazz.length()>0) { + writer.addAttribute(propertyToAttribute.get(prop),SaveService.classToAlias(clazz)); + } + } + + private static void restoreClass(TestElement el, HierarchicalStreamReader reader, String prop) { + String att=propertyToAttribute.get(prop); + String alias=reader.getAttribute(att); + if (alias!=null){ + alias=SaveService.aliasToClass(alias); + if (TestElement.GUI_CLASS.equals(prop)) { // mainly for TestElementConverter + alias = NameUpdater.getCurrentName(alias); + } + el.setProperty(prop,alias); + } + } + + private static void saveItem(TestElement el, HierarchicalStreamWriter writer, String prop, + boolean encode){ + String item=el.getPropertyAsString(prop); + if (item.length() > 0) { + if (encode) { + item=ConversionHelp.encode(item); + } + writer.addAttribute(propertyToAttribute.get(prop),item); + } + } + + private static void restoreItem(TestElement el, HierarchicalStreamReader reader, String prop, + boolean decode) { + String att=propertyToAttribute.get(prop); + String value=reader.getAttribute(att); + if (value!=null){ + if (decode) { + value=ConversionHelp.decode(value); + } + el.setProperty(prop,value); + } + } + + /** + * Check whether name specifies a special property + * + * @param name + * the name of the property to be checked + * @return true if name is the name of a special + * property + */ + public static boolean isSpecialProperty(String name) { + return propertyToAttribute.containsKey(name); + } + + /** + * Get the property name, updating it if necessary using {@link NameUpdater}. + * @param reader where to read the name attribute + * @param context the unmarshalling context + * + * @return the property name, may be null if the property has been deleted. + * @see #getUpgradePropertyName(String, UnmarshallingContext) + */ + public static String getPropertyName(HierarchicalStreamReader reader, UnmarshallingContext context) { + String name = ConversionHelp.decode(reader.getAttribute(ATT_NAME)); + return getUpgradePropertyName(name, context); + + } + + /** + * Get the property value, updating it if necessary using {@link NameUpdater}. + * + * Do not use for GUI_CLASS or TEST_CLASS. + * + * @param reader where to read the value + * @param context the unmarshalling context + * @param name the name of the property + * + * @return the property value, updated if necessary. + * @see #getUpgradePropertyValue(String, String, UnmarshallingContext) + */ + public static String getPropertyValue(HierarchicalStreamReader reader, UnmarshallingContext context, String name) { + String value = ConversionHelp.decode(reader.getValue()); + return getUpgradePropertyValue(name, value, context); + + } + + /** + * Update a property name using {@link NameUpdater}. + * @param name the original property name + * @param context the unmarshalling context + * + * @return the property name, may be null if the property has been deleted. + */ + public static String getUpgradePropertyName(String name, UnmarshallingContext context) { + String testClass = (String) context.get(SaveService.TEST_CLASS_NAME); + final String newName = NameUpdater.getCurrentName(name, testClass); + // Delete any properties whose name converts to the empty string + if (name.length() != 0 && newName.length()==0) { + return null; + } + return newName; + } + + /** + * Update a property value using {@link NameUpdater#getCurrentName(String, String, String)}. + * + * Do not use for GUI_CLASS or TEST_CLASS. + * + * @param name the original property name + * @param value the original property value + * @param context the unmarshalling context + * + * @return the property value, updated if necessary + */ + public static String getUpgradePropertyValue(String name, String value, UnmarshallingContext context) { + String testClass = (String) context.get(SaveService.TEST_CLASS_NAME); + return NameUpdater.getCurrentName(value, name, testClass); + } + + + /** + * Save the special properties: + *

    + *
  • TestElement.GUI_CLASS
  • + *
  • TestElement.TEST_CLASS
  • + *
  • TestElement.NAME
  • + *
  • TestElement.ENABLED
  • + *
+ * + * @param testElement + * element for which the special properties should be saved + * @param writer + * {@link HierarchicalStreamWriter} in which the special + * properties should be saved + */ + public static void saveSpecialProperties(TestElement testElement, HierarchicalStreamWriter writer) { + saveClass(testElement,writer,TestElement.GUI_CLASS); + saveClass(testElement,writer,TestElement.TEST_CLASS); + saveItem(testElement,writer,TestElement.NAME,true); + saveItem(testElement,writer,TestElement.ENABLED,false); + } + + /** + * Restore the special properties: + *
    + *
  • TestElement.GUI_CLASS
  • + *
  • TestElement.TEST_CLASS
  • + *
  • TestElement.NAME
  • + *
  • TestElement.ENABLED
  • + *
+ * + * @param testElement + * in which the special properties should be restored + * @param reader + * {@link HierarchicalStreamReader} from which the special + * properties should be restored + */ + public static void restoreSpecialProperties(TestElement testElement, HierarchicalStreamReader reader) { + restoreClass(testElement,reader,TestElement.GUI_CLASS); + restoreClass(testElement,reader,TestElement.TEST_CLASS); + restoreItem(testElement,reader,TestElement.NAME,true); + restoreItem(testElement,reader,TestElement.ENABLED,false); + } +} diff --git a/src/core/org/apache/jmeter/save/converters/HashTreeConverter.java b/src/core/org/apache/jmeter/save/converters/HashTreeConverter.java new file mode 100644 index 00000000000..efe6dcca048 --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/HashTreeConverter.java @@ -0,0 +1,84 @@ +/* + * 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.save.converters; + +import org.apache.jorphan.collections.HashTree; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class HashTreeConverter extends AbstractCollectionConverter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return HashTree.class.isAssignableFrom(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + HashTree tree = (HashTree) arg0; + for (Object item : tree.list()) { + writeItem(item, context, writer); + writeItem(tree.getTree(item), context, writer); + } + + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + boolean isKey = true; + Object current = null; + HashTree tree = (HashTree) createCollection(context.getRequiredType()); + while (reader.hasMoreChildren()) { + reader.moveDown(); + Object item = readItem(reader, context, tree); + if (isKey) { + tree.add(item); + current = item; + isKey = false; + } else { + tree.set(current, (HashTree) item); + isKey = true; + } + reader.moveUp(); + } + return tree; + } + + public HashTreeConverter(Mapper arg0) { + super(arg0); + } +} diff --git a/src/core/org/apache/jmeter/save/converters/IntegerPropertyConverter.java b/src/core/org/apache/jmeter/save/converters/IntegerPropertyConverter.java new file mode 100644 index 00000000000..801f594929e --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/IntegerPropertyConverter.java @@ -0,0 +1,65 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.IntegerProperty; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class IntegerPropertyConverter implements Converter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return IntegerProperty.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext arg2) { + IntegerProperty prop = (IntegerProperty) obj; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + writer.setValue(prop.getStringValue()); + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final String name = ConversionHelp.getPropertyName(reader, context); + if (name == null) { + return null; + } + IntegerProperty prop = new IntegerProperty(name, Integer.parseInt(reader.getValue())); + return prop; + } +} diff --git a/src/core/org/apache/jmeter/save/converters/LongPropertyConverter.java b/src/core/org/apache/jmeter/save/converters/LongPropertyConverter.java new file mode 100644 index 00000000000..7fc30723f63 --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/LongPropertyConverter.java @@ -0,0 +1,65 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.LongProperty; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class LongPropertyConverter implements Converter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return LongProperty.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext arg2) { + LongProperty prop = (LongProperty) obj; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + writer.setValue(prop.getStringValue()); + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final String name = ConversionHelp.getPropertyName(reader, context); + if (name == null) { + return null; + } + LongProperty prop = new LongProperty(name, Long.parseLong(reader.getValue())); + return prop; + } +} diff --git a/src/core/org/apache/jmeter/save/converters/MultiPropertyConverter.java b/src/core/org/apache/jmeter/save/converters/MultiPropertyConverter.java new file mode 100644 index 00000000000..64f1b7b0623 --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/MultiPropertyConverter.java @@ -0,0 +1,86 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.MapProperty; +import org.apache.jmeter.testelement.property.MultiProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class MultiPropertyConverter extends AbstractCollectionConverter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return CollectionProperty.class.equals(arg0) || MapProperty.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + MultiProperty prop = (MultiProperty) arg0; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + PropertyIterator iter = prop.iterator(); + while (iter.hasNext()) { + writeItem(iter.next(), context, writer); + } + + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + MultiProperty prop = (MultiProperty) createCollection(context.getRequiredType()); + prop.setName(ConversionHelp.decode(reader.getAttribute(ConversionHelp.ATT_NAME))); + while (reader.hasMoreChildren()) { + reader.moveDown(); + JMeterProperty subProp = (JMeterProperty) readItem(reader, context, prop); + if (subProp != null) { // could be null if it has been deleted via NameUpdater + prop.addProperty(subProp); + } + reader.moveUp(); + } + return prop; + } + + /** + * @param arg0 the mapper + */ + public MultiPropertyConverter(Mapper arg0) { + super(arg0); + } +} diff --git a/src/core/org/apache/jmeter/save/converters/SampleEventConverter.java b/src/core/org/apache/jmeter/save/converters/SampleEventConverter.java new file mode 100644 index 00000000000..ca4484b0b14 --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/SampleEventConverter.java @@ -0,0 +1,67 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.samplers.SampleEvent; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * XStream Converter for the SampleResult class + */ +public class SampleEventConverter implements Converter { + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return SampleEvent.class.equals(arg0); + } + + /** {@inheritDoc} */ + // TODO save hostname; save sample type (plain or http) + @Override + public void marshal(Object source, HierarchicalStreamWriter writer, + MarshallingContext context) { + SampleEvent evt = (SampleEvent) source; + Object res = evt.getResult(); + context.convertAnother(res); + } + + /** {@inheritDoc} */ + // TODO does not work yet; need to determine the sample type + @Override + public Object unmarshal(HierarchicalStreamReader reader, + UnmarshallingContext context) { + SampleEvent evt = new SampleEvent(); + return evt; + } +} diff --git a/src/core/org/apache/jmeter/save/converters/SampleResultConverter.java b/src/core/org/apache/jmeter/save/converters/SampleResultConverter.java new file mode 100644 index 00000000000..f0e9ebad99c --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/SampleResultConverter.java @@ -0,0 +1,486 @@ +/* + * 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.save.converters; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.save.SaveService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.Converter; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.mapper.Mapper; + +/** + * XStream Converter for the SampleResult class + */ +public class SampleResultConverter extends AbstractCollectionConverter { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String JAVA_LANG_STRING = "java.lang.String"; //$NON-NLS-1$ + private static final String ATT_CLASS = "class"; //$NON-NLS-1$ + + // Element tags. Must be unique. Keep sorted. + protected static final String TAG_COOKIES = "cookies"; //$NON-NLS-1$ + protected static final String TAG_METHOD = "method"; //$NON-NLS-1$ + protected static final String TAG_QUERY_STRING = "queryString"; //$NON-NLS-1$ + protected static final String TAG_REDIRECT_LOCATION = "redirectLocation"; //$NON-NLS-1$ + protected static final String TAG_REQUEST_HEADER = "requestHeader"; //$NON-NLS-1$ + + //NOT USED protected static final String TAG_URL = "requestUrl"; //$NON-NLS-1$ + + protected static final String TAG_RESPONSE_DATA = "responseData"; //$NON-NLS-1$ + protected static final String TAG_RESPONSE_HEADER = "responseHeader"; //$NON-NLS-1$ + protected static final String TAG_SAMPLER_DATA = "samplerData"; //$NON-NLS-1$ + protected static final String TAG_RESPONSE_FILE = "responseFile"; //$NON-NLS-1$ + + // samplerData attributes. Must be unique. Keep sorted by string value. + // Ensure the Listener documentation is updated when new attributes are added + private static final String ATT_BYTES = "by"; //$NON-NLS-1$ + private static final String ATT_DATA_ENCODING = "de"; //$NON-NLS-1$ + private static final String ATT_DATA_TYPE = "dt"; //$NON-NLS-1$ + private static final String ATT_ERROR_COUNT = "ec"; //$NON-NLS-1$ + private static final String ATT_HOSTNAME = "hn"; //$NON-NLS-1$ + private static final String ATT_LABEL = "lb"; //$NON-NLS-1$ + private static final String ATT_LATENCY = "lt"; //$NON-NLS-1$ + private static final String ATT_CONNECT_TIME = "ct"; //$NON-NLS-1$ + + private static final String ATT_ALL_THRDS = "na"; //$NON-NLS-1$ + private static final String ATT_GRP_THRDS = "ng"; //$NON-NLS-1$ + + // N.B. Originally the response code was saved with the code "rs" + // but retrieved with the code "rc". Changed to always use "rc", but + // allow for "rs" when restoring values. + private static final String ATT_RESPONSE_CODE = "rc"; //$NON-NLS-1$ + private static final String ATT_RESPONSE_MESSAGE = "rm"; //$NON-NLS-1$ + private static final String ATT_RESPONSE_CODE_OLD = "rs"; //$NON-NLS-1$ + + private static final String ATT_SUCCESS = "s"; //$NON-NLS-1$ + private static final String ATT_SAMPLE_COUNT = "sc"; //$NON-NLS-1$ + private static final String ATT_TIME = "t"; //$NON-NLS-1$ + private static final String ATT_IDLETIME = "it"; //$NON-NLS-1$ + private static final String ATT_THREADNAME = "tn"; //$NON-NLS-1$ + private static final String ATT_TIME_STAMP = "ts"; //$NON-NLS-1$ + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return SampleResult.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) { + SampleResult res = (SampleResult) obj; + SampleSaveConfiguration save = res.getSaveConfig(); + setAttributes(writer, context, res, save); + saveAssertions(writer, context, res, save); + saveSubResults(writer, context, res, save); + saveResponseHeaders(writer, context, res, save); + saveRequestHeaders(writer, context, res, save); + saveResponseData(writer, context, res, save); + saveSamplerData(writer, context, res, save); + } + + /** + * Save the data of the sample result to a stream + * + * @param writer + * stream to save objects into + * @param context + * context for xstream to allow nested objects + * @param res + * sample to be saved + * @param save + * configuration telling us what to save + */ + protected void saveSamplerData(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveSamplerData(res)) { + writeString(writer, TAG_SAMPLER_DATA, res.getSamplerData()); + } + if (save.saveUrl()) { + final URL url = res.getURL(); + if (url != null) { + writeItem(url, context, writer); + } + } + } + + /** + * Save the response from the sample result into the stream + * + * @param writer + * stream to save objects into + * @param context + * context for xstream to allow nested objects + * @param res + * sample to be saved + * @param save + * configuration telling us what to save + */ + protected void saveResponseData(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveResponseData(res)) { + writer.startNode(TAG_RESPONSE_DATA); + writer.addAttribute(ATT_CLASS, JAVA_LANG_STRING); + try { + if (SampleResult.TEXT.equals(res.getDataType())){ + writer.setValue(new String(res.getResponseData(), res.getDataEncodingWithDefault())); + } + // Otherwise don't save anything - no point + } catch (UnsupportedEncodingException e) { + writer.setValue("Unsupported encoding in response data, can't record."); + } + writer.endNode(); + } + if (save.saveFileName()){ + writer.startNode(TAG_RESPONSE_FILE); + writer.addAttribute(ATT_CLASS, JAVA_LANG_STRING); + writer.setValue(res.getResultFileName()); + writer.endNode(); + } + } + + /** + * Save request headers from the sample result into the stream + * + * @param writer + * stream to save objects into + * @param context + * context for xstream to allow nested objects + * @param res + * sample to be saved + * @param save + * configuration telling us what to save + */ + protected void saveRequestHeaders(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveRequestHeaders()) { + writeString(writer, TAG_REQUEST_HEADER, res.getRequestHeaders()); + } + } + + /** + * Save response headers from sample result into the stream + * + * @param writer + * stream to save objects into + * @param context + * context for xstream to allow nested objects + * @param res + * sample to be saved + * @param save + * configuration telling us what to save + */ + protected void saveResponseHeaders(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveResponseHeaders()) { + writeString(writer, TAG_RESPONSE_HEADER, res.getResponseHeaders()); + } + } + + /** + * Save sub results from sample result into the stream + * + * @param writer + * stream to save objects into + * @param context + * context for xstream to allow nested objects + * @param res + * sample to be saved + * @param save + * configuration telling us what to save + */ + protected void saveSubResults(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveSubresults()) { + SampleResult[] subResults = res.getSubResults(); + for (int i = 0; i < subResults.length; i++) { + subResults[i].setSaveConfig(save); + writeItem(subResults[i], context, writer); + } + } + } + + /** + * Save assertion results from the sample result into the stream + * + * @param writer + * stream to save objects into + * @param context + * context for xstream to allow nested objects + * @param res + * sample to be saved + * @param save + * configuration telling us what to save + */ + protected void saveAssertions(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveAssertions()) { + AssertionResult[] assertionResults = res.getAssertionResults(); + for (int i = 0; i < assertionResults.length; i++) { + writeItem(assertionResults[i], context, writer); + } + } + } + + /** + * Save attributes of the sample result to the stream + * + * @param writer + * stream to save objects into + * @param context + * context for xstream to allow nested objects + * @param res + * sample to be saved + * @param save + * configuration telling us what to save + */ + protected void setAttributes(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveTime()) { + writer.addAttribute(ATT_TIME, Long.toString(res.getTime())); + } + if (save.saveIdleTime()) { + writer.addAttribute(ATT_IDLETIME, Long.toString(res.getIdleTime())); + } + if (save.saveLatency()) { + writer.addAttribute(ATT_LATENCY, Long.toString(res.getLatency())); + } + if (save.saveConnectTime()) { + writer.addAttribute(ATT_CONNECT_TIME, Long.toString(res.getConnectTime())); + } + if (save.saveTimestamp()) { + writer.addAttribute(ATT_TIME_STAMP, Long.toString(res.getTimeStamp())); + } + if (save.saveSuccess()) { + writer.addAttribute(ATT_SUCCESS, Boolean.toString(res.isSuccessful())); + } + if (save.saveLabel()) { + writer.addAttribute(ATT_LABEL, ConversionHelp.encode(res.getSampleLabel())); + } + if (save.saveCode()) { + writer.addAttribute(ATT_RESPONSE_CODE, ConversionHelp.encode(res.getResponseCode())); + } + if (save.saveMessage()) { + writer.addAttribute(ATT_RESPONSE_MESSAGE, ConversionHelp.encode(res.getResponseMessage())); + } + if (save.saveThreadName()) { + writer.addAttribute(ATT_THREADNAME, ConversionHelp.encode(res.getThreadName())); + } + if (save.saveDataType()) { + writer.addAttribute(ATT_DATA_TYPE, ConversionHelp.encode(res.getDataType())); + } + if (save.saveEncoding()) { + writer.addAttribute(ATT_DATA_ENCODING, ConversionHelp.encode(res.getDataEncodingNoDefault())); + } + if (save.saveBytes()) { + writer.addAttribute(ATT_BYTES, String.valueOf(res.getBytes())); + } + if (save.saveSampleCount()){ + writer.addAttribute(ATT_SAMPLE_COUNT, String.valueOf(res.getSampleCount())); + writer.addAttribute(ATT_ERROR_COUNT, String.valueOf(res.getErrorCount())); + } + if (save.saveThreadCounts()){ + writer.addAttribute(ATT_GRP_THRDS, String.valueOf(res.getGroupThreads())); + writer.addAttribute(ATT_ALL_THRDS, String.valueOf(res.getAllThreads())); + } + SampleEvent event = (SampleEvent) context.get(SaveService.SAMPLE_EVENT_OBJECT); + if (event != null) { + if (save.saveHostname()){ + writer.addAttribute(ATT_HOSTNAME, event.getHostname()); + } + for (int i = 0; i < SampleEvent.getVarCount(); i++){ + writer.addAttribute(SampleEvent.getVarName(i), ConversionHelp.encode(event.getVarValue(i))); + } + } + } + + /** + * Write a tag with with a content of value to the + * writer + * + * @param writer + * writer to write the tag into + * @param tag + * name of the tag to use + * @param value + * content for tag + */ + protected void writeString(HierarchicalStreamWriter writer, String tag, String value) { + if (value != null) { + writer.startNode(tag); + writer.addAttribute(ATT_CLASS, JAVA_LANG_STRING); + writer.setValue(value); + writer.endNode(); + } + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + SampleResult res = (SampleResult) createCollection(context.getRequiredType()); + retrieveAttributes(reader, context, res); + while (reader.hasMoreChildren()) { + reader.moveDown(); + Object subItem = readItem(reader, context, res); + retrieveItem(reader, context, res, subItem); + reader.moveUp(); + } + + // If we have a file, but no data, then read the file + String resultFileName = res.getResultFileName(); + if (resultFileName.length()>0 + && res.getResponseData().length == 0) { + readFile(resultFileName,res); + } + return res; + } + + /** + * + * @param reader stream from which the objects should be read + * @param context context for xstream to allow nested objects + * @param res sample result into which the information should be retrieved + * @param subItem sub item which should be added into res + * @return true if the item was processed (for HTTPResultConverter) + */ + protected boolean retrieveItem(HierarchicalStreamReader reader, UnmarshallingContext context, SampleResult res, + Object subItem) { + String nodeName = reader.getNodeName(); + if (subItem instanceof AssertionResult) { + res.addAssertionResult((AssertionResult) subItem); + } else if (subItem instanceof SampleResult) { + res.storeSubResult((SampleResult) subItem); + } else if (nodeName.equals(TAG_RESPONSE_HEADER)) { + res.setResponseHeaders((String) subItem); + } else if (nodeName.equals(TAG_REQUEST_HEADER)) { + res.setRequestHeaders((String) subItem); + } else if (nodeName.equals(TAG_RESPONSE_DATA)) { + final String responseData = (String) subItem; + if (responseData.length() > 0) { + final String dataEncoding = res.getDataEncodingWithDefault(); + try { + res.setResponseData(responseData.getBytes(dataEncoding)); + } catch (UnsupportedEncodingException e) { + res.setResponseData(("Can't support the char set: " + dataEncoding), null); + res.setDataType(SampleResult.TEXT); + } + } + } else if (nodeName.equals(TAG_SAMPLER_DATA)) { + res.setSamplerData((String) subItem); + } else if (nodeName.equals(TAG_RESPONSE_FILE)) { + res.setResultFileName((String) subItem); + // Don't try restoring the URL TODO: why not? + } else { + return false; + } + return true; + } + + /** + * @param reader stream to read objects from + * @param context context for xstream to allow nested objects + * @param res sample result on which the attributes should be set + */ + protected void retrieveAttributes(HierarchicalStreamReader reader, UnmarshallingContext context, SampleResult res) { + res.setSampleLabel(ConversionHelp.decode(reader.getAttribute(ATT_LABEL))); + res.setDataEncoding(ConversionHelp.decode(reader.getAttribute(ATT_DATA_ENCODING))); + res.setDataType(ConversionHelp.decode(reader.getAttribute(ATT_DATA_TYPE))); + String oldrc=reader.getAttribute(ATT_RESPONSE_CODE_OLD); + if (oldrc!=null) { + res.setResponseCode(ConversionHelp.decode(oldrc)); + } else { + res.setResponseCode(ConversionHelp.decode(reader.getAttribute(ATT_RESPONSE_CODE))); + } + res.setResponseMessage(ConversionHelp.decode(reader.getAttribute(ATT_RESPONSE_MESSAGE))); + res.setSuccessful(Converter.getBoolean(reader.getAttribute(ATT_SUCCESS), true)); + res.setThreadName(ConversionHelp.decode(reader.getAttribute(ATT_THREADNAME))); + res.setStampAndTime(Converter.getLong(reader.getAttribute(ATT_TIME_STAMP)), + Converter.getLong(reader.getAttribute(ATT_TIME))); + res.setIdleTime(Converter.getLong(reader.getAttribute(ATT_IDLETIME))); + res.setLatency(Converter.getLong(reader.getAttribute(ATT_LATENCY))); + res.setConnectTime(Converter.getLong(reader.getAttribute(ATT_CONNECT_TIME))); + res.setBytes(Converter.getInt(reader.getAttribute(ATT_BYTES))); + res.setSampleCount(Converter.getInt(reader.getAttribute(ATT_SAMPLE_COUNT),1)); // default is 1 + res.setErrorCount(Converter.getInt(reader.getAttribute(ATT_ERROR_COUNT),0)); // default is 0 + res.setGroupThreads(Converter.getInt(reader.getAttribute(ATT_GRP_THRDS))); + res.setAllThreads(Converter.getInt(reader.getAttribute(ATT_ALL_THRDS))); + } + + protected void readFile(String resultFileName, SampleResult res) { + File in = null; + InputStream fis = null; + try { + in = new File(resultFileName); + fis = new BufferedInputStream(new FileInputStream(in)); + ByteArrayOutputStream outstream = new ByteArrayOutputStream(res.getBytes()); + byte[] buffer = new byte[4096]; + int len; + while ((len = fis.read(buffer)) > 0) { + outstream.write(buffer, 0, len); + } + outstream.close(); + res.setResponseData(outstream.toByteArray()); + } catch (FileNotFoundException e) { + log.warn(e.getLocalizedMessage()); + } catch (IOException e) { + log.warn(e.getLocalizedMessage()); + } finally { + IOUtils.closeQuietly(fis); + } + } + + + /** + * @param arg0 the mapper + */ + public SampleResultConverter(Mapper arg0) { + super(arg0); + } +} diff --git a/src/core/org/apache/jmeter/save/converters/SampleSaveConfigurationConverter.java b/src/core/org/apache/jmeter/save/converters/SampleSaveConfigurationConverter.java new file mode 100644 index 00000000000..b67fbfb536e --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/SampleSaveConfigurationConverter.java @@ -0,0 +1,173 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.samplers.SampleSaveConfiguration; + +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; +import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; +import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; +import com.thoughtworks.xstream.core.JVM; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.mapper.MapperWrapper; + +/* + * Allow new fields to be added to the SampleSaveConfiguration without + * changing the output JMX file unless it is necessary. + * + * TODO work out how to make shouldSerializeMember() conditionally return true. + */ +public class SampleSaveConfigurationConverter extends ReflectionConverter { + + private static final ReflectionProvider rp; + + static { + ReflectionProvider tmp; + try { + tmp = JVM.newReflectionProvider(); + } catch (NullPointerException e) {// Bug in above method + tmp = new PureJavaReflectionProvider(); + } + rp = tmp; + } + + private static final String TRUE = "true"; // $NON-NLS-1$ + + // N.B. These must agree with the new member names in SampleSaveConfiguration + private static final String NODE_FILENAME = "fileName"; // $NON-NLS-1$ + private static final String NODE_HOSTNAME = "hostname"; // $NON-NLS-1$ + private static final String NODE_URL = "url"; // $NON-NLS-1$ + private static final String NODE_BYTES = "bytes"; // $NON-NLS-1$ + private static final String NODE_THREAD_COUNT = "threadCounts"; // $NON-NLS-1$ + private static final String NODE_SAMPLE_COUNT = "sampleCount"; // $NON-NLS-1$ + private static final String NODE_IDLE_TIME = "idleTime"; // $NON-NLS-1$ + private static final String NODE_CONNECT_TIME = "connectTime"; // $NON-NLS-1$ + + // Additional member names which are currently not written out + private static final String NODE_DELIMITER = "delimiter"; // $NON-NLS-1$ + private static final String NODE_PRINTMS = "printMilliseconds"; // $NON-NLS-1$ + + + static class MyWrapper extends MapperWrapper{ + + public MyWrapper(Mapper wrapped) { + super(wrapped); + } + + /** {@inheritDoc} */ + @Override + public boolean shouldSerializeMember( + @SuppressWarnings("rawtypes") // superclass does not use types + Class definedIn, + String fieldName) { + if (SampleSaveConfiguration.class != definedIn) { return true; } + // These are new fields; not saved unless true + if (fieldName.equals(NODE_BYTES)) { return false; } + if (fieldName.equals(NODE_URL)) { return false; } + if (fieldName.equals(NODE_FILENAME)) { return false; } + if (fieldName.equals(NODE_HOSTNAME)) { return false; } + if (fieldName.equals(NODE_THREAD_COUNT)) { return false; } + if (fieldName.equals(NODE_SAMPLE_COUNT)) { return false; } + if (fieldName.equals(NODE_IDLE_TIME)) { return false; } + if (fieldName.equals(NODE_CONNECT_TIME)) { return false; } + + // These fields are not currently saved or restored + if (fieldName.equals(NODE_DELIMITER)) { return false; } + if (fieldName.equals(NODE_PRINTMS)) { return false; } + return true; + } + } + + public SampleSaveConfigurationConverter(Mapper arg0) { + super(new MyWrapper(arg0),rp); + } + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { + return SampleSaveConfiguration.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) { + super.marshal(obj, writer, context); // Save most things + + SampleSaveConfiguration prop = (SampleSaveConfiguration) obj; + + // Save the new fields - but only if they are not the default + createNode(writer,prop.saveBytes(),NODE_BYTES); + createNode(writer,prop.saveUrl(),NODE_URL); + createNode(writer,prop.saveFileName(),NODE_FILENAME); + createNode(writer,prop.saveHostname(),NODE_HOSTNAME); + createNode(writer,prop.saveThreadCounts(),NODE_THREAD_COUNT); + createNode(writer,prop.saveSampleCount(),NODE_SAMPLE_COUNT); + createNode(writer,prop.saveIdleTime(),NODE_IDLE_TIME); + createNode(writer, prop.saveConnectTime(), NODE_CONNECT_TIME); + } + + // Helper method to simplify marshall routine + private void createNode(HierarchicalStreamWriter writer, boolean save, String node) { + if (!save) { + return; + } + writer.startNode(node); + writer.setValue(TRUE); + writer.endNode(); + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final Class thisClass = SampleSaveConfiguration.class; + final Class requiredType = context.getRequiredType(); + if (requiredType != thisClass) { + throw new IllegalArgumentException("Unexpected class: "+requiredType.getName()); + } + SampleSaveConfiguration result = new SampleSaveConfiguration(); + result.setBytes(false); // Maintain backward compatibility (bytes was not in the JMX file) + while (reader.hasMoreChildren()) { + reader.moveDown(); + String nn = reader.getNodeName(); + if (!"formatter".equals(nn)){// Skip formatter (if present) bug 42674 $NON-NLS-1$ + String fieldName = mapper.realMember(thisClass, nn); + java.lang.reflect.Field field = reflectionProvider.getField(thisClass,fieldName); + Class type = field.getType(); + Object value = unmarshallField(context, result, type, field); + reflectionProvider.writeField(result, nn, value, thisClass); + } + reader.moveUp(); + } + return result; + } +} diff --git a/src/core/org/apache/jmeter/save/converters/StringPropertyConverter.java b/src/core/org/apache/jmeter/save/converters/StringPropertyConverter.java new file mode 100644 index 00000000000..860cf5ade76 --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/StringPropertyConverter.java @@ -0,0 +1,66 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.StringProperty; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class StringPropertyConverter implements Converter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return StringProperty.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext arg2) { + StringProperty prop = (StringProperty) obj; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + writer.setValue(ConversionHelp.encode(prop.getStringValue())); + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final String name = ConversionHelp.getPropertyName(reader, context); + if (name == null) { + return null; + } + final String value = ConversionHelp.getPropertyValue(reader, context, name); + StringProperty prop = new StringProperty(name, value); + return prop; + } +} diff --git a/src/core/org/apache/jmeter/save/converters/TestElementConverter.java b/src/core/org/apache/jmeter/save/converters/TestElementConverter.java new file mode 100644 index 00000000000..2117f6b3c41 --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/TestElementConverter.java @@ -0,0 +1,131 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.NameUpdater; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class TestElementConverter extends AbstractCollectionConverter { + private static final Logger log = LoggingManager.getLoggerForClass(); + + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return TestElement.class.isAssignableFrom(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + TestElement el = (TestElement) arg0; + if (SaveService.IS_TESTPLAN_FORMAT_22){ + ConversionHelp.saveSpecialProperties(el,writer); + } + PropertyIterator iter = el.propertyIterator(); + while (iter.hasNext()) { + JMeterProperty jmp=iter.next(); + // Skip special properties if required + if (!SaveService.IS_TESTPLAN_FORMAT_22 || !ConversionHelp.isSpecialProperty(jmp.getName())) { + // Don't save empty comments - except for the TestPlan (to maintain compatibility) + if (!( + TestElement.COMMENTS.equals(jmp.getName()) + && jmp.getStringValue().length()==0 + && !el.getClass().equals(TestPlan.class) + )) + { + writeItem(jmp, context, writer); + } + } + } + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + String classAttribute = reader.getAttribute(ConversionHelp.ATT_CLASS); + Class type; + if (classAttribute == null) { + type = mapper().realClass(reader.getNodeName()); + } else { + type = mapper().realClass(classAttribute); + } + // Update the test class name if necessary (Bug 52466) + String inputName = type.getName(); + String targetName = inputName; + String guiClassName = SaveService.aliasToClass(reader.getAttribute(ConversionHelp.ATT_TE_GUICLASS)); + targetName = NameUpdater.getCurrentTestName(inputName, guiClassName); + if (!targetName.equals(inputName)) { // remap the class name + type = mapper().realClass(targetName); + } + context.put(SaveService.TEST_CLASS_NAME, targetName); // needed by property converters (Bug 52466) + try { + TestElement el = (TestElement) type.newInstance(); + // No need to check version, just process the attributes if present + ConversionHelp.restoreSpecialProperties(el, reader); + // Slight hack - we need to ensure the TestClass is not reset by the previous call + el.setProperty(TestElement.TEST_CLASS, targetName); + while (reader.hasMoreChildren()) { + reader.moveDown(); + JMeterProperty prop = (JMeterProperty) readItem(reader, context, el); + if (prop != null) { // could be null if it has been deleted via NameUpdater + el.setProperty(prop); + } + reader.moveUp(); + } + return el; + } catch (InstantiationException e) { + log.error("TestElement not instantiable: " + type, e); + return null; + } catch (IllegalAccessException e) { + log.error("TestElement not instantiable: " + type, e); + return null; + } + } + + /** + * @param arg0 the mapper + */ + public TestElementConverter(Mapper arg0) { + super(arg0); + } +} diff --git a/src/core/org/apache/jmeter/save/converters/TestElementPropertyConverter.java b/src/core/org/apache/jmeter/save/converters/TestElementPropertyConverter.java new file mode 100644 index 00000000000..007a2097aea --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/TestElementPropertyConverter.java @@ -0,0 +1,146 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class TestElementPropertyConverter extends AbstractCollectionConverter { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String HEADER_CLASSNAME + = "org.apache.jmeter.protocol.http.control.Header"; // $NON-NLS-1$ + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return TestElementProperty.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + TestElementProperty prop = (TestElementProperty) arg0; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + Class clazz = prop.getObjectValue().getClass(); + writer.addAttribute(ConversionHelp.ATT_ELEMENT_TYPE, + SaveService.IS_TESTPLAN_FORMAT_22 ? mapper().serializedClass(clazz) : clazz.getName()); + if (SaveService.IS_TESTPLAN_FORMAT_22){ + TestElement te = (TestElement)prop.getObjectValue(); + ConversionHelp.saveSpecialProperties(te,writer); + } + PropertyIterator iter = prop.iterator(); + while (iter.hasNext()) { + JMeterProperty jmp=iter.next(); + // Skip special properties if required + if (!SaveService.IS_TESTPLAN_FORMAT_22 || !ConversionHelp.isSpecialProperty(jmp.getName())) + { + // Don't save empty comments + if (!(TestElement.COMMENTS.equals(jmp.getName()) + && jmp.getStringValue().length()==0)) + { + writeItem(jmp, context, writer); + } + } + } + //TODO clazz is probably always the same as testclass + } + + /* + * TODO - convert to work more like upgrade.properties/NameUpdater.java + * + * Special processing is carried out for the Header Class The String + * property TestElement.name is converted to Header.name for example: + * Mozilla%2F4.0+%28compatible%3B+MSIE+5.5%3B+Windows+98%29 + * User-Agent + * becomes Mozilla%2F4.0+%28compatible%3B+MSIE+5.5%3B+Windows+98%29 + * User-Agent + */ + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + try { + TestElementProperty prop = (TestElementProperty) createCollection(context.getRequiredType()); + prop.setName(ConversionHelp.decode(reader.getAttribute(ConversionHelp.ATT_NAME))); + String element = reader.getAttribute(ConversionHelp.ATT_ELEMENT_TYPE); + boolean isHeader = HEADER_CLASSNAME.equals(element); + prop.setObjectValue(mapper().realClass(element).newInstance());// Always decode + TestElement te = (TestElement)prop.getObjectValue(); + // No need to check version, just process the attributes if present + ConversionHelp.restoreSpecialProperties(te, reader); + while (reader.hasMoreChildren()) { + reader.moveDown(); + JMeterProperty subProp = (JMeterProperty) readItem(reader, context, prop); + if (subProp != null) { // could be null if it has been deleted via NameUpdater + if (isHeader) { + String name = subProp.getName(); + if (TestElement.NAME.equals(name)) { + subProp.setName("Header.name");// $NON-NLS-1$ + // Must be same as Header.HNAME - but that is built + // later + } + } + prop.addProperty(subProp); + } + reader.moveUp(); + } + return prop; + } catch (InstantiationException e) { + log.error("Couldn't unmarshall TestElementProperty", e); + return new TestElementProperty("ERROR", new ConfigTestElement());// $NON-NLS-1$ + } catch (IllegalAccessException e) { + log.error("Couldn't unmarshall TestElementProperty", e); + return new TestElementProperty("ERROR", new ConfigTestElement());// $NON-NLS-1$ + } + } + + /** + * @param arg0 the mapper + */ + public TestElementPropertyConverter(Mapper arg0) { + super(arg0); + } +} diff --git a/src/core/org/apache/jmeter/save/converters/TestResultWrapperConverter.java b/src/core/org/apache/jmeter/save/converters/TestResultWrapperConverter.java new file mode 100644 index 00000000000..ea055208dda --- /dev/null +++ b/src/core/org/apache/jmeter/save/converters/TestResultWrapperConverter.java @@ -0,0 +1,109 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 7, 2004 + */ +package org.apache.jmeter.save.converters; + +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.jmeter.reporters.ResultCollectorHelper; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.save.TestResultWrapper; + +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.mapper.Mapper; + +/** + * XStream Class to convert TestResultWrapper + * + */ +public class TestResultWrapperConverter extends AbstractCollectionConverter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this converter + */ + public static String getVersion() { + return "$Revision$"; //$NON-NLS-1$ + } + + /** + * @param arg0 the {@link Mapper} to be used + */ + public TestResultWrapperConverter(Mapper arg0) { + super(arg0); + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return TestResultWrapper.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter arg1, MarshallingContext arg2) { + // Not used, as the element is generated by the + // ResultCollector class + } + + /** + * Read test results from JTL files and pass them to the visualiser directly. + * If the ResultCollector helper object is defined, then pass the samples to that + * rather than adding them to the test result wrapper. + * + * @return the test result wrapper (may be empty) + * + * @see com.thoughtworks.xstream.converters.Converter#unmarshal(com.thoughtworks.xstream.io.HierarchicalStreamReader, + * com.thoughtworks.xstream.converters.UnmarshallingContext) + */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + TestResultWrapper results = new TestResultWrapper(); + Collection samples = new ArrayList(); + String ver = reader.getAttribute("version"); //$NON-NLS-1$ + if (ver == null || ver.length() == 0) { + ver = "1.0"; //$NON-NLS-1$ + } + results.setVersion(ver); + ConversionHelp.setInVersion(ver);// Make sure decoding follows input file + final ResultCollectorHelper resultCollectorHelper = (ResultCollectorHelper) context.get(SaveService.RESULTCOLLECTOR_HELPER_OBJECT); + while (reader.hasMoreChildren()) { + reader.moveDown(); + SampleResult sample = (SampleResult) readItem(reader, context, results); + if (resultCollectorHelper != null) { + resultCollectorHelper.add(sample); + } else { + samples.add(sample); + } + reader.moveUp(); + } + results.setSampleResults(samples); + return results; + } +} diff --git a/src/core/org/apache/jmeter/services/FileServer.java b/src/core/org/apache/jmeter/services/FileServer.java new file mode 100644 index 00000000000..c64edbe13df --- /dev/null +++ b/src/core/org/apache/jmeter/services/FileServer.java @@ -0,0 +1,556 @@ +/* + * 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. + * + */ + +/* + * Created on Oct 19, 2004 + */ +package org.apache.jmeter.services; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import org.apache.commons.collections.ArrayStack; +import org.apache.jmeter.gui.JMeterFileFilter; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * + * The point of this class is to provide thread-safe access to files, and to + * provide some simplifying assumptions about where to find files and how to + * name them. For instance, putting supporting files in the same directory as + * the saved test plan file allows users to refer to the file with just it's + * name - this FileServer class will find the file without a problem. + * Eventually, I want all in-test file access to be done through here, with the + * goal of packaging up entire test plans as a directory structure that can be + * sent via rmi to remote servers (currently, one must make sure the remote + * server has all support files in a relative-same location) and to package up + * test plans to execute on unknown boxes that only have Java installed. + */ +public class FileServer { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The default base used for resolving relative files, i.e.
+ * {@code System.getProperty("user.dir")} + */ + private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$ + + /** Default base prefix: {@value} */ + private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$ + + private static final String BASE_PREFIX = + JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$ + BASE_PREFIX_DEFAULT); + + //@GuardedBy("this") + private File base; + + //@GuardedBy("this") NOTE this also guards against possible window in checkForOpenFiles() + private final Map files = new HashMap(); + + private static final FileServer server = new FileServer(); + + private final Random random = new Random(); + + // volatile needed to ensure safe publication + private volatile String scriptName; + + // Cannot be instantiated + private FileServer() { + base = new File(DEFAULT_BASE); + log.info("Default base='"+DEFAULT_BASE+"'"); + } + + /** + * @return the singleton instance of the server. + */ + public static FileServer getFileServer() { + return server; + } + + /** + * Resets the current base to {@link #DEFAULT_BASE}. + */ + public synchronized void resetBase() { + checkForOpenFiles(); + base = new File(DEFAULT_BASE); + log.info("Reset base to'"+base+"'"); + } + + /** + * Sets the current base directory for relative file names from the provided path. + * If the path does not refer to an existing directory, then its parent is used. + * Normally the provided path is a file, so using the parent directory is appropriate. + * + * @param basedir the path to set, or {@code null} if the GUI is being cleared + * @throws IllegalStateException if files are still open + */ + public synchronized void setBasedir(String basedir) { + checkForOpenFiles(); // TODO should this be called if basedir == null? + if (basedir != null) { + File newBase = new File(basedir); + if (!newBase.isDirectory()) { + newBase = newBase.getParentFile(); + } + base = newBase; + log.info("Set new base='"+base+"'"); + } + } + + /** + * Sets the current base directory for relative file names from the provided script file. + * The parameter is assumed to be the path to a JMX file, so the base directory is derived + * from its parent. + * + * @param scriptPath the path of the script file; must be not be {@code null} + * @throws IllegalStateException if files are still open + * @throws IllegalArgumentException if scriptPath parameter is null + */ + public synchronized void setBaseForScript(File scriptPath) { + if (scriptPath == null){ + throw new IllegalArgumentException("scriptPath must not be null"); + } + setScriptName(scriptPath.getName()); + // getParentFile() may not work on relative paths + setBase(scriptPath.getAbsoluteFile().getParentFile()); + } + + /** + * Sets the current base directory for relative file names. + * + * @param jmxBase the path of the script file base directory, cannot be null + * @throws IllegalStateException if files are still open + * @throws IllegalArgumentException if {@code basepath} is null + */ + public synchronized void setBase(File jmxBase) { + if (jmxBase == null) { + throw new IllegalArgumentException("jmxBase must not be null"); + } + checkForOpenFiles(); + base = jmxBase; + log.info("Set new base='"+base+"'"); + } + + /** + * Check if there are entries in use. + *

+ * Caller must ensure that access to the files map is single-threaded as + * there is a window between checking the files Map and clearing it. + * + * @throws IllegalStateException if there are any entries still in use + */ + private void checkForOpenFiles() throws IllegalStateException { + if (filesOpen()) { // checks for entries in use + throw new IllegalStateException("Files are still open, cannot change base directory"); + } + files.clear(); // tidy up any unused entries + } + + public synchronized String getBaseDir() { + return base.getAbsolutePath(); + } + + public static String getDefaultBase(){ + return DEFAULT_BASE; + } + + /** + * Calculates the relative path from {@link #DEFAULT_BASE} to the current base, + * which must be the same as or a child of the default. + * + * @return the relative path, or {@code "."} if the path cannot be determined + */ + public synchronized File getBaseDirRelative() { + // Must first convert to absolute path names to ensure parents are available + File parent = new File(DEFAULT_BASE).getAbsoluteFile(); + File f = base.getAbsoluteFile(); + ArrayStack l = new ArrayStack(); + while (f != null) { + if (f.equals(parent)){ + if (l.isEmpty()){ + break; + } + File rel = new File((String) l.pop()); + while(!l.isEmpty()) { + rel = new File(rel, (String) l.pop()); + } + return rel; + } + l.push(f.getName()); + f = f.getParentFile(); + } + return new File("."); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + */ + public void reserveFile(String filename) { + reserveFile(filename,null); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + */ + public void reserveFile(String filename, String charsetName) { + reserveFile(filename, charsetName, filename, false); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + * @param alias - the name to be used to access the object (must not be null) + */ + public void reserveFile(String filename, String charsetName, String alias) { + reserveFile(filename, charsetName, alias, false); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + * @param alias - the name to be used to access the object (must not be null) + * @param hasHeader true if the file has a header line describing the contents + * @return the header line; may be null + */ + public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) { + if (filename == null){ + throw new IllegalArgumentException("Filename must not be null"); + } + if (alias == null){ + throw new IllegalArgumentException("Alias must not be null"); + } + FileEntry fileEntry = files.get(alias); + if (fileEntry == null) { + File f = new File(filename); + fileEntry = + new FileEntry(f.isAbsolute() ? f : new File(base, filename),null,charsetName); + if (filename.equals(alias)){ + log.info("Stored: "+filename); + } else { + log.info("Stored: "+filename+" Alias: "+alias); + } + files.put(alias, fileEntry); + if (hasHeader){ + try { + fileEntry.headerLine=readLine(alias, false); + } catch (IOException e) { + fileEntry.exception = e; + throw new IllegalArgumentException("Could not read file header line",e); + } + if (fileEntry.headerLine == null) { + fileEntry.exception = new EOFException("File is empty: " + fileEntry.file); + } + } + } + if (hasHeader && fileEntry.headerLine == null) { + throw new IllegalArgumentException("Could not read file header line", fileEntry.exception); + } + return fileEntry.headerLine; + } + + /** + * Get the next line of the named file, recycle by default. + * + * @param filename the filename or alias that was used to reserve the file + * @return String containing the next line in the file + * @throws IOException when reading of the file fails, or the file was not reserved properly + */ + public String readLine(String filename) throws IOException { + return readLine(filename, true); + } + + /** + * Get the next line of the named file, first line is name to false + * + * @param filename the filename or alias that was used to reserve the file + * @param recycle - should file be restarted at EOF? + * @return String containing the next line in the file (null if EOF reached and not recycle) + * @throws IOException when reading of the file fails, or the file was not reserved properly + */ + public String readLine(String filename, boolean recycle) throws IOException { + return readLine(filename, recycle, false); + } + /** + * Get the next line of the named file. + * + * @param filename the filename or alias that was used to reserve the file + * @param recycle - should file be restarted at EOF? + * @param firstLineIsNames - 1st line is fields names + * @return String containing the next line in the file (null if EOF reached and not recycle) + * @throws IOException when reading of the file fails, or the file was not reserved properly + */ + public synchronized String readLine(String filename, boolean recycle, + boolean firstLineIsNames) throws IOException { + FileEntry fileEntry = files.get(filename); + if (fileEntry != null) { + if (fileEntry.inputOutputObject == null) { + fileEntry.inputOutputObject = createBufferedReader(fileEntry); + } else if (!(fileEntry.inputOutputObject instanceof Reader)) { + throw new IOException("File " + filename + " already in use"); + } + BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject; + String line = reader.readLine(); + if (line == null && recycle) { + reader.close(); + reader = createBufferedReader(fileEntry); + fileEntry.inputOutputObject = reader; + if (firstLineIsNames) { + // read first line and forget + reader.readLine(); + } + line = reader.readLine(); + } + if (log.isDebugEnabled()) { log.debug("Read:"+line); } + return line; + } + throw new IOException("File never reserved: "+filename); + } + + /** + * + * @param alias the file name or alias + * @param recycle whether the file should be re-started on EOF + * @param firstLineIsNames whether the file contains a file header + * @param delim the delimiter to use for parsing + * @return the parsed line, will be empty if the file is at EOF + * @throws IOException when reading of the aliased file fails, or the file was not reserved properly + */ + public synchronized String[] getParsedLine(String alias, boolean recycle, boolean firstLineIsNames, char delim) throws IOException { + BufferedReader reader = getReader(alias, recycle, firstLineIsNames); + return CSVSaveService.csvReadFile(reader, delim); + } + + private BufferedReader getReader(String alias, boolean recycle, boolean firstLineIsNames) throws IOException { + FileEntry fileEntry = files.get(alias); + if (fileEntry != null) { + BufferedReader reader; + if (fileEntry.inputOutputObject == null) { + reader = createBufferedReader(fileEntry); + fileEntry.inputOutputObject = reader; + if (firstLineIsNames) { + // read first line and forget + reader.readLine(); + } + } else if (!(fileEntry.inputOutputObject instanceof Reader)) { + throw new IOException("File " + alias + " already in use"); + } else { + reader = (BufferedReader) fileEntry.inputOutputObject; + if (recycle) { // need to check if we are at EOF already + reader.mark(1); + int peek = reader.read(); + if (peek == -1) { // already at EOF + reader.close(); + reader = createBufferedReader(fileEntry); + fileEntry.inputOutputObject = reader; + if (firstLineIsNames) { + // read first line and forget + reader.readLine(); + } + } else { // OK, we still have some data, restore it + reader.reset(); + } + } + } + return reader; + } else { + throw new IOException("File never reserved: "+alias); + } + } + + private BufferedReader createBufferedReader(FileEntry fileEntry) throws IOException { + FileInputStream fis = new FileInputStream(fileEntry.file); + InputStreamReader isr = null; + // If file encoding is specified, read using that encoding, otherwise use default platform encoding + String charsetName = fileEntry.charSetEncoding; + if(!JOrphanUtils.isBlank(charsetName)) { + isr = new InputStreamReader(fis, charsetName); + } else { + isr = new InputStreamReader(fis); + } + return new BufferedReader(isr); + } + + public synchronized void write(String filename, String value) throws IOException { + FileEntry fileEntry = files.get(filename); + if (fileEntry != null) { + if (fileEntry.inputOutputObject == null) { + fileEntry.inputOutputObject = createBufferedWriter(fileEntry); + } else if (!(fileEntry.inputOutputObject instanceof Writer)) { + throw new IOException("File " + filename + " already in use"); + } + BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject; + if (log.isDebugEnabled()) { log.debug("Write:"+value); } + writer.write(value); + } else { + throw new IOException("File never reserved: "+filename); + } + } + + private BufferedWriter createBufferedWriter(FileEntry fileEntry) throws IOException { + FileOutputStream fos = new FileOutputStream(fileEntry.file); + OutputStreamWriter osw = null; + // If file encoding is specified, write using that encoding, otherwise use default platform encoding + String charsetName = fileEntry.charSetEncoding; + if(!JOrphanUtils.isBlank(charsetName)) { + osw = new OutputStreamWriter(fos, charsetName); + } else { + osw = new OutputStreamWriter(fos); + } + return new BufferedWriter(osw); + } + + public synchronized void closeFiles() throws IOException { + for (Map.Entry me : files.entrySet()) { + closeFile(me.getKey(),me.getValue() ); + } + files.clear(); + } + + /** + * @param name the name or alias of the file to be closed + * @throws IOException when closing of the aliased file fails + */ + public synchronized void closeFile(String name) throws IOException { + FileEntry fileEntry = files.get(name); + closeFile(name, fileEntry); + } + + private void closeFile(String name, FileEntry fileEntry) throws IOException { + if (fileEntry != null && fileEntry.inputOutputObject != null) { + log.info("Close: "+name); + fileEntry.inputOutputObject.close(); + fileEntry.inputOutputObject = null; + } + } + + boolean filesOpen() { // package access for test code only + for (FileEntry fileEntry : files.values()) { + if (fileEntry.inputOutputObject != null) { + return true; + } + } + return false; + } + + /** + * Method will get a random file in a base directory + *

+ * TODO hey, not sure this + * method belongs here. FileServer is for threadsafe File access relative to + * current test's base directory. + * + * @param basedir + * name of the directory in which the files can be found + * @param extensions + * array of allowed extensions, if null is given, + * any file be allowed + * @return a random File from the basedir that matches one of + * the extensions + */ + public File getRandomFile(String basedir, String[] extensions) { + File input = null; + if (basedir != null) { + File src = new File(basedir); + if (src.isDirectory() && src.list() != null) { + File[] lfiles = src.listFiles(new JMeterFileFilter(extensions)); + int count = lfiles.length; + input = lfiles[random.nextInt(count)]; + } + } + return input; + } + + private static class FileEntry{ + private String headerLine; + private Throwable exception; + private final File file; + private Closeable inputOutputObject; + private final String charSetEncoding; + FileEntry(File f, Closeable o, String e){ + file=f; + inputOutputObject=o; + charSetEncoding=e; + } + } + + /** + * Resolve a file name that may be relative to the base directory. If the + * name begins with the value of the JMeter property + * "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is + * assumed to be relative to the basename. + * + * @param relativeName + * filename that should be checked for + * jmeter.save.saveservice.base_prefix + * @return the updated filename + */ + public static String resolveBaseRelativeName(String relativeName) { + if (relativeName.startsWith(BASE_PREFIX)){ + String newName = relativeName.substring(BASE_PREFIX.length()); + return new File(getFileServer().getBaseDir(),newName).getAbsolutePath(); + } + return relativeName; + } + + /** + * @return JMX Script name + * @since 2.6 + */ + public String getScriptName() { + return scriptName; + } + + /** + * @param scriptName Script name + * @since 2.6 + */ + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } +} diff --git a/src/core/org/apache/jmeter/swing/HtmlPane.java b/src/core/org/apache/jmeter/swing/HtmlPane.java new file mode 100644 index 00000000000..303c5672057 --- /dev/null +++ b/src/core/org/apache/jmeter/swing/HtmlPane.java @@ -0,0 +1,56 @@ +/* + * 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.swing; + +import java.awt.Rectangle; + +import javax.swing.JTextPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements an HTML Pane with local hyperlinking enabled. + */ +public class HtmlPane extends JTextPane { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public HtmlPane() { + this.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + String ref = e.getURL().getRef(); + if (ref != null) { + log.debug("reference to scroll to = '" + ref + "'"); + if (ref.length() > 0) { + scrollToReference(ref); + } else { // href="#" + scrollRectToVisible(new Rectangle(1,1,1,1)); + } + } + } + } + }); + } +} diff --git a/src/core/org/apache/jmeter/testbeans/BeanInfoSupport.java b/src/core/org/apache/jmeter/testbeans/BeanInfoSupport.java new file mode 100644 index 00000000000..0b73890028a --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/BeanInfoSupport.java @@ -0,0 +1,301 @@ +/* + * 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.testbeans; + +import java.awt.Image; +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.EventSetDescriptor; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.MethodDescriptor; +import java.beans.PropertyDescriptor; +import java.beans.SimpleBeanInfo; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; +import org.apache.jmeter.testbeans.gui.TypeEditor; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Support class for test bean beanInfo objects. It will help using the + * introspector to get most of the information, to then modify it at will. + *

+ * To use, subclass it, create a subclass with a parameter-less constructor + * that: + *

    + *
  1. Calls super(beanClass) + *
  2. Modifies the property descriptors, bean descriptor, etc. at will. + *
+ *

+ * Even before any such modifications, a resource bundle named xxxResources + * (where xxx is the fully qualified bean class name) will be obtained if + * available and used to localize the following: + *

    + *
  • Bean's display name -- from property displayName. + *
  • Properties' display names -- from properties propertyName.displayName. + *
  • Properties' short descriptions -- from properties propertyName.shortDescription. + *
+ *

+ * The resource bundle will be stored as the bean descriptor's "resourceBundle" + * attribute, so that it can be used for further localization. TestBeanGUI, for + * example, uses it to obtain the group's display names from properties groupName.displayName. + * + * @version $Revision$ + */ +public abstract class BeanInfoSupport extends SimpleBeanInfo { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Some known attribute names, just for convenience: + public static final String TAGS = GenericTestBeanCustomizer.TAGS; + + /** Whether the field must be defined (i.e. is required); Boolean, defaults to FALSE */ + public static final String NOT_UNDEFINED = GenericTestBeanCustomizer.NOT_UNDEFINED; + + /** Whether the field disallows JMeter expressions; Boolean, default FALSE */ + public static final String NOT_EXPRESSION = GenericTestBeanCustomizer.NOT_EXPRESSION; + + /** Whether the field disallows constant values different from the provided tags; Boolean, default FALSE */ + public static final String NOT_OTHER = GenericTestBeanCustomizer.NOT_OTHER; + + /** If specified, create a multi-line editor */ + public static final String MULTILINE = GenericTestBeanCustomizer.MULTILINE; + + /** Default value, must be provided if {@link #NOT_UNDEFINED} is TRUE */ + public static final String DEFAULT = GenericTestBeanCustomizer.DEFAULT; + + /** Pointer to the resource bundle, if any (will generally be null) */ + public static final String RESOURCE_BUNDLE = GenericTestBeanCustomizer.RESOURCE_BUNDLE; + + /** TextEditor property */ + public static final String TEXT_LANGUAGE = GenericTestBeanCustomizer.TEXT_LANGUAGE; + + /** The BeanInfo for our class as obtained by the introspector. */ + private final BeanInfo rootBeanInfo; + + /** The descriptor for our class */ + private final BeanDescriptor beanDescriptor; + + /** The icons for this bean. */ + private final Image[] icons = new Image[5]; + + /** The class for which we're providing the bean info. */ + private final Class beanClass; + + /** + * Construct a BeanInfo for the given class. + * + * @param beanClass + * class for which to construct a BeanInfo + */ + protected BeanInfoSupport(Class beanClass) { + this.beanClass= beanClass; + + try { + rootBeanInfo = Introspector.getBeanInfo(beanClass, Introspector.IGNORE_IMMEDIATE_BEANINFO); + } catch (IntrospectionException e) { + throw new Error("Can't introspect "+beanClass, e); // Programming error: bail out. + } + + // N.B. JVMs other than Sun may return different instances each time + // so we cache the value here (and avoid having to fetch it every time) + beanDescriptor = rootBeanInfo.getBeanDescriptor(); + + try { + ResourceBundle resourceBundle = ResourceBundle.getBundle( + beanClass.getName() + "Resources", // $NON-NLS-1$ + JMeterUtils.getLocale()); + + // Store the resource bundle as an attribute of the BeanDescriptor: + getBeanDescriptor().setValue(RESOURCE_BUNDLE, resourceBundle); + final String dnKey = "displayName"; + // Localize the bean name + if (resourceBundle.containsKey(dnKey)) { // $NON-NLS-1$ + getBeanDescriptor().setDisplayName(resourceBundle.getString(dnKey)); // $NON-NLS-1$ + } else { + log.debug("Localized display name not available for bean " + beanClass); + } + // Localize the property names and descriptions: + PropertyDescriptor[] properties = getPropertyDescriptors(); + for (PropertyDescriptor property : properties) { + String name = property.getName(); + final String propDnKey = name + ".displayName"; + if(resourceBundle.containsKey(propDnKey)) { + property.setDisplayName(resourceBundle.getString(propDnKey)); // $NON-NLS-1$ + } else { + log.debug("Localized display name not available for property " + name + " in " + beanClass); + } + final String propSdKey = name + ".shortDescription"; + if(resourceBundle.containsKey(propSdKey)) { + property.setShortDescription(resourceBundle.getString(propSdKey)); + } else { + log.debug("Localized short description not available for property " + name + " in " + beanClass); + } + } + } catch (MissingResourceException e) { + log.warn("Localized strings not available for bean " + beanClass, e); + } catch (Exception e) { + log.warn("Something bad happened when loading bean info for bean " + beanClass, e); + } + } + + /** + * Get the property descriptor for the property of the given name. + * + * @param name + * property name + * @return descriptor for a property of that name, or null if there's none + */ + protected PropertyDescriptor property(String name) { + for (PropertyDescriptor propdesc : getPropertyDescriptors()) { + if (propdesc.getName().equals(name)) { + return propdesc; + } + } + log.error("Cannot find property: " + name + " in class " + beanClass); + return null; + } + + /** + * Get the property descriptor for the property of the given name. + * Sets the GUITYPE to the provided editor. + * + * @param name + * property name + * @param editor the TypeEditor enum that describes the property editor + * + * @return descriptor for a property of that name, or null if there's none + */ + protected PropertyDescriptor property(String name, TypeEditor editor) { + PropertyDescriptor property = property(name); + if (property != null) { + property.setValue(GenericTestBeanCustomizer.GUITYPE, editor); + } + return property; + } + + /** + * Get the property descriptor for the property of the given name. + * Sets the GUITYPE to the provided enum. + * + * @param name + * property name + * @param enumClass the enum class that is to be used by the editor + * @return descriptor for a property of that name, or null if there's none + */ + protected PropertyDescriptor property(final String name, + final Class> enumClass) { + PropertyDescriptor property = property(name); + if (property != null) { + property.setValue(GenericTestBeanCustomizer.GUITYPE, enumClass); + // we also provide the resource bundle + property.setValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE, getBeanDescriptor().getValue(RESOURCE_BUNDLE)); + } + return property; + } + + /** + * Set the bean's 16x16 colour icon. + * + * @param resourceName + * A pathname relative to the directory holding the class file of + * the current class. + */ + protected void setIcon(String resourceName) { + icons[ICON_COLOR_16x16] = loadImage(resourceName); + } + + /** Number of groups created so far by createPropertyGroup. */ + private int numCreatedGroups = 0; + + /** + * Utility method to group and order properties. + *

+ * It will assign the given group name to each of the named properties, and + * set their order attribute so that they are shown in the given order. + *

+ * The created groups will get order 1, 2, 3,... in the order in which they + * are created. + * + * @param group + * name of the group + * @param names + * property names in the desired order + */ + protected void createPropertyGroup(String group, String[] names) { + for (int i = 0; i < names.length; i++) { // i is used below + log.debug("Getting property for: " + names[i]); + PropertyDescriptor p = property(names[i]); + p.setValue(GenericTestBeanCustomizer.GROUP, group); + p.setValue(GenericTestBeanCustomizer.ORDER, Integer.valueOf(i)); + } + numCreatedGroups++; + getBeanDescriptor().setValue(GenericTestBeanCustomizer.ORDER(group), Integer.valueOf(numCreatedGroups)); + } + + /** {@inheritDoc} */ + @Override + public BeanInfo[] getAdditionalBeanInfo() { + return rootBeanInfo.getAdditionalBeanInfo(); + } + + /** {@inheritDoc} */ + @Override + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + /** {@inheritDoc} */ + @Override + public int getDefaultEventIndex() { + return rootBeanInfo.getDefaultEventIndex(); + } + + /** {@inheritDoc} */ + @Override + public int getDefaultPropertyIndex() { + return rootBeanInfo.getDefaultPropertyIndex(); + } + + /** {@inheritDoc} */ + @Override + public EventSetDescriptor[] getEventSetDescriptors() { + return rootBeanInfo.getEventSetDescriptors(); + } + + /** {@inheritDoc} */ + @Override + public Image getIcon(int iconKind) { + return icons[iconKind]; + } + + /** {@inheritDoc} */ + @Override + public MethodDescriptor[] getMethodDescriptors() { + return rootBeanInfo.getMethodDescriptors(); + } + + /** {@inheritDoc} */ + @Override + public PropertyDescriptor[] getPropertyDescriptors() { + return rootBeanInfo.getPropertyDescriptors(); + } +} diff --git a/src/core/org/apache/jmeter/testbeans/TestBean.java b/src/core/org/apache/jmeter/testbeans/TestBean.java new file mode 100644 index 00000000000..8e0ef6bb76d --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/TestBean.java @@ -0,0 +1,30 @@ +/* + * 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. + * + */ + +/* + * Created on May 21, 2004 + */ +package org.apache.jmeter.testbeans; + +/** + * Marker interface to tell JMeter to make a Test Bean Gui for the class. + * + */ +public interface TestBean { + +} diff --git a/src/core/org/apache/jmeter/testbeans/TestBeanBeanInfo.java b/src/core/org/apache/jmeter/testbeans/TestBeanBeanInfo.java new file mode 100644 index 00000000000..1ef6dca44dc --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/TestBeanBeanInfo.java @@ -0,0 +1,104 @@ +/* + * 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.testbeans; + +import java.awt.Image; +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.EventSetDescriptor; +import java.beans.MethodDescriptor; +import java.beans.PropertyDescriptor; + +/** + * This is the BeanInfo object for the TestBean class. It acts as a "stopper" + * for the introspector: we don't want it to look at properties defined at this + * or higher classes. + *

+ * Note this is really needed since using Introspector.getBeanInfo with a stop + * class is not an option because: + *

    + *
  1. The API does not define a 3-parameter getBeanInfo in which you can use a + * stop class AND flags. [Why? I guess this is a bug in the spec.] + *
  2. java.beans.Introspector is buggy and, opposite to what's stated in the + * Javadocs, only results of getBeanInfo(Class) are actually cached. + *
+ * + * @version $Revision$ + */ +public class TestBeanBeanInfo implements BeanInfo { + + @Override + public BeanInfo[] getAdditionalBeanInfo() { + return new BeanInfo[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public BeanDescriptor getBeanDescriptor() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public int getDefaultEventIndex() { + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public int getDefaultPropertyIndex() { + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public EventSetDescriptor[] getEventSetDescriptors() { + return new EventSetDescriptor[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public Image getIcon(int iconKind) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MethodDescriptor[] getMethodDescriptors() { + return new MethodDescriptor[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyDescriptor[] getPropertyDescriptors() { + return new PropertyDescriptor[0]; + } +} diff --git a/src/core/org/apache/jmeter/testbeans/TestBeanHelper.java b/src/core/org/apache/jmeter/testbeans/TestBeanHelper.java new file mode 100644 index 00000000000..b843dba7215 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/TestBeanHelper.java @@ -0,0 +1,218 @@ +/* + * 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.testbeans; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.LinkedList; + +import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; +import org.apache.jmeter.testbeans.gui.TableEditor; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.MultiProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.Converter; +import org.apache.log.Logger; + +/** + * This is an experimental class. An attempt to address the complexity of + * writing new JMeter components. + *

+ * TestBean currently extends AbstractTestElement to support + * backward-compatibility, but the property-value-map may later on be separated + * from the test beans themselves. To ensure this will be doable with minimum + * damage, all inherited methods are deprecated. + * + */ +public class TestBeanHelper { + protected static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Prepare the bean for work by populating the bean's properties from the + * property value map. + *

+ * + * @param el the TestElement to be prepared + * @deprecated to limit it's usage in expectation of moving it elsewhere. + */ + @Deprecated + public static void prepare(TestElement el) { + if (!(el instanceof TestBean)) { + return; + } + try { + BeanInfo beanInfo = Introspector.getBeanInfo(el.getClass()); + PropertyDescriptor[] descs = beanInfo.getPropertyDescriptors(); + + if (log.isDebugEnabled()) { + log.debug("Preparing " + el.getClass()); + } + + for (PropertyDescriptor desc : descs) { + if (isDescriptorIgnored(desc)) { + if (log.isDebugEnabled()) { + log.debug("Ignoring property '" + desc.getName() + "' in " + el.getClass().getCanonicalName()); + } + continue; + } + // Obtain a value of the appropriate type for this property. + JMeterProperty jprop = el.getProperty(desc.getName()); + Class type = desc.getPropertyType(); + Object value = unwrapProperty(desc, jprop, type); + + if (log.isDebugEnabled()) { + log.debug("Setting " + jprop.getName() + "=" + value); + } + + // Set the bean's property to the value we just obtained: + if (value != null || !type.isPrimitive()) + // We can't assign null to primitive types. + { + Method writeMethod = desc.getWriteMethod(); + if (writeMethod!=null) { + invokeOrBailOut(el, writeMethod, new Object[] {value}); + } + } + } + } catch (IntrospectionException e) { + log.error("Couldn't set properties for " + el.getClass().getName(), e); + } catch (UnsatisfiedLinkError ule) { // Can occur running headless on Jenkins + log.error("Couldn't set properties for " + el.getClass().getName()); + throw ule; + } + } + + private static Object unwrapProperty(PropertyDescriptor desc, JMeterProperty jprop, Class type) { + Object value; + if(jprop instanceof TestElementProperty) + { + TestElement te = ((TestElementProperty)jprop).getElement(); + if(te instanceof TestBean) + { + prepare(te); + } + value = te; + } + else if(jprop instanceof MultiProperty) + { + value = unwrapCollection((MultiProperty)jprop,(String)desc.getValue(TableEditor.CLASSNAME)); + } + // value was not provided, and this is allowed + else if (jprop instanceof NullProperty && + // use negative condition so missing (null) value is treated as FALSE + ! Boolean.TRUE.equals(desc.getValue(GenericTestBeanCustomizer.NOT_UNDEFINED))) { + value=null; + } else { + value = Converter.convert(jprop.getStringValue(), type); + } + return value; + } + + private static Object unwrapCollection(MultiProperty prop,String type) + { + if(prop instanceof CollectionProperty) + { + Collection values = new LinkedList(); + PropertyIterator iter = prop.iterator(); + while(iter.hasNext()) + { + try + { + values.add(unwrapProperty(null,iter.next(),Class.forName(type))); + } + catch(Exception e) + { + log.error("Couldn't convert object: " + prop.getObjectValue() + " to " + type,e); + } + } + return values; + } + return null; + } + + /** + * Utility method that invokes a method and does the error handling around + * the invocation. + * + * @param invokee + * the object on which the method should be invoked + * @param method + * the method which should be invoked + * @param params + * the parameters for the method + * @return the result of the method invocation. + */ + private static Object invokeOrBailOut(Object invokee, Method method, Object[] params) { + try { + return method.invoke(invokee, params); + } catch (IllegalArgumentException e) { + throw new Error(createMessage(invokee, method, params), e); + } catch (IllegalAccessException e) { + throw new Error(createMessage(invokee, method, params), e); + } catch (InvocationTargetException e) { + throw new Error(createMessage(invokee, method, params), e); + } + } + + private static String createMessage(Object invokee, Method method, Object[] params){ + StringBuilder sb = new StringBuilder(); + sb.append("This should never happen. Tried to invoke:\n"); + sb.append(invokee.getClass().getName()); + sb.append("#"); + sb.append(method.getName()); + sb.append("("); + for(Object o : params) { + if (o != null) { + sb.append(o.getClass().getSimpleName()); + sb.append(' '); + } + sb.append(o); + sb.append(' '); + } + sb.append(")"); + return sb.toString(); + } + + /** + * Checks whether the descriptor should be ignored, i.e. + *
    + *
  • isHidden
  • + *
  • isExpert and JMeter not using expert mode
  • + *
  • no read method
  • + *
  • no write method
  • + *
+ * @param descriptor the {@link PropertyDescriptor} to be checked + * @return true if the descriptor should be ignored + */ + public static boolean isDescriptorIgnored(PropertyDescriptor descriptor) { + return descriptor.isHidden() + || (descriptor.isExpert() && !JMeterUtils.isExpertMode()) + || descriptor.getReadMethod() == null + || descriptor.getWriteMethod() == null; + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/BooleanPropertyEditor.java b/src/core/org/apache/jmeter/testbeans/gui/BooleanPropertyEditor.java new file mode 100644 index 00000000000..0af032c8bd8 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/BooleanPropertyEditor.java @@ -0,0 +1,66 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyEditorSupport; + +/** + * Property Editor which handles Boolean properties. + */ +public class BooleanPropertyEditor extends PropertyEditorSupport { + + // These are the mixed-case values as returned by the RI JVM boolean property editor + // However, they are different from the lower-case values returned by e.g. Boolean.FALSE.toString() + private static final String FALSE = "False"; // $NON-NLS-1$ + private static final String TRUE = "True"; // $NON-NLS-1$ + + private static final String[] TAGS = {TRUE, FALSE}; + + // Make sure we return one of the TAGS + @Override + public String getAsText() { + Object value = getValue(); + return value instanceof Boolean ? toString((Boolean) value) : null; + } + + private String toString(Boolean value) { + return value.booleanValue() ? TRUE : FALSE; + } + + @Override + public void setAsText(String text) { + this.setValue(text); + } + + @Override + public void setValue(Object value){ + if (value instanceof String) { + super.setValue(Boolean.valueOf((String) value)); + } else if (value == null || value instanceof Boolean) { + super.setValue(value); // not sure if null is passed in but no harm in setting it + } else { + throw new java.lang.IllegalArgumentException("Unexpected type: "+value.getClass().getName()); + } + } + + @Override + public String[] getTags() { + return TAGS; + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/ComboStringEditor.java b/src/core/org/apache/jmeter/testbeans/gui/ComboStringEditor.java new file mode 100644 index 00000000000..b9f5ab30528 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/ComboStringEditor.java @@ -0,0 +1,321 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditorSupport; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.text.JTextComponent; + +import org.apache.jmeter.gui.ClearGui; +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements a property editor for possibly null String properties + * that supports custom editing (i.e.: provides a GUI component) based on a + * combo box. + *

+ * The provided GUI is a combo box with: + *

    + *
  • An option for "undefined" (corresponding to the null value), unless the + * noUndefined property is set. + *
  • An option for each value in the tags property. + *
  • The possibility to write your own value, unless the noEdit + * property is set. + *
+ * + */ +class ComboStringEditor extends PropertyEditorSupport implements ItemListener, ClearGui { + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + /** + * The list of options to be offered by this editor. + */ + private final String[] tags; + + /** + * The edited property's default value. + */ + private String initialEditValue; + + private final JComboBox combo; + + private final DefaultComboBoxModel model; + + /* + * Map of translations for tags; only created if there is at least + * one tag and a ResourceBundle has been provided. + */ + private final Map validTranslations; + + private boolean startingEdit = false; + + /* + * True iff we're currently processing an event triggered by the user + * selecting the "Edit" option. Used to prevent reverting the combo to + * non-editable during processing of secondary events. + */ + + // Needs to be visible to test cases + final Object UNDEFINED = new UniqueObject("property_undefined"); //$NON-NLS-1$ + + private final Object EDIT = new UniqueObject("property_edit"); //$NON-NLS-1$ + + // The minimum index of the tags in the combo box + private final int minTagIndex; + + // The maximum index of the tags in the combo box + private final int maxTagIndex; + + @Deprecated // only for use from test code + ComboStringEditor() { + this(null, false, false); + } + + ComboStringEditor(PropertyDescriptor descriptor) { + this((String[])descriptor.getValue(GenericTestBeanCustomizer.TAGS), + GenericTestBeanCustomizer.notExpression(descriptor), + GenericTestBeanCustomizer.notNull(descriptor), + (ResourceBundle) descriptor.getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE)); + } + + ComboStringEditor(String []tags, boolean noEdit, boolean noUndefined) { + this(tags, noEdit, noUndefined, null); + } + + ComboStringEditor(String []pTags, boolean noEdit, boolean noUndefined, ResourceBundle rb) { + + tags = pTags == null ? EMPTY_STRING_ARRAY : pTags.clone(); + + model = new DefaultComboBoxModel(); + + if (rb != null && tags.length > 0) { + validTranslations=new HashMap(); + for (String tag : this.tags) { + validTranslations.put(tag, rb.getString(tag)); + } + } else { + validTranslations=null; + } + + if (!noUndefined) { + model.addElement(UNDEFINED); + } + if (tags.length == 0) { + this.minTagIndex = Integer.MAX_VALUE; + this.maxTagIndex = Integer.MIN_VALUE; + } else { + this.minTagIndex=model.getSize(); // track where tags start ... + for (String tag : this.tags) { + model.addElement(translate(tag)); + } + this.maxTagIndex=model.getSize(); // ... and where they end + } + if (!noEdit) { + model.addElement(EDIT); + } + + combo = new JComboBox(model); + combo.addItemListener(this); + combo.setEditable(false); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean supportsCustomEditor() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Component getCustomEditor() { + return combo; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getValue() { + return getAsText(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getAsText() { + final Object value = combo.getSelectedItem(); + if (UNDEFINED.equals(value)) { + return null; + } + final int item = combo.getSelectedIndex(); + // Check if the entry index corresponds to a tag, if so return the tag + // This also works if the tags were not translated + if (item >= minTagIndex && item <= maxTagIndex) { + return tags[item-minTagIndex]; + } + // Not a tag entry, return the original value + return (String) value; + } + + /** + * {@inheritDoc} + */ + @Override + public void setValue(Object value) { + setAsText((String) value); + } + + /** + * {@inheritDoc} + */ + @Override + public void setAsText(String value) { + combo.setEditable(true); + + if (value == null) { + combo.setSelectedItem(UNDEFINED); + } else { + combo.setSelectedItem(translate(value)); + } + + if (!startingEdit && combo.getSelectedIndex() >= 0) { + combo.setEditable(false); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + if (EDIT.equals(e.getItem())) { + startingEdit = true; + startEditing(); + startingEdit = false; + } else { + if (!startingEdit && combo.getSelectedIndex() >= 0) { + combo.setEditable(false); + } + + firePropertyChange(); + } + } + } + + private void startEditing() { + JTextComponent textField = (JTextComponent) combo.getEditor().getEditorComponent(); + + combo.setEditable(true); + + textField.requestFocusInWindow(); + String text = translate(initialEditValue); + if (text == null) { + text = ""; // will revert to last valid value if invalid + } + + combo.setSelectedItem(text); + + int i = text.indexOf("${}"); + if (i != -1) { + textField.setCaretPosition(i + 2); + } else { + textField.selectAll(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getTags() { + return tags.clone(); + } + + /** + * @param object the initial edit value + */ + public void setInitialEditValue(String object) { + initialEditValue = object; + } + + /** + * This is a funny hack: if you use a plain String, entering the text of the + * string in the editor will make the combo revert to that option -- which + * actually amounts to making that string 'reserved'. I preferred to avoid + * this by using a different type having a controlled .toString(). + */ + private static class UniqueObject { + private final String propKey; + private final String propValue; + + UniqueObject(String propKey) { + this.propKey = propKey; + this.propValue = JMeterUtils.getResString(propKey); + } + + @Override + public String toString() { + return propValue; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof UniqueObject) { + return propKey.equals(((UniqueObject) other).propKey); + } + return false; + } + + @Override + public int hashCode() { + return propKey.hashCode(); + } + } + + @Override + public void clearGui() { + setAsText(initialEditValue); + } + + // Replace a string with its translation, if one exists + private String translate(String input) { + if (validTranslations != null) { + final String entry = validTranslations.get(input); + return entry != null ? entry : input; + } + return input; + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/EnumEditor.java b/src/core/org/apache/jmeter/testbeans/gui/EnumEditor.java new file mode 100644 index 00000000000..24a0deea048 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/EnumEditor.java @@ -0,0 +1,103 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditorSupport; +import java.util.ResourceBundle; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; + +import org.apache.jmeter.gui.ClearGui; + +/** + * This class implements a property editor for String properties based on an enum + * that supports custom editing (i.e.: provides a GUI component) based on a + * combo box. + *

+ * The provided GUI is a combo box with an option for each value in the enum. + *

+ */ +class EnumEditor extends PropertyEditorSupport implements ClearGui { + + private final JComboBox combo; + + private final DefaultComboBoxModel model; + + private final int defaultIndex; + + public EnumEditor(final PropertyDescriptor descriptor, final Class> enumClazz, final ResourceBundle rb) { + model = new DefaultComboBoxModel(); + combo = new JComboBox(model); + combo.setEditable(false); + for(Enum e : enumClazz.getEnumConstants()) { + model.addElement(rb.getObject(e.toString())); + } + Object def = descriptor.getValue(GenericTestBeanCustomizer.DEFAULT); + if (def instanceof Integer) { + defaultIndex = ((Integer) def).intValue(); + } else { + defaultIndex = 0; + } + combo.setSelectedIndex(defaultIndex); + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + @Override + public Component getCustomEditor() { + return combo; + } + + @Override + public Object getValue() { + return Integer.valueOf(combo.getSelectedIndex()); + } + + @Override + public String getAsText() { + Object value = combo.getSelectedItem(); + return (String) value; + } + + @Override + public void setValue(Object value) { + if (value instanceof Enum){ + combo.setSelectedIndex(((Enum) value).ordinal()); + } else if (value instanceof Integer) { + combo.setSelectedIndex(((Integer) value).intValue()); + } else { + combo.setSelectedItem(value); + } + } + + @Override + public void setAsText(String value) { + combo.setSelectedItem(value); + } + + @Override + public void clearGui() { + combo.setSelectedIndex(defaultIndex); + } + +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/FieldStringEditor.java b/src/core/org/apache/jmeter/testbeans/gui/FieldStringEditor.java new file mode 100644 index 00000000000..cf6e7a6ef81 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/FieldStringEditor.java @@ -0,0 +1,135 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyEditorSupport; + +import javax.swing.JTextField; + +//import org.apache.jorphan.logging.LoggingManager; +//import org.apache.log.Logger; + +/** + * This class implements a property editor for non-null String properties that + * supports custom editing (i.e.: provides a GUI component) based on a text + * field. + *

+ * The provided GUI is a simple text field. + * + */ +class FieldStringEditor extends PropertyEditorSupport implements ActionListener, FocusListener { +// private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * This will hold the text editing component, either a plain JTextField (in + * cases where the combo box would not have other options than 'Edit'), or + * the text editing component in the combo box. + */ + private final JTextField textField; + + /** + * Value on which we started the editing. Used to avoid firing + * PropertyChanged events when there's not been such change. + */ + private String initialValue = ""; + + protected FieldStringEditor() { + super(); + + textField = new JTextField(); + textField.addActionListener(this); + textField.addFocusListener(this); + } + + @Override + public String getAsText() { + return textField.getText(); + } + + @Override + public void setAsText(String value) { + initialValue = value; + textField.setText(value); + } + + @Override + public Object getValue() { + return getAsText(); + } + + @Override + public void setValue(Object value) { + if (value instanceof String) { + setAsText((String) value); + } else { + throw new IllegalArgumentException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Component getCustomEditor() { + return textField; + } + + // TODO should this implement supportsCustomEditor() ? + + /** + * {@inheritDoc} + */ + @Override + public void firePropertyChange() { + String newValue = getAsText(); + + if (initialValue.equals(newValue)) { + return; + } + initialValue = newValue; + + super.firePropertyChange(); + } + + /** + * {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent e) { + firePropertyChange(); + } + + /** + * {@inheritDoc} + */ + @Override + public void focusGained(FocusEvent e) { + } + + /** + * {@inheritDoc} + */ + @Override + public void focusLost(FocusEvent e) { + firePropertyChange(); + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/FileEditor.java b/src/core/org/apache/jmeter/testbeans/gui/FileEditor.java new file mode 100644 index 00000000000..834b16ccc17 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/FileEditor.java @@ -0,0 +1,232 @@ +/* + * 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.testbeans.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.IntrospectionException; +import java.beans.PropertyChangeListener; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorSupport; +import java.io.File; + +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JPanel; + +import org.apache.jmeter.gui.util.FileDialoger; + +/** + * A property editor for File properties. + *

+ * Note that it never gives out File objects, but always Strings. This is + * because JMeter is now too dumb to handle File objects (there's no + * FileProperty). + * + */ +public class FileEditor implements PropertyEditor, ActionListener { + + /** + * The editor's panel. + */ + private final JPanel panel; + + /** + * The editor handling the text field inside: + */ + private final PropertyEditor editor; + + /** + * @throws IntrospectionException + * when introspection fails while creating a dummy + * PropertyDescriptor + * @deprecated Only for use by test cases + */ + @Deprecated + public FileEditor() throws IntrospectionException { + this(new PropertyDescriptor("dummy", null, null)); + } + + /** + * Construct a {@link FileEditor} using the properties of the given + * {@link PropertyDescriptor} + * + * @param descriptor + * the {@link PropertyDescriptor} to be used. Must not be null + * @throws IllegalArgumentException + * when descriptor is null + */ + public FileEditor(PropertyDescriptor descriptor) { + if (descriptor == null) { + throw new IllegalArgumentException("Descriptor must not be null"); + } + + // Create a button to trigger the file chooser: + JButton button = new JButton("Browse..."); + button.addActionListener(this); + + // Get a WrapperEditor to provide the field or combo -- we'll delegate + // most methods to it: + boolean notNull = GenericTestBeanCustomizer.notNull(descriptor); + boolean notExpression = GenericTestBeanCustomizer.notExpression(descriptor); + boolean notOther = GenericTestBeanCustomizer.notOther(descriptor); + Object defaultValue = descriptor.getValue(GenericTestBeanCustomizer.DEFAULT); + ComboStringEditor cse = new ComboStringEditor(null, notExpression && notOther, notNull); + editor = new WrapperEditor(this, new SimpleFileEditor(), cse, + !notNull, // acceptsNull + !notExpression, // acceptsExpressions + !notOther, // acceptsOther + defaultValue); // default + + // Create a panel containing the combo and the button: + panel = new JPanel(new BorderLayout(5, 0)); + panel.add(editor.getCustomEditor(), BorderLayout.CENTER); + panel.add(button, BorderLayout.EAST); + } + + /** + * {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = FileDialoger.promptToOpenFile(); + + if (chooser == null){ + return; + } + + setValue(chooser.getSelectedFile().getPath()); + } + + /** + * {@inheritDoc} + */ + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + editor.addPropertyChangeListener(listener); + } + + /** + * @return the text + */ + @Override + public String getAsText() { + return editor.getAsText(); + } + + /** + * @return custom editor panel + */ + @Override + public Component getCustomEditor() { + return panel; + } + + /** + * @return the Java initialisation string + */ + @Override + public String getJavaInitializationString() { + return editor.getJavaInitializationString(); + } + + /** + * @return the editor tags + */ + @Override + public String[] getTags() { + return editor.getTags(); + } + + /** + * @return the value + */ + @Override + public Object getValue() { + return editor.getValue(); + } + + /** + * @return true if the editor is paintable + */ + @Override + public boolean isPaintable() { + return editor.isPaintable(); + } + + /** + * {@inheritDoc} + */ + @Override + public void paintValue(Graphics gfx, Rectangle box) { + editor.paintValue(gfx, box); + } + + /** + * {@inheritDoc} + */ + @Override + public void removePropertyChangeListener(PropertyChangeListener listener) { + editor.removePropertyChangeListener(listener); + } + + /** + * {@inheritDoc} + */ + @Override + public void setAsText(String text) throws IllegalArgumentException { + editor.setAsText(text); + } + + /** + * {@inheritDoc} + */ + @Override + public void setValue(Object value) { + editor.setValue(value); + } + + /** + * @return true if supports a custom editor + */ + @Override + public boolean supportsCustomEditor() { + return editor.supportsCustomEditor(); + } + + private static class SimpleFileEditor extends PropertyEditorSupport { + + @Override + public String getAsText() { + Object value = super.getValue(); + if (value instanceof File) { + return ((File) value).getPath(); + } + return (String) value; // assume it's string + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + super.setValue(new File(text)); + } + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java b/src/core/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java new file mode 100644 index 00000000000..68ccb98a965 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java @@ -0,0 +1,791 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.beans.BeanInfo; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorManager; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingConstants; + +import org.apache.commons.lang3.ClassUtils; +import org.apache.jmeter.gui.ClearGui; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The GenericTestBeanCustomizer is designed to provide developers with a + * mechanism to quickly implement GUIs for new components. + *

+ * It allows editing each of the public exposed properties of the edited type 'a + * la JavaBeans': as far as the types of those properties have an associated + * editor, there's no GUI development required. + *

+ * This class understands the following PropertyDescriptor attributes: + *

+ *
group: String
+ *
Group under which the property should be shown in the GUI. The string is + * also used as a group title (but see comment on resourceBundle below). The + * default group is "".
+ *
order: Integer
+ *
Order in which the property will be shown in its group. A smaller + * integer means higher up in the GUI. The default order is 0. Properties of + * equal order are sorted alphabetically.
+ *
tags: String[]
+ *
List of values to be offered for the property in addition to those + * offered by its property editor.
+ *
notUndefined: Boolean
+ *
If true, the property should not be left undefined. A default + * attribute must be provided if this is set.
+ *
notExpression: Boolean
+ *
If true, the property content should always be constant: JMeter + * 'expressions' (strings using ${var}, etc...) can't be used.
+ *
notOther: Boolean
+ *
If true, the property content must always be one of the tags values or + * null.
+ *
default: Object
+ *
Initial value for the property's GUI. Must be provided and be non-null + * if notUndefined is set. Must be one of the provided tags (or null) if + * notOther is set. + *
+ *

+ * The following BeanDescriptor attributes are also understood: + *

+ *
group.group.order: Integer
+ *
where group is a group name used in a group + * attribute in one or more PropertyDescriptors. Defines the order in which the + * group will be shown in the GUI. A smaller integer means higher up in the GUI. + * The default order is 0. Groups of equal order are sorted alphabetically.
+ *
resourceBundle: ResourceBundle
+ *
A resource bundle to be used for GUI localization: group display names + * will be obtained from property "group.displayName" if + * available (where group is the group name). + *
+ */ +public class GenericTestBeanCustomizer extends JPanel implements SharedCustomizer { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Need to register Editors for Java classes because we cannot create them + // in the same package, nor can we create them in the built-in search patch of packages, + // as that is not part of the public API + static { + PropertyEditorManager.registerEditor(Long.class, LongPropertyEditor.class); + PropertyEditorManager.registerEditor(Integer.class, IntegerPropertyEditor.class); + PropertyEditorManager.registerEditor(Boolean.class, BooleanPropertyEditor.class); + } + + public static final String GROUP = "group"; //$NON-NLS-1$ + + public static final String ORDER = "order"; //$NON-NLS-1$ + + /** + * Array of permissible values. + *

+ * Must be provided if: + *

    + *
  • {@link #NOT_OTHER} is TRUE, and
  • + *
  • {@link PropertyEditor#getTags()} is null
  • + *
+ */ + public static final String TAGS = "tags"; //$NON-NLS-1$ + + /** + * Whether the field must be defined (i.e. is required); + * Boolean, defaults to FALSE + */ + public static final String NOT_UNDEFINED = "notUndefined"; //$NON-NLS-1$ + + /** Whether the field disallows JMeter expressions; Boolean, default FALSE */ + public static final String NOT_EXPRESSION = "notExpression"; //$NON-NLS-1$ + + /** Whether the field disallows constant values different from the provided tags; Boolean, default FALSE */ + public static final String NOT_OTHER = "notOther"; //$NON-NLS-1$ + + /** If specified, create a multi-line editor */ + public static final String MULTILINE = "multiline"; + + /** Default value, must be provided if {@link #NOT_UNDEFINED} is TRUE */ + public static final String DEFAULT = "default"; //$NON-NLS-1$ + + /** Pointer to the resource bundle, if any (will generally be null) */ + public static final String RESOURCE_BUNDLE = "resourceBundle"; //$NON-NLS-1$ + + /** Property editor override; must be an enum of type {@link TypeEditor} */ + public static final String GUITYPE = "guiType"; // $NON-NLS-$ + + /** TextEditor property */ + public static final String TEXT_LANGUAGE = "textLanguage"; //$NON-NLS-1$ + + public static final String ORDER(String group) { + return "group." + group + ".order"; + } + + public static final String DEFAULT_GROUP = ""; + + @SuppressWarnings("unused") // TODO - use or remove + private int scrollerCount = 0; + + /** + * BeanInfo object for the class of the objects being edited. + */ + private transient BeanInfo beanInfo; + + /** + * Property descriptors from the beanInfo. + */ + private transient PropertyDescriptor[] descriptors; + + /** + * Property editors -- or null if the property can't be edited. Unused if + * customizerClass==null. + */ + private transient PropertyEditor[] editors; + + /** + * Message format for property field labels: + */ + private MessageFormat propertyFieldLabelMessage; + + /** + * Message format for property tooltips: + */ + private MessageFormat propertyToolTipMessage; + + /** + * The Map we're currently customizing. Set by setObject(). + */ + private Map propertyMap; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public GenericTestBeanCustomizer(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + /** + * Create a customizer for a given test bean type. + * + * @param testBeanClass + * a subclass of TestBean + * @see org.apache.jmeter.testbeans.TestBean + */ + GenericTestBeanCustomizer(BeanInfo beanInfo) { + super(); + + this.beanInfo = beanInfo; + + // Get and sort the property descriptors: + descriptors = beanInfo.getPropertyDescriptors(); + Arrays.sort(descriptors, new PropertyComparator(beanInfo)); + + // Obtain the propertyEditors: + editors = new PropertyEditor[descriptors.length]; + int scriptLanguageIndex = 0; + int textAreaEditorIndex = 0; + for (int i = 0; i < descriptors.length; i++) { // Index is also used for accessing editors array + PropertyDescriptor descriptor = descriptors[i]; + String name = descriptor.getName(); + + // Don't get editors for hidden or non-read-write properties: + if (TestBeanHelper.isDescriptorIgnored(descriptor)) { + log.debug("Skipping editor for property " + name); + editors[i] = null; + continue; + } + + PropertyEditor propertyEditor; + Object guiType = descriptor.getValue(GUITYPE); + if (guiType instanceof TypeEditor) { + propertyEditor = ((TypeEditor) guiType).getInstance(descriptor); + } else if (guiType instanceof Class && Enum.class.isAssignableFrom((Class) guiType)) { + @SuppressWarnings("unchecked") // we check the class type above + final Class> enumClass = (Class>) guiType; + propertyEditor = new EnumEditor(descriptor, enumClass, (ResourceBundle) descriptor.getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE)); + } else { + Class editorClass = descriptor.getPropertyEditorClass(); + if (log.isDebugEnabled()) { + log.debug("Property " + name + " has editor class " + editorClass); + } + + if (editorClass != null) { + try { + propertyEditor = (PropertyEditor) editorClass.newInstance(); + } catch (InstantiationException e) { + log.error("Can't create property editor.", e); + throw new Error(e.toString()); + } catch (IllegalAccessException e) { + log.error("Can't create property editor.", e); + throw new Error(e.toString()); + } + } else { + Class c = descriptor.getPropertyType(); + propertyEditor = PropertyEditorManager.findEditor(c); + } + } + + if (propertyEditor == null) { + log.warn("No editor for property: " + name + + " type: " + descriptor.getPropertyType() + + " in bean: " + beanInfo.getBeanDescriptor().getDisplayName() + ); + editors[i] = null; + continue; + } + + if (log.isDebugEnabled()) { + log.debug("Property " + name + " has property editor " + propertyEditor); + } + + validateAttributes(descriptor, propertyEditor); + + if (!propertyEditor.supportsCustomEditor()) { + propertyEditor = createWrapperEditor(propertyEditor, descriptor); + + if (log.isDebugEnabled()) { + log.debug("Editor for property " + name + " is wrapped in " + propertyEditor); + } + } + if(propertyEditor instanceof TestBeanPropertyEditor) + { + ((TestBeanPropertyEditor)propertyEditor).setDescriptor(descriptor); + } + + if (propertyEditor instanceof TextAreaEditor) { + textAreaEditorIndex = i; + } + if (propertyEditor.getCustomEditor() instanceof JScrollPane) { + scrollerCount++; + } + + editors[i] = propertyEditor; + + // Initialize the editor with the provided default value or null: + setEditorValue(i, descriptor.getValue(DEFAULT)); + + if (name.equals("scriptLanguage")) { + scriptLanguageIndex = i; + } + + } + // In case of BSF and JSR elements i want to add textAreaEditor as a listener to scriptLanguage ComboBox. + String beanName = this.beanInfo.getBeanDescriptor().getName(); + if (beanName.startsWith("BSF") || beanName.startsWith("JSR223")) { // $NON-NLS-1$ $NON-NLS-2$ + WrapperEditor we = (WrapperEditor) editors[scriptLanguageIndex]; + TextAreaEditor tae = (TextAreaEditor) editors[textAreaEditorIndex]; + we.addChangeListener(tae); + } + + // Obtain message formats: + propertyFieldLabelMessage = new MessageFormat(JMeterUtils.getResString("property_as_field_label")); //$NON-NLS-1$ + propertyToolTipMessage = new MessageFormat(JMeterUtils.getResString("property_tool_tip")); //$NON-NLS-1$ + + // Initialize the GUI: + init(); + } + + /** + * Validate the descriptor attributes. + * + * @param pd the descriptor + * @param pe the propertyEditor + */ + private static void validateAttributes(PropertyDescriptor pd, PropertyEditor pe) { + final Object deflt = pd.getValue(DEFAULT); + if (deflt == null) { + if (notNull(pd)) { + log.warn(getDetails(pd) + " requires a value but does not provide a default."); + } + } else { + final Class defltClass = deflt.getClass(); // the DEFAULT class + // Convert int to Integer etc: + final Class propClass = ClassUtils.primitiveToWrapper(pd.getPropertyType()); + if (!propClass.isAssignableFrom(defltClass) ){ + log.warn(getDetails(pd) + " has a DEFAULT of class " + defltClass.getCanonicalName()); + } + } + if (notOther(pd) && pd.getValue(TAGS) == null && pe.getTags() == null) { + log.warn(getDetails(pd) + " does not have tags but other values are not allowed."); + } + if (!notNull(pd)) { + Class propertyType = pd.getPropertyType(); + if (propertyType.isPrimitive()) { + log.warn(getDetails(pd) + " allows null but is a primitive type"); + } + } + if (!pd.attributeNames().hasMoreElements()) { + log.warn(getDetails(pd) + " does not appear to have been configured"); + } + } + + /** + * Identify the property from the descriptor. + * + * @param pd + * @return the property details + */ + private static String getDetails(PropertyDescriptor pd) { + StringBuilder sb = new StringBuilder(); + sb.append(pd.getReadMethod().getDeclaringClass().getName()); + sb.append('#'); + sb.append(pd.getName()); + sb.append('('); + sb.append(pd.getPropertyType().getCanonicalName()); + sb.append(')'); + return sb.toString(); + } + + /** + * Find the default typeEditor and a suitable guiEditor for the given + * property descriptor, and combine them in a WrapperEditor. + * + * @param typeEditor + * @param descriptor + * @return the wrapper editor + */ + private WrapperEditor createWrapperEditor(PropertyEditor typeEditor, PropertyDescriptor descriptor) { + String[] editorTags = typeEditor.getTags(); + String[] additionalTags = (String[]) descriptor.getValue(TAGS); + String[] tags = null; + if (editorTags == null) { + tags = additionalTags; + } else if (additionalTags == null) { + tags = editorTags; + } else { + tags = new String[editorTags.length + additionalTags.length]; + int j = 0; + for (String editorTag : editorTags) { + tags[j++] = editorTag; + } + for (String additionalTag : additionalTags) { + tags[j++] = additionalTag; + } + } + + boolean notNull = notNull(descriptor); + boolean notExpression = notExpression(descriptor); + boolean notOther = notOther(descriptor); + + PropertyEditor guiEditor; + if (notNull && tags == null) { + guiEditor = new FieldStringEditor(); + } else { + guiEditor = new ComboStringEditor(tags, notExpression && notOther, notNull, + (ResourceBundle) descriptor.getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE)); + } + + WrapperEditor wrapper = new WrapperEditor(typeEditor, guiEditor, + !notNull, // acceptsNull + !notExpression, // acceptsExpressions + !notOther, // acceptsOther + descriptor.getValue(DEFAULT)); + + return wrapper; + } + + /** + * Returns true if the property disallows constant values different from the provided tags. + * + * @param descriptor the property descriptor + * @return true if the attribute {@link #NOT_OTHER} is defined and equal to Boolean.TRUE; + * otherwise the default is false + */ + static boolean notOther(PropertyDescriptor descriptor) { + boolean notOther = Boolean.TRUE.equals(descriptor.getValue(NOT_OTHER)); + return notOther; + } + + /** + * Returns true if the property does not allow JMeter expressions. + * + * @param descriptor the property descriptor + * @return true if the attribute {@link #NOT_EXPRESSION} is defined and equal to Boolean.TRUE; + * otherwise the default is false + */ + static boolean notExpression(PropertyDescriptor descriptor) { + boolean notExpression = Boolean.TRUE.equals(descriptor.getValue(NOT_EXPRESSION)); + return notExpression; + } + + /** + * Returns true if the property must be defined (i.e. is required); + * + * @param descriptor the property descriptor + * @return true if the attribute {@link #NOT_UNDEFINED} is defined and equal to Boolean.TRUE; + * otherwise the default is false + */ + static boolean notNull(PropertyDescriptor descriptor) { + boolean notNull = Boolean.TRUE.equals(descriptor.getValue(NOT_UNDEFINED)); + return notNull; + } + + /** + * Set the value of the i-th property, properly reporting a possible + * failure. + * + * @param i + * the index of the property in the descriptors and editors + * arrays + * @param value + * the value to be stored in the editor + * + * @throws IllegalArgumentException + * if the editor refuses the value + */ + private void setEditorValue(int i, Object value) throws IllegalArgumentException { + editors[i].setValue(value); + } + + + /** + * {@inheritDoc} + * @param map must be an instance of Map<String, Object> + */ + @SuppressWarnings("unchecked") + @Override + public void setObject(Object map) { + propertyMap = (Map) map; + + if (propertyMap.size() == 0) { + // Uninitialized -- set it to the defaults: + for (PropertyDescriptor descriptor : descriptors) { + Object value = descriptor.getValue(DEFAULT); + String name = descriptor.getName(); + if (value != null) { + propertyMap.put(name, value); + log.debug("Set " + name + "= " + value); + } + firePropertyChange(name, null, value); + } + } + + // Now set the editors to the element's values: + for (int i = 0; i < editors.length; i++) { + if (editors[i] == null) { + continue; + } + try { + setEditorValue(i, propertyMap.get(descriptors[i].getName())); + } catch (IllegalArgumentException e) { + // I guess this can happen as a result of a bad + // file read? In this case, it would be better to replace the + // incorrect value with anything valid, e.g. the default value + // for the property. + // But for the time being, I just prefer to be aware of any + // problems occuring here, most likely programming errors, + // so I'll bail out. + // (MS Note) Can't bail out - newly create elements have blank + // values and must get the defaults. + // Also, when loading previous versions of jmeter test scripts, + // some values + // may not be right, and should get default values - MS + // TODO: review this and possibly change to: + setEditorValue(i, descriptors[i].getValue(DEFAULT)); + } + } + } + +// /** +// * Find the index of the property of the given name. +// * +// * @param name +// * the name of the property +// * @return the index of that property in the descriptors array, or -1 if +// * there's no property of this name. +// */ +// private int descriptorIndex(String name) // NOTUSED +// { +// for (int i = 0; i < descriptors.length; i++) { +// if (descriptors[i].getName().equals(name)) { +// return i; +// } +// } +// return -1; +// } + + /** + * Initialize the GUI. + */ + private void init() { + setLayout(new GridBagLayout()); + + GridBagConstraints cl = new GridBagConstraints(); // for labels + cl.gridx = 0; + cl.anchor = GridBagConstraints.EAST; + cl.insets = new Insets(0, 1, 0, 1); + + GridBagConstraints ce = new GridBagConstraints(); // for editors + ce.fill = GridBagConstraints.BOTH; + ce.gridx = 1; + ce.weightx = 1.0; + ce.insets = new Insets(0, 1, 0, 1); + + GridBagConstraints cp = new GridBagConstraints(); // for panels + cp.fill = GridBagConstraints.BOTH; + cp.gridx = 1; + cp.gridy = GridBagConstraints.RELATIVE; + cp.gridwidth = 2; + cp.weightx = 1.0; + + JPanel currentPanel = this; + String currentGroup = DEFAULT_GROUP; + int y = 0; + + for (int i = 0; i < editors.length; i++) { + if (editors[i] == null) { + continue; + } + + if (log.isDebugEnabled()) { + log.debug("Laying property " + descriptors[i].getName()); + } + + String g = group(descriptors[i]); + if (!currentGroup.equals(g)) { + if (currentPanel != this) { + add(currentPanel, cp); + } + currentGroup = g; + currentPanel = new JPanel(new GridBagLayout()); + currentPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + groupDisplayName(g))); + cp.weighty = 0.0; + y = 0; + } + + Component customEditor = editors[i].getCustomEditor(); + + boolean multiLineEditor = false; + if (customEditor.getPreferredSize().height > 50 || customEditor instanceof JScrollPane + || descriptors[i].getValue(MULTILINE) != null) { + // TODO: the above works in the current situation, but it's + // just a hack. How to get each editor to report whether it + // wants to grow bigger? Whether the property label should + // be at the left or at the top of the editor? ...? + multiLineEditor = true; + } + + JLabel label = createLabel(descriptors[i]); + label.setLabelFor(customEditor); + + cl.gridy = y; + cl.gridwidth = multiLineEditor ? 2 : 1; + cl.anchor = multiLineEditor ? GridBagConstraints.CENTER : GridBagConstraints.EAST; + currentPanel.add(label, cl); + + ce.gridx = multiLineEditor ? 0 : 1; + ce.gridy = multiLineEditor ? ++y : y; + ce.gridwidth = multiLineEditor ? 2 : 1; + ce.weighty = multiLineEditor ? 1.0 : 0.0; + + cp.weighty += ce.weighty; + + currentPanel.add(customEditor, ce); + + y++; + } + if (currentPanel != this) { + add(currentPanel, cp); + } + + // Add a 0-sized invisible component that will take all the vertical + // space that nobody wants: + cp.weighty = 0.0001; + add(Box.createHorizontalStrut(0), cp); + } + + private JLabel createLabel(PropertyDescriptor desc) { + String text = desc.getDisplayName(); + if (!"".equals(text)) { + text = propertyFieldLabelMessage.format(new Object[] { desc.getDisplayName() }); + } + // if the displayName is the empty string, leave it like that. + JLabel label = new JLabel(text); + label.setHorizontalAlignment(SwingConstants.TRAILING); + text = propertyToolTipMessage.format(new Object[] { desc.getName(), desc.getShortDescription() }); + label.setToolTipText(text); + + return label; + } + + /** + * Obtain a property descriptor's group. + * + * @param descriptor + * @return the group String. + */ + private static String group(PropertyDescriptor d) { + String group = (String) d.getValue(GROUP); + if (group == null){ + group = DEFAULT_GROUP; + } + return group; + } + + /** + * Obtain a group's display name + */ + private String groupDisplayName(String group) { + ResourceBundle b = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(RESOURCE_BUNDLE); + if (b == null) { + return group; + } + String key = new StringBuilder(group).append(".displayName").toString(); + if (b.containsKey(key)) { + return b.getString(key); + } else { + return group; + } + } + + /** + * Comparator used to sort properties for presentation in the GUI. + */ + private static class PropertyComparator implements Comparator, Serializable { + private static final long serialVersionUID = 240L; + + private final BeanInfo beanInfo; + public PropertyComparator(BeanInfo beanInfo) { + this.beanInfo = beanInfo; + } + + @Override + public int compare(PropertyDescriptor d1, PropertyDescriptor d2) { + int result; + + String g1 = group(d1), g2 = group(d2); + Integer go1 = groupOrder(g1), go2 = groupOrder(g2); + + result = go1.compareTo(go2); + if (result != 0) { + return result; + } + + result = g1.compareTo(g2); + if (result != 0) { + return result; + } + + Integer po1 = propertyOrder(d1), po2 = propertyOrder(d2); + result = po1.compareTo(po2); + if (result != 0) { + return result; + } + + return d1.getName().compareTo(d2.getName()); + } + + /** + * Obtain a group's order. + * + * @param group + * group name + * @return the group's order (zero by default) + */ + private Integer groupOrder(String group) { + Integer order = (Integer) beanInfo.getBeanDescriptor().getValue(ORDER(group)); + if (order == null) { + order = Integer.valueOf(0); + } + return order; + } + + /** + * Obtain a property's order. + * + * @param d + * @return the property's order attribute (zero by default) + */ + private Integer propertyOrder(PropertyDescriptor d) { + Integer order = (Integer) d.getValue(ORDER); + if (order == null) { + order = Integer.valueOf(0); + } + return order; + } + } + + /** + * Save values from the GUI fields into the property map + */ + void saveGuiFields() { + for (int i = 0; i < editors.length; i++) { + PropertyEditor propertyEditor=editors[i]; // might be null (e.g. in testing) + if (propertyEditor != null) { + Object value = propertyEditor.getValue(); + String name = descriptors[i].getName(); + if (value == null) { + propertyMap.remove(name); + if (log.isDebugEnabled()) { + log.debug("Unset " + name); + } + } else { + propertyMap.put(name, value); + if (log.isDebugEnabled()) { + log.debug("Set " + name + "= " + value); + } + } + } + } + } + + void clearGuiFields() { + for (int i = 0; i < editors.length; i++) { + PropertyEditor propertyEditor=editors[i]; // might be null (e.g. in testing) + if (propertyEditor != null) { + try { + if (propertyEditor instanceof ClearGui) { + ((ClearGui) propertyEditor).clearGui(); + } else if (propertyEditor instanceof WrapperEditor){ + WrapperEditor we = (WrapperEditor) propertyEditor; + String tags[]=we.getTags(); + if (tags != null && tags.length > 0) { + we.setAsText(tags[0]); + } else { + we.resetValue(); + } + } else { + propertyEditor.setAsText(""); + } + } catch (IllegalArgumentException ex){ + log.error("Failed to set field "+descriptors[i].getName(),ex); + } + } + } + } + +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/IntegerPropertyEditor.java b/src/core/org/apache/jmeter/testbeans/gui/IntegerPropertyEditor.java new file mode 100644 index 00000000000..90f8558bbec --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/IntegerPropertyEditor.java @@ -0,0 +1,44 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyEditorSupport; + +/** + * Property Editor which handles Integer properties. + * Uses {@link Integer#decode(String)} so supports hex and octal input. + */ +public class IntegerPropertyEditor extends PropertyEditorSupport { + + @Override + public void setAsText(String text) { + this.setValue(text); + } + + @Override + public void setValue(Object value){ + if (value instanceof String) { + super.setValue(Integer.decode((String) value)); // handles hex as well + } else if (value == null || value instanceof Integer) { + super.setValue(value); // not sure if null is passed in but no harm in setting it + } else { + throw new java.lang.IllegalArgumentException("Unexpected type: "+value.getClass().getName()); + } + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/LongPropertyEditor.java b/src/core/org/apache/jmeter/testbeans/gui/LongPropertyEditor.java new file mode 100644 index 00000000000..6b94dfe5b90 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/LongPropertyEditor.java @@ -0,0 +1,44 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyEditorSupport; + +/** + * Property Editor which handles Long properties. + * Uses {@link Long#decode(String)} so supports hex and octal input. + */ +public class LongPropertyEditor extends PropertyEditorSupport { + + @Override + public void setAsText(String text) { + this.setValue(text); + } + + @Override + public void setValue(Object value){ + if (value instanceof String) { + super.setValue(Long.decode((String) value)); // handles hex as well + } else if (value == null || value instanceof Long) { + super.setValue(value); // not sure if null is passed in but no harm in setting it + } else { + throw new java.lang.IllegalArgumentException("Unexpected type: "+value.getClass().getName()); + } + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/PasswordEditor.java b/src/core/org/apache/jmeter/testbeans/gui/PasswordEditor.java new file mode 100644 index 00000000000..597bedbce61 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/PasswordEditor.java @@ -0,0 +1,131 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyEditorSupport; + +import javax.swing.JPasswordField; + +/** + * This class implements a property editor for non-null String properties that + * supports custom editing (i.e.: provides a GUI component) based on a text + * field. + *

+ * The provided GUI is a simple password field. + * + */ +public class PasswordEditor extends PropertyEditorSupport implements ActionListener, FocusListener { + + private JPasswordField textField; + + /** + * Value on which we started the editing. Used to avoid firing + * PropertyChanged events when there's not been such change. + */ + private String initialValue = ""; + + protected PasswordEditor() { + super(); + + textField = new JPasswordField(); + textField.addActionListener(this); + textField.addFocusListener(this); + } + + @Override + public String getAsText() { + return new String(textField.getPassword()); + } + + @Override + public void setAsText(String value) { + initialValue = value; + textField.setText(value); + } + + @Override + public Object getValue() { + return getAsText(); + } + + @Override + public void setValue(Object value) { + if (value instanceof String) { + setAsText((String) value); + } else { + throw new IllegalArgumentException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Component getCustomEditor() { + return textField; + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + /** + * Avoid needlessly firing PropertyChanged events. + *

+ * {@inheritDoc} + */ + @Override + public void firePropertyChange() { + String newValue = getAsText(); + + if (initialValue.equals(newValue)) { + return; + } + initialValue = newValue; + + super.firePropertyChange(); + } + + /** + * {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent e) { + firePropertyChange(); + } + + /** + * {@inheritDoc} + */ + @Override + public void focusGained(FocusEvent e) { + } + + /** + * {@inheritDoc} + */ + @Override + public void focusLost(FocusEvent e) { + firePropertyChange(); + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/SharedCustomizer.java b/src/core/org/apache/jmeter/testbeans/gui/SharedCustomizer.java new file mode 100644 index 00000000000..62bf3dca1aa --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/SharedCustomizer.java @@ -0,0 +1,30 @@ +/* + * 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.testbeans.gui; + +import java.beans.Customizer; + +/** + * Tagging interface to mark a customizer class as shareable among elements of + * the same type. + *

+ * The interface is equivalent to Customizer -- the only difference is that + * setElement can be called multiple times to change the element it works on. + * + */ +public interface SharedCustomizer extends Customizer { +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/TableEditor.java b/src/core/org/apache/jmeter/testbeans/gui/TableEditor.java new file mode 100644 index 00000000000..9418bc5b4e7 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/TableEditor.java @@ -0,0 +1,313 @@ +/* + * 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.testbeans.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditorSupport; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import javax.swing.CellEditor; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; + +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.log.Logger; + +/** + * Table editor for TestBean GUI properties. + * Currently only works for: + *

    + *
  • property type Collection of {@link String}s, where there is a single header entry
  • + *
+ */ +public class TableEditor extends PropertyEditorSupport implements FocusListener,TestBeanPropertyEditor,TableModelListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * attribute name for class name of a table row; + * value must be java.lang.String, or a class which supports set and get/is methods for the property name. + */ + public static final String CLASSNAME = "tableObject.classname"; // $NON-NLS-1$ + + /** + * attribute name for table headers, value must be a String array. + * If {@link #CLASSNAME} is java.lang.String, there must be only a single entry. + */ + public static final String HEADERS = "table.headers"; // $NON-NLS-1$ + + /** attribute name for property names within the {@link #CLASSNAME}, value must be String array */ + public static final String OBJECT_PROPERTIES = "tableObject.properties"; // $NON-NLS-1$ + + private JTable table; + private ObjectTableModel model; + private Class clazz; + private PropertyDescriptor descriptor; + private final JButton addButton,removeButton,clearButton; + + public TableEditor() { + addButton = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + addButton.addActionListener(new AddListener()); + removeButton = new JButton(JMeterUtils.getResString("remove")); // $NON-NLS-1$ + removeButton.addActionListener(new RemoveListener()); + clearButton = new JButton(JMeterUtils.getResString("clear")); // $NON-NLS-1$ + clearButton.addActionListener(new ClearListener()); + } + + @Override + public String getAsText() { + return null; + } + + @Override + public Component getCustomEditor() { + JComponent pane = makePanel(); + pane.doLayout(); + pane.validate(); + return pane; + } + + private JComponent makePanel() + { + JPanel p = new JPanel(new BorderLayout()); + JScrollPane scroller = new JScrollPane(table); + scroller.setPreferredSize(scroller.getMinimumSize()); + p.add(scroller,BorderLayout.CENTER); + JPanel south = new JPanel(); + south.add(addButton); + south.add(removeButton); + south.add(clearButton); + p.add(south,BorderLayout.SOUTH); + return p; + } + + @Override + public Object getValue() { + return model.getObjectList(); + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + //not interested in this method. + } + + @Override + public void setValue(Object value) { + if(value != null) + { + model.setRows(convertCollection((Collection)value)); + } + else model.clearData(); + this.firePropertyChange(); + } + + private Collection convertCollection(Collection values) + { + List l = new LinkedList(); + for(Object obj : values) + { + if(obj instanceof TestElementProperty) + { + l.add(((TestElementProperty)obj).getElement()); + } + else + { + l.add(obj); + } + } + return l; + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + /** + * For the table editor, the CLASSNAME attribute must simply be the name of the class of object it will hold + * where each row holds one object. + */ + @Override + public void setDescriptor(PropertyDescriptor descriptor) { + this.descriptor = descriptor; + String value = (String)descriptor.getValue(CLASSNAME); + if (value == null) { + throw new RuntimeException("The Table Editor requires the CLASSNAME atttribute be set - the name of the object to represent a row"); + } + try { + clazz = Class.forName(value); + initializeModel(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find the CLASSNAME class "+ value, e); + } + } + + void initializeModel() + { + Object hdrs = descriptor.getValue(HEADERS); + if (!(hdrs instanceof String[])){ + throw new RuntimeException("attribute HEADERS must be a String array"); + } + if(clazz == String.class) + { + model = new ObjectTableModel((String[])hdrs,new Functor[0],new Functor[0],new Class[]{String.class}); + } + else + { + Object value = descriptor.getValue(OBJECT_PROPERTIES); + if (!(value instanceof String[])) { + throw new RuntimeException("attribute OBJECT_PROPERTIES must be a String array"); + } + String[] props = (String[])value; + Functor[] writers = new Functor[props.length]; + Functor[] readers = new Functor[props.length]; + Class[] editors = new Class[props.length]; + int count = 0; + for(String propName : props) + { + propName = propName.substring(0,1).toUpperCase(Locale.ENGLISH) + propName.substring(1); + writers[count] = createWriter(clazz,propName); + readers[count] = createReader(clazz,propName); + editors[count] = getArgForWriter(clazz,propName); + count++; + } + model = new ObjectTableModel((String[])hdrs,readers,writers,editors); + } + model.addTableModelListener(this); + table = new JTable(model); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.addFocusListener(this); + } + + Functor createWriter(Class c,String propName) + { + String setter = "set" + propName; // $NON-NLS-1$ + return new Functor(setter); + } + + Functor createReader(Class c,String propName) + { + String getter = "get" + propName; // $NON-NLS-1$ + try + { + c.getMethod(getter,new Class[0]); + return new Functor(getter); + } + catch(Exception e) { return new Functor("is" + propName); } + } + + Class getArgForWriter(Class c,String propName) + { + String setter = "set" + propName; // $NON-NLS-1$ + for(Method m : c.getMethods()) + { + if(m.getName().equals(setter)) + { + return m.getParameterTypes()[0]; + } + } + return null; + } + + @Override + public void tableChanged(TableModelEvent e) { + this.firePropertyChange(); + } + + @Override + public void focusGained(FocusEvent e) { + + } + + @Override + public void focusLost(FocusEvent e) { + final int editingRow = table.getEditingRow(); + final int editingColumn = table.getEditingColumn(); + CellEditor ce = null; + if (editingRow != -1 && editingColumn != -1){ + ce = table.getCellEditor(editingRow,editingColumn); + } + Component editor = table.getEditorComponent(); + if(ce != null && (editor == null || editor != e.getOppositeComponent())) + { + ce.stopCellEditing(); + } + else if(editor != null) + { + editor.addFocusListener(this); + } + this.firePropertyChange(); + } + + private class AddListener implements ActionListener + { + @Override + public void actionPerformed(ActionEvent e) + { + try + { + model.addRow(clazz.newInstance()); + }catch(Exception err) + { + log.error("The class type given to TableEditor was not instantiable. ",err); + } + } + } + + private class RemoveListener implements ActionListener + { + @Override + public void actionPerformed(ActionEvent e) + { + int row = table.getSelectedRow(); + if (row >= 0) { + model.removeRow(row); + } + } + } + + private class ClearListener implements ActionListener + { + @Override + public void actionPerformed(ActionEvent e) + { + model.clearData(); + } + } + +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java b/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java new file mode 100644 index 00000000000..f73410c462f --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java @@ -0,0 +1,522 @@ +/* + * 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.testbeans.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.Customizer; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditorManager; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.swing.JPopupMenu; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.assertions.gui.AbstractAssertionGui; +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.gui.AbstractControllerGui; +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.AbstractProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.timers.Timer; +import org.apache.jmeter.timers.gui.AbstractTimerGui; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * JMeter GUI element editing for TestBean elements. + *

+ * The actual GUI is always a bean customizer: if the bean descriptor provides + * one, it will be used; otherwise, a GenericTestBeanCustomizer will be created + * for this purpose. + *

+ * Those customizers deviate from the standards only in that, instead of a bean, + * they will receive a Map in the setObject call. This will be a property name + * to value Map. The customizer is also in charge of initializing empty Maps + * with sensible initial values. + *

+ * If the provided Customizer class implements the SharedCustomizer interface, + * the same instance of the customizer will be reused for all beans of the type: + * setObject(map) can then be called multiple times. Otherwise, one separate + * instance will be used for each element. For efficiency reasons, most + * customizers should implement SharedCustomizer. + * + */ +public class TestBeanGUI extends AbstractJMeterGuiComponent implements JMeterGUIComponent, LocaleChangeListener{ + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Class testBeanClass; + + private transient BeanInfo beanInfo; + + private final Class customizerClass; + + /** + * The single customizer if the customizer class implements + * SharedCustomizer, null otherwise. + */ + private Customizer customizer = null; + + /** + * TestElement to Customizer map if customizer is null. This is necessary to + * avoid the cost of creating a new customizer on each edit. The cache size + * needs to be limited, though, to avoid memory issues when editing very + * large test plans. + */ + @SuppressWarnings("unchecked") + private final Map customizers = new LRUMap(20); + + /** + * Index of the customizer in the JPanel's child component list: + */ + private int customizerIndexInPanel; + + /** + * The property name to value map that the active customizer edits: + */ + private final Map propertyMap = new HashMap(); + + /** + * Whether the GUI components have been created. + */ + private boolean initialized = false; + + static { + List paths = new LinkedList(); + paths.add("org.apache.jmeter.testbeans.gui");// $NON-NLS-1$ + paths.addAll(Arrays.asList(PropertyEditorManager.getEditorSearchPath())); + String s = JMeterUtils.getPropDefault("propertyEditorSearchPath", null);// $NON-NLS-1$ + if (s != null) { + paths.addAll(Arrays.asList(JOrphanUtils.split(s, ",", "")));// $NON-NLS-1$ // $NON-NLS-2$ + } + PropertyEditorManager.setEditorSearchPath(paths.toArray(new String[paths.size()])); + } + + /** + * @deprecated Dummy for JUnit test purposes only + */ + @Deprecated + public TestBeanGUI() { + log.warn("Constructor only for use in testing");// $NON-NLS-1$ + testBeanClass = null; + customizerClass = null; + beanInfo = null; + } + + public TestBeanGUI(Class testBeanClass) { + super(); + log.debug("testing class: " + testBeanClass.getName()); + // A quick verification, just in case: + if (!TestBean.class.isAssignableFrom(testBeanClass)) { + Error e = new Error(); + log.error("This should never happen!", e); + throw e; // Programming error: bail out. + } + + this.testBeanClass = testBeanClass; + + // Get the beanInfo: + try { + beanInfo = Introspector.getBeanInfo(testBeanClass); + } catch (IntrospectionException e) { + log.error("Can't get beanInfo for " + testBeanClass.getName(), e); + throw new Error(e.toString()); // Programming error. Don't + // continue. + } + + customizerClass = beanInfo.getBeanDescriptor().getCustomizerClass(); + + // Creation of the customizer and GUI initialization is delayed until + // the + // first + // configure call. We don't need all that just to find out the static + // label, menu + // categories, etc! + initialized = false; + JMeterUtils.addLocaleChangeListener(this); + } + + private Customizer createCustomizer() { + try { + return (Customizer) customizerClass.newInstance(); + } catch (InstantiationException e) { + log.error("Could not instantiate customizer of class " + customizerClass, e); + throw new Error(e.toString()); + } catch (IllegalAccessException e) { + log.error("Could not instantiate customizer of class " + customizerClass, e); + throw new Error(e.toString()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getStaticLabel() { + if (beanInfo == null){ + return "null";// $NON-NLS-1$ + } + return beanInfo.getBeanDescriptor().getDisplayName(); + } + + /** + * {@inheritDoc} + */ + @Override +public TestElement createTestElement() { + try { + TestElement element = (TestElement) testBeanClass.newInstance(); + // In other GUI component, clearGUI resets the value to defaults one as there is one GUI per Element + // With TestBeanGUI as it's shared, its default values are only known here, we must call setValues with + // element (as it holds default values) + // otherwise we will get values as computed by customizer reset and not default ones + if(initialized) { + setValues(element); + } + // configure(element); + // super.clear(); // set name, enabled. + modifyTestElement(element); // put the default values back into the + // new element + return element; + } catch (InstantiationException e) { + log.error("Can't create test element", e); + throw new Error(e.toString()); // Programming error. Don't + // continue. + } catch (IllegalAccessException e) { + log.error("Can't create test element", e); + throw new Error(e.toString()); // Programming error. Don't + // continue. + } + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement element) { + // Fetch data from screen fields + if (customizer instanceof GenericTestBeanCustomizer) { + GenericTestBeanCustomizer gtbc = (GenericTestBeanCustomizer) customizer; + gtbc.saveGuiFields(); + } + configureTestElement(element); + + // Copy all property values from the map into the element: + for (PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) { + String name = desc.getName(); + Object value = propertyMap.get(name); + log.debug("Modify " + name + " to " + value); + if (value == null) { + if (GenericTestBeanCustomizer.notNull(desc)) { // cannot be null + setPropertyInElement(element, name, desc.getValue(GenericTestBeanCustomizer.DEFAULT)); + } else { + element.removeProperty(name); + } + } else { + setPropertyInElement(element, name, value); + } + } + } + + /** + * @param element + * @param name + */ + private void setPropertyInElement(TestElement element, String name, Object value) { + JMeterProperty jprop = AbstractProperty.createProperty(value); + jprop.setName(name); + element.setProperty(jprop); + } + + /** + * {@inheritDoc} + */ + @Override + public JPopupMenu createPopupMenu() { + if (Timer.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultTimerMenu(); + } + else if(Sampler.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultSamplerMenu(); + } + else if(ConfigElement.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultConfigElementMenu(); + } + else if(Assertion.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultAssertionMenu(); + } + else if(PostProcessor.class.isAssignableFrom(testBeanClass) || + PreProcessor.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultExtractorMenu(); + } + else if(Visualizer.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultVisualizerMenu(); + } + else if(Controller.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultControllerMenu(); + } + else { + log.warn("Cannot determine PopupMenu for "+testBeanClass.getName()); + return MenuFactory.getDefaultMenu(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement element) { + if (!initialized){ + init(); + } + clearGui(); + + super.configure(element); + + setValues(element); + + initialized = true; + } + + /** + * Get values from element to fill propertyMap and setup customizer + * @param element TestElement + */ + private void setValues(TestElement element) { + // Copy all property values into the map: + for (PropertyIterator jprops = element.propertyIterator(); jprops.hasNext();) { + JMeterProperty jprop = jprops.next(); + propertyMap.put(jprop.getName(), jprop.getObjectValue()); + } + + if (customizer != null) { + customizer.setObject(propertyMap); + } else { + if (initialized){ + remove(customizerIndexInPanel); + } + Customizer c = customizers.get(element); + if (c == null) { + c = createCustomizer(); + c.setObject(propertyMap); + customizers.put(element, c); + } + add((Component) c, BorderLayout.CENTER); + } + } + + /** {@inheritDoc} */ + @Override + public Collection getMenuCategories() { + List menuCategories = new LinkedList(); + BeanDescriptor bd = beanInfo.getBeanDescriptor(); + + // We don't want to show expert beans in the menus unless we're + // in expert mode: + if (bd.isExpert() && !JMeterUtils.isExpertMode()) { + return null; + } + + int matches = setupGuiClasses(menuCategories); + if (matches == 0) { + log.error("Could not assign GUI class to " + testBeanClass.getName()); + } else if (matches > 1) {// may be impossible, but no harm in + // checking ... + log.error("More than 1 GUI class found for " + testBeanClass.getName()); + } + return menuCategories; + } + + /** + * Setup GUI class + * @return number of matches + */ + public int setupGuiClasses() { + return setupGuiClasses(new ArrayList()); + } + + /** + * Setup GUI class + * @param menuCategories List menu categories + * @return number of matches + */ + private int setupGuiClasses(List menuCategories ) { + int matches = 0;// How many classes can we assign from? + // TODO: there must be a nicer way... + BeanDescriptor bd = beanInfo.getBeanDescriptor(); + if (Assertion.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.ASSERTIONS); + bd.setValue(TestElement.GUI_CLASS, AbstractAssertionGui.class.getName()); + matches++; + } + if (ConfigElement.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.CONFIG_ELEMENTS); + bd.setValue(TestElement.GUI_CLASS, AbstractConfigGui.class.getName()); + matches++; + } + if (Controller.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.CONTROLLERS); + bd.setValue(TestElement.GUI_CLASS, AbstractControllerGui.class.getName()); + matches++; + } + if (Visualizer.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.LISTENERS); + bd.setValue(TestElement.GUI_CLASS, AbstractVisualizer.class.getName()); + matches++; + } + if (PostProcessor.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.POST_PROCESSORS); + bd.setValue(TestElement.GUI_CLASS, AbstractPostProcessorGui.class.getName()); + matches++; + } + if (PreProcessor.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.PRE_PROCESSORS); + bd.setValue(TestElement.GUI_CLASS, AbstractPreProcessorGui.class.getName()); + matches++; + } + if (Sampler.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.SAMPLERS); + bd.setValue(TestElement.GUI_CLASS, AbstractSamplerGui.class.getName()); + matches++; + } + if (Timer.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.TIMERS); + bd.setValue(TestElement.GUI_CLASS, AbstractTimerGui.class.getName()); + matches++; + } + return matches; + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + customizerIndexInPanel = getComponentCount(); + + if (customizerClass == null) { + customizer = new GenericTestBeanCustomizer(beanInfo); + } else if (SharedCustomizer.class.isAssignableFrom(customizerClass)) { + customizer = createCustomizer(); + } + + if (customizer != null){ + add((Component) customizer, BorderLayout.CENTER); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getLabelResource() { + // @see getStaticLabel + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + if (customizer instanceof GenericTestBeanCustomizer) { + GenericTestBeanCustomizer gtbc = (GenericTestBeanCustomizer) customizer; + gtbc.clearGuiFields(); + } + propertyMap.clear(); + } + + public boolean isHidden() { + return beanInfo.getBeanDescriptor().isHidden(); + } + + public boolean isExpert() { + return beanInfo.getBeanDescriptor().isExpert(); + } + + /** + * Handle Locale Change by reloading BeanInfo + * @param event {@link LocaleChangeEvent} + */ + @Override + public void localeChanged(LocaleChangeEvent event) { + try { + beanInfo = Introspector.getBeanInfo(testBeanClass); + setupGuiClasses(); + } catch (IntrospectionException e) { + log.error("Can't get beanInfo for " + testBeanClass.getName(), e); + JMeterUtils.reportErrorToUser("Can't get beanInfo for " + testBeanClass.getName()); + } + } + + /** + * {@inheritDoc}} + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#getDocAnchor() + */ + @Override + public String getDocAnchor() { + ResourceBundle resourceBundle = ResourceBundle.getBundle( + testBeanClass.getName() + "Resources", // $NON-NLS-1$ + new Locale("","")); + + String name = resourceBundle.getString("displayName"); + return name.replace(' ', '_'); + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/TestBeanPropertyEditor.java b/src/core/org/apache/jmeter/testbeans/gui/TestBeanPropertyEditor.java new file mode 100644 index 00000000000..d0f899bdba5 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/TestBeanPropertyEditor.java @@ -0,0 +1,28 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; + +public interface TestBeanPropertyEditor extends PropertyEditor { + + void setDescriptor(PropertyDescriptor descriptor); + +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/TextAreaEditor.java b/src/core/org/apache/jmeter/testbeans/gui/TextAreaEditor.java new file mode 100644 index 00000000000..2d566340262 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/TextAreaEditor.java @@ -0,0 +1,144 @@ +/* + * 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. + * + */ + +/* + * Created on May 21, 2004 + */ +package org.apache.jmeter.testbeans.gui; + +import java.awt.Component; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditorSupport; + +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; + +public class TextAreaEditor extends PropertyEditorSupport implements FocusListener, PropertyChangeListener { + + private final JSyntaxTextArea textUI; + + private final JTextScrollPane scroller; + + /** {@inheritDoc} */ + @Override + public void focusGained(FocusEvent e) { + } + + /** {@inheritDoc} */ + @Override + public void focusLost(FocusEvent e) { + firePropertyChange(); + } + + private final void init() {// called from ctor, so must not be overridable + textUI.discardAllEdits(); + textUI.addFocusListener(this); + } + + /** + * + */ + public TextAreaEditor() { + super(); + textUI = new JSyntaxTextArea(20, 20); + scroller = new JTextScrollPane(textUI, true); + init(); + } + + /** + * @param source the source used for event firing + */ + // TODO is this ever used? + public TextAreaEditor(Object source) { + super(source); + textUI = new JSyntaxTextArea(20, 20); + scroller = new JTextScrollPane(textUI, true); + init(); + setValue(source); + } + + /** + * Construct a {@link TextAreaEditor} using the properties of a given + * {@link PropertyDescriptor} + * + * @param descriptor + * to be used for the editor. Must not be null + */ + public TextAreaEditor(PropertyDescriptor descriptor) { + textUI = new JSyntaxTextArea(20, 20); + textUI.setLanguage((String) descriptor.getValue(GenericTestBeanCustomizer.TEXT_LANGUAGE)); + scroller = new JTextScrollPane(textUI, true); + init(); + } + + /** {@inheritDoc} */ + @Override + public String getAsText() { + return textUI.getText(); + } + + /** {@inheritDoc} */ + @Override + public Component getCustomEditor() { + return scroller; + } + + /** {@inheritDoc} */ + @Override + public void setAsText(String text) throws IllegalArgumentException { + textUI.setInitialText(text); + textUI.setCaretPosition(0); + } + + /** {@inheritDoc} */ + @Override + public void setValue(Object value) { + if (value != null) { + textUI.setInitialText(value.toString()); + textUI.setCaretPosition(0); + } else { + textUI.setInitialText(""); + } + } + + /** {@inheritDoc} */ + @Override + public Object getValue() { + return textUI.getText(); + } + + /** {@inheritDoc} */ + @Override + public boolean supportsCustomEditor() { + return true; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + Object source = evt.getSource(); + if (source instanceof ComboStringEditor) { + ComboStringEditor cse = (ComboStringEditor) source; + String lang = cse.getAsText().toLowerCase(); + textUI.setLanguage(lang); + } + } +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/TypeEditor.java b/src/core/org/apache/jmeter/testbeans/gui/TypeEditor.java new file mode 100644 index 00000000000..dbf395f6f09 --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/TypeEditor.java @@ -0,0 +1,36 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; + +/** + * Allow direct specification of property editors. + */ +public enum TypeEditor { + FileEditor {@Override PropertyEditor getInstance(PropertyDescriptor descriptor) { return new FileEditor(descriptor); }}, + PasswordEditor {@Override PropertyEditor getInstance(PropertyDescriptor descriptor) { return new PasswordEditor(); }}, + TableEditor {@Override PropertyEditor getInstance(PropertyDescriptor descriptor) { return new TableEditor(); }}, + TextAreaEditor {@Override PropertyEditor getInstance(PropertyDescriptor descriptor) { return new TextAreaEditor(descriptor); }}, + ComboStringEditor {@Override PropertyEditor getInstance(PropertyDescriptor descriptor) { return new ComboStringEditor(descriptor); }}, + ; + // Some editors may need the descriptor + abstract PropertyEditor getInstance(PropertyDescriptor descriptor); +} diff --git a/src/core/org/apache/jmeter/testbeans/gui/WrapperEditor.java b/src/core/org/apache/jmeter/testbeans/gui/WrapperEditor.java new file mode 100644 index 00000000000..a8ca0bd42fe --- /dev/null +++ b/src/core/org/apache/jmeter/testbeans/gui/WrapperEditor.java @@ -0,0 +1,453 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorSupport; +import java.util.Arrays; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This is an implementation of a full-fledged property editor, providing both + * object-text transformation and an editor GUI (a custom editor component), + * from two simpler property editors providing only one of these functionalities + * each, namely: + *

+ *
typeEditor + *
+ *
Provides suitable object-to-string and string-to-object transformation + * for the property's type. That is: it's a simple editor that only need to + * support the set/getAsText and set/getValue methods.
+ *
guiEditor
+ *
Provides a suitable GUI for the property, but works on [possibly null] + * String values. That is: it supportsCustomEditor, but get/setAsText and + * get/setValue are indentical.
+ *
+ *

+ * The resulting editor provides optional support for null values (you can + * choose whether null is to be a valid property value). It also + * provides optional support for JMeter 'expressions' (you can choose whether + * they make valid property values). + * + */ +class WrapperEditor extends PropertyEditorSupport implements PropertyChangeListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The type's property editor. + */ + private final PropertyEditor typeEditor; + + /** + * The gui property editor + */ + private final PropertyEditor guiEditor; + + /** + * Whether to allow null as a property value. + */ + private final boolean acceptsNull; + + /** + * Whether to allow JMeter 'expressions' as property values. + */ + private final boolean acceptsExpressions; + + /** + * Whether to allow any constant values different from the provided tags. + */ + private final boolean acceptsOther; + + /** Default value to be used to (re-)initialiase the field */ + private final Object defaultValue; + + /** + * Keep track of the last valid value in the editor, so that we can revert + * to it if the user enters an invalid value. + */ + private String lastValidValue = null; + + /** + * Constructor for use when a PropertyEditor is delegating to us. + */ + WrapperEditor(Object source, PropertyEditor typeEditor, PropertyEditor guiEditor, boolean acceptsNull, + boolean acceptsExpressions, boolean acceptsOther, Object defaultValue) { + super(); + if (source != null) { + super.setSource(source); + } + this.typeEditor = typeEditor; + this.guiEditor = guiEditor; + this.acceptsNull = acceptsNull; + this.acceptsExpressions = acceptsExpressions; + this.acceptsOther = acceptsOther; + this.defaultValue = defaultValue; + initialize(); + } + + /** + * Constructor for use for regular instantiation and by subclasses. + */ + WrapperEditor(PropertyEditor typeEditor, PropertyEditor guiEditor, boolean acceptsNull, boolean acceptsExpressions, + boolean acceptsOther, Object defaultValue) { + this(null, typeEditor, guiEditor, acceptsNull, acceptsExpressions, acceptsOther, defaultValue); + } + + final void resetValue(){ + setValue(defaultValue); + lastValidValue = getAsText(); + } + + private void initialize() { + + resetValue(); + + if (guiEditor instanceof ComboStringEditor) { + String[] tags = ((ComboStringEditor) guiEditor).getTags(); + + // Provide an initial edit value if necessary -- this is an + // heuristic that tries to provide the most convenient + // initial edit value: + + String v; + if (!acceptsOther) { + v = "${}"; //$NON-NLS-1$ + } else if (isValidValue("")) { //$NON-NLS-1$ + v = ""; //$NON-NLS-1$ + } else if (acceptsExpressions) { + v = "${}"; //$NON-NLS-1$ + } else if (tags != null && tags.length > 0) { + v = tags[0]; + } else { + v = getAsText(); + } + + ((ComboStringEditor) guiEditor).setInitialEditValue(v); + } + + guiEditor.addPropertyChangeListener(this); + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + @Override + public Component getCustomEditor() { + return guiEditor.getCustomEditor(); + } + + @Override + public String[] getTags() { + return guiEditor.getTags(); + } + + /** + * Determine wheter a string is one of the known tags. + * + * @param text + * @return true iif text equals one of the getTags() + */ + private boolean isATag(String text) { + String[] tags = getTags(); + if (tags == null) { + return false; + } + for (int i = 0; i < tags.length; i++) { + if (tags[i].equals(text)) { + return true; + } + } + return false; + } + + /** + * Determine whether a string is a valid value for the property. + * + * @param text + * the value to be checked + * @return true iif text is a valid value + */ + private boolean isValidValue(String text) { + if (text == null) { + return acceptsNull; + } + + if (acceptsExpressions && isExpression(text)) { + return true; + } + + // Not an expression (isn't or can't be), not null. + + // The known tags are assumed to be valid: + if (isATag(text)) { + return true; + } + // Was not a tag, so if we can't accept other values... + if (!acceptsOther) { + return false; + } + + // Delegate the final check to the typeEditor: + try { + typeEditor.setAsText(text); + } catch (IllegalArgumentException e1) { + // setAsText failed: not valid + return false; + } + // setAsText succeeded: valid + return true; + } + + /** + * This method is used to do some low-cost defensive programming: it is + * called when a condition that the program logic should prevent from + * happening occurs. I hope this will help early detection of logical bugs + * in property value handling. + * + * @throws Error + * always throws an error. + */ + private final void shouldNeverHappen(String msg) throws Error { + throw new Error(msg); // Programming error: bail out. + } + + /** + * Same as shouldNeverHappen(), but provide a source exception. + * + * @param e + * the exception that helped identify the problem + * @throws Error + * always throws one. + */ + private final void shouldNeverHappen(Exception e) throws Error { + throw new Error(e.toString()); // Programming error: bail out. + } + + /** + * Check if a string is a valid JMeter 'expression'. + *

+ * The current implementation is very basic: it just accepts any string + * containing "${" as a valid expression. TODO: improve, but keep returning + * true for "${}". + */ + private final boolean isExpression(String text) { + return text.indexOf("${") != -1;//$NON-NLS-1$ + } + + /** + * Same as isExpression(String). + * + * @param text + * @return true iif text is a String and isExpression(text). + */ + private final boolean isExpression(Object text) { + return text instanceof String && isExpression((String) text); + } + + /** + * @see java.beans.PropertyEditor#getValue() + * @see org.apache.jmeter.testelement.property.JMeterProperty + */ + @Override + public Object getValue() { + String text = (String) guiEditor.getValue(); + + Object value; + + if (text == null) { + if (!acceptsNull) { + shouldNeverHappen("Text is null but null is not allowed"); + } + value = null; + } else { + if (acceptsExpressions && isExpression(text)) { + value = text; + } else { + // not an expression (isn't or can't be), not null. + + // a check, just in case: + if (!acceptsOther && !isATag(text)) { + shouldNeverHappen("Text is not a tag but other entries are not allowed"); + } + + try { + // Bug 44314 Number field does not seem to accept "" + try { + typeEditor.setAsText(text); + } catch (NumberFormatException e) { + if (text.length()==0){ + text="0";//$NON-NLS-1$ + typeEditor.setAsText(text); + } else { + shouldNeverHappen(e); + } + } + } catch (IllegalArgumentException e) { + shouldNeverHappen(e); + } + value = typeEditor.getValue(); + } + } + + if (log.isDebugEnabled()) { + log.debug("->" + (value != null ? value.getClass().getName() : "NULL") + ":" + value); + } + return value; + } + + @Override + public final void setValue(Object value) { /// final because called from ctor + String text; + + if (log.isDebugEnabled()) { + log.debug("<-" + (value != null ? value.getClass().getName() : "NULL") + ":" + value); + } + + if (value == null) { + if (!acceptsNull) { + throw new IllegalArgumentException("Null is not allowed"); + } + text = null; + } else if (acceptsExpressions && isExpression(value)) { + text = (String) value; + } else { + // Not an expression (isn't or can't be), not null. + typeEditor.setValue(value); // may throw IllegalArgumentExc. + text = fixGetAsTextBug(typeEditor.getAsText()); + + if (!acceptsOther && !isATag(text)) { + throw new IllegalArgumentException("Value not allowed: '" + text + "' is not in " + Arrays.toString(getTags())); + } + } + + guiEditor.setValue(text); + } + + /* + * Fix bug in JVMs that return true/false rather than True/False + * from the type editor getAsText() method + */ + private String fixGetAsTextBug(String asText) { + if (asText == null){ + return asText; + } + if (asText.equals("true")){ + log.debug("true=>True");// so we can detect it + return "True"; + } + if (asText.equals("false")){ + log.debug("false=>False");// so we can detect it + return "False"; + } + return asText; + } + + @Override + public String getAsText() { + String text = fixGetAsTextBug(guiEditor.getAsText()); + + if (text == null) { + if (!acceptsNull) { + shouldNeverHappen("Text is null, but null is not allowed"); + } + } else if (!acceptsExpressions || !isExpression(text)) { + // not an expression (can't be or isn't), not null. + try { + typeEditor.setAsText(text); // ensure value is propagated to editor + } catch (IllegalArgumentException e) { + shouldNeverHappen(e); + } + text = fixGetAsTextBug(typeEditor.getAsText()); + + // a check, just in case: + if (!acceptsOther && !isATag(text)) { + shouldNeverHappen("Text is not a tag, but other values are not allowed"); + } + } + + if (log.isDebugEnabled()) { + log.debug("->\"" + text + "\""); + } + return text; + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (log.isDebugEnabled()) { + log.debug(text == null ? "<-null" : "<-\"" + text + "\""); + } + + String value; + + if (text == null) { + if (!acceptsNull) { + throw new IllegalArgumentException("Null parameter not allowed"); + } + value = null; + } else { + if (acceptsExpressions && isExpression(text)) { + value = text; + } else { + // Some editors do tiny transformations (e.g. "true" to + // "True",...): + typeEditor.setAsText(text); // may throw IllegalArgumentException + value = typeEditor.getAsText(); + + if (!acceptsOther && !isATag(text)) { + throw new IllegalArgumentException("Value not allowed: "+text); + } + } + } + + guiEditor.setValue(value); + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + String text = fixGetAsTextBug(guiEditor.getAsText()); + if (isValidValue(text)) { + lastValidValue = text; + firePropertyChange(); + } else { + if (GuiPackage.getInstance() == null){ + log.warn("Invalid value: "+text+" "+typeEditor); + } else { + JOptionPane.showMessageDialog(guiEditor.getCustomEditor().getParent(), + JMeterUtils.getResString("property_editor.value_is_invalid_message"),//$NON-NLS-1$ + JMeterUtils.getResString("property_editor.value_is_invalid_title"), //$NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + } + // Revert to the previous value: + guiEditor.setAsText(lastValidValue); + } + } + + public void addChangeListener(PropertyChangeListener listener) { + guiEditor.addPropertyChangeListener(listener); + } +} diff --git a/src/core/org/apache/jmeter/testelement/AbstractScopedAssertion.java b/src/core/org/apache/jmeter/testelement/AbstractScopedAssertion.java new file mode 100644 index 00000000000..18a201dab18 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/AbstractScopedAssertion.java @@ -0,0 +1,49 @@ +/* + * 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.testelement; + +/** + *

+ * Super-class for all Assertions that can be applied to main sample, sub-samples or both. + * Test elements merely need to extend this class to support scoping. + *

+ * + *

+ * Their corresponding GUI classes need to add the AssertionScopePanel to the GUI + * using the AbstractAssertionGui methods: + *

    + *
  • createScopePanel()
  • + *
  • saveScopeSettings()
  • + *
  • showScopeSettings()
  • + *
+ */ +public abstract class AbstractScopedAssertion extends AbstractScopedTestElement { + + private static final long serialVersionUID = 240L; + + //+ JMX attributes - do not change + private static final String SCOPE = "Assertion.scope"; + //- JMX + + @Override + protected String getScopeName() { + return SCOPE; + } + +} diff --git a/src/core/org/apache/jmeter/testelement/AbstractScopedTestElement.java b/src/core/org/apache/jmeter/testelement/AbstractScopedTestElement.java new file mode 100644 index 00000000000..7b1858cba1b --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/AbstractScopedTestElement.java @@ -0,0 +1,166 @@ +/* + * 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.testelement; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.jmeter.samplers.SampleResult; + +/** + *

+ * Super-class for TestElements that can be applied to main sample, sub-samples or both. + * [Assertions use a different class because they use a different value for the {@link #getScopeName()} constant] + *

+ * + *

+ * Their corresponding GUI classes need to add the ScopePanel to the GUI + * using the AbstractXXXGui methods: + *

    + *
  • createScopePanel()
  • + *
  • saveScopeSettings()
  • + *
  • showScopeSettings()
  • + *
+ */ +public abstract class AbstractScopedTestElement extends AbstractTestElement { + + private static final long serialVersionUID = 240L; + + //+ JMX attributes - do not change + private static final String SCOPE = "Sample.scope"; // $NON-NLS-1$ + private static final String SCOPE_PARENT = "parent"; // $NON-NLS-1$ + private static final String SCOPE_CHILDREN = "children"; // $NON-NLS-1$ + private static final String SCOPE_ALL = "all"; // $NON-NLS-1$ + private static final String SCOPE_VARIABLE = "variable"; // $NON-NLS-1$ + private static final String SCOPE_VARIABLE_NAME = "Scope.variable"; // $NON-NLS-1$ + //- JMX + + + protected String getScopeName() { + return SCOPE; + } + + /** + * Get the scope setting + * @return the scope, default parent + */ + public String fetchScope() { + return getPropertyAsString(getScopeName(), SCOPE_PARENT); + } + + /** + * Is the assertion to be applied to the main (parent) sample? + * + * @param scope + * name of the scope to be checked + * @return true if the assertion is to be applied to the parent + * sample. + */ + public boolean isScopeParent(String scope) { + return scope.equals(SCOPE_PARENT); + } + + /** + * Is the assertion to be applied to the sub-samples (children)? + * + * @param scope + * name of the scope to be checked + * @return true if the assertion is to be applied to the + * children. + */ + public boolean isScopeChildren(String scope) { + return scope.equals(SCOPE_CHILDREN); + } + + /** + * Is the assertion to be applied to the all samples? + * + * @param scope + * name of the scope to be checked + * @return true if the assertion is to be applied to the all + * samples. + */ + public boolean isScopeAll(String scope) { + return scope.equals(SCOPE_ALL); + } + + /** + * Is the assertion to be applied to the all samples? + * + * @param scope + * name of the scope to be checked + * @return true if the assertion is to be applied to the all + * samples. + */ + public boolean isScopeVariable(String scope) { + return scope.equals(SCOPE_VARIABLE); + } + + /** + * Is the assertion to be applied to the all samples? + * + * @return true if the assertion is to be applied to the all samples. + */ + protected boolean isScopeVariable() { + return isScopeVariable(fetchScope()); + } + + public String getVariableName(){ + return getPropertyAsString(SCOPE_VARIABLE_NAME, ""); + } + + public void setScopeParent() { + removeProperty(getScopeName()); + } + + public void setScopeChildren() { + setProperty(getScopeName(), SCOPE_CHILDREN); + } + + public void setScopeAll() { + setProperty(getScopeName(), SCOPE_ALL); + } + + public void setScopeVariable(String variableName) { + setProperty(getScopeName(), SCOPE_VARIABLE); + setProperty(SCOPE_VARIABLE_NAME, variableName); + } + + /** + * Generate a list of qualifying sample results, + * depending on the scope. + * + * @param result current sample + * @return list containing the current sample and/or its child samples + */ + protected List getSampleList(SampleResult result) { + List sampleList = new ArrayList(); + + String scope = fetchScope(); + if (isScopeParent(scope) || isScopeAll(scope)) { + sampleList.add(result); + } + if (isScopeChildren(scope) || isScopeAll(scope)) { + for (SampleResult subResult : result.getSubResults()) { + sampleList.add(subResult); + } + } + return sampleList; + } +} diff --git a/src/core/org/apache/jmeter/testelement/AbstractTestElement.java b/src/core/org/apache/jmeter/testelement/AbstractTestElement.java new file mode 100644 index 00000000000..403312e4f75 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/AbstractTestElement.java @@ -0,0 +1,658 @@ +/* + * 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.testelement; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.gui.Searchable; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.testelement.property.MapProperty; +import org.apache.jmeter.testelement.property.MultiProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.PropertyIteratorImpl; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + */ +public abstract class AbstractTestElement implements TestElement, Serializable, Searchable { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Map propMap = + Collections.synchronizedMap(new LinkedHashMap()); + + /** + * Holds properties added when isRunningVersion is true + */ + private transient Set temporaryProperties; + + private transient boolean runningVersion = false; + + // Thread-specific variables saved here to save recalculation + private transient JMeterContext threadContext = null; + + private transient String threadName = null; + + @Override + public Object clone() { + try { + TestElement clonedElement = this.getClass().newInstance(); + + PropertyIterator iter = propertyIterator(); + while (iter.hasNext()) { + clonedElement.setProperty(iter.next().clone()); + } + clonedElement.setRunningVersion(runningVersion); + return clonedElement; + } catch (InstantiationException e) { + throw new AssertionError(e); // clone should never return null + } catch (IllegalAccessException e) { + throw new AssertionError(e); // clone should never return null + } + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + propMap.clear(); + } + + /** + * {@inheritDoc} + *

+ * Default implementation - does nothing + */ + @Override + public void clearTestElementChildren(){ + // NOOP + } + + /** + * {@inheritDoc} + */ + @Override + public void removeProperty(String key) { + propMap.remove(key); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (o instanceof AbstractTestElement) { + return ((AbstractTestElement) o).propMap.equals(propMap); + } else { + return false; + } + } + + // TODO temporary hack to avoid unnecessary bug reports for subclasses + + /** + * {@inheritDoc} + */ + @Override + public int hashCode(){ + return System.identityHashCode(this); + } + + /* + * URGENT: TODO - sort out equals and hashCode() - at present equal + * instances can/will have different hashcodes - problem is, when a proper + * hashcode is used, tests stop working, e.g. listener data disappears when + * switching views... This presumably means that instances currently + * regarded as equal, aren't really equal. + * + * @see java.lang.Object#hashCode() + */ + // This would be sensible, but does not work: + // public int hashCode() + // { + // return propMap.hashCode(); + // } + + /** + * {@inheritDoc} + */ + @Override + public void addTestElement(TestElement el) { + mergeIn(el); + } + + @Override + public void setName(String name) { + setProperty(TestElement.NAME, name); + } + + @Override + public String getName() { + return getPropertyAsString(TestElement.NAME); + } + + @Override + public void setComment(String comment){ + setProperty(new StringProperty(TestElement.COMMENTS, comment)); + } + + @Override + public String getComment(){ + return getProperty(TestElement.COMMENTS).getStringValue(); + } + + /** + * Get the named property. If it doesn't exist, a new NullProperty object is + * created with the same name and returned. + */ + @Override + public JMeterProperty getProperty(String key) { + JMeterProperty prop = propMap.get(key); + if (prop == null) { + prop = new NullProperty(key); + } + return prop; + } + + @Override + public void traverse(TestElementTraverser traverser) { + PropertyIterator iter = propertyIterator(); + traverser.startTestElement(this); + while (iter.hasNext()) { + traverseProperty(traverser, iter.next()); + } + traverser.endTestElement(this); + } + + protected void traverseProperty(TestElementTraverser traverser, JMeterProperty value) { + traverser.startProperty(value); + if (value instanceof TestElementProperty) { + ((TestElement) value.getObjectValue()).traverse(traverser); + } else if (value instanceof CollectionProperty) { + traverseCollection((CollectionProperty) value, traverser); + } else if (value instanceof MapProperty) { + traverseMap((MapProperty) value, traverser); + } + traverser.endProperty(value); + } + + protected void traverseMap(MapProperty map, TestElementTraverser traverser) { + PropertyIterator iter = map.valueIterator(); + while (iter.hasNext()) { + traverseProperty(traverser, iter.next()); + } + } + + protected void traverseCollection(CollectionProperty col, TestElementTraverser traverser) { + PropertyIterator iter = col.iterator(); + while (iter.hasNext()) { + traverseProperty(traverser, iter.next()); + } + } + + @Override + public int getPropertyAsInt(String key) { + return getProperty(key).getIntValue(); + } + + @Override + public int getPropertyAsInt(String key, int defaultValue) { + JMeterProperty jmp = getProperty(key); + return jmp instanceof NullProperty ? defaultValue : jmp.getIntValue(); + } + + @Override + public boolean getPropertyAsBoolean(String key) { + return getProperty(key).getBooleanValue(); + } + + @Override + public boolean getPropertyAsBoolean(String key, boolean defaultVal) { + JMeterProperty jmp = getProperty(key); + return jmp instanceof NullProperty ? defaultVal : jmp.getBooleanValue(); + } + + @Override + public float getPropertyAsFloat(String key) { + return getProperty(key).getFloatValue(); + } + + @Override + public long getPropertyAsLong(String key) { + return getProperty(key).getLongValue(); + } + + @Override + public long getPropertyAsLong(String key, long defaultValue) { + JMeterProperty jmp = getProperty(key); + return jmp instanceof NullProperty ? defaultValue : jmp.getLongValue(); + } + + @Override + public double getPropertyAsDouble(String key) { + return getProperty(key).getDoubleValue(); + } + + @Override + public String getPropertyAsString(String key) { + return getProperty(key).getStringValue(); + } + + @Override + public String getPropertyAsString(String key, String defaultValue) { + JMeterProperty jmp = getProperty(key); + return jmp instanceof NullProperty ? defaultValue : jmp.getStringValue(); + } + + /** + * Add property to test element + * @param property {@link JMeterProperty} to add to current Test Element + * @param clone clone property + */ + protected void addProperty(JMeterProperty property, boolean clone) { + JMeterProperty propertyToPut = property; + if(clone) { + propertyToPut = property.clone(); + } + if (isRunningVersion()) { + setTemporary(propertyToPut); + } else { + clearTemporary(property); + } + JMeterProperty prop = getProperty(property.getName()); + + if (prop instanceof NullProperty || (prop instanceof StringProperty && prop.getStringValue().equals(""))) { + propMap.put(property.getName(), propertyToPut); + } else { + prop.mergeIn(propertyToPut); + } + } + + /** + * Add property to test element without cloning it + * @param property {@link JMeterProperty} + */ + protected void addProperty(JMeterProperty property) { + addProperty(property, false); + } + + /** + * Remove property from temporaryProperties + * @param property {@link JMeterProperty} + */ + protected void clearTemporary(JMeterProperty property) { + if (temporaryProperties != null) { + temporaryProperties.remove(property); + } + } + + /** + * Log the properties of the test element + * + * @see TestElement#setProperty(JMeterProperty) + */ + protected void logProperties() { + if (log.isDebugEnabled()) { + PropertyIterator iter = propertyIterator(); + while (iter.hasNext()) { + JMeterProperty prop = iter.next(); + log.debug("Property " + prop.getName() + " is temp? " + isTemporary(prop) + " and is a " + + prop.getObjectValue()); + } + } + } + + @Override + public void setProperty(JMeterProperty property) { + if (isRunningVersion()) { + if (getProperty(property.getName()) instanceof NullProperty) { + addProperty(property); + } else { + getProperty(property.getName()).setObjectValue(property.getObjectValue()); + } + } else { + propMap.put(property.getName(), property); + } + } + + @Override + public void setProperty(String name, String value) { + setProperty(new StringProperty(name, value)); + } + + /** + * Create a String property - but only if it is not the default. + * This is intended for use when adding new properties to JMeter + * so that JMX files are not expanded unnecessarily. + * + * N.B. - must agree with the default applied when reading the property. + * + * @param name property name + * @param value current value + * @param dflt default + */ + @Override + public void setProperty(String name, String value, String dflt) { + if (dflt.equals(value)) { + removeProperty(name); + } else { + setProperty(new StringProperty(name, value)); + } + } + + @Override + public void setProperty(String name, boolean value) { + setProperty(new BooleanProperty(name, value)); + } + + /** + * Create a boolean property - but only if it is not the default. + * This is intended for use when adding new properties to JMeter + * so that JMX files are not expanded unnecessarily. + * + * N.B. - must agree with the default applied when reading the property. + * + * @param name property name + * @param value current value + * @param dflt default + */ + @Override + public void setProperty(String name, boolean value, boolean dflt) { + if (value == dflt) { + removeProperty(name); + } else { + setProperty(new BooleanProperty(name, value)); + } + } + + @Override + public void setProperty(String name, int value) { + setProperty(new IntegerProperty(name, value)); + } + + /** + * Create an int property - but only if it is not the default. + * This is intended for use when adding new properties to JMeter + * so that JMX files are not expanded unnecessarily. + * + * N.B. - must agree with the default applied when reading the property. + * + * @param name property name + * @param value current value + * @param dflt default + */ + @Override + public void setProperty(String name, int value, int dflt) { + if (value == dflt) { + removeProperty(name); + } else { + setProperty(new IntegerProperty(name, value)); + } + } + + @Override + public void setProperty(String name, long value) { + setProperty(new LongProperty(name, value)); + } + + /** + * Create a long property - but only if it is not the default. + * This is intended for use when adding new properties to JMeter + * so that JMX files are not expanded unnecessarily. + * + * N.B. - must agree with the default applied when reading the property. + * + * @param name property name + * @param value current value + * @param dflt default + */ + @Override + public void setProperty(String name, long value, long dflt) { + if (value == dflt) { + removeProperty(name); + } else { + setProperty(new LongProperty(name, value)); + } + } + + @Override + public PropertyIterator propertyIterator() { + return new PropertyIteratorImpl(propMap.values()); + } + + /** + * Add to this the properties of element (by reference) + * @param element {@link TestElement} + */ + protected void mergeIn(TestElement element) { + PropertyIterator iter = element.propertyIterator(); + while (iter.hasNext()) { + JMeterProperty prop = iter.next(); + addProperty(prop, false); + } + } + + /** + * Returns the runningVersion. + */ + @Override + public boolean isRunningVersion() { + return runningVersion; + } + + /** + * Sets the runningVersion. + * + * @param runningVersion + * the runningVersion to set + */ + @Override + public void setRunningVersion(boolean runningVersion) { + this.runningVersion = runningVersion; + PropertyIterator iter = propertyIterator(); + while (iter.hasNext()) { + iter.next().setRunningVersion(runningVersion); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion() { + Iterator> iter = propMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + JMeterProperty prop = entry.getValue(); + if (isTemporary(prop)) { + iter.remove(); + clearTemporary(prop); + } else { + prop.recoverRunningVersion(this); + } + } + emptyTemporary(); + } + + /** + * Clears temporaryProperties + */ + protected void emptyTemporary() { + if (temporaryProperties != null) { + temporaryProperties.clear(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isTemporary(JMeterProperty property) { + if (temporaryProperties == null) { + return false; + } else { + return temporaryProperties.contains(property); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setTemporary(JMeterProperty property) { + if (temporaryProperties == null) { + temporaryProperties = new LinkedHashSet(); + } + temporaryProperties.add(property); + if (property instanceof MultiProperty) { + PropertyIterator iter = ((MultiProperty) property).iterator(); + while (iter.hasNext()) { + setTemporary(iter.next()); + } + } + } + + /** + * @return Returns the threadContext. + */ + @Override + public JMeterContext getThreadContext() { + if (threadContext == null) { + /* + * Only samplers have the thread context set up by JMeterThread at + * present, so suppress the warning for now + */ + // log.warn("ThreadContext was not set up - should only happen in + // JUnit testing..." + // ,new Throwable("Debug")); + threadContext = JMeterContextService.getContext(); + } + return threadContext; + } + + /** + * @param inthreadContext + * The threadContext to set. + */ + @Override + public void setThreadContext(JMeterContext inthreadContext) { + if (threadContext != null) { + if (inthreadContext != threadContext) { + throw new RuntimeException("Attempting to reset the thread context"); + } + } + this.threadContext = inthreadContext; + } + + /** + * @return Returns the threadName. + */ + @Override + public String getThreadName() { + return threadName; + } + + /** + * @param inthreadName + * The threadName to set. + */ + @Override + public void setThreadName(String inthreadName) { + if (threadName != null) { + if (!threadName.equals(inthreadName)) { + throw new RuntimeException("Attempting to reset the thread name"); + } + } + this.threadName = inthreadName; + } + + public AbstractTestElement() { + super(); + } + + /** + * {@inheritDoc} + */ + // Default implementation + @Override + public boolean canRemove() { + return true; + } + + // Moved from JMeter class + @Override + public boolean isEnabled() { + return getProperty(TestElement.ENABLED) instanceof NullProperty || getPropertyAsBoolean(TestElement.ENABLED); + } + + @Override + public void setEnabled(boolean enabled) { + setProperty(new BooleanProperty(TestElement.ENABLED, enabled)); + } + + /** + * {@inheritDoc}} + */ + @Override + public List getSearchableTokens() { + List result = new ArrayList(25); + PropertyIterator iterator = propertyIterator(); + while(iterator.hasNext()) { + JMeterProperty jMeterProperty = iterator.next(); + result.add(jMeterProperty.getName()); + result.add(jMeterProperty.getStringValue()); + } + return result; + } + + /** + * Add to result the values of propertyNames + * @param result List of values of propertyNames + * @param propertyNames Set of names of properties to extract + */ + protected final void addPropertiesValues(List result, Set propertyNames) { + PropertyIterator iterator = propertyIterator(); + while(iterator.hasNext()) { + JMeterProperty jMeterProperty = iterator.next(); + if(propertyNames.contains(jMeterProperty.getName())) { + result.add(jMeterProperty.getStringValue()); + } + } + } +} diff --git a/src/core/org/apache/jmeter/testelement/AbstractTestElementBeanInfo.java b/src/core/org/apache/jmeter/testelement/AbstractTestElementBeanInfo.java new file mode 100644 index 00000000000..0a180deae2a --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/AbstractTestElementBeanInfo.java @@ -0,0 +1,104 @@ +/* + * 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.testelement; + +import java.awt.Image; +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.EventSetDescriptor; +import java.beans.MethodDescriptor; +import java.beans.PropertyDescriptor; + +/** + * This is the BeanInfo object for the AbstractTestElement class. It acts as a "stopper" + * for the introspector: we don't want it to look at properties defined at this + * or higher classes. + *

+ * Note this is really needed since using Introspector.getBeanInfo with a stop + * class is not an option because: + *

    + *
  1. The API does not define a 3-parameter getBeanInfo in which you can use a + * stop class AND flags. [Why? I guess this is a bug in the spec.] + *
  2. java.beans.Introspector is buggy and, opposite to what's stated in the + * Javadocs, only results of getBeanInfo(Class) are actually cached. + *
+ * + * @version $Revision$ + */ +public class AbstractTestElementBeanInfo implements BeanInfo { + + @Override + public BeanInfo[] getAdditionalBeanInfo() { + return new BeanInfo[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public BeanDescriptor getBeanDescriptor() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public int getDefaultEventIndex() { + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public int getDefaultPropertyIndex() { + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public EventSetDescriptor[] getEventSetDescriptors() { + return new EventSetDescriptor[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public Image getIcon(int iconKind) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MethodDescriptor[] getMethodDescriptors() { + return new MethodDescriptor[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyDescriptor[] getPropertyDescriptors() { + return new PropertyDescriptor[0]; + } +} diff --git a/src/core/org/apache/jmeter/testelement/OnErrorTestElement.java b/src/core/org/apache/jmeter/testelement/OnErrorTestElement.java new file mode 100644 index 00000000000..c27ac80d23d --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/OnErrorTestElement.java @@ -0,0 +1,78 @@ +/* + * 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. + * + */ + +/* + * Created on Dec 9, 2003 + * + */ +package org.apache.jmeter.testelement; + +import org.apache.jmeter.testelement.property.IntegerProperty; + +/** + * @version $Revision$ + */ +public abstract class OnErrorTestElement extends AbstractTestElement { + private static final long serialVersionUID = 240L; + + /* Action to be taken when a Sampler error occurs */ + public static final int ON_ERROR_CONTINUE = 0; + + public static final int ON_ERROR_STOPTHREAD = 1; + + public static final int ON_ERROR_STOPTEST = 2; + + public static final int ON_ERROR_STOPTEST_NOW = 3; + + public static final int ON_ERROR_START_NEXT_THREAD_LOOP = 4; + + /* Property name */ + public static final String ON_ERROR_ACTION = "OnError.action"; + + protected OnErrorTestElement() { + super(); + } + + public void setErrorAction(int value) { + setProperty(new IntegerProperty(ON_ERROR_ACTION, value)); + } + + public int getErrorAction() { + return getPropertyAsInt(ON_ERROR_ACTION); + } + + public boolean isContinue() { + return getErrorAction() == ON_ERROR_CONTINUE; + } + + public boolean isStopThread() { + return getErrorAction() == ON_ERROR_STOPTHREAD; + } + + public boolean isStopTest() { + return getErrorAction() == ON_ERROR_STOPTEST; + } + + public boolean isStopTestNow() { + return getErrorAction() == ON_ERROR_STOPTEST_NOW; + } + + public boolean isStartNextThreadLoop() { + return getErrorAction() == ON_ERROR_START_NEXT_THREAD_LOOP; + } +} diff --git a/src/core/org/apache/jmeter/testelement/TestCloneable.java b/src/core/org/apache/jmeter/testelement/TestCloneable.java new file mode 100644 index 00000000000..63437300e58 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/TestCloneable.java @@ -0,0 +1,26 @@ +/* + * 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. + * + */ + +/* + * Created on May 28, 2004 + */ +package org.apache.jmeter.testelement; + +public interface TestCloneable extends Cloneable { + Object clone(); +} diff --git a/src/core/org/apache/jmeter/testelement/TestElement.java b/src/core/org/apache/jmeter/testelement/TestElement.java new file mode 100644 index 00000000000..a3b1b4ba3d3 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/TestElement.java @@ -0,0 +1,337 @@ +/* + * 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.testelement; + +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; + +public interface TestElement extends Cloneable { + String NAME = "TestElement.name"; //$NON-NLS-1$ + + String GUI_CLASS = "TestElement.gui_class"; //$NON-NLS-1$ + + String ENABLED = "TestElement.enabled"; //$NON-NLS-1$ + + String TEST_CLASS = "TestElement.test_class"; //$NON-NLS-1$ + + // Needed by AbstractTestElement. + // Also TestElementConverter and TestElementPropertyConverter for handling empty comments + String COMMENTS = "TestPlan.comments"; //$NON-NLS-1$ + // N.B. Comments originally only applied to Test Plans, hence the name - which can now not be easily changed + + void addTestElement(TestElement child); + + /** + * This method should clear any test element properties that are merged + * by {@link #addTestElement(TestElement)}. + */ + void clearTestElementChildren(); + + void setProperty(String key, String value); + + void setProperty(String key, String value, String dflt); + + void setProperty(String key, boolean value); + + void setProperty(String key, boolean value, boolean dflt); + + void setProperty(String key, int value); + + void setProperty(String key, int value, int dflt); + + void setProperty(String name, long value); + + void setProperty(String name, long value, long dflt); + + /** + * Check if ENABLED property is present and true ; defaults to true + * + * @return true if element is enabled + */ + boolean isEnabled(); + + /** + * Set the enabled status of the test element + * @param enabled the status to set + */ + void setEnabled(boolean enabled); + + /** + * Returns true or false whether the element is the running version. + * + * @return true if the element is the running version + */ + boolean isRunningVersion(); + + /** + * Test whether a given property is only a temporary resident of the + * TestElement + * + * @param property + * the property to be tested + * @return true if property is temporary + */ + boolean isTemporary(JMeterProperty property); + + /** + * Indicate that the given property should be only a temporary property in + * the TestElement + * + * @param property + * void + */ + void setTemporary(JMeterProperty property); + + /** + * Return a property as a boolean value. + * + * @param key + * the name of the property to get + * @return the value of the property + */ + boolean getPropertyAsBoolean(String key); + + /** + * Return a property as a boolean value or a default value if no property + * could be found. + * + * @param key + * the name of the property to get + * @param defaultValue + * the default value to use + * @return the value of the property, or defaultValue if no + * property could be found + */ + boolean getPropertyAsBoolean(String key, boolean defaultValue); + + /** + * Return a property as a long value. + * + * @param key + * the name of the property to get + * @return the value of the property + */ + long getPropertyAsLong(String key); + + /** + * Return a property as a long value or a default value if no property + * could be found. + * + * @param key + * the name of the property to get + * @param defaultValue + * the default value to use + * @return the value of the property, or defaultValue if no + * property could be found + */ + long getPropertyAsLong(String key, long defaultValue); + + /** + * Return a property as an int value. + * + * @param key + * the name of the property to get + * @return the value of the property + */ + int getPropertyAsInt(String key); + + /** + * Return a property as an int value or a default value if no property + * could be found. + * + * @param key + * the name of the property to get + * @param defaultValue + * the default value to use + * @return the value of the property, or defaultValue if no + * property could be found + */ + int getPropertyAsInt(String key, int defaultValue); + + /** + * Return a property as a float value. + * + * @param key + * the name of the property to get + * @return the value of the property + */ + float getPropertyAsFloat(String key); + + /** + * Return a property as a double value. + * + * @param key + * the name of the property to get + * @return the value of the property + */ + double getPropertyAsDouble(String key); + + /** + * Make the test element the running version, or make it no longer the + * running version. This tells the test element that it's current state must + * be retrievable by a call to recoverRunningVersion(). It is kind of like + * making the TestElement Read- Only, but not as strict. Changes can be made + * and the element can be modified, but the state of the element at the time + * of the call to setRunningVersion() must be recoverable. + * + * @param run + * flag whether this element should be the running version + */ + void setRunningVersion(boolean run); + + /** + * Tells the test element to return to the state it was in when + * setRunningVersion(true) was called. + */ + void recoverRunningVersion(); + + /** + * Clear the TestElement of all data. + */ + void clear(); + // TODO - yet another ambiguous name - does it need changing? + // See also: Clearable, JMeterGUIComponent + + /** + * Return a property as a string value. + * + * @param key + * the name of the property to get + * @return the value of the property + */ + String getPropertyAsString(String key); + + /** + * Return a property as an string value or a default value if no property + * could be found. + * + * @param key + * the name of the property to get + * @param defaultValue + * the default value to use + * @return the value of the property, or defaultValue if no + * property could be found + */ + String getPropertyAsString(String key, String defaultValue); + + /** + * Sets and overwrites a property in the TestElement. This call will be + * ignored if the TestElement is currently a "running version". + * + * @param property + * the property to be set + */ + void setProperty(JMeterProperty property); + + /** + * Given the name of the property, returns the appropriate property from + * JMeter. If it is null, a NullProperty object will be returned. + * + * @param propName + * the name of the property to get + * @return {@link JMeterProperty} stored under the name, or + * {@link NullProperty} if no property can be found + */ + JMeterProperty getProperty(String propName); + + /** + * Get a Property Iterator for the TestElements properties. + * + * @return PropertyIterator + */ + PropertyIterator propertyIterator(); + + /** + * Remove property stored under the key + * + * @param key + * name of the property to be removed + */ + void removeProperty(String key); + + // lifecycle methods + + Object clone(); + + /** + * Convenient way to traverse a test element. + * + * @param traverser + * The traverser that is notified of the contained elements + */ + void traverse(TestElementTraverser traverser); + + /** + * @return Returns the threadContext. + */ + JMeterContext getThreadContext(); + + /** + * @param threadContext + * The threadContext to set. + */ + void setThreadContext(JMeterContext threadContext); + + /** + * @return Returns the threadName. + */ + String getThreadName(); + + /** + * @param threadName + * The threadName to set. + */ + void setThreadName(String threadName); + + /** + * Called by Remove to determine if it is safe to remove the element. The + * element can either clean itself up, and return true, or the element can + * return false. + * + * @return true if safe to remove the element + */ + boolean canRemove(); + + /** + * Get the name of this test element + * @return name of this element + */ + String getName(); + + /** + * @param name + * of this element + */ + void setName(String name); + + /** + * @return comment associated with this element + */ + String getComment(); + + /** + * Associates a comment with this element + * + * @param comment + * to be associated + */ + void setComment(String comment); +} diff --git a/src/core/org/apache/jmeter/testelement/TestElementTraverser.java b/src/core/org/apache/jmeter/testelement/TestElementTraverser.java new file mode 100644 index 00000000000..a3c16762345 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/TestElementTraverser.java @@ -0,0 +1,61 @@ +/* + * 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.testelement; + +import org.apache.jmeter.testelement.property.JMeterProperty; + +/** + * For traversing Test Elements, which contain property that can be other test + * elements, strings, collections, maps, objects + * + * @version $Revision$ + */ +public interface TestElementTraverser { + + /** + * Notification that a new test element is about to be traversed. + * + * @param el element to be traversed + */ + void startTestElement(TestElement el); + + /** + * Notification that the test element is now done. + * + * @param el element that was traversed + */ + void endTestElement(TestElement el); + + /** + * Notification that a property is starting. This could be a test element + * property or a Map property - depends on the context. + * + * @param key property to be traversed + */ + void startProperty(JMeterProperty key); + + /** + * Notification that a property is ending. Again, this could be a test + * element or a Map property, depending on the context. + * + * @param key property that was traversed + */ + void endProperty(JMeterProperty key); + +} diff --git a/src/core/org/apache/jmeter/testelement/TestIterationListener.java b/src/core/org/apache/jmeter/testelement/TestIterationListener.java new file mode 100644 index 00000000000..225e9c3d19d --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/TestIterationListener.java @@ -0,0 +1,35 @@ +/* + * 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.testelement; + +import org.apache.jmeter.engine.event.LoopIterationEvent; + +public interface TestIterationListener { + + /** + * Each time through a Thread Group's test script, an iteration event is + * fired for each thread. + * + * This will be after the test elements have been cloned, so in general + * the instance will not be the same as the ones the start/end methods call. + * + * @param event the iteration event + */ + void testIterationStart(LoopIterationEvent event); +} diff --git a/src/core/org/apache/jmeter/testelement/TestListener.java b/src/core/org/apache/jmeter/testelement/TestListener.java new file mode 100644 index 00000000000..94b79cd7f45 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/TestListener.java @@ -0,0 +1,29 @@ +/* + * 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.testelement; + +/** + * TestListener interface is used for methods that are called at different + * stages of each test. + * + * @deprecated since 2.8, please use {@link TestStateListener} and/or {@link TestIterationListener} + */ +@Deprecated +public interface TestListener extends TestStateListener, TestIterationListener { +} diff --git a/src/core/org/apache/jmeter/testelement/TestPlan.java b/src/core/org/apache/jmeter/testelement/TestPlan.java new file mode 100644 index 00000000000..f2e2e58491c --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/TestPlan.java @@ -0,0 +1,273 @@ +/* + * 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.testelement; + +import java.io.IOException; +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.NewDriver; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class TestPlan extends AbstractTestElement implements Serializable, TestStateListener { + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //+ JMX field names - do not change values + private static final String FUNCTIONAL_MODE = "TestPlan.functional_mode"; //$NON-NLS-1$ + + private static final String USER_DEFINED_VARIABLES = "TestPlan.user_defined_variables"; //$NON-NLS-1$ + + private static final String SERIALIZE_THREADGROUPS = "TestPlan.serialize_threadgroups"; //$NON-NLS-1$ + + private static final String CLASSPATHS = "TestPlan.user_define_classpath"; //$NON-NLS-1$ + + private static final String TEARDOWN_ON_SHUTDOWN = "TestPlan.tearDown_on_shutdown"; //$NON-NLS-1$ + + //- JMX field names + + private static final String CLASSPATH_SEPARATOR = ","; //$NON-NLS-1$ + + private static final String BASEDIR = "basedir"; + + private transient List threadGroups = new LinkedList(); + + // There's only 1 test plan, so can cache the mode here + private static volatile boolean functionalMode = false; + + public TestPlan() { + // this("Test Plan"); + // setFunctionalMode(false); + // setSerialized(false); + } + + public TestPlan(String name) { + setName(name); + // setFunctionalMode(false); + // setSerialized(false); + } + + // create transient item + private Object readResolve(){ + threadGroups = new LinkedList(); + return this; + } + + public void prepareForPreCompile() + { + getVariables().setRunningVersion(true); + } + + /** + * Fetches the functional mode property + * + * @return functional mode + */ + public boolean isFunctionalMode() { + return getPropertyAsBoolean(FUNCTIONAL_MODE); + } + + public void setUserDefinedVariables(Arguments vars) { + setProperty(new TestElementProperty(USER_DEFINED_VARIABLES, vars)); + } + + public JMeterProperty getUserDefinedVariablesAsProperty() { + return getProperty(USER_DEFINED_VARIABLES); + } + + public String getBasedir() { + return getPropertyAsString(BASEDIR); + } + + // Does not appear to be used yet + public void setBasedir(String b) { + setProperty(BASEDIR, b); + } + + public Arguments getArguments() { + return getVariables(); + } + + public Map getUserDefinedVariables() { + Arguments args = getVariables(); + return args.getArgumentsAsMap(); + } + + private Arguments getVariables() { + Arguments args = (Arguments) getProperty(USER_DEFINED_VARIABLES).getObjectValue(); + if (args == null) { + args = new Arguments(); + setUserDefinedVariables(args); + } + return args; + } + + public void setFunctionalMode(boolean funcMode) { + setProperty(new BooleanProperty(FUNCTIONAL_MODE, funcMode)); + functionalMode = funcMode; + } + + /** + * Gets the static copy of the functional mode + * + * @return mode + */ + public static boolean getFunctionalMode() { + return functionalMode; + } + + public void setSerialized(boolean serializeTGs) { + setProperty(new BooleanProperty(SERIALIZE_THREADGROUPS, serializeTGs)); + } + + public void setTearDownOnShutdown(boolean tearDown) { + setProperty(TEARDOWN_ON_SHUTDOWN, tearDown, false); + } + + public boolean isTearDownOnShutdown() { + return getPropertyAsBoolean(TEARDOWN_ON_SHUTDOWN, false); + } + + /** + * Set the classpath for the test plan. If the classpath is made up from + * more then one path, the parts must be separated with + * {@link TestPlan#CLASSPATH_SEPARATOR}. + * + * @param text + * the classpath to be set + */ + public void setTestPlanClasspath(String text) { + setProperty(CLASSPATHS,text); + } + + public void setTestPlanClasspathArray(String[] text) { + StringBuilder cat = new StringBuilder(); + for (int idx=0; idx < text.length; idx++) { + if (idx > 0) { + cat.append(CLASSPATH_SEPARATOR); + } + cat.append(text[idx]); + } + this.setTestPlanClasspath(cat.toString()); + } + + public String[] getTestPlanClasspathArray() { + return JOrphanUtils.split(this.getTestPlanClasspath(),CLASSPATH_SEPARATOR); + } + + /** + * Returns the classpath + * @return classpath + */ + public String getTestPlanClasspath() { + return getPropertyAsString(CLASSPATHS); + } + + /** + * Fetch the serialize threadgroups property + * + * @return serialized setting + */ + public boolean isSerialized() { + return getPropertyAsBoolean(SERIALIZE_THREADGROUPS); + } + + public void addParameter(String name, String value) { + getVariables().addArgument(name, value); + } + + @Override + public void addTestElement(TestElement tg) { + super.addTestElement(tg); + if (tg instanceof AbstractThreadGroup && !isRunningVersion()) { + addThreadGroup((AbstractThreadGroup) tg); + } + } + + /** + * Adds a feature to the AbstractThreadGroup attribute of the TestPlan object. + * + * @param group + * the feature to be added to the AbstractThreadGroup attribute + */ + public void addThreadGroup(AbstractThreadGroup group) { + threadGroups.add(group); + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded() { + try { + FileServer.getFileServer().closeFiles(); + } catch (IOException e) { + log.error("Problem closing files at end of test", e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded(String host) { + testEnded(); + + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted() { + if (getBasedir() != null && getBasedir().length() > 0) { + try { + FileServer.getFileServer().setBasedir(FileServer.getFileServer().getBaseDir() + getBasedir()); + } catch (IllegalStateException e) { + log.error("Failed to set file server base dir with " + getBasedir(), e); + } + } + // we set the classpath + String[] paths = this.getTestPlanClasspathArray(); + for (int idx=0; idx < paths.length; idx++) { + NewDriver.addURL(paths[idx]); + log.info("add " + paths[idx] + " to classpath"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted(String host) { + testStarted(); + } + +} diff --git a/src/core/org/apache/jmeter/testelement/TestStateListener.java b/src/core/org/apache/jmeter/testelement/TestStateListener.java new file mode 100644 index 00000000000..86e66e78afc --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/TestStateListener.java @@ -0,0 +1,102 @@ +/* + * 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.testelement; + +/** + * @since 2.8 + */ +public interface TestStateListener { + + /** + *

+ * Called just before the start of the test from the main engine thread. + * + * This is before the test elements are cloned. + * + * Note that not all the test + * variables will have been set up at this point. + *

+ * + *

+ * + * N.B. testStarted() and testEnded() are called from different threads. + * + *

+ * @see org.apache.jmeter.engine.StandardJMeterEngine#run() + * + */ + void testStarted(); + + /** + *

+ * Called just before the start of the test from the main engine thread. + * + * This is before the test elements are cloned. + * + * Note that not all the test + * variables will have been set up at this point. + *

+ * + *

+ * + * N.B. testStarted() and testEnded() are called from different threads. + * + *

+ * @see org.apache.jmeter.engine.StandardJMeterEngine#run() + * @param host name of host + */ + void testStarted(String host); + + /** + *

+ * Called once for all threads after the end of a test. + * + * This will use the same element instances as at the start of the test. + *

+ * + *

+ * + * N.B. testStarted() and testEnded() are called from different threads. + * + *

+ * @see org.apache.jmeter.engine.StandardJMeterEngine#stopTest() + * + */ + void testEnded(); + + /** + *

+ * Called once for all threads after the end of a test. + * + * This will use the same element instances as at the start of the test. + *

+ * + *

+ * + * N.B. testStarted() and testEnded() are called from different threads. + * + *

+ * @see org.apache.jmeter.engine.StandardJMeterEngine#stopTest() + * @param host name of host + * + */ + + void testEnded(String host); + +} diff --git a/src/core/org/apache/jmeter/testelement/ThreadListener.java b/src/core/org/apache/jmeter/testelement/ThreadListener.java new file mode 100644 index 00000000000..7f2fcf3f816 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/ThreadListener.java @@ -0,0 +1,44 @@ +/* + * 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.testelement; + +/** + * Allow threads to perform startup and closedown if necessary + * + */ +public interface ThreadListener { + /** + * Called for each thread before starting sampling. + * WARNING: this is called before any Config test elements are processed, + * so any properties they define will not have been merged in yet. + * + * @see org.apache.jmeter.threads.JMeterThread#threadStarted() + * + */ + void threadStarted(); + + /** + * Called for each thread after all samples have been processed. + * + * @see org.apache.jmeter.threads.JMeterThread#threadFinished(org.apache.jmeter.engine.event.LoopIterationListener) + * + */ + void threadFinished(); + +} diff --git a/src/core/org/apache/jmeter/testelement/VariablesCollection.java b/src/core/org/apache/jmeter/testelement/VariablesCollection.java new file mode 100644 index 00000000000..ae14dbfce6c --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/VariablesCollection.java @@ -0,0 +1,44 @@ +/* + * 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.testelement; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.threads.JMeterVariables; + +/** + * @version $Revision$ + */ +public class VariablesCollection implements Serializable { + + private static final long serialVersionUID = 240L; + + private final Map varMap = new HashMap(); + + public void addJMeterVariables(JMeterVariables jmVars) { + varMap.put(Thread.currentThread().getName(), jmVars); + } + + public JMeterVariables getVariables() { + return varMap.get(Thread.currentThread().getName()); + } + +} diff --git a/src/core/org/apache/jmeter/testelement/WorkBench.java b/src/core/org/apache/jmeter/testelement/WorkBench.java new file mode 100644 index 00000000000..51a954a9c51 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/WorkBench.java @@ -0,0 +1,40 @@ +/* + * 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.testelement; + +public class WorkBench extends AbstractTestElement { + + private static final long serialVersionUID = 240L; + + // JMX property name: do not change + private static final String SAVE_WORKBENCH = "WorkBench.save"; + + private static final boolean SAVE_WORKBENCH_DEFAULT = false; + + public WorkBench() { + } + + public boolean getSaveWorkBench() { + return getPropertyAsBoolean(SAVE_WORKBENCH, SAVE_WORKBENCH_DEFAULT); + } + + public void setSaveWorkBench(boolean saveWorkBench) { + setProperty(SAVE_WORKBENCH, saveWorkBench, SAVE_WORKBENCH_DEFAULT); + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/AbstractProperty.java b/src/core/org/apache/jmeter/testelement/property/AbstractProperty.java new file mode 100644 index 00000000000..541884d3e34 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/AbstractProperty.java @@ -0,0 +1,420 @@ +/* + * 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.testelement.property; + +import java.util.Collection; +import java.util.Map; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public abstract class AbstractProperty implements JMeterProperty { + private static final long serialVersionUID = 240L; + + //TODO consider using private logs for each derived class + protected static final Logger log = LoggingManager.getLoggerForClass(); + + private String name; + + private transient boolean runningVersion = false; + + // private static StringProperty defaultProperty = new StringProperty(); + + public AbstractProperty(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null"); + } + this.name = name; + } + + public AbstractProperty() { + this(""); + } + + protected boolean isEqualType(JMeterProperty prop) { + if (this.getClass().equals(prop.getClass())) { + return true; + } else { + return false; + } + } + + /** {@inheritDoc} */ + @Override + public boolean isRunningVersion() { + return runningVersion; + } + + /** {@inheritDoc} */ + @Override + public String getName() { + return name; + } + + /** {@inheritDoc} */ + @Override + public void setName(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null"); + } + this.name = name; + } + + /** {@inheritDoc} */ + @Override + public void setRunningVersion(boolean runningVersion) { + this.runningVersion = runningVersion; + } + + protected PropertyIterator getIterator(Collection values) { + return new PropertyIteratorImpl(values); + } + + /** {@inheritDoc} */ + @Override + public AbstractProperty clone() { + try { + AbstractProperty prop = (AbstractProperty) super.clone(); + prop.name = name; + prop.runningVersion = runningVersion; + return prop; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); // clone should never return null + } + } + + /** + * Returns 0 if string is invalid or null. + * + * @see JMeterProperty#getIntValue() + */ + @Override + public int getIntValue() { + String val = getStringValue(); + if (val == null || val.length()==0) { + return 0; + } + try { + return Integer.parseInt(val); + } catch (NumberFormatException e) { + return 0; + } + } + + /** + * Returns 0 if string is invalid or null. + * + * @see JMeterProperty#getLongValue() + */ + @Override + public long getLongValue() { + String val = getStringValue(); + if (val == null || val.length()==0) { + return 0; + } + try { + return Long.parseLong(val); + } catch (NumberFormatException e) { + return 0; + } + } + + /** + * Returns 0 if string is invalid or null. + * + * @see JMeterProperty#getDoubleValue() + */ + @Override + public double getDoubleValue() { + String val = getStringValue(); + if (val == null || val.length()==0) { + return 0; + } + try { + return Double.parseDouble(val); + } catch (NumberFormatException e) { + log.error("Tried to parse a non-number string to an integer", e); + return 0; + } + } + + /** + * Returns 0 if string is invalid or null. + * + * @see JMeterProperty#getFloatValue() + */ + @Override + public float getFloatValue() { + String val = getStringValue(); + if (val == null || val.length()==0) { + return 0; + } + try { + return Float.parseFloat(val); + } catch (NumberFormatException e) { + log.error("Tried to parse a non-number string to an integer", e); + return 0; + } + } + + /** + * Returns false if string is invalid or null. + * + * @see JMeterProperty#getBooleanValue() + */ + @Override + public boolean getBooleanValue() { + String val = getStringValue(); + if (val == null || val.length()==0) { + return false; + } + return Boolean.parseBoolean(val); + } + + /** + * Determines if the two objects are equal by comparing names and values + * + * @return true if names are equal and values are equal (or both null) + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof JMeterProperty)) { + return false; + } + if (this == o) { + return true; + } + JMeterProperty jpo = (JMeterProperty) o; + if (!name.equals(jpo.getName())) { + return false; + } + Object o1 = getObjectValue(); + Object o2 = jpo.getObjectValue(); + return o1 == null ? o2 == null : o1.equals(o2); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int result = 17; + result = result * 37 + name.hashCode();// name cannot be null + Object o = getObjectValue(); + result = result * 37 + (o == null ? 0 : o.hashCode()); + return result; + } + + /** + * Compares two JMeterProperty object values. N.B. Does not compare names + * + * @param arg0 + * JMeterProperty to compare against + * @return 0 if equal values or both values null; -1 otherwise + * @see Comparable#compareTo(Object) + */ + @Override + public int compareTo(JMeterProperty arg0) { + // We don't expect the string values to ever be null. But (as in + // bug 19499) sometimes they are. So have null compare less than + // any other value. Log a warning so we can try to find the root + // cause of the null value. + String val = getStringValue(); + String val2 = arg0.getStringValue(); + if (val == null) { + log.warn("Warning: Unexpected null value for property: " + name); + + if (val2 == null) { + // Two null values -- return equal + return 0; + } else { + return -1; + } + } + return val.compareTo(val2); + } + + /** + * Get the property type for this property. Used to convert raw values into + * JMeterProperties. + * + * @return property type of this property + */ + protected Class getPropertyType() { + return getClass(); + } + + protected JMeterProperty getBlankProperty() { + try { + JMeterProperty prop = getPropertyType().newInstance(); + if (prop instanceof NullProperty) { + return new StringProperty(); + } + return prop; + } catch (Exception e) { + return new StringProperty(); + } + } + + protected static JMeterProperty getBlankProperty(Object item) { + if (item == null) { + return new NullProperty(); + } + if (item instanceof String) { + return new StringProperty("", item.toString()); + } else if (item instanceof Boolean) { + return new BooleanProperty("", ((Boolean) item).booleanValue()); + } else if (item instanceof Float) { + return new FloatProperty("", ((Float) item).floatValue()); + } else if (item instanceof Double) { + return new DoubleProperty("", ((Double) item).doubleValue()); + } else if (item instanceof Integer) { + return new IntegerProperty("", ((Integer) item).intValue()); + } else if (item instanceof Long) { + return new LongProperty("", ((Long) item).longValue()); + } else { + return new StringProperty("", item.toString()); + } + } + + /** + * Convert a collection of objects into JMeterProperty objects. + * + * @param coll Collection of any type of object + * @return Collection of JMeterProperty objects + */ + protected Collection normalizeList(Collection coll) { + if (coll.isEmpty()) { + @SuppressWarnings("unchecked") // empty collection, local var is here to allow SuppressWarnings + Collection okColl = (Collection) coll; + return okColl; + } + try { + @SuppressWarnings("unchecked") // empty collection + Collection newColl = coll.getClass().newInstance(); + for (Object item : coll) { + newColl.add(convertObject(item)); + } + return newColl; + } catch (Exception e) {// should not happen + log.error("Cannot create copy of "+coll.getClass().getName(),e); + return null; + } + } + + /** + * Given a Map, it converts the Map into a collection of JMeterProperty + * objects, appropriate for a MapProperty object. + * + * @param coll + * Map to convert + * @return converted Map + */ + protected Map normalizeMap(Map coll) { + if (coll.isEmpty()) { + @SuppressWarnings("unchecked")// empty collection ok to cast, local var is here to allow SuppressWarnings + Map emptyColl = (Map) coll; + return emptyColl; + } + try { + @SuppressWarnings("unchecked") // empty collection + Map newColl = coll.getClass().newInstance(); + for (Map.Entry entry : ((Map)coll).entrySet()) { + Object key = entry.getKey(); + Object prop = entry.getValue(); + String item=null; + if (key instanceof String) { + item = (String) key; + } else { + if (key != null) { + log.error("Expected key type String, found: "+key.getClass().getName()); + item = key.toString(); + } + } + newColl.put(item, convertObject(prop)); + } + return newColl; + } catch (Exception e) {// should not happen + log.error("Cannot create copy of "+coll.getClass().getName(),e); + return null; + } + } + + public static JMeterProperty createProperty(Object item) { + JMeterProperty prop = makeProperty(item); + if (prop == null) { + prop = getBlankProperty(item); + } + return prop; + } + + /** + * Create a JMeterProperty from an object. + * The object can be one of: + *
    + *
  • JMeterProperty - returned unchanged
  • + *
  • TestElement => TestElementProperty with the same name
  • + *
  • Map|Collection => Map|CollectionProperty with the name = item.hashCode
  • + *
+ * @param item object to be turned into a propery + * @return the JMeterProperty + */ + protected static JMeterProperty makeProperty(Object item) { + if (item instanceof JMeterProperty) { + return (JMeterProperty) item; + } + if (item instanceof TestElement) { + return new TestElementProperty(((TestElement) item).getName(), + (TestElement) item); + } + if (item instanceof Collection) { + return new CollectionProperty(Integer.toString(item.hashCode()), (Collection) item); + } + if (item instanceof Map) { + return new MapProperty(Integer.toString(item.hashCode()), (Map) item); + } + return null; + } + + protected JMeterProperty convertObject(Object item) { + JMeterProperty prop = makeProperty(item); + if (prop == null) { + prop = getBlankProperty(); + prop.setName(Integer.toString(item.hashCode())); + prop.setObjectValue(item); + } + return prop; + } + + /** + * Provides the string representation of the property. + * + * @return the string value + */ + @Override + public String toString() { + // N.B. Other classes rely on this returning just the string. + return getStringValue(); + } + + /** {@inheritDoc} */ + @Override + public void mergeIn(JMeterProperty prop) { + // NOOP + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/BooleanProperty.java b/src/core/org/apache/jmeter/testelement/property/BooleanProperty.java new file mode 100644 index 00000000000..e6dd93d0a42 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/BooleanProperty.java @@ -0,0 +1,99 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision$ + */ +public class BooleanProperty extends AbstractProperty { + private static final long serialVersionUID = 233L; + + private boolean value; + + private transient boolean savedValue; + + public BooleanProperty(String name, boolean v) { + super(name); + value = v; + } + + public BooleanProperty() { + super(); + } + + @Override + public void setObjectValue(Object v) { + if (v instanceof Boolean) { + value = ((Boolean) v).booleanValue(); + } else { + value = Boolean.parseBoolean(v.toString()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getStringValue() { + return Boolean.toString(value); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getObjectValue() { + return Boolean.valueOf(value); + } + + @Override + public BooleanProperty clone() { + BooleanProperty prop = (BooleanProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean getBooleanValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } + +} diff --git a/src/core/org/apache/jmeter/testelement/property/CollectionProperty.java b/src/core/org/apache/jmeter/testelement/property/CollectionProperty.java new file mode 100644 index 00000000000..79af62aeff2 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/CollectionProperty.java @@ -0,0 +1,219 @@ +/* + * 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.testelement.property; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.jmeter.testelement.TestElement; + +public class CollectionProperty extends MultiProperty { + + private static final long serialVersionUID = 221L; // Remember to change this when the class changes ... + + private Collection value; + + private transient Collection savedValue; + + public CollectionProperty(String name, Collection value) { + super(name); + this.value = normalizeList(value); + } + + public CollectionProperty() { + super(); + value = new ArrayList(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof CollectionProperty) { + if (value != null) { + return value.equals(((JMeterProperty) o).getObjectValue()); + } + } + return false; + } + + @Override + public int hashCode() { + return (value == null ? 0 : value.hashCode()); + } + + public void remove(String prop) { + PropertyIterator iter = iterator(); + while (iter.hasNext()) { + if (iter.next().getName().equals(prop)) { + iter.remove(); + } + } + } + + public void set(int index, String prop) { + if (value instanceof List) { + ((List) value).set(index, new StringProperty(prop, prop)); + } + } + + public void set(int index, JMeterProperty prop) { + if (value instanceof List) { + ((List) value).set(index, prop); + } + } + + public JMeterProperty get(int row) { + if (value instanceof List) { + return ((List) value).get(row); + } + return null; + } + + public void remove(int index) { + if (value instanceof List) { + ((List) value).remove(index); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setObjectValue(Object v) { + if (v instanceof Collection) { + setCollection((Collection) v); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyIterator iterator() { + return getIterator(value); + } + + /** + * {@inheritDoc} + */ + @Override + public String getStringValue() { + return value.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getObjectValue() { + return value; + } + + public int size() { + return value.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public CollectionProperty clone() { + CollectionProperty prop = (CollectionProperty) super.clone(); + prop.value = cloneCollection(); + return prop; + } + + private Collection cloneCollection() { + try { + @SuppressWarnings("unchecked") // value is of type Collection + Collection newCol = value.getClass().newInstance(); + PropertyIterator iter = iterator(); + while (iter.hasNext()) { + newCol.add(iter.next().clone()); + } + return newCol; + } catch (Exception e) { + log.error("Couldn't clone collection", e); + return value; + } + } + + public void setCollection(Collection coll) { + value = normalizeList(coll); + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addProperty(JMeterProperty prop) { + value.add(prop); + } + + public void addItem(Object item) { + addProperty(convertObject(item)); + } + + /** + * Figures out what kind of properties this collection is holding and + * returns the class type. + * + * @see AbstractProperty#getPropertyType() + */ + @Override + protected Class getPropertyType() { + if (value != null && value.size() > 0) { + return value.iterator().next().getClass(); + } + return NullProperty.class; + } + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + recoverRunningVersionOfSubElements(owner); + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean running) { + super.setRunningVersion(running); + if (running) { + savedValue = value; + } else { + savedValue = null; + } + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/DoubleProperty.java b/src/core/org/apache/jmeter/testelement/property/DoubleProperty.java new file mode 100644 index 00000000000..43d02a1bdb8 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/DoubleProperty.java @@ -0,0 +1,143 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision$ + */ +public class DoubleProperty extends NumberProperty { + private static final long serialVersionUID = 240L; + + private double value; + + private double savedValue; + + public DoubleProperty(String name, double value) { + super(name); + this.value = value; + } + + public DoubleProperty() { + } + + public void setValue(float value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(Number n) { + value = n.doubleValue(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(String n) throws NumberFormatException { + value = Double.parseDouble(n); + } + + /** + * {@inheritDoc} + */ + @Override + public String getStringValue() { + return Double.toString(value); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getObjectValue() { + return Double.valueOf(value); + } + + /** + * {@inheritDoc} + */ + @Override + public DoubleProperty clone() { + DoubleProperty prop = (DoubleProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean getBooleanValue() { + return value > 0 ? true : false; + } + + /** + * {@inheritDoc} + */ + @Override + public double getDoubleValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public float getFloatValue() { + return (float) value; + } + + /** + * {@inheritDoc} + */ + @Override + public int getIntValue() { + return (int) value; + } + + /** + * @see JMeterProperty#getLongValue() + */ + @Override + public long getLongValue() { + return (long) value; + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/FloatProperty.java b/src/core/org/apache/jmeter/testelement/property/FloatProperty.java new file mode 100644 index 00000000000..a2f88d3b8b0 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/FloatProperty.java @@ -0,0 +1,143 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision$ + */ +public class FloatProperty extends NumberProperty { + private static final long serialVersionUID = 240L; + + private float value; + + private float savedValue; + + public FloatProperty(String name, float value) { + super(name); + this.value = value; + } + + public FloatProperty() { + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } + + public void setValue(float value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(Number n) { + value = n.floatValue(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(String n) throws NumberFormatException { + value = Float.parseFloat(n); + } + + /** + * {@inheritDoc} + */ + @Override + public String getStringValue() { + return Float.toString(value); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getObjectValue() { + return Float.valueOf(value); + } + + /** + * {@inheritDoc} + */ + @Override + public FloatProperty clone() { + FloatProperty prop = (FloatProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean getBooleanValue() { + return value > 0 ? true : false; + } + + /** + * {@inheritDoc} + */ + @Override + public double getDoubleValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public float getFloatValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public int getIntValue() { + return (int) value; + } + + /** + * {@inheritDoc} + */ + @Override + public long getLongValue() { + return (long) value; + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/FunctionProperty.java b/src/core/org/apache/jmeter/testelement/property/FunctionProperty.java new file mode 100644 index 00000000000..700058071fc --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/FunctionProperty.java @@ -0,0 +1,131 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; + +/** + * Class that implements the Function property + */ +public class FunctionProperty extends AbstractProperty { + private static final long serialVersionUID = 233L; + + private transient CompoundVariable function; + + private int testIteration = -1; + + private String cacheValue; + + public FunctionProperty(String name, CompoundVariable func) { + super(name); + function = func; + } + + public FunctionProperty() { + super(); + } + + @Override + public void setObjectValue(Object v) { + if (v instanceof CompoundVariable && !isRunningVersion()) { + function = (CompoundVariable) v; + } else { + cacheValue = v.toString(); + } + } + + @Override + public boolean equals(Object o) { + if (o instanceof FunctionProperty) { + if (function != null) { + return function.equals(((JMeterProperty) o).getObjectValue()); + } + } + return false; + } + + @Override + public int hashCode(){ + int hash = super.hashCode(); + if (function != null) { + hash = hash*37 + function.hashCode(); + } + return hash; + } + + /** + * Executes the function (and caches the value for the duration of the test + * iteration) if the property is a running version. Otherwise, the raw + * string representation of the function is provided. + * + * @see JMeterProperty#getStringValue() + */ + @Override + public String getStringValue() { + JMeterContext ctx = JMeterContextService.getContext();// Expensive, so + // do + // once + if (!isRunningVersion() /*|| !ctx.isSamplingStarted()*/) { + log.debug("Not running version, return raw function string"); + return function.getRawParameters(); + } + if(!ctx.isSamplingStarted()) { + return function.execute(); + } + log.debug("Running version, executing function"); + int iter = ctx.getVariables() != null ? ctx.getVariables().getIteration() : -1; + if (iter < testIteration) { + testIteration = -1; + } + if (iter > testIteration || cacheValue == null) { + testIteration = iter; + cacheValue = function.execute(); + } + return cacheValue; + + } + + /** + * @see JMeterProperty#getObjectValue() + */ + @Override + public Object getObjectValue() { + return function; + } + + @Override + public FunctionProperty clone() { + FunctionProperty prop = (FunctionProperty) super.clone(); + prop.cacheValue = cacheValue; + prop.testIteration = testIteration; + prop.function = function; + return prop; + } + + /** + * @see JMeterProperty#recoverRunningVersion(TestElement) + */ + @Override + public void recoverRunningVersion(TestElement owner) { + cacheValue = null; + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/IntegerProperty.java b/src/core/org/apache/jmeter/testelement/property/IntegerProperty.java new file mode 100644 index 00000000000..e19016172f9 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/IntegerProperty.java @@ -0,0 +1,148 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision$ + */ +public class IntegerProperty extends NumberProperty { + private static final long serialVersionUID = 240L; + + private int value; + + private int savedValue; + + public IntegerProperty(String name, int value) { + super(name); + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } + + public IntegerProperty(String name) { + super(name); + } + + public IntegerProperty() { + super(); + } + + public void setValue(int value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(Number n) { + value = n.intValue(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(String n) throws NumberFormatException { + value = Integer.parseInt(n); + } + + /** + * @see JMeterProperty#getStringValue() + */ + @Override + public String getStringValue() { + return Integer.toString(value); + } + + /** + * @see JMeterProperty#getObjectValue() + */ + @Override + public Object getObjectValue() { + return Integer.valueOf(value); + } + + /** + * {@inheritDoc} + */ + @Override + public IntegerProperty clone() { + IntegerProperty prop = (IntegerProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * @see JMeterProperty#getBooleanValue() + */ + @Override + public boolean getBooleanValue() { + return getIntValue() > 0 ? true : false; + } + + /** + * @see JMeterProperty#getDoubleValue() + */ + @Override + public double getDoubleValue() { + return value; + } + + /** + * @see JMeterProperty#getFloatValue() + */ + @Override + public float getFloatValue() { + return value; + } + + /** + * @see JMeterProperty#getIntValue() + */ + @Override + public int getIntValue() { + return value; + } + + /** + * @see JMeterProperty#getLongValue() + */ + @Override + public long getLongValue() { + return value; + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/JMeterProperty.java b/src/core/org/apache/jmeter/testelement/property/JMeterProperty.java new file mode 100644 index 00000000000..b2a89442372 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/JMeterProperty.java @@ -0,0 +1,96 @@ +/* + * 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.testelement.property; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.TestElement; + +public interface JMeterProperty extends Serializable, Cloneable, Comparable { + /** + * Returns whether the property is a running version. + * + * @return flag whether this property is a running version + */ + boolean isRunningVersion(); + + /** + * The name of the property. Typically this should match the name that keys + * the property's location in the test elements Map. + * + * @return the name of the property + */ + String getName(); + + /** + * Set the property name. + * + * @param name the name of the property + */ + void setName(String name); + + /** + * Make the property a running version or turn it off as the running + * version. A property that is made a running version will preserve the + * current state in such a way that it is retrievable by a future call to + * 'recoverRunningVersion()'. Additionally, a property that is a running + * version will resolve all functions prior to returning it's property + * value. A non-running version property will return functions as their + * uncompiled string representation. + * + * @param runningVersion flag whether this property is a running version + */ + void setRunningVersion(boolean runningVersion); + + /** + * Tell the property to revert to the state at the time + * setRunningVersion(true) was called. + * + * @param owner the owning element + */ + void recoverRunningVersion(TestElement owner); + + /** + * Take the given property object and merge it's value with the current + * property object. For most property types, this will simply be ignored. + * But for collection properties and test element properties, more complex + * behavior is required. + * + * @param prop the property object to merge into this property + */ + void mergeIn(JMeterProperty prop); + + int getIntValue(); + + long getLongValue(); + + double getDoubleValue(); + + float getFloatValue(); + + boolean getBooleanValue(); + + String getStringValue(); + + Object getObjectValue(); + + void setObjectValue(Object value); + + JMeterProperty clone(); +} diff --git a/src/core/org/apache/jmeter/testelement/property/LongProperty.java b/src/core/org/apache/jmeter/testelement/property/LongProperty.java new file mode 100644 index 00000000000..51132acbeac --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/LongProperty.java @@ -0,0 +1,144 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision$ + */ +public class LongProperty extends NumberProperty { + private static final long serialVersionUID = 240L; + + private long value; + + private long savedValue; + + public LongProperty(String name, long value) { + super(name); + this.value = value; + } + + public LongProperty() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } + + public void setValue(int value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(Number n) { + value = n.longValue(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(String n) throws NumberFormatException { + value = Long.parseLong(n); + } + + /** + * @see JMeterProperty#getStringValue() + */ + @Override + public String getStringValue() { + return Long.toString(value); + } + + /** + * @see JMeterProperty#getObjectValue() + */ + @Override + public Object getObjectValue() { + return Long.valueOf(value); + } + + /** + * {@inheritDoc} + */ + @Override + public LongProperty clone() { + LongProperty prop = (LongProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * @see JMeterProperty#getBooleanValue() + */ + @Override + public boolean getBooleanValue() { + return getLongValue() > 0 ? true : false; + } + + /** + * @see JMeterProperty#getDoubleValue() + */ + @Override + public double getDoubleValue() { + return value; + } + + /** + * @see JMeterProperty#getFloatValue() + */ + @Override + public float getFloatValue() { + return value; + } + + /** + * @see JMeterProperty#getIntValue() + */ + @Override + public int getIntValue() { + return (int) value; + } + + /** + * @see JMeterProperty#getLongValue() + */ + @Override + public long getLongValue() { + return value; + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/MapProperty.java b/src/core/org/apache/jmeter/testelement/property/MapProperty.java new file mode 100644 index 00000000000..05684a846c7 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/MapProperty.java @@ -0,0 +1,177 @@ +/* + * 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.testelement.property; + +import java.util.Map; + +import org.apache.jmeter.testelement.TestElement; + +public class MapProperty extends MultiProperty { + + private static final long serialVersionUID = 221L; // Remember to change this when the class changes ... + + private Map value; + + private transient Map savedValue = null; + + public MapProperty(String name, Map value) { + super(name); + log.info("map = " + value); + this.value = normalizeMap(value); + log.info("normalized map = " + this.value); + } + + public MapProperty() { + super(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (o instanceof MapProperty) { + if (value != null) { + return value.equals(((JMeterProperty) o).getObjectValue()); + } + } + return false; + } + + @Override + public int hashCode(){ + int hash = super.hashCode(); + if (value != null) { + hash = hash*37 + value.hashCode(); + } + return hash; + } + + /** {@inheritDoc} */ + @Override + public void setObjectValue(Object v) { + if (v instanceof Map) { + setMap((Map) v); + } + } + + /** {@inheritDoc} */ + @Override + public void addProperty(JMeterProperty prop) { + addProperty(prop.getName(), prop); + } + + public JMeterProperty get(String key) { + return value.get(key); + } + + /** + * Figures out what kind of properties this collection is holding and + * returns the class type. + * + * @see AbstractProperty#getPropertyType() + */ + @Override + protected Class getPropertyType() { + if (value.size() > 0) { + return valueIterator().next().getClass(); + } + return NullProperty.class; + } + + /** {@inheritDoc} */ + @Override + public String getStringValue() { + return value.toString(); + } + + /** {@inheritDoc} */ + @Override + public Object getObjectValue() { + return value; + } + + /** {@inheritDoc} */ + @Override + public MapProperty clone() { + MapProperty prop = (MapProperty) super.clone(); + prop.value = cloneMap(); + return prop; + } + + private Map cloneMap() { + try { + @SuppressWarnings("unchecked") // value is the correct class + Map newCol = value.getClass().newInstance(); + PropertyIterator iter = valueIterator(); + while (iter.hasNext()) { + JMeterProperty item = iter.next(); + newCol.put(item.getName(), item.clone()); + } + return newCol; + } catch (Exception e) { + log.error("Couldn't clone map", e); + return value; + } + } + + public PropertyIterator valueIterator() { + return getIterator(value.values()); + } + + public void addProperty(String name, JMeterProperty prop) { + if (!value.containsKey(name)) { + value.put(name, prop); + } + } + + public void setMap(Map newMap) { + value = normalizeMap(newMap); + } + + /** {@inheritDoc} */ + @Override + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + recoverRunningVersionOfSubElements(owner); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + value.clear(); + } + + /** {@inheritDoc} */ + @Override + public PropertyIterator iterator() { + return valueIterator(); + } + + /** {@inheritDoc} */ + @Override + public void setRunningVersion(boolean running) { + super.setRunningVersion(running); + if (running) { + savedValue = value; + } else { + savedValue = null; + } + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/MultiProperty.java b/src/core/org/apache/jmeter/testelement/property/MultiProperty.java new file mode 100644 index 00000000000..36a73d35589 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/MultiProperty.java @@ -0,0 +1,97 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * For JMeterProperties that hold multiple properties within, provides a simple + * interface for retrieving a property iterator for the sub values. + * + * @version $Revision$ + */ +public abstract class MultiProperty extends AbstractProperty { + private static final long serialVersionUID = 240L; + + public MultiProperty() { + super(); + } + + public MultiProperty(String name) { + super(name); + } + + /** + * Get the property iterator to iterate through the sub-values of this + * JMeterProperty. + * + * @return an iterator for the sub-values of this property + */ + public abstract PropertyIterator iterator(); + + /** + * Add a property to the collection. + * + * @param prop the {@link JMeterProperty} to add + */ + public abstract void addProperty(JMeterProperty prop); + + /** + * Clear away all values in the property. + */ + public abstract void clear(); + + @Override + public void setRunningVersion(boolean running) { + super.setRunningVersion(running); + PropertyIterator iter = iterator(); + while (iter.hasNext()) { + iter.next().setRunningVersion(running); + } + } + + protected void recoverRunningVersionOfSubElements(TestElement owner) { + PropertyIterator iter = iterator(); + while (iter.hasNext()) { + JMeterProperty prop = iter.next(); + if (owner.isTemporary(prop)) { + iter.remove(); + } else { + prop.recoverRunningVersion(owner); + } + } + } + + @Override + public void mergeIn(JMeterProperty prop) { + if (prop.getObjectValue() == getObjectValue()) { + return; + } + log.debug("merging in " + prop.getClass()); + if (prop instanceof MultiProperty) { + PropertyIterator iter = ((MultiProperty) prop).iterator(); + while (iter.hasNext()) { + JMeterProperty item = iter.next(); + addProperty(item); + } + } else { + addProperty(prop); + } + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/NullProperty.java b/src/core/org/apache/jmeter/testelement/property/NullProperty.java new file mode 100644 index 00000000000..6ea1180dcdd --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/NullProperty.java @@ -0,0 +1,133 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * A null property. + * + */ +public final class NullProperty extends AbstractProperty { + private static final long serialVersionUID = 240L; + + private JMeterProperty tempValue; // TODO - why does null property have a value? + + public NullProperty(String name) { + super(name); + } + + public NullProperty() { + super(); + } + + /** + * @see JMeterProperty#getStringValue() + */ + @Override + public String getStringValue() { + if (tempValue != null) { + return tempValue.getStringValue(); + } + return ""; + } + + @Override + public void setObjectValue(Object v) { + // NOOP + } + + /** + * @see JMeterProperty#getObjectValue() + */ + @Override + public Object getObjectValue() { + return null; + } + + /** + * @see JMeterProperty#isRunningVersion() + */ + @Override + public boolean isRunningVersion() { + return false; + } + + /** + * @see JMeterProperty#mergeIn(JMeterProperty) + */ + @Override + public void mergeIn(JMeterProperty prop) { + tempValue = prop; + } + + @Override + public NullProperty clone() { + return this; + } + + /** + * @see JMeterProperty#getBooleanValue() + */ + @Override + public boolean getBooleanValue() { + return false; + } + + /** + * @see JMeterProperty#getDoubleValue() + */ + @Override + public double getDoubleValue() { + return 0; + } + + /** + * @see JMeterProperty#getFloatValue() + */ + @Override + public float getFloatValue() { + return 0; + } + + /** + * @see JMeterProperty#getIntValue() + */ + @Override + public int getIntValue() { + return 0; + } + + /** + * @see JMeterProperty#getLongValue() + */ + @Override + public long getLongValue() { + return 0; + } + + /** + * @see JMeterProperty#recoverRunningVersion(TestElement) + */ + @Override + public void recoverRunningVersion(TestElement owner) { + tempValue = null; + } + +} diff --git a/src/core/org/apache/jmeter/testelement/property/NumberProperty.java b/src/core/org/apache/jmeter/testelement/property/NumberProperty.java new file mode 100644 index 00000000000..92b07669660 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/NumberProperty.java @@ -0,0 +1,80 @@ +/* + * 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. + * + */ + +/* + * Created on May 5, 2003 + */ +package org.apache.jmeter.testelement.property; + +public abstract class NumberProperty extends AbstractProperty { + private static final long serialVersionUID = 240L; + + public NumberProperty() { + super(); + } + + public NumberProperty(String name) { + super(name); + } + + /** + * Set the value of the property with a Number object. + * + * @param n the value to set + */ + protected abstract void setNumberValue(Number n); + + /** + * Set the value of the property with a String object. + * + * @param n + * the number to set as a string representation + * @throws NumberFormatException + * if the number n can not be converted to a + * {@link Number} + */ + protected abstract void setNumberValue(String n) throws NumberFormatException; + + @Override + public void setObjectValue(Object v) { + if (v instanceof Number) { + setNumberValue((Number) v); + } else { + try { + setNumberValue(v.toString()); + } catch (RuntimeException e) { + } + } + } + + /** + * @see Comparable#compareTo(Object) + */ + @Override + public int compareTo(JMeterProperty arg0) { + double compareValue = getDoubleValue() - arg0.getDoubleValue(); + + if (compareValue < 0) { + return -1; + } else if (compareValue == 0) { + return 0; + } else { + return 1; + } + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/ObjectProperty.java b/src/core/org/apache/jmeter/testelement/property/ObjectProperty.java new file mode 100644 index 00000000000..1804a1b1c2c --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/ObjectProperty.java @@ -0,0 +1,124 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 16, 2004 + */ +package org.apache.jmeter.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +public class ObjectProperty extends AbstractProperty { + private static final long serialVersionUID = 1; + + private Object value; + + private Object savedValue; + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + super.setRunningVersion(runningVersion); + if (runningVersion) { + savedValue = value; + } else { + savedValue = null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public ObjectProperty clone() { + ObjectProperty p = (ObjectProperty) super.clone(); + p.value = value; + return p; + } + + /** + * Default constructor. Constructs an {@link ObjectProperty} with no name + * and a null value + */ + public ObjectProperty() { + super(); + // TODO Auto-generated constructor stub + } + + /** + * Constructs an instance with name as its name and a + * null value. + * + * @param name + * the name of this property + */ + public ObjectProperty(String name) { + super(name); + } + + /** + * Constructs an instance with name as its name and the giveN + * value. + * + * @param name + * the name of this property + * @param p + * the value for this property + */ + public ObjectProperty(String name, Object p) { + super(name); + value = p; + } + + /** + * {@inheritDoc} + */ + @Override + public String getStringValue() { + return value.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getObjectValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void setObjectValue(Object value) { + this.value = value; + + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/PropertyIterator.java b/src/core/org/apache/jmeter/testelement/property/PropertyIterator.java new file mode 100644 index 00000000000..d67b27dd907 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/PropertyIterator.java @@ -0,0 +1,27 @@ +/* + * 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.testelement.property; + +public interface PropertyIterator { + boolean hasNext(); + + JMeterProperty next(); + + void remove(); +} diff --git a/src/core/org/apache/jmeter/testelement/property/PropertyIteratorImpl.java b/src/core/org/apache/jmeter/testelement/property/PropertyIteratorImpl.java new file mode 100644 index 00000000000..2ec39278ce1 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/PropertyIteratorImpl.java @@ -0,0 +1,50 @@ +/* + * 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.testelement.property; + +import java.util.Collection; +import java.util.Iterator; + +public class PropertyIteratorImpl implements PropertyIterator { + + private final Iterator iter; + + public PropertyIteratorImpl(Collection value) { + iter = value.iterator(); + } + + /** {@inheritDoc} */ + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + /** {@inheritDoc} */ + @Override + public JMeterProperty next() { + return iter.next(); + } + + /** {@inheritDoc} */ + @Override + public void remove() { + iter.remove(); + } + +} diff --git a/src/core/org/apache/jmeter/testelement/property/StringProperty.java b/src/core/org/apache/jmeter/testelement/property/StringProperty.java new file mode 100644 index 00000000000..c8b43afce03 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/StringProperty.java @@ -0,0 +1,108 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision$ + */ +public class StringProperty extends AbstractProperty { + private static final long serialVersionUID = 233L; + + private String value; + + private transient String savedValue; + + public StringProperty(String name, String value) { + super(name); + this.value = value; + } + + public StringProperty() { + super(); + } + + /** + * @see JMeterProperty#setRunningVersion(boolean) + */ + @Override + public void setRunningVersion(boolean runningVersion) { + super.setRunningVersion(runningVersion); + if (runningVersion) { + savedValue = value; + } else { + savedValue = null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setObjectValue(Object v) { + value = v.toString(); + } + + /** + * @see JMeterProperty#getStringValue() + */ + @Override + public String getStringValue() { + return value; + } + + /** + * @see JMeterProperty#getObjectValue() + */ + @Override + public Object getObjectValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public StringProperty clone() { + StringProperty prop = (StringProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * Sets the value. + * + * @param value + * The value to set + */ + public void setValue(String value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + } +} diff --git a/src/core/org/apache/jmeter/testelement/property/TestElementProperty.java b/src/core/org/apache/jmeter/testelement/property/TestElementProperty.java new file mode 100644 index 00000000000..d011642baf7 --- /dev/null +++ b/src/core/org/apache/jmeter/testelement/property/TestElementProperty.java @@ -0,0 +1,166 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +public class TestElementProperty extends MultiProperty { + private static final long serialVersionUID = 233L; + + private TestElement value; + + private transient TestElement savedValue = null; + + public TestElementProperty(String name, TestElement value) { + super(name); + this.value = value; + } + + public TestElementProperty() { + super(); + } + + /** + * Determines if two test elements are equal. + * + * @return true if the value is not null and equals the other Objects value; + * false otherwise (even if both values are null) + */ + @Override + public boolean equals(Object o) { + if (o instanceof TestElementProperty) { + if (this == o) { + return true; + } + if (value != null) { + return value.equals(((JMeterProperty) o).getObjectValue()); + } + } + return false; + } + + @Override + public int hashCode() { + return value == null ? 0 : value.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getStringValue() { + return value.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setObjectValue(Object v) { + if (v instanceof TestElement) { + value = (TestElement) v; + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object getObjectValue() { + return value; + } + + public TestElement getElement() { + return value; + } + + public void setElement(TestElement el) { + value = el; + } + + /** + * {@inheritDoc} + */ + @Override + public TestElementProperty clone() { + TestElementProperty prop = (TestElementProperty) super.clone(); + prop.value = (TestElement) value.clone(); + return prop; + } + + /** + * {@inheritDoc} + */ + @Override + public void mergeIn(JMeterProperty prop) { + if (isEqualType(prop)) { + value.addTestElement((TestElement) prop.getObjectValue()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + value.recoverRunningVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + super.setRunningVersion(runningVersion); + value.setRunningVersion(runningVersion); + if (runningVersion) { + savedValue = value; + } else { + savedValue = null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addProperty(JMeterProperty prop) { + value.setProperty(prop); + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value.clear(); + + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyIterator iterator() { + return value.propertyIterator(); + } +} diff --git a/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java b/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java new file mode 100644 index 00000000000..615138b3517 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java @@ -0,0 +1,262 @@ +/* + * 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.threads; + +import java.io.Serializable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.LoopController; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.collections.ListedHashTree; + +/** + * ThreadGroup holds the settings for a JMeter thread group. + * + * This class is intended to be ThreadSafe. + */ +public abstract class AbstractThreadGroup extends AbstractTestElement + implements Serializable, Controller, JMeterThreadMonitor, TestCompilerHelper { + + private static final long serialVersionUID = 240L; + + // Only create the map if it is required + private transient final ConcurrentMap children = + TestCompiler.IS_USE_STATIC_SET ? null : new ConcurrentHashMap(); + + private static final Object DUMMY = new Object(); + + /** Action to be taken when a Sampler error occurs */ + public static final String ON_SAMPLE_ERROR = "ThreadGroup.on_sample_error"; // int + + /** Continue, i.e. ignore sampler errors */ + public static final String ON_SAMPLE_ERROR_CONTINUE = "continue"; + + /** Start next loop for current thread if sampler error occurs */ + public static final String ON_SAMPLE_ERROR_START_NEXT_LOOP = "startnextloop"; + + /** Stop current thread if sampler error occurs */ + public static final String ON_SAMPLE_ERROR_STOPTHREAD = "stopthread"; + + /** Stop test (all threads) if sampler error occurs, the entire test is stopped at the end of any current samples */ + public static final String ON_SAMPLE_ERROR_STOPTEST = "stoptest"; + + /** Stop test NOW (all threads) if sampler error occurs, the entire test is stopped abruptly. Any current samplers are interrupted if possible. */ + public static final String ON_SAMPLE_ERROR_STOPTEST_NOW = "stoptestnow"; + + /** Number of threads in the thread group */ + public static final String NUM_THREADS = "ThreadGroup.num_threads"; + + public static final String MAIN_CONTROLLER = "ThreadGroup.main_controller"; + + private final AtomicInteger numberOfThreads = new AtomicInteger(0); // Number of active threads in this group + + /** {@inheritDoc} */ + @Override + public boolean isDone() { + return getSamplerController().isDone(); + } + + /** {@inheritDoc} */ + @Override + public Sampler next() { + return getSamplerController().next(); + } + + /** + * Get the sampler controller. + * + * @return the sampler controller. + */ + public Controller getSamplerController() { + return (Controller) getProperty(MAIN_CONTROLLER).getObjectValue(); + } + + /** + * Set the sampler controller. + * + * @param c + * the sampler controller. + */ + public void setSamplerController(LoopController c) { + c.setContinueForever(false); + setProperty(new TestElementProperty(MAIN_CONTROLLER, c)); + } + + /** + * Add a test element. + * + * @param child + * the test element to add. + */ + @Override + public void addTestElement(TestElement child) { + getSamplerController().addTestElement(child); + } + + /** + * {@inheritDoc} + */ + @Override + public final boolean addTestElementOnce(TestElement child){ + if (children.putIfAbsent(child, DUMMY) == null) { + addTestElement(child); + return true; + } + return false; + } + + /** {@inheritDoc} */ + @Override + public void addIterationListener(LoopIterationListener lis) { + getSamplerController().addIterationListener(lis); + } + + /** {@inheritDoc} */ + @Override + public void removeIterationListener(LoopIterationListener iterationListener) { + getSamplerController().removeIterationListener(iterationListener); + } + + /** {@inheritDoc} */ + @Override + public void initialize() { + Controller c = getSamplerController(); + JMeterProperty property = c.getProperty(TestElement.NAME); + property.setObjectValue(getName()); // Copy our name into that of the controller + property.setRunningVersion(property.isRunningVersion());// otherwise name reverts + c.initialize(); + } + + /** + * Start next iteration after an error + */ + public void startNextLoop() { + ((LoopController) getSamplerController()).startNextLoop(); + } + + /** + * NOOP + */ + @Override + public void triggerEndOfLoop() { + // NOOP + } + + /** + * Set the total number of threads to start + * + * @param numThreads + * the number of threads. + */ + public void setNumThreads(int numThreads) { + setProperty(new IntegerProperty(NUM_THREADS, numThreads)); + } + + /** + * Increment the number of active threads + */ + void incrNumberOfThreads() { + numberOfThreads.incrementAndGet(); + } + + /** + * Decrement the number of active threads + */ + void decrNumberOfThreads() { + numberOfThreads.decrementAndGet(); + } + + /** + * Get the number of active threads + * + * @return the number of active threads + */ + public int getNumberOfThreads() { + return numberOfThreads.get(); + } + + /** + * Get the number of threads. + * + * @return the number of threads. + */ + public int getNumThreads() { + return this.getPropertyAsInt(AbstractThreadGroup.NUM_THREADS); + } + + /** + * Check if a sampler error should cause thread to start next loop. + * + * @return true if thread should start next loop + */ + public boolean getOnErrorStartNextLoop() { + return getPropertyAsString(AbstractThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_START_NEXT_LOOP); + } + + /** + * Check if a sampler error should cause thread to stop. + * + * @return true if thread should stop + */ + public boolean getOnErrorStopThread() { + return getPropertyAsString(AbstractThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTHREAD); + } + + /** + * Check if a sampler error should cause test to stop. + * + * @return true if test (all threads) should stop + */ + public boolean getOnErrorStopTest() { + return getPropertyAsString(AbstractThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTEST); + } + + /** + * Check if a sampler error should cause test to stop now. + * + * @return true if test (all threads) should stop immediately + */ + public boolean getOnErrorStopTestNow() { + return getPropertyAsString(AbstractThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTEST_NOW); + } + + public abstract boolean stopThread(String threadName, boolean now); + + public abstract int numberOfActiveThreads(); + + public abstract void start(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine); + + public abstract boolean verifyThreadsStopped(); + + public abstract void waitThreadsStopped(); + + public abstract void tellThreadsToStop(); + + public abstract void stop(); +} diff --git a/src/core/org/apache/jmeter/threads/FindTestElementsUpToRootTraverser.java b/src/core/org/apache/jmeter/threads/FindTestElementsUpToRootTraverser.java new file mode 100644 index 00000000000..0cec79ec4a3 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/FindTestElementsUpToRootTraverser.java @@ -0,0 +1,103 @@ +/* + * 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.threads; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HashTreeTraverser implementation that stores in a Stack all + * the Test Elements on the path to a particular node. + */ +public class FindTestElementsUpToRootTraverser implements HashTreeTraverser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final LinkedList stack = new LinkedList(); + + /** + * Node to find in TestTree + */ + private final Object nodeToFind; + /** + * Once we find the node in the Tree we stop recording nodes + */ + private boolean stopRecording = false; + + /** + * @param nodeToFind Node to find + */ + public FindTestElementsUpToRootTraverser(Object nodeToFind) { + this.nodeToFind = nodeToFind; + } + + /** {@inheritDoc} */ + @Override + public void addNode(Object node, HashTree subTree) { + if(stopRecording) { + return; + } + if(node == nodeToFind) { + this.stopRecording = true; + } + stack.addLast((TestElement) node); + } + + /** {@inheritDoc} */ + @Override + public void subtractNode() { + if(stopRecording) { + return; + } + if(log.isDebugEnabled()) { + log.debug("Subtracting node, stack size = " + stack.size()); + } + stack.removeLast(); + } + + /** {@inheritDoc} */ + @Override + public void processPath() { + //NOOP + } + + /** + * Returns all controllers that where in Tree down to nodeToFind in reverse order (from leaf to root) + * @return List of {@link Controller} + */ + public List getControllersToRoot() { + List result = new ArrayList(stack.size()); + LinkedList stackLocalCopy = new LinkedList(stack); + while(stackLocalCopy.size()>0) { + TestElement te = stackLocalCopy.getLast(); + if(te instanceof Controller) { + result.add((Controller)te); + } + stackLocalCopy.removeLast(); + } + return result; + } +} diff --git a/src/core/org/apache/jmeter/threads/JMeterContext.java b/src/core/org/apache/jmeter/threads/JMeterContext.java new file mode 100644 index 00000000000..b104aaa1412 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/JMeterContext.java @@ -0,0 +1,201 @@ +/* + * 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.threads; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; + +/** + * Holds context for a thread. + * Generated by JMeterContextService. + * + * The class is not thread-safe - it is only intended for use within a single thread. + */ +public class JMeterContext { + private JMeterVariables variables; + + private SampleResult previousResult; + + private Sampler currentSampler; + + private Sampler previousSampler; + + private boolean samplingStarted; + + private StandardJMeterEngine engine; + + private JMeterThread thread; + + private AbstractThreadGroup threadGroup; + + private int threadNum; + + private boolean restartNextLoop = false; + + private ConcurrentHashMap samplerContext = new ConcurrentHashMap(5); + + JMeterContext() { + clear0(); + } + + public void clear() { + clear0(); + } + + private void clear0() { + variables = null; + previousResult = null; + currentSampler = null; + previousSampler = null; + samplingStarted = false; + threadNum = 0; + thread = null; + samplerContext.clear(); + } + + /** + * Gives access to the JMeter variables for the current thread. + * + * @return a pointer to the JMeter variables. + */ + public JMeterVariables getVariables() { + return variables; + } + + public void setVariables(JMeterVariables vars) { + this.variables = vars; + } + + public SampleResult getPreviousResult() { + return previousResult; + } + + public void setPreviousResult(SampleResult result) { + this.previousResult = result; + } + + public Sampler getCurrentSampler() { + return currentSampler; + } + + public void setCurrentSampler(Sampler sampler) { + this.previousSampler = currentSampler; + this.currentSampler = sampler; + } + + /** + * Returns the previousSampler. + * + * @return Sampler + */ + public Sampler getPreviousSampler() { + return previousSampler; + } + + /** + * Returns the threadNum. + * + * @return int + */ + public int getThreadNum() { + return threadNum; + } + + /** + * Sets the threadNum. + * + * @param threadNum + * the threadNum to set + */ + public void setThreadNum(int threadNum) { + this.threadNum = threadNum; + } + + public JMeterThread getThread() { + return this.thread; + } + + public void setThread(JMeterThread thread) { + this.thread = thread; + } + + public AbstractThreadGroup getThreadGroup() { + return this.threadGroup; + } + + public void setThreadGroup(AbstractThreadGroup threadgrp) { + this.threadGroup = threadgrp; + } + + public StandardJMeterEngine getEngine() { + return engine; + } + + public void setEngine(StandardJMeterEngine engine) { + this.engine = engine; + } + + public boolean isSamplingStarted() { + return samplingStarted; + } + + public void setSamplingStarted(boolean b) { + samplingStarted = b; + } + + /** + * if set to true a restart of the loop will occurs + * + * @param restartNextLoop + * flag whether restart will occur + */ + public void setRestartNextLoop(boolean restartNextLoop) { + this.restartNextLoop = restartNextLoop; + } + + /** + * a restart of the loop was required ? + * @return the restartNextLoop + */ + public boolean isRestartNextLoop() { + return restartNextLoop; + } + + /** + * Clean cached data after sample + */ + public void cleanAfterSample() { + if(previousResult != null) { + previousResult.cleanAfterSample(); + } + samplerContext.clear(); + } + + /** + * Sampler context is cleaned up as soon as Post-Processor have ended + * @return Context to use within PostProcessors to cache data + */ + public Map getSamplerContext() { + return samplerContext; + } +} diff --git a/src/core/org/apache/jmeter/threads/JMeterContextService.java b/src/core/org/apache/jmeter/threads/JMeterContextService.java new file mode 100644 index 00000000000..d6aac25d487 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/JMeterContextService.java @@ -0,0 +1,181 @@ +/* + * 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.threads; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Provides context service for JMeter threads. + * Keeps track of active and total thread counts. + */ +public final class JMeterContextService { + private static final ThreadLocal threadContext = new ThreadLocal() { + @Override + public JMeterContext initialValue() { + return new JMeterContext(); + } + }; + + //@GuardedGy("this") + private static long testStart = 0; + + //@GuardedGy("this") + private static int numberOfActiveThreads = 0; + + //@GuardedGy("this") + private static int numberOfThreadsStarted = 0; + + //@GuardedGy("this") + private static int numberOfThreadsFinished = 0; + + //@GuardedGy("this") + private static int totalThreads = 0; + + /** + * Private constructor to prevent instantiation. + */ + private JMeterContextService() { + } + + /** + * Gives access to the current thread context. + * + * @return the current thread Context + */ + public static JMeterContext getContext() { + return threadContext.get(); + } + + /** + * Allows the thread Context to be completely cleared. + *
+ * Invokes {@link ThreadLocal#remove()}. + */ + static void removeContext(){ // Currently only used by JMeterThread + threadContext.remove(); + } + + /** + * Replace Thread Context by the parameter. Currently only used by the + * private class ASyncSample in + * {@link org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase + * HTTPSamplerBase} + * + * @param context + * {@link JMeterContext} + */ + public static void replaceContext(JMeterContext context) { + threadContext.remove(); + threadContext.set(context); + } + /** + * Method is called by the JMeterEngine class when a test run is started. + * Zeroes numberOfActiveThreads. + * Saves current time in a field and in the JMeter property "TESTSTART.MS" + */ + public static synchronized void startTest() { + if (testStart == 0) { + numberOfActiveThreads = 0; + testStart = System.currentTimeMillis(); + JMeterUtils.setProperty("TESTSTART.MS",Long.toString(testStart));// $NON-NLS-1$ + } + } + + /** + * Increment number of active threads. + */ + static synchronized void incrNumberOfThreads() { + numberOfActiveThreads++; + numberOfThreadsStarted++; + } + + /** + * Decrement number of active threads. + */ + static synchronized void decrNumberOfThreads() { + numberOfActiveThreads--; + numberOfThreadsFinished++; + } + + /** + * Get the number of currently active threads + * @return active thread count + */ + public static synchronized int getNumberOfThreads() { + return numberOfActiveThreads; + } + + // return all the associated counts together + public static synchronized ThreadCounts getThreadCounts() { + return new ThreadCounts(numberOfActiveThreads, numberOfThreadsStarted, numberOfThreadsFinished); + } + + /** + * Called by MainFrame#testEnded(). + * Clears start time field. + */ + public static synchronized void endTest() { + testStart = 0; + } + + public static synchronized long getTestStartTime() {// NOT USED + return testStart; + } + + /** + * Get the total number of threads (>= active) + * @return total thread count + */ + public static synchronized int getTotalThreads() { + return totalThreads; + } + + /** + * Update the total number of threads + * @param thisGroup number of threads in this thread group + */ + public static synchronized void addTotalThreads(int thisGroup) { + totalThreads += thisGroup; + } + + /** + * Set total threads to zero; also clears started and finished counts + */ + public static synchronized void clearTotalThreads() { + totalThreads = 0; + numberOfThreadsStarted = 0; + numberOfThreadsFinished = 0; + } + + public static class ThreadCounts { + + public final int activeThreads; + + public final int startedThreads; + + public final int finishedThreads; + + ThreadCounts (int active, int started, int finished) { + activeThreads = active; + startedThreads = started; + finishedThreads = finished; + } + } + +} diff --git a/src/core/org/apache/jmeter/threads/JMeterThread.java b/src/core/org/apache/jmeter/threads/JMeterThread.java new file mode 100644 index 00000000000..1ca44ebace2 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/JMeterThread.java @@ -0,0 +1,925 @@ +/* + * 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.threads; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.TransactionSampler; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestIterationListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.timers.Timer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.collections.SearchByClass; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopTestException; +import org.apache.jorphan.util.JMeterStopTestNowException; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.log.Logger; + +/** + * The JMeter interface to the sampling process, allowing JMeter to see the + * timing, add listeners for sampling events and to stop the sampling process. + * + */ +public class JMeterThread implements Runnable, Interruptible { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String PACKAGE_OBJECT = "JMeterThread.pack"; // $NON-NLS-1$ + + public static final String LAST_SAMPLE_OK = "JMeterThread.last_sample_ok"; // $NON-NLS-1$ + + private static final String TRUE = Boolean.toString(true); // i.e. "true" + + /** How often to check for shutdown during ramp-up, default 1000ms */ + private static final int RAMPUP_GRANULARITY = + JMeterUtils.getPropDefault("jmeterthread.rampup.granularity", 1000); // $NON-NLS-1$ + + private final Controller controller; + + private final HashTree testTree; + + private final TestCompiler compiler; + + private final JMeterThreadMonitor monitor; + + private final JMeterVariables threadVars; + + // Note: this is only used to implement TestIterationListener#testIterationStart + // Since this is a frequent event, it makes sense to create the list once rather than scanning each time + // The memory used will be released when the thread finishes + private final Collection testIterationStartListeners; + + private final ListenerNotifier notifier; + + /* + * The following variables are set by StandardJMeterEngine. + * This is done before start() is called, so the values will be published to the thread safely + * TODO - consider passing them to the constructor, so that they can be made final + * (to avoid adding lots of parameters, perhaps have a parameter wrapper object. + */ + private String threadName; + + private int initialDelay = 0; + + private int threadNum = 0; + + private long startTime = 0; + + private long endTime = 0; + + private boolean scheduler = false; + // based on this scheduler is enabled or disabled + + // Gives access to parent thread threadGroup + private AbstractThreadGroup threadGroup; + + private StandardJMeterEngine engine = null; // For access to stop methods. + + /* + * The following variables may be set/read from multiple threads. + */ + private volatile boolean running; // may be set from a different thread + + private volatile boolean onErrorStopTest; + + private volatile boolean onErrorStopTestNow; + + private volatile boolean onErrorStopThread; + + private volatile boolean onErrorStartNextLoop; + + private volatile Sampler currentSampler; + + private final ReentrantLock interruptLock = new ReentrantLock(); // ensure that interrupt cannot overlap with shutdown + + public JMeterThread(HashTree test, JMeterThreadMonitor monitor, ListenerNotifier note) { + this.monitor = monitor; + threadVars = new JMeterVariables(); + testTree = test; + compiler = new TestCompiler(testTree); + controller = (Controller) testTree.getArray()[0]; + SearchByClass threadListenerSearcher = new SearchByClass(TestIterationListener.class); // TL - IS + test.traverse(threadListenerSearcher); + testIterationStartListeners = threadListenerSearcher.getSearchResults(); + notifier = note; + running = true; + } + + public void setInitialContext(JMeterContext context) { + threadVars.putAll(context.getVariables()); + } + + /** + * Enable the scheduler for this JMeterThread. + * + * @param sche + * flag whether the scheduler should be enabled + */ + public void setScheduled(boolean sche) { + this.scheduler = sche; + } + + /** + * Set the StartTime for this Thread. + * + * @param stime the StartTime value. + */ + public void setStartTime(long stime) { + startTime = stime; + } + + /** + * Get the start time value. + * + * @return the start time value. + */ + public long getStartTime() { + return startTime; + } + + /** + * Set the EndTime for this Thread. + * + * @param etime + * the EndTime value. + */ + public void setEndTime(long etime) { + endTime = etime; + } + + /** + * Get the end time value. + * + * @return the end time value. + */ + public long getEndTime() { + return endTime; + } + + /** + * Check the scheduled time is completed. + * + */ + private void stopScheduler() { + long now = System.currentTimeMillis(); + long delay = now - endTime; + if ((delay >= 0)) { + running = false; + log.info("Stopping because end time detected by thread: " + threadName); + } + } + + /** + * Wait until the scheduled start time if necessary + * + */ + private void startScheduler() { + long delay = (startTime - System.currentTimeMillis()); + delayBy(delay, "startScheduler"); + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + /* + * See below for reason for this change. Just in case this causes problems, + * allow the change to be backed out + */ + private static final boolean startEarlier = + JMeterUtils.getPropDefault("jmeterthread.startearlier", true); // $NON-NLS-1$ + + private static final boolean reversePostProcessors = + JMeterUtils.getPropDefault("jmeterthread.reversePostProcessors",false); // $NON-NLS-1$ + + static { + if (startEarlier) { + log.info("jmeterthread.startearlier=true (see jmeter.properties)"); + } else { + log.info("jmeterthread.startearlier=false (see jmeter.properties)"); + } + if (reversePostProcessors) { + log.info("Running PostProcessors in reverse order"); + } else { + log.info("Running PostProcessors in forward order"); + } + } + + @Override + public void run() { + // threadContext is not thread-safe, so keep within thread + JMeterContext threadContext = JMeterContextService.getContext(); + LoopIterationListener iterationListener=null; + + try { + iterationListener = initRun(threadContext); + while (running) { + Sampler sam = controller.next(); + while (running && sam != null) { + process_sampler(sam, null, threadContext); + threadContext.cleanAfterSample(); + if(onErrorStartNextLoop || threadContext.isRestartNextLoop()) { + if(threadContext.isRestartNextLoop()) { + triggerEndOfLoopOnParentControllers(sam, threadContext); + sam = null; + threadContext.getVariables().put(LAST_SAMPLE_OK, TRUE); + threadContext.setRestartNextLoop(false); + } else { + boolean lastSampleFailed = !TRUE.equals(threadContext.getVariables().get(LAST_SAMPLE_OK)); + if(lastSampleFailed) { + if(log.isDebugEnabled()) { + log.debug("StartNextLoop option is on, Last sample failed, starting next loop"); + } + triggerEndOfLoopOnParentControllers(sam, threadContext); + sam = null; + threadContext.getVariables().put(LAST_SAMPLE_OK, TRUE); + } else { + sam = controller.next(); + } + } + } + else { + sam = controller.next(); + } + } + if (controller.isDone()) { + running = false; + log.info("Thread is done: " + threadName); + } + } + } + // Might be found by contoller.next() + catch (JMeterStopTestException e) { + log.info("Stopping Test: " + e.toString()); + stopTest(); + } + catch (JMeterStopTestNowException e) { + log.info("Stopping Test Now: " + e.toString()); + stopTestNow(); + } catch (JMeterStopThreadException e) { + log.info("Stop Thread seen: " + e.toString()); + } catch (Exception e) { + log.error("Test failed!", e); + } catch (ThreadDeath e) { + throw e; // Must not ignore this one + } catch (Error e) {// Make sure errors are output to the log file + log.error("Test failed!", e); + } finally { + currentSampler = null; // prevent any further interrupts + try { + interruptLock.lock(); // make sure current interrupt is finished, prevent another starting yet + threadContext.clear(); + log.info("Thread finished: " + threadName); + threadFinished(iterationListener); + monitor.threadFinished(this); // Tell the monitor we are done + JMeterContextService.removeContext(); // Remove the ThreadLocal entry + } + finally { + interruptLock.unlock(); // Allow any pending interrupt to complete (OK because currentSampler == null) + } + } + } + + /** + * Trigger end of loop on parent controllers up to Thread Group + * @param sam Sampler Base sampler + * @param threadContext + */ + private void triggerEndOfLoopOnParentControllers(Sampler sam, JMeterContext threadContext) { + // Find parent controllers of current sampler + FindTestElementsUpToRootTraverser pathToRootTraverser=null; + TransactionSampler transactionSampler = null; + if(sam instanceof TransactionSampler) { + transactionSampler = (TransactionSampler) sam; + pathToRootTraverser = new FindTestElementsUpToRootTraverser((transactionSampler).getTransactionController()); + } else { + pathToRootTraverser = new FindTestElementsUpToRootTraverser(sam); + } + testTree.traverse(pathToRootTraverser); + List controllersToReinit = pathToRootTraverser.getControllersToRoot(); + + // Trigger end of loop condition on all parent controllers of current sampler + for (Iterator iterator = controllersToReinit + .iterator(); iterator.hasNext();) { + Controller parentController = iterator.next(); + if(parentController instanceof AbstractThreadGroup) { + AbstractThreadGroup tg = (AbstractThreadGroup) parentController; + tg.startNextLoop(); + } else { + parentController.triggerEndOfLoop(); + } + } + if(transactionSampler!=null) { + process_sampler(transactionSampler, null, threadContext); + } + } + + /** + * Process the current sampler, handling transaction samplers. + * + * @param current sampler + * @param parent sampler + * @param threadContext + * @return SampleResult if a transaction was processed + */ + @SuppressWarnings("deprecation") // OK to call TestBeanHelper.prepare() + private SampleResult process_sampler(Sampler current, Sampler parent, JMeterContext threadContext) { + SampleResult transactionResult = null; + try { + // Check if we are running a transaction + TransactionSampler transactionSampler = null; + if(current instanceof TransactionSampler) { + transactionSampler = (TransactionSampler) current; + } + // Find the package for the transaction + SamplePackage transactionPack = null; + if(transactionSampler != null) { + transactionPack = compiler.configureTransactionSampler(transactionSampler); + + // Check if the transaction is done + if(transactionSampler.isTransactionDone()) { + // Get the transaction sample result + transactionResult = transactionSampler.getTransactionResult(); + transactionResult.setThreadName(threadName); + transactionResult.setGroupThreads(threadGroup.getNumberOfThreads()); + transactionResult.setAllThreads(JMeterContextService.getNumberOfThreads()); + + // Check assertions for the transaction sample + checkAssertions(transactionPack.getAssertions(), transactionResult, threadContext); + // Notify listeners with the transaction sample result + if (!(parent instanceof TransactionSampler)){ + notifyListeners(transactionPack.getSampleListeners(), transactionResult); + } + compiler.done(transactionPack); + // Transaction is done, we do not have a sampler to sample + current = null; + } + else { + Sampler prev = current; + // It is the sub sampler of the transaction that will be sampled + current = transactionSampler.getSubSampler(); + if (current instanceof TransactionSampler){ + SampleResult res = process_sampler(current, prev, threadContext);// recursive call + threadContext.setCurrentSampler(prev); + current=null; + if (res!=null){ + transactionSampler.addSubSamplerResult(res); + } + } + } + } + + // Check if we have a sampler to sample + if(current != null) { + threadContext.setCurrentSampler(current); + // Get the sampler ready to sample + SamplePackage pack = compiler.configureSampler(current); + runPreProcessors(pack.getPreProcessors()); + + // Hack: save the package for any transaction controllers + threadVars.putObject(PACKAGE_OBJECT, pack); + + delay(pack.getTimers()); + Sampler sampler = pack.getSampler(); + sampler.setThreadContext(threadContext); + // TODO should this set the thread names for all the subsamples? + // might be more efficient than fetching the name elsewehere + sampler.setThreadName(threadName); + TestBeanHelper.prepare(sampler); + + // Perform the actual sample + currentSampler = sampler; + SampleResult result = sampler.sample(null); + currentSampler = null; + // TODO: remove this useless Entry parameter + + // If we got any results, then perform processing on the result + if (result != null) { + result.setGroupThreads(threadGroup.getNumberOfThreads()); + result.setAllThreads(JMeterContextService.getNumberOfThreads()); + result.setThreadName(threadName); + SampleResult[]subResults = result.getSubResults(); + if(subResults != null) { + for (SampleResult subResult : subResults) { + subResult.setGroupThreads(threadGroup.getNumberOfThreads()); + subResult.setAllThreads(JMeterContextService.getNumberOfThreads()); + subResult.setThreadName(threadName); + } + } + threadContext.setPreviousResult(result); + runPostProcessors(pack.getPostProcessors()); + checkAssertions(pack.getAssertions(), result, threadContext); + // Do not send subsamples to listeners which receive the transaction sample + List sampleListeners = getSampleListeners(pack, transactionPack, transactionSampler); + notifyListeners(sampleListeners, result); + compiler.done(pack); + // Add the result as subsample of transaction if we are in a transaction + if(transactionSampler != null) { + transactionSampler.addSubSamplerResult(result); + } + + // Check if thread or test should be stopped + if (result.isStopThread() || (!result.isSuccessful() && onErrorStopThread)) { + stopThread(); + } + if (result.isStopTest() || (!result.isSuccessful() && onErrorStopTest)) { + stopTest(); + } + if (result.isStopTestNow() || (!result.isSuccessful() && onErrorStopTestNow)) { + stopTestNow(); + } + if(result.isStartNextThreadLoop()) { + threadContext.setRestartNextLoop(true); + } + } else { + compiler.done(pack); // Finish up + } + } + if (scheduler) { + // checks the scheduler to stop the iteration + stopScheduler(); + } + } catch (JMeterStopTestException e) { + log.info("Stopping Test: " + e.toString()); + stopTest(); + } catch (JMeterStopThreadException e) { + log.info("Stopping Thread: " + e.toString()); + stopThread(); + } catch (Exception e) { + if (current != null) { + log.error("Error while processing sampler '"+current.getName()+"' :", e); + } else { + log.error("", e); + } + } + return transactionResult; + } + + /** + * Get the SampleListeners for the sampler. Listeners who receive transaction sample + * will not be in this list. + * + * @param samplePack + * @param transactionPack + * @param transactionSampler + * @return the listeners who should receive the sample result + */ + private List getSampleListeners(SamplePackage samplePack, SamplePackage transactionPack, TransactionSampler transactionSampler) { + List sampleListeners = samplePack.getSampleListeners(); + // Do not send subsamples to listeners which receive the transaction sample + if(transactionSampler != null) { + ArrayList onlySubSamplerListeners = new ArrayList(); + List transListeners = transactionPack.getSampleListeners(); + for(SampleListener listener : sampleListeners) { + // Check if this instance is present in transaction listener list + boolean found = false; + for(SampleListener trans : transListeners) { + // Check for the same instance + if(trans == listener) { + found = true; + break; + } + } + if(!found) { + onlySubSamplerListeners.add(listener); + } + } + sampleListeners = onlySubSamplerListeners; + } + return sampleListeners; + } + + /** + * @param threadContext + * @return the iteration listener + */ + private IterationListener initRun(JMeterContext threadContext) { + threadContext.setVariables(threadVars); + threadContext.setThreadNum(getThreadNum()); + threadContext.getVariables().put(LAST_SAMPLE_OK, TRUE); + threadContext.setThread(this); + threadContext.setThreadGroup(threadGroup); + threadContext.setEngine(engine); + testTree.traverse(compiler); + // listeners = controller.getListeners(); + if (scheduler) { + // set the scheduler to start + startScheduler(); + } + rampUpDelay(); // TODO - how to handle thread stopped here + log.info("Thread started: " + Thread.currentThread().getName()); + /* + * Setting SamplingStarted before the contollers are initialised allows + * them to access the running values of functions and variables (however + * it does not seem to help with the listeners) + */ + if (startEarlier) { + threadContext.setSamplingStarted(true); + } + controller.initialize(); + IterationListener iterationListener = new IterationListener(); + controller.addIterationListener(iterationListener); + if (!startEarlier) { + threadContext.setSamplingStarted(true); + } + threadStarted(); + return iterationListener; + } + + private void threadStarted() { + JMeterContextService.incrNumberOfThreads(); + threadGroup.incrNumberOfThreads(); + GuiPackage gp =GuiPackage.getInstance(); + if (gp != null) {// check there is a GUI + gp.getMainFrame().updateCounts(); + } + ThreadListenerTraverser startup = new ThreadListenerTraverser(true); + testTree.traverse(startup); // call ThreadListener.threadStarted() + } + + private void threadFinished(LoopIterationListener iterationListener) { + ThreadListenerTraverser shut = new ThreadListenerTraverser(false); + testTree.traverse(shut); // call ThreadListener.threadFinished() + JMeterContextService.decrNumberOfThreads(); + threadGroup.decrNumberOfThreads(); + GuiPackage gp = GuiPackage.getInstance(); + if (gp != null){// check there is a GUI + gp.getMainFrame().updateCounts(); + } + if (iterationListener != null) { // probably not possible, but check anyway + controller.removeIterationListener(iterationListener); + } + } + + // N.B. This is only called at the start and end of a thread, so there is not + // necessary to cache the search results, thus saving memory + private static class ThreadListenerTraverser implements HashTreeTraverser { + private final boolean isStart; + + private ThreadListenerTraverser(boolean start) { + isStart = start; + } + + @Override + public void addNode(Object node, HashTree subTree) { + if (node instanceof ThreadListener) { + ThreadListener tl = (ThreadListener) node; + if (isStart) { + tl.threadStarted(); + } else { + tl.threadFinished(); + } + } + } + + @Override + public void subtractNode() { + } + + @Override + public void processPath() { + } + } + + public String getThreadName() { + return threadName; + } + + public void stop() { // Called by StandardJMeterEngine, TestAction and AccessLogSampler + running = false; + log.info("Stopping: " + threadName); + } + + /** {@inheritDoc} */ + @Override + public boolean interrupt(){ + try { + interruptLock.lock(); + Sampler samp = currentSampler; // fetch once; must be done under lock + if (samp instanceof Interruptible){ // (also protects against null) + log.warn("Interrupting: " + threadName + " sampler: " +samp.getName()); + try { + boolean found = ((Interruptible)samp).interrupt(); + if (!found) { + log.warn("No operation pending"); + } + return found; + } catch (Exception e) { + log.warn("Caught Exception interrupting sampler: "+e.toString()); + } + } else if (samp != null){ + log.warn("Sampler is not Interruptible: "+samp.getName()); + } + } finally { + interruptLock.unlock(); + } + return false; + } + + private void stopTest() { + running = false; + log.info("Stop Test detected by thread: " + threadName); + if (engine != null) { + engine.askThreadsToStop(); + } + } + + private void stopTestNow() { + running = false; + log.info("Stop Test Now detected by thread: " + threadName); + if (engine != null) { + engine.stopTest(); + } + } + + private void stopThread() { + running = false; + log.info("Stop Thread detected by thread: " + threadName); + } + + @SuppressWarnings("deprecation") // OK to call TestBeanHelper.prepare() + private void checkAssertions(List assertions, SampleResult parent, JMeterContext threadContext) { + for (Assertion assertion : assertions) { + TestBeanHelper.prepare((TestElement) assertion); + if (assertion instanceof AbstractScopedAssertion){ + AbstractScopedAssertion scopedAssertion = (AbstractScopedAssertion) assertion; + String scope = scopedAssertion.fetchScope(); + if (scopedAssertion.isScopeParent(scope) || scopedAssertion.isScopeAll(scope) || scopedAssertion.isScopeVariable(scope)){ + processAssertion(parent, assertion); + } + if (scopedAssertion.isScopeChildren(scope) || scopedAssertion.isScopeAll(scope)){ + SampleResult children[] = parent.getSubResults(); + boolean childError = false; + for (int i=0;i extractors) { + ListIterator iter; + if (reversePostProcessors) {// Original (rather odd) behaviour + iter = extractors.listIterator(extractors.size());// start at the end + while (iter.hasPrevious()) { + PostProcessor ex = iter.previous(); + TestBeanHelper.prepare((TestElement) ex); + ex.process(); + } + } else { + for (PostProcessor ex : extractors) { + TestBeanHelper.prepare((TestElement) ex); + ex.process(); + } + } + } + + @SuppressWarnings("deprecation") // OK to call TestBeanHelper.prepare() + private void runPreProcessors(List preProcessors) { + for (PreProcessor ex : preProcessors) { + if (log.isDebugEnabled()) { + log.debug("Running preprocessor: " + ((AbstractTestElement) ex).getName()); + } + TestBeanHelper.prepare((TestElement) ex); + ex.process(); + } + } + + @SuppressWarnings("deprecation") // OK to call TestBeanHelper.prepare() + private void delay(List timers) { + long sum = 0; + for (Timer timer : timers) { + TestBeanHelper.prepare((TestElement) timer); + sum += timer.delay(); + } + if (sum > 0) { + try { + TimeUnit.MILLISECONDS.sleep(sum); + } catch (InterruptedException e) { + log.warn("The delay timer was interrupted - probably did not wait as long as intended."); + } + } + } + + void notifyTestListeners() { + threadVars.incIteration(); + for (TestIterationListener listener : testIterationStartListeners) { + if (listener instanceof TestElement) { + listener.testIterationStart(new LoopIterationEvent(controller, threadVars.getIteration())); + ((TestElement) listener).recoverRunningVersion(); + } else { + listener.testIterationStart(new LoopIterationEvent(controller, threadVars.getIteration())); + } + } + } + + private void notifyListeners(List listeners, SampleResult result) { + SampleEvent event = new SampleEvent(result, threadGroup.getName(), threadVars); + notifier.notifyListeners(event, listeners); + + } + + /** + * Set rampup delay for JMeterThread Thread + * @param delay Rampup delay for JMeterThread + */ + public void setInitialDelay(int delay) { + initialDelay = delay; + } + + /** + * Initial delay if ramp-up period is active for this threadGroup. + */ + private void rampUpDelay() { + delayBy(initialDelay, "RampUp"); + } + + /** + * Wait for delay with RAMPUP_GRANULARITY + * @param delay delay in ms + * @param type Delay type + */ + protected final void delayBy(long delay, String type) { + if (delay > 0) { + long start = System.currentTimeMillis(); + long end = start + delay; + long now=0; + long pause = RAMPUP_GRANULARITY; + while(running && (now = System.currentTimeMillis()) < end) { + long togo = end - now; + if (togo < pause) { + pause = togo; + } + try { + TimeUnit.MILLISECONDS.sleep(pause); // delay between checks + } catch (InterruptedException e) { + if (running) { // Don't bother reporting stop test interruptions + log.warn(type+" delay for "+threadName+" was interrupted. Waited "+(now - start)+" milli-seconds out of "+delay); + } + break; + } + } + } + } + + /** + * Returns the threadNum. + * + * @return the threadNum + */ + public int getThreadNum() { + return threadNum; + } + + /** + * Sets the threadNum. + * + * @param threadNum + * the threadNum to set + */ + public void setThreadNum(int threadNum) { + this.threadNum = threadNum; + } + + private class IterationListener implements LoopIterationListener { + /** + * {@inheritDoc} + */ + @Override + public void iterationStart(LoopIterationEvent iterEvent) { + notifyTestListeners(); + } + } + + /** + * Save the engine instance for access to the stop methods + * + * @param engine the engine which is used + */ + public void setEngine(StandardJMeterEngine engine) { + this.engine = engine; + } + + /** + * Should Test stop on sampler error? + * + * @param b - + * true or false + */ + public void setOnErrorStopTest(boolean b) { + onErrorStopTest = b; + } + + /** + * Should Test stop abruptly on sampler error? + * + * @param b - + * true or false + */ + public void setOnErrorStopTestNow(boolean b) { + onErrorStopTestNow = b; + } + + /** + * Should Thread stop on Sampler error? + * + * @param b - + * true or false + */ + public void setOnErrorStopThread(boolean b) { + onErrorStopThread = b; + } + + /** + * Should Thread start next loop on Sampler error? + * + * @param b - + * true or false + */ + public void setOnErrorStartNextLoop(boolean b) { + onErrorStartNextLoop = b; + } + + public void setThreadGroup(AbstractThreadGroup group) { + this.threadGroup = group; + } + +} diff --git a/src/core/org/apache/jmeter/threads/JMeterThreadMonitor.java b/src/core/org/apache/jmeter/threads/JMeterThreadMonitor.java new file mode 100644 index 00000000000..befc40f92c3 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/JMeterThreadMonitor.java @@ -0,0 +1,26 @@ +/* + * 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.threads; + +/** + * @version $Revision$ + */ +public interface JMeterThreadMonitor { + void threadFinished(JMeterThread thread); +} diff --git a/src/core/org/apache/jmeter/threads/JMeterVariables.java b/src/core/org/apache/jmeter/threads/JMeterVariables.java new file mode 100644 index 00000000000..d8d9c081e90 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/JMeterVariables.java @@ -0,0 +1,151 @@ +/* + * 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.threads; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Class which defines JMeter variables. + * These are similar to properties, but they are local to a single thread. + */ +public class JMeterVariables { + private final Map variables = new HashMap(); + + private int iteration = 0; + + // Property names to preload into JMeter variables: + private static final String [] PRE_LOAD = { + "START.MS", // $NON-NLS-1$ + "START.YMD", // $NON-NLS-1$ + "START.HMS", //$NON-NLS-1$ + "TESTSTART.MS", // $NON-NLS-1$ + }; + + public JMeterVariables() { + preloadVariables(); + } + + private void preloadVariables(){ + for (int i = 0; i < PRE_LOAD.length; i++){ + String property=PRE_LOAD[i]; + String value=JMeterUtils.getProperty(property); + if (value != null){ + variables.put(property,value); + } + } + } + + public String getThreadName() { + return Thread.currentThread().getName(); + } + + public int getIteration() { + return iteration; + } + + public void incIteration() { + iteration++; + } + + // Does not appear to be used + public void initialize() { + variables.clear(); + preloadVariables(); + } + + /** + * Remove a variable. + * + * @param key the variable name to remove + * + * @return the variable value, or {@code null} if there was no such variable + */ + public Object remove(String key) { + return variables.remove(key); + } + + /** + * Creates or updates a variable with a String value. + * + * @param key the variable name + * @param value the variable value + */ + public void put(String key, String value) { + variables.put(key, value); + } + + /** + * Creates or updates a variable with a value that does not have to be a String. + * + * @param key the variable name + * @param value the variable value + */ + public void putObject(String key, Object value) { + variables.put(key, value); + } + + public void putAll(Map vars) { + variables.putAll(vars); + } + + public void putAll(JMeterVariables vars) { + putAll(vars.variables); + } + + /** + * Gets the value of a variable, coerced to a String. + * + * @param key the name of the variable + * @return the value of the variable, or {@code null} if it does not exist + */ + public String get(String key) { + return (String) variables.get(key); + } + + /** + * Gets the value of a variable (not converted to String). + * + * @param key the name of the variable + * @return the value of the variable, or {@code null} if it does not exist + */ + public Object getObject(String key) { + return variables.get(key); + } + + /** + * Gets a read-only Iterator over the variables. + * + * @return the iterator + */ + public Iterator> getIterator(){ + return Collections.unmodifiableMap(variables).entrySet().iterator() ; + } + + // Used by DebugSampler + public Set> entrySet(){ + return Collections.unmodifiableMap(variables).entrySet(); + } +} diff --git a/src/core/org/apache/jmeter/threads/ListenerNotifier.java b/src/core/org/apache/jmeter/threads/ListenerNotifier.java new file mode 100644 index 00000000000..4aaae8aa053 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/ListenerNotifier.java @@ -0,0 +1,271 @@ +/* + * 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. + * + */ + +///////////////////////////////////////// +//////// +//////// This code is mostly unused at present +//////// it seems that only notifyListeners() +//////// is used. +//////// +//////// However, it does look useful. +//////// And it may one day be used... +//////// +///////////////////////////////////////// + +package org.apache.jmeter.threads; + +import java.util.List; + +//import org.apache.commons.collections.Buffer; +//import org.apache.commons.collections.BufferUtils; +//import org.apache.commons.collections.buffer.UnboundedFifoBuffer; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +///** +// * The ListenerNotifier thread is responsible for performing +// * asynchronous notifications that a sample has occurred. Each time a sample +// * occurs, the addLast method should be called to add the sample +// * and its list of listeners to the notification queue. This thread will then +// * notify those listeners asynchronously at some future time. +// *

+// * In the current implementation, the notifications will be made in batches, +// * with 2 seconds between the beginning of successive batches. If the notifier +// * thread starts to get behind, the priority of the thread will be increased in +// * an attempt to help it to keep up. +// * +// * @see org.apache.jmeter.samplers.SampleListener +// * +// */ +/** + * Processes sample events. + * The current implementation processes events in the calling thread + * using {@link #notifyListeners(SampleEvent, List)} + * The other code is not used currently, so is commented out. + */ +public class ListenerNotifier { + private static final Logger log = LoggingManager.getLoggerForClass(); + + + /** + * Notify a list of listeners that a sample has occurred. + * + * @param res + * the sample event that has occurred. Must be non-null. + * @param listeners + * a list of the listeners which should be notified. This list + * must not be null and must contain only SampleListener + * elements. + */ + @SuppressWarnings("deprecation") // TestBeanHelper.prepare() is OK + public void notifyListeners(SampleEvent res, List listeners) { + for (SampleListener sampleListener : listeners) { + try { + TestBeanHelper.prepare((TestElement) sampleListener); + sampleListener.sampleOccurred(res); + } catch (RuntimeException e) { + log.error("Detected problem in Listener: ", e); + log.info("Continuing to process further listeners"); + } + } + } + +// /** +// * The number of milliseconds between batches of notifications. +// */ +// private static final int SLEEP_TIME = 2000; +// +// /** +// * Indicates whether or not this thread should remain running. The thread +// * will continue running after this field is set to false until the next +// * batch of notifications has been completed and the notification queue is +// * empty. +// */ +// private boolean running = true; +// +// /** +// * Indicates whether or not this thread has stopped. No further +// * notifications will be performed. +// */ +// private boolean isStopped = true; +// +// /** +// * The queue containing the notifications to be performed. Each notification +// * consists of a pair of entries in this queue. The first is the +// * {@link org.apache.jmeter.samplers.SampleEvent SampleEvent} representing +// * the sample. The second is a List of +// * {@link org.apache.jmeter.samplers.SampleListener SampleListener}s which +// * should be notified. +// */ +// private Buffer listenerEvents = BufferUtils.synchronizedBuffer(new UnboundedFifoBuffer()); +// +// /** +// * Stops the ListenerNotifier thread. The thread will continue processing +// * any events remaining in the notification queue before it actually stops, +// * but this method will return immediately. +// */ +// public void stop() { +// running = false; +// } +// +// /** +// * Indicates whether or not the thread has stopped. This will not return +// * true until the stop method has been called and any +// * remaining notifications in the queue have been completed. +// * +// * @return true if the ListenerNotifier has completely stopped, false +// * otherwise +// */ +// public boolean isStopped() { +// return isStopped; +// } +// +// /** +// * Process the events in the notification queue until the thread has been +// * told to stop and the notification queue is empty. +// *

+// * In the current implementation, this method will iterate continually until +// * the thread is told to stop. In each iteration it will process any +// * notifications that are in the queue at the beginning of the iteration, +// * and then will sleep until it is time to start the next batch. As long as +// * the thread is keeping up, each batch should start 2 seconds after the +// * beginning of the last batch. This exact behavior is subject to change. +// */ +// public void run() { +// boolean isMaximumPriority = false; +// int normalCount = 0; +// +// while (running) { +// long startTime = System.currentTimeMillis(); +// processNotifications(); +// long sleep = SLEEP_TIME - (System.currentTimeMillis() - startTime); +// +// // If the thread has been told to stop then we shouldn't sleep +// if (!running) { +// break; +// } +// +// if (sleep < 0) { +// isMaximumPriority = true; +// normalCount = 0; +// if (log.isInfoEnabled()) { +// log.info("ListenerNotifier exceeded maximum " + "notification time by " + (-sleep) + "ms"); +// } +// boostPriority(); +// } else { +// normalCount++; +// +// // If there have been three consecutive iterations since the +// // last iteration which took too long to execute, return the +// // thread to normal priority. +// if (isMaximumPriority && normalCount >= 3) { +// isMaximumPriority = false; +// unboostPriority(); +// } +// +// if (log.isDebugEnabled()) { +// log.debug("ListenerNotifier sleeping for " + sleep + "ms"); +// } +// +// try { +// Thread.sleep(sleep); +// } catch (InterruptedException e) { +// } +// } +// } +// +// // Make sure that all pending notifications are processed before +// // actually ending the thread. +// processNotifications(); +// isStopped = true; +// } +// +// /** +// * Process all of the pending notifications. Only the samples which are in +// * the queue when this method is called will be processed. Any samples added +// * between the time when this method is called and when it exits are saved +// * for the next batch. +// */ +// private void processNotifications() { +// int listenerEventsSize = listenerEvents.size(); +// if (log.isDebugEnabled()) { +// log.debug("ListenerNotifier: processing " + listenerEventsSize + " events"); +// } +// +// while (listenerEventsSize > 0) { +// // Since this is a FIFO and this is the only place we remove +// // from it (only from a single thread) we don't have to remove +// // these two items in one atomic operation. Each individual +// // remove is atomic (because we use a synchronized buffer), +// // which is necessary since the buffer can be accessed from +// // other threads (to add things to the buffer). +// SampleEvent res = (SampleEvent) listenerEvents.remove(); +// List listeners = (List) listenerEvents.remove(); +// +// notifyListeners(res, listeners); +// +// listenerEventsSize -= 2; +// } +// } +// +// /** +// * Boost the priority of the current thread to maximum priority. If the +// * thread is already at maximum priority then this will have no effect. +// */ +// private void boostPriority() { +// if (Thread.currentThread().getPriority() != Thread.MAX_PRIORITY) { +// log.info("ListenerNotifier: Boosting thread priority to maximum."); +// Thread.currentThread().setPriority(Thread.MAX_PRIORITY); +// } +// } +// +// /** +// * Return the priority of the current thread to normal. If the thread is +// * already at normal priority then this will have no effect. +// */ +// private void unboostPriority() { +// if (Thread.currentThread().getPriority() != Thread.NORM_PRIORITY) { +// log.info("ListenerNotifier: Returning thread priority to normal."); +// Thread.currentThread().setPriority(Thread.NORM_PRIORITY); +// } +// } +// +// /** +// * Add a new sample event to the notification queue. The notification will +// * be performed asynchronously and this method will return immediately. +// * +// * @param item +// * the sample event that has occurred. Must be non-null. +// * @param listeners +// * a list of the listeners which should be notified. This list +// * must not be null and must contain only SampleListener +// * elements. +// */ +// public void addLast(SampleEvent item, List listeners) { +// // Must use explicit synchronization here so that the item and +// // listeners are added together atomically +// synchronized (listenerEvents) { +// listenerEvents.add(item); +// listenerEvents.add(listeners); +// } +// } +} diff --git a/src/core/org/apache/jmeter/threads/PostThreadGroup.java b/src/core/org/apache/jmeter/threads/PostThreadGroup.java new file mode 100644 index 00000000000..514aa45e3e2 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/PostThreadGroup.java @@ -0,0 +1,28 @@ +/* + * 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.threads; + + +/** + * PostThreadGroup is a special type of ThreadGroup that can be used for + * performing actions at the end of a test for cleanup and such. + */ +public class PostThreadGroup extends ThreadGroup { + private static final long serialVersionUID = 240L; +} diff --git a/src/core/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java b/src/core/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java new file mode 100644 index 00000000000..41145795165 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java @@ -0,0 +1,38 @@ +/* + * 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.threads; + +/** + * Interface notified when number of active threads changes + * @since 2.10 + */ +public interface RemoteThreadsLifeCycleListener { + + /** + * + * @param numberOfThreads number of active threads + */ + void threadNumberIncreased(int numberOfThreads); + + /** + * + * @param numberOfThreads number of active threads + */ + void threadNumberDecreased(int numberOfThreads); +} diff --git a/src/core/org/apache/jmeter/threads/RemoteThreadsListener.java b/src/core/org/apache/jmeter/threads/RemoteThreadsListener.java new file mode 100644 index 00000000000..0a34d84c09f --- /dev/null +++ b/src/core/org/apache/jmeter/threads/RemoteThreadsListener.java @@ -0,0 +1,40 @@ +/* + * 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.threads; + +import java.rmi.RemoteException; + +/** + * RMI Interface that allows notification of remote start/end of threads + * @since 2.10 + */ +public interface RemoteThreadsListener extends java.rmi.Remote { + + /** + * @see org.apache.jmeter.testelement.ThreadListener#threadStarted() + * @throws RemoteException when remote calling of the method fails + */ + void threadStarted() throws RemoteException; + + /** + * @see org.apache.jmeter.testelement.ThreadListener#threadFinished() + * @throws RemoteException when remote calling of the method fails + */ + void threadFinished() throws RemoteException; +} diff --git a/src/core/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java b/src/core/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java new file mode 100644 index 00000000000..fd40d023bdd --- /dev/null +++ b/src/core/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java @@ -0,0 +1,115 @@ +/* + * 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.threads; + +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; +import java.util.ArrayList; +import java.util.List; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +/** + * RMI Implementation, client side code (ie executed on Controller) + * @since 2.10 + */ +public class RemoteThreadsListenerImpl extends UnicastRemoteObject implements + RemoteThreadsListener, ThreadListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + private final List listeners = new ArrayList(); + + /** + * + */ + private static final long serialVersionUID = 4790505101521183660L; + /** + * + */ + private static final int DEFAULT_LOCAL_PORT = + JMeterUtils.getPropDefault("client.rmi.localport", 0); // $NON-NLS-1$ + + /** + * @throws RemoteException if failed to export object + */ + public RemoteThreadsListenerImpl() throws RemoteException { + super(DEFAULT_LOCAL_PORT); + try { + List listClasses = ClassFinder.findClassesThatExtend( + JMeterUtils.getSearchPaths(), + new Class[] {RemoteThreadsLifeCycleListener.class }); + for (String strClassName : listClasses) { + try { + if(log.isDebugEnabled()) { + log.debug("Loading class: "+ strClassName); + } + Class commandClass = Class.forName(strClassName); + if (!Modifier.isAbstract(commandClass.getModifiers())) { + if(log.isDebugEnabled()) { + log.debug("Instantiating: "+ commandClass.getName()); + } + RemoteThreadsLifeCycleListener listener = (RemoteThreadsLifeCycleListener) commandClass.newInstance(); + listeners.add(listener); + } + } catch (Exception e) { + log.error("Exception registering "+RemoteThreadsLifeCycleListener.class.getName() + " with implementation:"+strClassName, e); + } + } + } catch (IOException e) { + log.error("Exception finding implementations of "+RemoteThreadsLifeCycleListener.class, e); + } + } + + /** + * + * @see RemoteThreadsListener#threadStarted() + */ + @Override + public void threadStarted() { + JMeterContextService.incrNumberOfThreads(); + GuiPackage gp =GuiPackage.getInstance(); + if (gp != null) {// check there is a GUI + gp.getMainFrame().updateCounts(); + } + for (RemoteThreadsLifeCycleListener listener : listeners) { + listener.threadNumberIncreased(JMeterContextService.getNumberOfThreads()); + } + } + + /* (non-Javadoc) + * @see org.apache.jmeter.samplers.RemoteThreadsListener#threadFinished() + */ + @Override + public void threadFinished() { + JMeterContextService.decrNumberOfThreads(); + GuiPackage gp =GuiPackage.getInstance(); + if (gp != null) {// check there is a GUI + gp.getMainFrame().updateCounts(); + } + for (RemoteThreadsLifeCycleListener listener : listeners) { + listener.threadNumberDecreased(JMeterContextService.getNumberOfThreads()); + } + } +} diff --git a/src/core/org/apache/jmeter/threads/RemoteThreadsListenerTestElement.java b/src/core/org/apache/jmeter/threads/RemoteThreadsListenerTestElement.java new file mode 100644 index 00000000000..209f96a81b4 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/RemoteThreadsListenerTestElement.java @@ -0,0 +1,43 @@ +/* + * 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.threads; + +import org.apache.jmeter.engine.ConvertListeners; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.testelement.ThreadListener; + +/** + * Empty implementation only used to be able to do replacement by {@link ConvertListeners} + * @since 2.10 + */ +public class RemoteThreadsListenerTestElement implements Remoteable, ThreadListener { + + @Override + public void threadStarted() { + // NOOP + } + + /** + * + */ + @Override + public void threadFinished() { + // NOOP + } +} diff --git a/src/core/org/apache/jmeter/threads/RemoteThreadsListenerWrapper.java b/src/core/org/apache/jmeter/threads/RemoteThreadsListenerWrapper.java new file mode 100644 index 00000000000..f73b3da8514 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/RemoteThreadsListenerWrapper.java @@ -0,0 +1,66 @@ +/* + * 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.threads; + +import java.io.Serializable; +import java.rmi.RemoteException; + +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * server side wrapper, used to notify RMI client + * @since 2.10 + */ +public class RemoteThreadsListenerWrapper extends AbstractTestElement implements ThreadListener, Serializable, + NoThreadClone { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private RemoteThreadsListener listener; + + public RemoteThreadsListenerWrapper(RemoteThreadsListener l) { + listener = l; + } + + public RemoteThreadsListenerWrapper() { + } + + @Override + public void threadStarted() { + try { + listener.threadStarted(); + } catch (RemoteException err) { + log.error("", err); // $NON-NLS-1$ + } + } + + @Override + public void threadFinished() { + try { + listener.threadFinished(); + } catch (RemoteException err) { + log.error("", err); // $NON-NLS-1$ + } + } +} diff --git a/src/core/org/apache/jmeter/threads/SamplePackage.java b/src/core/org/apache/jmeter/threads/SamplePackage.java new file mode 100644 index 00000000000..c8f71a92888 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/SamplePackage.java @@ -0,0 +1,230 @@ +/* + * 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.threads; + +import java.util.List; + +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.timers.Timer; + +/** + * Packages methods related to sample handling.
+ * A SamplePackage contains all elements associated to a Sampler: + *

    + *
  • SampleListener(s)
  • + *
  • Timer(s)
  • + *
  • Assertion(s)
  • + *
  • PreProcessor(s)
  • + *
  • PostProcessor(s)
  • + *
  • ConfigTestElement(s)
  • + *
  • Controller(s)
  • + *
+ */ +public class SamplePackage { + + private final List sampleListeners; + + private final List timers; + + private final List assertions; + + private final List postProcessors; + + private final List preProcessors; + + private final List configs; + + private final List controllers; + + private Sampler sampler; + + public SamplePackage( + List configs, + List listeners, + List timers, + List assertions, + List postProcessors, + List preProcessors, + List controllers) { + this.configs = configs; + this.sampleListeners = listeners; + this.timers = timers; + this.assertions = assertions; + this.postProcessors = postProcessors; + this.preProcessors = preProcessors; + this.controllers = controllers; + } + + /** + * Make the SamplePackage the running version, or make it no longer the + * running version. This tells to each element of the SamplePackage that it's current state must + * be retrievable by a call to recoverRunningVersion(). + * @param running boolean + * @see TestElement#setRunningVersion(boolean) + */ + public void setRunningVersion(boolean running) { + setRunningVersion(configs, running); + setRunningVersion(sampleListeners, running); + setRunningVersion(assertions, running); + setRunningVersion(timers, running); + setRunningVersion(postProcessors, running); + setRunningVersion(preProcessors, running); + setRunningVersion(controllers, running); + sampler.setRunningVersion(running); + } + + private void setRunningVersion(List list, boolean running) { + @SuppressWarnings("unchecked") // all implementations extend TestElement + List telist = (List)list; + for (TestElement te : telist) { + te.setRunningVersion(running); + } + } + + private void recoverRunningVersion(List list) { + @SuppressWarnings("unchecked") // All implementations extend TestElement + List telist = (List)list; + for (TestElement te : telist) { + te.recoverRunningVersion(); + } + } + + /** + * Recover each member of SamplePackage to the state before the call of setRunningVersion(true) + * @see TestElement#recoverRunningVersion() + */ + public void recoverRunningVersion() { + recoverRunningVersion(configs); + recoverRunningVersion(sampleListeners); + recoverRunningVersion(assertions); + recoverRunningVersion(timers); + recoverRunningVersion(postProcessors); + recoverRunningVersion(preProcessors); + recoverRunningVersion(controllers); + sampler.recoverRunningVersion(); + } + + /** + * @return List of {@link SampleListener}s + */ + public List getSampleListeners() { + return sampleListeners; + } + + /** + * Add Sample Listener + * @param listener {@link SampleListener} + */ + public void addSampleListener(SampleListener listener) { + sampleListeners.add(listener); + } + + /** + * @return List of {@link Timer}s + */ + public List getTimers() { + return timers; + } + + + /** + * Add Post processor + * @param ex {@link PostProcessor} + */ + public void addPostProcessor(PostProcessor ex) { + postProcessors.add(ex); + } + + /** + * Add Pre processor + * @param pre {@link PreProcessor} + */ + public void addPreProcessor(PreProcessor pre) { + preProcessors.add(pre); + } + + /** + * Add Timer + * @param timer {@link Timer} + */ + public void addTimer(Timer timer) { + timers.add(timer); + } + + /** + * Add Assertion + * @param asser {@link Assertion} + */ + public void addAssertion(Assertion asser) { + assertions.add(asser); + } + + /** + * @return List of {@link Assertion} + */ + public List getAssertions() { + return assertions; + } + + /** + * @return List of {@link PostProcessor}s + */ + public List getPostProcessors() { + return postProcessors; + } + + /** + * @return {@link Sampler} + */ + public Sampler getSampler() { + return sampler; + } + + /** + * @param s {@link Sampler} + */ + public void setSampler(Sampler s) { + sampler = s; + } + + /** + * Returns the preProcessors. + * @return List of {@link PreProcessor} + */ + public List getPreProcessors() { + return preProcessors; + } + + /** + * Returns the configs. + * + * @return List of {@link ConfigTestElement} + */ + public List getConfigs() { + return configs; + } + +} diff --git a/src/core/org/apache/jmeter/threads/SetupThreadGroup.java b/src/core/org/apache/jmeter/threads/SetupThreadGroup.java new file mode 100644 index 00000000000..0d1411a7db7 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/SetupThreadGroup.java @@ -0,0 +1,33 @@ +/* + * 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.threads; + + +/** + * SetupThreadGroup.java is a special type of ThreadGroup that can be used for + * setting up of a test before the bulk of the test executes later. + * + */ +public class SetupThreadGroup extends ThreadGroup { + private static final long serialVersionUID = 240L; + + public SetupThreadGroup() { + super(); + } +} diff --git a/src/core/org/apache/jmeter/threads/TestCompiler.java b/src/core/org/apache/jmeter/threads/TestCompiler.java new file mode 100644 index 00000000000..692ebe6c4d7 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/TestCompiler.java @@ -0,0 +1,329 @@ +/* + * 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.threads; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.TransactionController; +import org.apache.jmeter.control.TransactionSampler; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.engine.util.NoConfigMerge; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.timers.Timer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HashTreeTraverser implementation that traverses the Test Tree to build: + *
    + *
  • A map with key Sampler and as value the associated SamplePackage
  • + *
  • A map with key TransactionController and as value the associated SamplePackage
  • + *
+ */ +public class TestCompiler implements HashTreeTraverser { + + private static final Logger LOG = LoggingManager.getLoggerForClass(); + + /** + * Set this property {@value} to true to revert to using a shared static set. + */ + private static final String USE_STATIC_SET = "TestCompiler.useStaticSet"; + + /** + * The default value - {@value} - assumed for {@link #USE_STATIC_SET}. + */ + private static final boolean USE_STATIC_SET_DEFAULT = false; + + public static final boolean IS_USE_STATIC_SET = JMeterUtils.getPropDefault(USE_STATIC_SET, USE_STATIC_SET_DEFAULT); + + /** + * This set keeps track of which ObjectPairs have been seen. + * It seems to be used to prevent adding a child to a parent if the child has already been added. + * If the ObjectPair (child, parent) is present, then the child has been added. + * Otherwise, the child is added to the parent and the pair is added to the Set. + */ + private static final Set PAIRING = new HashSet(); + + private final LinkedList stack = new LinkedList(); + + private final Map samplerConfigMap = new HashMap(); + + private final Map transactionControllerConfigMap = + new HashMap(); + + private final HashTree testTree; + + public TestCompiler(HashTree testTree) { + this.testTree = testTree; + } + + /** + * Clears the pairing Set Called by StandardJmeterEngine at the start of a + * test run. + */ + public static void initialize() { + // synch is probably not needed as only called before run starts + synchronized (PAIRING) { + PAIRING.clear(); + } + } + + /** + * Configures sampler from SamplePackage extracted from Test plan and returns it + * @param sampler {@link Sampler} + * @return {@link SamplePackage} + */ + public SamplePackage configureSampler(Sampler sampler) { + SamplePackage pack = samplerConfigMap.get(sampler); + pack.setSampler(sampler); + configureWithConfigElements(sampler, pack.getConfigs()); + return pack; + } + + /** + * Configures Transaction Sampler from SamplePackage extracted from Test plan and returns it + * @param transactionSampler {@link TransactionSampler} + * @return {@link SamplePackage} + */ + public SamplePackage configureTransactionSampler(TransactionSampler transactionSampler) { + TransactionController controller = transactionSampler.getTransactionController(); + SamplePackage pack = transactionControllerConfigMap.get(controller); + pack.setSampler(transactionSampler); + return pack; + } + + /** + * Reset pack to its initial state + * @param pack the {@link SamplePackage} to reset + */ + public void done(SamplePackage pack) { + pack.recoverRunningVersion(); + } + + /** {@inheritDoc} */ + @Override + public void addNode(Object node, HashTree subTree) { + stack.addLast((TestElement) node); + } + + /** {@inheritDoc} */ + @Override + public void subtractNode() { + LOG.debug("Subtracting node, stack size = " + stack.size()); + TestElement child = stack.getLast(); + trackIterationListeners(stack); + if (child instanceof Sampler) { + saveSamplerConfigs((Sampler) child); + } + else if(child instanceof TransactionController) { + saveTransactionControllerConfigs((TransactionController) child); + } + stack.removeLast(); + if (stack.size() > 0) { + TestElement parent = stack.getLast(); + boolean duplicate = false; + // Bug 53750: this condition used to be in ObjectPair#addTestElements() + if (parent instanceof Controller && (child instanceof Sampler || child instanceof Controller)) { + if (!IS_USE_STATIC_SET && parent instanceof TestCompilerHelper) { + TestCompilerHelper te = (TestCompilerHelper) parent; + duplicate = !te.addTestElementOnce(child); + } else { // this is only possible for 3rd party controllers by default + ObjectPair pair = new ObjectPair(child, parent); + synchronized (PAIRING) {// Called from multiple threads + if (!PAIRING.contains(pair)) { + parent.addTestElement(child); + PAIRING.add(pair); + } else { + duplicate = true; + } + } + } + } + if (duplicate) { + LOG.warn("Unexpected duplicate for " + parent.getClass().getName() + " and " + child.getClass().getName()); + } + } + } + + @SuppressWarnings("deprecation") // TestBeanHelper.prepare() is OK + private void trackIterationListeners(LinkedList p_stack) { + TestElement child = p_stack.getLast(); + if (child instanceof LoopIterationListener) { + ListIterator iter = p_stack.listIterator(p_stack.size()); + while (iter.hasPrevious()) { + TestElement item = iter.previous(); + if (item == child) { + continue; + } + if (item instanceof Controller) { + TestBeanHelper.prepare(child); + ((Controller) item).addIterationListener((LoopIterationListener) child); + break; + } + } + } + } + + /** {@inheritDoc} */ + @Override + public void processPath() { + } + + private void saveSamplerConfigs(Sampler sam) { + List configs = new LinkedList(); + List controllers = new LinkedList(); + List listeners = new LinkedList(); + List timers = new LinkedList(); + List assertions = new LinkedList(); + LinkedList posts = new LinkedList(); + LinkedList pres = new LinkedList(); + for (int i = stack.size(); i > 0; i--) { + addDirectParentControllers(controllers, stack.get(i - 1)); + List tempPre = new LinkedList (); + List tempPost = new LinkedList(); + for (Object item : testTree.list(stack.subList(0, i))) { + if ((item instanceof ConfigTestElement)) { + configs.add((ConfigTestElement) item); + } + if (item instanceof SampleListener) { + listeners.add((SampleListener) item); + } + if (item instanceof Timer) { + timers.add((Timer) item); + } + if (item instanceof Assertion) { + assertions.add((Assertion) item); + } + if (item instanceof PostProcessor) { + tempPost.add((PostProcessor) item); + } + if (item instanceof PreProcessor) { + tempPre.add((PreProcessor) item); + } + } + pres.addAll(0, tempPre); + posts.addAll(0, tempPost); + } + + SamplePackage pack = new SamplePackage(configs, listeners, timers, assertions, + posts, pres, controllers); + pack.setSampler(sam); + pack.setRunningVersion(true); + samplerConfigMap.put(sam, pack); + } + + private void saveTransactionControllerConfigs(TransactionController tc) { + List configs = new LinkedList(); + List controllers = new LinkedList(); + List listeners = new LinkedList(); + List timers = new LinkedList(); + List assertions = new LinkedList(); + LinkedList posts = new LinkedList(); + LinkedList pres = new LinkedList(); + for (int i = stack.size(); i > 0; i--) { + addDirectParentControllers(controllers, stack.get(i - 1)); + for (Object item : testTree.list(stack.subList(0, i))) { + if (item instanceof SampleListener) { + listeners.add((SampleListener) item); + } + if (item instanceof Assertion) { + assertions.add((Assertion) item); + } + } + } + + SamplePackage pack = new SamplePackage(configs, listeners, timers, assertions, + posts, pres, controllers); + pack.setSampler(new TransactionSampler(tc, tc.getName())); + pack.setRunningVersion(true); + transactionControllerConfigMap.put(tc, pack); + } + + /** + * @param controllers + * @param i + */ + private void addDirectParentControllers(List controllers, TestElement maybeController) { + if (maybeController instanceof Controller) { + LOG.debug("adding controller: " + maybeController + " to sampler config"); + controllers.add((Controller) maybeController); + } + } + + private static class ObjectPair + { + private final TestElement child; + private final TestElement parent; + + public ObjectPair(TestElement child, TestElement parent) { + this.child = child; + this.parent = parent; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return child.hashCode() + parent.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (o instanceof ObjectPair) { + return child == ((ObjectPair) o).child && parent == ((ObjectPair) o).parent; + } + return false; + } + } + + private void configureWithConfigElements(Sampler sam, List configs) { + sam.clearTestElementChildren(); + for (ConfigTestElement config : configs) { + if (!(config instanceof NoConfigMerge)) + { + if(sam instanceof ConfigMergabilityIndicator) { + if(((ConfigMergabilityIndicator)sam).applies(config)) { + sam.addTestElement(config); + } + } else { + // Backward compatibility + sam.addTestElement(config); + } + } + } + } +} diff --git a/src/core/org/apache/jmeter/threads/TestCompilerHelper.java b/src/core/org/apache/jmeter/threads/TestCompilerHelper.java new file mode 100644 index 00000000000..a7d66b25c8d --- /dev/null +++ b/src/core/org/apache/jmeter/threads/TestCompilerHelper.java @@ -0,0 +1,48 @@ +/* + * 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.threads; + +import org.apache.jmeter.testelement.TestElement; + +/** + * Bug 53796 - TestCompiler uses static Set which can grow huge + * + * This interface is a means to allow the pair data to be saved with the parent + * instance, thus allowing it to be garbage collected when the thread completes. + * + * This uses a bit more memory, as each controller test element includes the data + * structure to contain the child element. However, there is no need to store the + * parent element. + * + * @since 2.8 + */ +public interface TestCompilerHelper { + + /** + * Add child test element only if it has not already been added. + *

+ * Only for use by TestCompiler. + * + * @param child + * the {@link TestElement} to be added + * @return true if the child was added + */ + boolean addTestElementOnce(TestElement child); + +} diff --git a/src/core/org/apache/jmeter/threads/ThreadGroup.java b/src/core/org/apache/jmeter/threads/ThreadGroup.java new file mode 100644 index 00000000000..eca50eebfdb --- /dev/null +++ b/src/core/org/apache/jmeter/threads/ThreadGroup.java @@ -0,0 +1,579 @@ +/* + * 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.threads; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.engine.TreeCloner; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.ListedHashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * ThreadGroup holds the settings for a JMeter thread group. + * + * This class is intended to be ThreadSafe. + */ +public class ThreadGroup extends AbstractThreadGroup { + private static final long serialVersionUID = 280L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long WAIT_TO_DIE = JMeterUtils.getPropDefault("jmeterengine.threadstop.wait", 5 * 1000); // 5 seconds + + /** How often to check for shutdown during ramp-up, default 1000ms */ + private static final int RAMPUP_GRANULARITY = + JMeterUtils.getPropDefault("jmeterthread.rampup.granularity", 1000); // $NON-NLS-1$ + + //+ JMX entries - do not change the string values + + /** Ramp-up time */ + public static final String RAMP_TIME = "ThreadGroup.ramp_time"; + + /** Whether thread startup is delayed until required */ + public static final String DELAYED_START = "ThreadGroup.delayedStart"; + + /** Whether scheduler is being used */ + public static final String SCHEDULER = "ThreadGroup.scheduler"; + + /** Scheduler absolute start time */ + public static final String START_TIME = "ThreadGroup.start_time"; + + /** Scheduler absolute end time */ + public static final String END_TIME = "ThreadGroup.end_time"; + + /** Scheduler duration, overrides end time */ + public static final String DURATION = "ThreadGroup.duration"; + + /** Scheduler start delay, overrides start time */ + public static final String DELAY = "ThreadGroup.delay"; + + //- JMX entries + + private transient Thread threadStarter; + + // List of active threads + private final Map allThreads = new ConcurrentHashMap(); + + /** + * Is test (still) running? + */ + private volatile boolean running = false; + + /** + * Are we using delayed startup? + */ + private boolean delayedStartup; + + /** + * No-arg constructor. + */ + public ThreadGroup() { + } + + /** + * Set whether scheduler is being used + * + * @param Scheduler true is scheduler is to be used + */ + public void setScheduler(boolean Scheduler) { + setProperty(new BooleanProperty(SCHEDULER, Scheduler)); + } + + /** + * Get whether scheduler is being used + * + * @return true if scheduler is being used + */ + public boolean getScheduler() { + return getPropertyAsBoolean(SCHEDULER); + } + + /** + * Set the absolute StartTime value. + * + * @param stime - + * the StartTime value. + */ + public void setStartTime(long stime) { + setProperty(new LongProperty(START_TIME, stime)); + } + + /** + * Get the absolute start time value. + * + * @return the start time value. + */ + public long getStartTime() { + return getPropertyAsLong(START_TIME); + } + + /** + * Get the desired duration of the thread group test run + * + * @return the duration (in secs) + */ + public long getDuration() { + return getPropertyAsLong(DURATION); + } + + /** + * Set the desired duration of the thread group test run + * + * @param duration + * in seconds + */ + public void setDuration(long duration) { + setProperty(new LongProperty(DURATION, duration)); + } + + /** + * Get the startup delay + * + * @return the delay (in secs) + */ + public long getDelay() { + return getPropertyAsLong(DELAY); + } + + /** + * Set the startup delay + * + * @param delay + * in seconds + */ + public void setDelay(long delay) { + setProperty(new LongProperty(DELAY, delay)); + } + + /** + * Set the EndTime value. + * + * @param etime - + * the EndTime value. + */ + public void setEndTime(long etime) { + setProperty(new LongProperty(END_TIME, etime)); + } + + /** + * Get the end time value. + * + * @return the end time value. + */ + public long getEndTime() { + return getPropertyAsLong(END_TIME); + } + + /** + * Set the ramp-up value. + * + * @param rampUp + * the ramp-up value. + */ + public void setRampUp(int rampUp) { + setProperty(new IntegerProperty(RAMP_TIME, rampUp)); + } + + /** + * Get the ramp-up value. + * + * @return the ramp-up value. + */ + public int getRampUp() { + return getPropertyAsInt(ThreadGroup.RAMP_TIME); + } + + private boolean isDelayedStartup() { + return getPropertyAsBoolean(DELAYED_START); + } + + /** + * This will schedule the time for the JMeterThread. + * + * @param thread JMeterThread + */ + private void scheduleThread(JMeterThread thread, long now) { + // if true the Scheduler is enabled + if (getScheduler()) { + // set the start time for the Thread + if (getDelay() > 0) {// Duration is in seconds + thread.setStartTime(getDelay() * 1000 + now); + } else { + long start = getStartTime(); + if (start < now) { + start = now; // Force a sensible start time + } + thread.setStartTime(start); + } + + // set the endtime for the Thread + if (getDuration() > 0) {// Duration is in seconds + thread.setEndTime(getDuration() * 1000 + (thread.getStartTime())); + } else { + thread.setEndTime(getEndTime()); + } + + // Enables the scheduler + thread.setScheduled(true); + } + } + + + /** + * Wait for delay with RAMPUP_GRANULARITY + * @param delay delay in ms + */ + private void delayBy(long delay) { + if (delay > 0) { + long start = System.currentTimeMillis(); + long end = start + delay; + long now=0; + long pause = RAMPUP_GRANULARITY; // maximum pause to use + while(running && (now = System.currentTimeMillis()) < end) { + long togo = end - now; + if (togo < pause) { + pause = togo; + } + pause(pause); // delay between checks + } + } + } + + @Override + public void start(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine) { + running = true; + int numThreads = getNumThreads(); + int rampUp = getRampUp(); + float perThreadDelay = ((float) (rampUp * 1000) / (float) getNumThreads()); + + delayedStartup = isDelayedStartup(); // Fetch once; needs to stay constant + log.info("Starting thread group number " + groupCount + + " threads " + numThreads + + " ramp-up " + rampUp + + " perThread " + perThreadDelay + + " delayedStart=" + delayedStartup); + if (delayedStartup) { + threadStarter = new Thread(new ThreadStarter(groupCount, notifier, threadGroupTree, engine), getName()+"-ThreadStarter"); + threadStarter.setDaemon(true); + threadStarter.start(); + // N.B. we don't wait for the thread to complete, as that would prevent parallel TGs + } else { + long now = System.currentTimeMillis(); // needs to be same time for all threads in the group + final JMeterContext context = JMeterContextService.getContext(); + for (int i = 0; running && i < numThreads; i++) { + JMeterThread jmThread = makeThread(groupCount, notifier, threadGroupTree, engine, i, context); + scheduleThread(jmThread, now); // set start and end time + jmThread.setInitialDelay((int)(i * perThreadDelay)); + Thread newThread = new Thread(jmThread, jmThread.getThreadName()); + registerStartedThread(jmThread, newThread); + newThread.start(); + } + } + log.info("Started thread group number "+groupCount); + } + + /** + * Register Thread when it starts + * @param jMeterThread {@link JMeterThread} + * @param newThread Thread + */ + private void registerStartedThread(JMeterThread jMeterThread, Thread newThread) { + allThreads.put(jMeterThread, newThread); + } + + private JMeterThread makeThread(int groupCount, + ListenerNotifier notifier, ListedHashTree threadGroupTree, + StandardJMeterEngine engine, int i, + JMeterContext context) { // N.B. Context needs to be fetched in the correct thread + boolean onErrorStopTest = getOnErrorStopTest(); + boolean onErrorStopTestNow = getOnErrorStopTestNow(); + boolean onErrorStopThread = getOnErrorStopThread(); + boolean onErrorStartNextLoop = getOnErrorStartNextLoop(); + String groupName = getName(); + final JMeterThread jmeterThread = new JMeterThread(cloneTree(threadGroupTree), this, notifier); + jmeterThread.setThreadNum(i); + jmeterThread.setThreadGroup(this); + jmeterThread.setInitialContext(context); + final String threadName = groupName + " " + (groupCount) + "-" + (i + 1); + jmeterThread.setThreadName(threadName); + jmeterThread.setEngine(engine); + jmeterThread.setOnErrorStopTest(onErrorStopTest); + jmeterThread.setOnErrorStopTestNow(onErrorStopTestNow); + jmeterThread.setOnErrorStopThread(onErrorStopThread); + jmeterThread.setOnErrorStartNextLoop(onErrorStartNextLoop); + return jmeterThread; + } + + /** + * Stop thread called threadName: + *

    + *
  1. stop JMeter thread
  2. + *
  3. interrupt JMeter thread
  4. + *
  5. interrupt underlying thread
  6. + *
+ * @param threadName String thread name + * @param now boolean for stop + * @return true if thread stopped + */ + @Override + public boolean stopThread(String threadName, boolean now) { + for(Entry entry : allThreads.entrySet()){ + JMeterThread thrd = entry.getKey(); + if (thrd.getThreadName().equals(threadName)){ + thrd.stop(); + thrd.interrupt(); + if (now) { + Thread t = entry.getValue(); + if (t != null) { + t.interrupt(); + } + } + return true; + } + } + return false; + } + + /** + * Called by JMeterThread when it finishes + */ + @Override + public void threadFinished(JMeterThread thread) { + log.debug("Ending thread " + thread.getThreadName()); + allThreads.remove(thread); + } + + /** + * For each thread, invoke: + *
    + *
  • {@link JMeterThread#stop()} - set stop flag
  • + *
  • {@link JMeterThread#interrupt()} - interrupt sampler
  • + *
  • {@link Thread#interrupt()} - interrupt JVM thread
  • + *
+ */ + @Override + public void tellThreadsToStop() { + running = false; + if (delayedStartup) { + try { + threadStarter.interrupt(); + } catch (Exception e) { + log.warn("Exception occured interrupting ThreadStarter"); + } + } + for (Entry entry : allThreads.entrySet()) { + JMeterThread item = entry.getKey(); + item.stop(); // set stop flag + item.interrupt(); // interrupt sampler if possible + Thread t = entry.getValue(); + if (t != null ) { // Bug 49734 + t.interrupt(); // also interrupt JVM thread + } + } + } + + + /** + * For each thread, invoke: + *
    + *
  • {@link JMeterThread#stop()} - set stop flag
  • + *
+ */ + @Override + public void stop() { + running = false; + if (delayedStartup) { + try { + threadStarter.interrupt(); + } catch (Exception e) { + log.warn("Exception occured interrupting ThreadStarter"); + } + } + for (JMeterThread item : allThreads.keySet()) { + item.stop(); + } + } + + /** + * @return number of active threads + */ + @Override + public int numberOfActiveThreads() { + return allThreads.size(); + } + + /** + * @return boolean true if all threads stopped + */ + @Override + public boolean verifyThreadsStopped() { + boolean stoppedAll = true; + if (delayedStartup){ + stoppedAll = verifyThreadStopped(threadStarter); + } + for (Thread t : allThreads.values()) { + stoppedAll = stoppedAll && verifyThreadStopped(t); + } + return stoppedAll; + } + + /** + * Verify thread stopped and return true if stopped successfully + * @param thread Thread + * @return boolean + */ + private boolean verifyThreadStopped(Thread thread) { + boolean stopped = true; + if (thread != null) { + if (thread.isAlive()) { + try { + thread.join(WAIT_TO_DIE); + } catch (InterruptedException e) { + } + if (thread.isAlive()) { + stopped = false; + log.warn("Thread won't exit: " + thread.getName()); + } + } + } + return stopped; + } + + /** + * Wait for all Group Threads to stop + */ + @Override + public void waitThreadsStopped() { + if (delayedStartup) { + waitThreadStopped(threadStarter); + } + for (Thread t : allThreads.values()) { + waitThreadStopped(t); + } + } + + /** + * Wait for thread to stop + * @param thread Thread + */ + private void waitThreadStopped(Thread thread) { + if (thread != null) { + while (thread.isAlive()) { + try { + thread.join(WAIT_TO_DIE); + } catch (InterruptedException e) { + } + } + } + } + + private ListedHashTree cloneTree(ListedHashTree tree) { + TreeCloner cloner = new TreeCloner(true); + tree.traverse(cloner); + return cloner.getClonedTree(); + } + + private void pause(long ms){ + try { + TimeUnit.MILLISECONDS.sleep(ms); + } catch (InterruptedException e) { + // TODO Is this silent exception intended + } + } + + /** + * Starts Threads using ramp up + */ + class ThreadStarter implements Runnable { + + private final int groupCount; + private final ListenerNotifier notifier; + private final ListedHashTree threadGroupTree; + private final StandardJMeterEngine engine; + private final JMeterContext context; + + public ThreadStarter(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine) { + super(); + this.groupCount = groupCount; + this.notifier = notifier; + this.threadGroupTree = threadGroupTree; + this.engine = engine; + // Store context from Root Thread to pass it to created threads + this.context = JMeterContextService.getContext(); + + } + + @Override + public void run() { + // Copy in ThreadStarter thread context from calling Thread + JMeterContextService.getContext().setVariables(this.context.getVariables()); + long now = System.currentTimeMillis(); // needs to be constant for all threads + long endtime = 0; + final boolean usingScheduler = getScheduler(); + if (usingScheduler) { + // set the start time for the Thread + if (getDelay() > 0) {// Duration is in seconds + delayBy(getDelay() * 1000); + } else { + long start = getStartTime(); + if (start >= now) { + delayBy(start-now); + } + // else start immediately + } + // set the endtime for the Thread + endtime = getDuration(); + if (endtime > 0) {// Duration is in seconds, starting from when the threads start + endtime = endtime *1000 + System.currentTimeMillis(); + } else { + endtime = getEndTime(); + } + } + final int numThreads = getNumThreads(); + final int perTthreadDelay = Math.round(((float) (getRampUp() * 1000) / (float) numThreads)); + for (int i = 0; running && i < numThreads; i++) { + if (i > 0) { + pause(perTthreadDelay); // ramp-up delay (except first) + } + if (usingScheduler && System.currentTimeMillis() > endtime) { + break; // no point continuing beyond the end time + } + JMeterThread jmThread = makeThread(groupCount, notifier, threadGroupTree, engine, i, context); + jmThread.setInitialDelay(0); // Already waited + if (usingScheduler) { + jmThread.setScheduled(true); + jmThread.setEndTime(endtime); + } + Thread newThread = new Thread(jmThread, jmThread.getThreadName()); + newThread.setDaemon(false); // ThreadStarter is daemon, but we don't want sampler threads to be so too + registerStartedThread(jmThread, newThread); + newThread.start(); + } + } + } +} diff --git a/src/core/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java b/src/core/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java new file mode 100644 index 00000000000..bfa9a0d9335 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java @@ -0,0 +1,184 @@ +/* + * 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.threads.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.util.Arrays; +import java.util.Collection; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JPanel; + +import javax.swing.JPopupMenu; +import javax.swing.JRadioButton; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.util.JMeterUtils; + +public abstract class AbstractThreadGroupGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + // Sampler error action buttons + private JRadioButton continueBox; + + private JRadioButton startNextLoop; + + private JRadioButton stopThrdBox; + + private JRadioButton stopTestBox; + + private JRadioButton stopTestNowBox; + + public AbstractThreadGroupGui(){ + super(); + init(); + initGui(); + } + + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.THREADS }); + } + + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + pop.add(MenuFactory.makeMenus(new String[] { + MenuFactory.CONTROLLERS, + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.TIMERS, + MenuFactory.PRE_PROCESSORS, + MenuFactory.SAMPLERS, + MenuFactory.POST_PROCESSORS, + MenuFactory.ASSERTIONS, + MenuFactory.LISTENERS, + }, + JMeterUtils.getResString("add"), // $NON-NLS-1$ + ActionNames.ADD)); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop, false); + return pop; + } + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + @Override + public void clearGui(){ + super.clearGui(); + initGui(); + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createOnErrorPanel()); + add(box, BorderLayout.NORTH); + } + + private void initGui() { + continueBox.setSelected(true); + } + + private JPanel createOnErrorPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("sampler_on_error_action"))); // $NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + + continueBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_continue")); // $NON-NLS-1$ + group.add(continueBox); + panel.add(continueBox); + + startNextLoop = new JRadioButton(JMeterUtils.getResString("sampler_on_error_start_next_loop")); // $NON-NLS-1$ + group.add(startNextLoop); + panel.add(startNextLoop); + + stopThrdBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_thread")); // $NON-NLS-1$ + group.add(stopThrdBox); + panel.add(stopThrdBox); + + stopTestBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test")); // $NON-NLS-1$ + group.add(stopTestBox); + panel.add(stopTestBox); + + stopTestNowBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test_now")); // $NON-NLS-1$ + group.add(stopTestNowBox); + panel.add(stopTestNowBox); + + return panel; + } + + private void setSampleErrorBoxes(AbstractThreadGroup te) { + if (te.getOnErrorStopTest()) { + stopTestBox.setSelected(true); + } else if (te.getOnErrorStopTestNow()) { + stopTestNowBox.setSelected(true); + } else if (te.getOnErrorStopThread()) { + stopThrdBox.setSelected(true); + } else if (te.getOnErrorStartNextLoop()) { + startNextLoop.setSelected(true); + } else { + continueBox.setSelected(true); + } + } + + private String onSampleError() { + if (stopTestBox.isSelected()) { + return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTEST; + } + if (stopTestNowBox.isSelected()) { + return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTEST_NOW; + } + if (stopThrdBox.isSelected()) { + return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTHREAD; + } + if (startNextLoop.isSelected()) { + return AbstractThreadGroup.ON_SAMPLE_ERROR_START_NEXT_LOOP; + } + + // Defaults to continue + return AbstractThreadGroup.ON_SAMPLE_ERROR_CONTINUE; + } + + @Override + public void configure(TestElement tg) { + super.configure(tg); + setSampleErrorBoxes((AbstractThreadGroup) tg); + } + + @Override + protected void configureTestElement(TestElement tg) { + super.configureTestElement(tg); + tg.setProperty(new StringProperty(AbstractThreadGroup.ON_SAMPLE_ERROR, onSampleError())); + } + +} diff --git a/src/core/org/apache/jmeter/threads/gui/PostThreadGroupGui.java b/src/core/org/apache/jmeter/threads/gui/PostThreadGroupGui.java new file mode 100644 index 00000000000..a89cd398fde --- /dev/null +++ b/src/core/org/apache/jmeter/threads/gui/PostThreadGroupGui.java @@ -0,0 +1,45 @@ +/* + * 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.threads.gui; + +import java.awt.event.ItemListener; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.PostThreadGroup; + +public class PostThreadGroupGui extends ThreadGroupGui implements ItemListener { + private static final long serialVersionUID = 240L; + + public PostThreadGroupGui() { + super(false); + } + + @Override + public String getLabelResource() { + return "post_thread_group_title"; // $NON-NLS-1$ + + } + + @Override + public TestElement createTestElement() { + PostThreadGroup tg = new PostThreadGroup(); + modifyTestElement(tg); + return tg; + } +} diff --git a/src/core/org/apache/jmeter/threads/gui/SetupThreadGroupGui.java b/src/core/org/apache/jmeter/threads/gui/SetupThreadGroupGui.java new file mode 100644 index 00000000000..57faf82f58a --- /dev/null +++ b/src/core/org/apache/jmeter/threads/gui/SetupThreadGroupGui.java @@ -0,0 +1,45 @@ +/* + * 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.threads.gui; + +import java.awt.event.ItemListener; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.SetupThreadGroup; + +public class SetupThreadGroupGui extends ThreadGroupGui implements ItemListener { + private static final long serialVersionUID = 240L; + + public SetupThreadGroupGui() { + super(false); + } + + @Override + public String getLabelResource() { + return "setup_thread_group_title"; // $NON-NLS-1$ + + } + + @Override + public TestElement createTestElement() { + SetupThreadGroup tg = new SetupThreadGroup(); + modifyTestElement(tg); + return tg; + } +} diff --git a/src/core/org/apache/jmeter/threads/gui/ThreadGroupGui.java b/src/core/org/apache/jmeter/threads/gui/ThreadGroupGui.java new file mode 100644 index 00000000000..d029a467891 --- /dev/null +++ b/src/core/org/apache/jmeter/threads/gui/ThreadGroupGui.java @@ -0,0 +1,304 @@ +/* + * 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.threads.gui; + +import java.awt.BorderLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.Date; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.LoopController; +import org.apache.jmeter.control.gui.LoopControlPanel; +import org.apache.jmeter.gui.util.JDateField; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.threads.ThreadGroup; +import org.apache.jmeter.util.JMeterUtils; + +public class ThreadGroupGui extends AbstractThreadGroupGui implements ItemListener { + private static final long serialVersionUID = 240L; + + private LoopControlPanel loopPanel; + + private VerticalPanel mainPanel; + + private static final String THREAD_NAME = "Thread Field"; + + private static final String RAMP_NAME = "Ramp Up Field"; + + private JTextField threadInput; + + private JTextField rampInput; + + private JDateField start; + + private JDateField end; + + private final boolean showDelayedStart; + + private JCheckBox delayedStart; + + private JCheckBox scheduler; + + private JTextField duration; + + private JTextField delay; // Relative start-up time + + public ThreadGroupGui() { + this(true); + } + + public ThreadGroupGui(boolean showDelayedStart) { + super(); + this.showDelayedStart = showDelayedStart; + init(); + initGui(); + } + + @Override + public TestElement createTestElement() { + ThreadGroup tg = new ThreadGroup(); + modifyTestElement(tg); + return tg; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement tg) { + super.configureTestElement(tg); + if (tg instanceof AbstractThreadGroup) { + ((AbstractThreadGroup) tg).setSamplerController((LoopController) loopPanel.createTestElement()); + } + + tg.setProperty(AbstractThreadGroup.NUM_THREADS, threadInput.getText()); + tg.setProperty(ThreadGroup.RAMP_TIME, rampInput.getText()); + tg.setProperty(new LongProperty(ThreadGroup.START_TIME, start.getDate().getTime())); + tg.setProperty(new LongProperty(ThreadGroup.END_TIME, end.getDate().getTime())); + if (showDelayedStart) { + tg.setProperty(ThreadGroup.DELAYED_START, delayedStart.isSelected(), false); + } + tg.setProperty(new BooleanProperty(ThreadGroup.SCHEDULER, scheduler.isSelected())); + tg.setProperty(ThreadGroup.DURATION, duration.getText()); + tg.setProperty(ThreadGroup.DELAY, delay.getText()); + } + + @Override + public void configure(TestElement tg) { + super.configure(tg); + threadInput.setText(tg.getPropertyAsString(AbstractThreadGroup.NUM_THREADS)); + rampInput.setText(tg.getPropertyAsString(ThreadGroup.RAMP_TIME)); + loopPanel.configure((TestElement) tg.getProperty(AbstractThreadGroup.MAIN_CONTROLLER).getObjectValue()); + if (showDelayedStart) { + delayedStart.setSelected(tg.getPropertyAsBoolean(ThreadGroup.DELAYED_START)); + } + scheduler.setSelected(tg.getPropertyAsBoolean(ThreadGroup.SCHEDULER)); + + if (scheduler.isSelected()) { + mainPanel.setVisible(true); + } else { + mainPanel.setVisible(false); + } + + // Check if the property exists + String s = tg.getPropertyAsString(ThreadGroup.START_TIME); + if (s.length() == 0) {// Must be an old test plan + start.setDate(new Date()); + end.setDate(new Date()); + } else { + start.setDate(new Date(tg.getPropertyAsLong(ThreadGroup.START_TIME))); + end.setDate(new Date(tg.getPropertyAsLong(ThreadGroup.END_TIME))); + } + duration.setText(tg.getPropertyAsString(ThreadGroup.DURATION)); + delay.setText(tg.getPropertyAsString(ThreadGroup.DELAY)); + } + + @Override + public void itemStateChanged(ItemEvent ie) { + if (ie.getItem().equals(scheduler)) { + if (scheduler.isSelected()) { + mainPanel.setVisible(true); + } else { + mainPanel.setVisible(false); + } + } + } + + private JPanel createControllerPanel() { + loopPanel = new LoopControlPanel(false); + LoopController looper = (LoopController) loopPanel.createTestElement(); + looper.setLoops(1); + loopPanel.configure(looper); + return loopPanel; + } + + /** + * Create a panel containing the StartTime field and corresponding label. + * + * @return a GUI panel containing the StartTime field + */ + private JPanel createStartTimePanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("starttime")); //$NON-NLS-1$ + panel.add(label, BorderLayout.WEST); + start = new JDateField(); + panel.add(start, BorderLayout.CENTER); + return panel; + } + + /** + * Create a panel containing the EndTime field and corresponding label. + * + * @return a GUI panel containing the EndTime field + */ + private JPanel createEndTimePanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("endtime")); // $NON-NLS-1$ + panel.add(label, BorderLayout.WEST); + + end = new JDateField(); + panel.add(end, BorderLayout.CENTER); + return panel; + } + + /** + * Create a panel containing the Duration field and corresponding label. + * + * @return a GUI panel containing the Duration field + */ + private JPanel createDurationPanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("duration")); // $NON-NLS-1$ + panel.add(label, BorderLayout.WEST); + duration = new JTextField(); + panel.add(duration, BorderLayout.CENTER); + return panel; + } + + /** + * Create a panel containing the Duration field and corresponding label. + * + * @return a GUI panel containing the Duration field + */ + private JPanel createDelayPanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("delay")); // $NON-NLS-1$ + panel.add(label, BorderLayout.WEST); + delay = new JTextField(); + panel.add(delay, BorderLayout.CENTER); + return panel; + } + + @Override + public String getLabelResource() { + return "threadgroup"; // $NON-NLS-1$ + } + + @Override + public void clearGui(){ + super.clearGui(); + initGui(); + } + + // Initialise the gui field values + private void initGui(){ + threadInput.setText("1"); // $NON-NLS-1$ + rampInput.setText("1"); // $NON-NLS-1$ + loopPanel.clearGui(); + if (showDelayedStart) { + delayedStart.setSelected(false); + } + scheduler.setSelected(false); + Date today = new Date(); + end.setDate(today); + start.setDate(today); + delay.setText(""); // $NON-NLS-1$ + duration.setText(""); // $NON-NLS-1$ + } + + private void init() { + // THREAD PROPERTIES + VerticalPanel threadPropsPanel = new VerticalPanel(); + threadPropsPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("thread_properties"))); // $NON-NLS-1$ + + // NUMBER OF THREADS + JPanel threadPanel = new JPanel(new BorderLayout(5, 0)); + + JLabel threadLabel = new JLabel(JMeterUtils.getResString("number_of_threads")); // $NON-NLS-1$ + threadPanel.add(threadLabel, BorderLayout.WEST); + + threadInput = new JTextField(5); + threadInput.setName(THREAD_NAME); + threadLabel.setLabelFor(threadInput); + threadPanel.add(threadInput, BorderLayout.CENTER); + + threadPropsPanel.add(threadPanel); + + // RAMP-UP + JPanel rampPanel = new JPanel(new BorderLayout(5, 0)); + JLabel rampLabel = new JLabel(JMeterUtils.getResString("ramp_up")); // $NON-NLS-1$ + rampPanel.add(rampLabel, BorderLayout.WEST); + + rampInput = new JTextField(5); + rampInput.setName(RAMP_NAME); + rampLabel.setLabelFor(rampInput); + rampPanel.add(rampInput, BorderLayout.CENTER); + + threadPropsPanel.add(rampPanel); + + // LOOP COUNT + threadPropsPanel.add(createControllerPanel()); + + // mainPanel.add(threadPropsPanel, BorderLayout.NORTH); + // add(mainPanel, BorderLayout.CENTER); + + if (showDelayedStart) { + delayedStart = new JCheckBox(JMeterUtils.getResString("delayed_start")); // $NON-NLS-1$ + threadPropsPanel.add(delayedStart); + } + scheduler = new JCheckBox(JMeterUtils.getResString("scheduler")); // $NON-NLS-1$ + scheduler.addItemListener(this); + threadPropsPanel.add(scheduler); + mainPanel = new VerticalPanel(); + mainPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("scheduler_configuration"))); // $NON-NLS-1$ + mainPanel.add(createStartTimePanel()); + mainPanel.add(createEndTimePanel()); + mainPanel.add(createDurationPanel()); + mainPanel.add(createDelayPanel()); + mainPanel.setVisible(false); + VerticalPanel intgrationPanel = new VerticalPanel(); + intgrationPanel.add(threadPropsPanel); + intgrationPanel.add(mainPanel); + add(intgrationPanel, BorderLayout.CENTER); + } +} diff --git a/src/core/org/apache/jmeter/timers/Timer.java b/src/core/org/apache/jmeter/timers/Timer.java new file mode 100644 index 00000000000..f888a94ed6d --- /dev/null +++ b/src/core/org/apache/jmeter/timers/Timer.java @@ -0,0 +1,36 @@ +/* + * 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.timers; + +import java.io.Serializable; + +/** + * This interface defines those methods that must be implemented by timer + * plugins. + * + */ +public interface Timer extends Serializable { + /** + * This method is called after a sampling process is done to know how much + * time the sampling thread has to wait until sampling again. + * + * @return the computed delay value. + */ + long delay(); +} diff --git a/src/core/org/apache/jmeter/timers/gui/AbstractTimerGui.java b/src/core/org/apache/jmeter/timers/gui/AbstractTimerGui.java new file mode 100644 index 00000000000..90178beb45b --- /dev/null +++ b/src/core/org/apache/jmeter/timers/gui/AbstractTimerGui.java @@ -0,0 +1,66 @@ +/* + * 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.timers.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage timers. + * + * @version $Revision$ + */ +public abstract class AbstractTimerGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most timer + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + @Override + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultTimerMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#TIMERS}, which is + * appropriate for most timer components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.TIMERS }); + } +} diff --git a/src/core/org/apache/jmeter/util/BSFBeanInfoSupport.java b/src/core/org/apache/jmeter/util/BSFBeanInfoSupport.java new file mode 100644 index 00000000000..f9d7ebcb169 --- /dev/null +++ b/src/core/org/apache/jmeter/util/BSFBeanInfoSupport.java @@ -0,0 +1,48 @@ +/* + * 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.util; + +import java.util.Arrays; +import java.util.Properties; + +import org.apache.jmeter.testbeans.TestBean; + +/** + * Parent class to handle common GUI design for BSF test elements + */ +public abstract class BSFBeanInfoSupport extends ScriptingBeanInfoSupport { + + private static final String[] LANGUAGE_TAGS; + + static { + Properties languages = JMeterUtils.loadProperties("org/apache/bsf/Languages.properties"); // $NON-NLS-1$ + LANGUAGE_TAGS = new String[languages.size() + 1]; + int i = 0; + for (Object language : languages.keySet()) { + LANGUAGE_TAGS[i++] = language.toString(); + } + LANGUAGE_TAGS[i] = "jexl"; // $NON-NLS-1$ + Arrays.sort(LANGUAGE_TAGS); + } + + protected BSFBeanInfoSupport(Class beanClass) { + super(beanClass, LANGUAGE_TAGS); + } + +} diff --git a/src/core/org/apache/jmeter/util/BSFJavaScriptEngine.java b/src/core/org/apache/jmeter/util/BSFJavaScriptEngine.java new file mode 100644 index 00000000000..530b68fbdf3 --- /dev/null +++ b/src/core/org/apache/jmeter/util/BSFJavaScriptEngine.java @@ -0,0 +1,243 @@ +/* + * 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.util; + +import java.util.Iterator; +import java.util.Vector; + +import org.apache.bsf.BSFDeclaredBean; +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.bsf.util.BSFEngineImpl; +import org.apache.bsf.util.BSFFunctions; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.ImporterTopLevel; +import org.mozilla.javascript.JavaScriptException; +import org.mozilla.javascript.NativeJavaObject; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.WrappedException; +import org.mozilla.javascript.Wrapper; + +/** + * This is the interface to Netscape's Rhino (JavaScript) from the + * Bean Scripting Framework. + *

+ * The original version of this code was first written by Adam Peller + * for use in LotusXSL. Sanjiva took his code and adapted it for BSF. + * + * Modified for JMeter to fix bug BSF-22. + */ +public class BSFJavaScriptEngine extends BSFEngineImpl { + /** + * The global script object, where all embedded functions are defined, + * as well as the standard ECMA "core" objects. + */ + private Scriptable global; + + /** + * Return an object from an extension. + * @param object Object on which to make the call (ignored). + * @param method The name of the method to call. + * @param args an array of arguments to be + * passed to the extension, which may be either + * Vectors of Nodes, or Strings. + */ + @Override + public Object call(Object object, String method, Object[] args) + throws BSFException { + + Object retval = null; + Context cx; + + try { + cx = Context.enter(); + + // REMIND: convert arg list Vectors here? + + Object fun = global.get(method, global); + // NOTE: Source and line arguments are nonsense in a call(). + // Any way to make these arguments *sensible? + if (fun == Scriptable.NOT_FOUND) + throw new EvaluatorException("function " + method + + " not found.", "none", 0); + + cx.setOptimizationLevel(-1); + cx.setGeneratingDebug(false); + cx.setGeneratingSource(false); + cx.setOptimizationLevel(0); + cx.setDebugger(null, null); + + retval = + ((Function) fun).call(cx, global, global, args); + +// ScriptRuntime.call(cx, fun, global, args, global); + + if (retval instanceof Wrapper) + retval = ((Wrapper) retval).unwrap(); + } + catch (Throwable t) { + handleError(t); + } + finally { + Context.exit(); + } + return retval; + } + + @Override + public void declareBean(BSFDeclaredBean bean) throws BSFException { + if ((bean.bean instanceof Number) || + (bean.bean == null) || + (bean.bean instanceof String) || + (bean.bean instanceof Boolean)) { + global.put(bean.name, global, bean.bean); + } + else { + // Must wrap non-scriptable objects before presenting to Rhino + Scriptable wrapped = Context.toObject(bean.bean, global); + global.put(bean.name, global, wrapped); + } + } + + /** + * This is used by an application to evaluate a string containing + * some expression. + */ + @Override + public Object eval(String source, int lineNo, int columnNo, Object oscript) + throws BSFException { + + String scriptText = oscript.toString(); + Object retval = null; + Context cx; + + try { + cx = Context.enter(); + + cx.setOptimizationLevel(-1); + cx.setGeneratingDebug(false); + cx.setGeneratingSource(false); + cx.setOptimizationLevel(0); + cx.setDebugger(null, null); + + retval = cx.evaluateString(global, scriptText, + source, lineNo, + null); + + if (retval instanceof NativeJavaObject) + retval = ((NativeJavaObject) retval).unwrap(); + + } + catch (Throwable t) { // includes JavaScriptException, rethrows Errors + handleError(t); + } + finally { + Context.exit(); + } + return retval; + } + + private void handleError(Throwable t) throws BSFException { + if (t instanceof WrappedException) + t = ((WrappedException) t).getWrappedException(); + + String message = null; + Throwable target = t; + + if (t instanceof JavaScriptException) { + message = t.getLocalizedMessage(); + + // Is it an exception wrapped in a JavaScriptException? + Object value = ((JavaScriptException) t).getValue(); + if (value instanceof Throwable) { + // likely a wrapped exception from a LiveConnect call. + // Display its stack trace as a diagnostic + target = (Throwable) value; + } + } + else if (t instanceof EvaluatorException || + t instanceof SecurityException) { + message = t.getLocalizedMessage(); + } + else if (t instanceof RuntimeException) { + message = "Internal Error: " + t.toString(); + } + else if (t instanceof StackOverflowError) { + message = "Stack Overflow"; + } + + if (message == null) + message = t.toString(); + + if (t instanceof Error && !(t instanceof StackOverflowError)) { + // Re-throw Errors because we're supposed to let the JVM see it + // Don't re-throw StackOverflows, because we know we've + // corrected the situation by aborting the loop and + // a long stacktrace would end up on the user's console + throw (Error) t; + } + else { + throw new BSFException(BSFException.REASON_OTHER_ERROR, + "JavaScript Error: " + message, + target); + } + } + + /** + * Initialize the engine. + * Put the manager into the context-manager + * map hashtable too. + */ + @Override + public void initialize(BSFManager mgr, String lang, + @SuppressWarnings("rawtypes") // superclass does not support types + Vector declaredBeans) + throws BSFException { + + super.initialize(mgr, lang, declaredBeans); + + // Initialize context and global scope object + try { + Context cx = Context.enter(); + global = new ImporterTopLevel(cx); + Scriptable bsf = Context.toObject(new BSFFunctions(mgr, this), global); + global.put("bsf", global, bsf); + + for( + @SuppressWarnings("unchecked") + Iterator it = declaredBeans.iterator(); + it.hasNext();) { + declareBean(it.next()); + } + } + catch (Throwable t) { + handleError(t); + } + finally { + Context.exit(); + } + } + + @Override + public void undeclareBean(BSFDeclaredBean bean) throws BSFException { + global.delete(bean.name); + } +} diff --git a/src/core/org/apache/jmeter/util/BSFTestElement.java b/src/core/org/apache/jmeter/util/BSFTestElement.java new file mode 100644 index 00000000000..4f0e125a58c --- /dev/null +++ b/src/core/org/apache/jmeter/util/BSFTestElement.java @@ -0,0 +1,136 @@ +/* + * 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.util; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.Properties; + +import org.apache.bsf.BSFEngine; +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.commons.io.FileUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public abstract class BSFTestElement extends ScriptingTestElement + implements Serializable +{ + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + static { + BSFManager.registerScriptingEngine("jexl", //$NON-NLS-1$ + "org.apache.commons.jexl.bsf.JexlEngine", //$NON-NLS-1$ + new String[]{"jexl"}); //$NON-NLS-1$ + log.info("Registering JMeter version of JavaScript engine as work-round for BSF-22"); + BSFManager.registerScriptingEngine("javascript", //$NON-NLS-1$ + "org.apache.jmeter.util.BSFJavaScriptEngine", //$NON-NLS-1$ + new String[]{"js"}); //$NON-NLS-1$ + } + + public BSFTestElement() { + super(); + } + + protected BSFManager getManager() throws BSFException { + BSFManager mgr = new BSFManager(); + initManager(mgr); + return mgr; + } + + protected void initManager(BSFManager mgr) throws BSFException{ + final String label = getName(); + final String fileName = getFilename(); + final String scriptParameters = getParameters(); + // Use actual class name for log + final Logger logger = LoggingManager.getLoggerForShortName(getClass().getName()); + mgr.declareBean("log", logger, Logger.class); // $NON-NLS-1$ + mgr.declareBean("Label",label, String.class); // $NON-NLS-1$ + mgr.declareBean("FileName",fileName, String.class); // $NON-NLS-1$ + mgr.declareBean("Parameters", scriptParameters, String.class); // $NON-NLS-1$ + String [] args=JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$ + mgr.declareBean("args",args,args.getClass());//$NON-NLS-1$ + // Add variables for access to context and variables + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + Properties props = JMeterUtils.getJMeterProperties(); + + mgr.declareBean("ctx", jmctx, jmctx.getClass()); // $NON-NLS-1$ + mgr.declareBean("vars", vars, vars.getClass()); // $NON-NLS-1$ + mgr.declareBean("props", props, props.getClass()); // $NON-NLS-1$ + // For use in debugging: + mgr.declareBean("OUT", System.out, PrintStream.class); // $NON-NLS-1$ + + // Most subclasses will need these: + Sampler sampler = jmctx.getCurrentSampler(); + mgr.declareBean("sampler", sampler, Sampler.class); + SampleResult prev = jmctx.getPreviousResult(); + mgr.declareBean("prev", prev, SampleResult.class); + } + + protected void processFileOrScript(BSFManager mgr) throws BSFException{ + BSFEngine bsfEngine = mgr.loadScriptingEngine(getScriptLanguage()); + final String scriptFile = getFilename(); + if (scriptFile.length() == 0) { + bsfEngine.exec("[script]",0,0,getScript()); + } else {// we have a file, read and process it + try { + String script=FileUtils.readFileToString(new File(scriptFile)); + bsfEngine.exec(scriptFile,0,0,script); + } catch (IOException e) { + log.warn(e.getLocalizedMessage()); + throw new BSFException(BSFException.REASON_IO_ERROR,"Problem reading script file",e); + } + } + } + + protected Object evalFileOrScript(BSFManager mgr) throws BSFException{ + BSFEngine bsfEngine = mgr.loadScriptingEngine(getScriptLanguage()); + final String scriptFile = getFilename(); + if (scriptFile.length() == 0) { + return bsfEngine.eval("[script]",0,0,getScript()); + } else {// we have a file, read and process it + try { + String script=FileUtils.readFileToString(new File(scriptFile)); + return bsfEngine.eval(scriptFile,0,0,script); + } catch (IOException e) { + log.warn(e.getLocalizedMessage()); + throw new BSFException(BSFException.REASON_IO_ERROR,"Problem reading script file",e); + } + } + } + + public String getScriptLanguage() { + return scriptLanguage; + } + + public void setScriptLanguage(String s) { + scriptLanguage = s; + } +} diff --git a/src/core/org/apache/jmeter/util/BeanShellBeanInfoSupport.java b/src/core/org/apache/jmeter/util/BeanShellBeanInfoSupport.java new file mode 100644 index 00000000000..a5240399a53 --- /dev/null +++ b/src/core/org/apache/jmeter/util/BeanShellBeanInfoSupport.java @@ -0,0 +1,64 @@ +/* + * 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.util; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TextAreaEditor; + +/** + * Parent class to handle common GUI design + */ +public abstract class BeanShellBeanInfoSupport extends BeanInfoSupport { + + protected BeanShellBeanInfoSupport(Class beanClass) { + super(beanClass); + PropertyDescriptor p; + + p = property("resetInterpreter"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + + createPropertyGroup("resetGroup", new String[] { "resetInterpreter" }); + + p = property("parameters"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + createPropertyGroup("parameterGroup", new String[] { "parameters" }); + + p = property("filename"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + createPropertyGroup("filenameGroup", new String[] { "filename" }); + + p = property("script"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p.setPropertyEditorClass(TextAreaEditor.class); + + createPropertyGroup("scripting", new String[] { "script" }); + } + +} diff --git a/src/core/org/apache/jmeter/util/BeanShellClient.java b/src/core/org/apache/jmeter/util/BeanShellClient.java new file mode 100644 index 00000000000..3c24127ee4b --- /dev/null +++ b/src/core/org/apache/jmeter/util/BeanShellClient.java @@ -0,0 +1,115 @@ +/* + * 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.util; + +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; + +// N.B. Do not call any JMeter methods; the jar is standalone + + +/** + * Implements a client that can talk to the JMeter BeanShell server. + */ +public class BeanShellClient { + + private static final int MINARGS = 3; + + public static void main(String [] args) throws Exception{ + if (args.length < MINARGS){ + System.out.println("Please provide "+MINARGS+" or more arguments:"); + System.out.println("serverhost serverport filename [arg1 arg2 ...]"); + System.out.println("e.g. "); + System.out.println("localhost 9000 extras/remote.bsh apple blake 7"); + return; + } + String host=args[0]; + String portString = args[1]; + String file=args[2]; + + int port=Integer.parseInt(portString)+1;// convert to telnet port + + System.out.println("Connecting to BSH server on "+host+":"+portString); + + Socket sock = new Socket(host,port); + InputStream is = sock.getInputStream(); + SockRead sockRead = new SockRead(is); + sockRead.start(); + + OutputStream os = sock.getOutputStream(); + sendLine("bsh.prompt=\"\";",os);// Prompt is unnecessary + + sendLine("String [] args={",os); + for (int i=MINARGS; i -1) { + char c = (char) x; + System.out.print(c); + } + } catch (IOException e) { + // TODO Why empty block ? + } finally { + System.out.println("... disconnected from server."); + } + } + } +} diff --git a/src/core/org/apache/jmeter/util/BeanShellInterpreter.java b/src/core/org/apache/jmeter/util/BeanShellInterpreter.java new file mode 100644 index 00000000000..46bf316703e --- /dev/null +++ b/src/core/org/apache/jmeter/util/BeanShellInterpreter.java @@ -0,0 +1,224 @@ +/* + * 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.util; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +/** + * BeanShell setup function - encapsulates all the access to the BeanShell + * Interpreter in a single class. + * + * The class uses dynamic class loading to access BeanShell, which means that + * all the source files can be built without needing access to the bsh jar. + * + * If the beanshell jar is not present at run-time, an error will be logged + * + */ + +public class BeanShellInterpreter { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Method bshGet; + + private static final Method bshSet; + + private static final Method bshEval; + + private static final Method bshSource; + + private static final Class bshClass; + + private static final String BSH_INTERPRETER = "bsh.Interpreter"; //$NON-NLS-1$ + + static { + // Temporary copies, so can set the final ones + Method get = null, eval = null, set = null, source = null; + Class clazz = null; + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try { + clazz = loader.loadClass(BSH_INTERPRETER); + Class string = String.class; + Class object = Object.class; + + get = clazz.getMethod("get", //$NON-NLS-1$ + new Class[] { string }); + eval = clazz.getMethod("eval", //$NON-NLS-1$ + new Class[] { string }); + set = clazz.getMethod("set", //$NON-NLS-1$ + new Class[] { string, object }); + source = clazz.getMethod("source", //$NON-NLS-1$ + new Class[] { string }); + } catch (ClassNotFoundException e) { + log.error("Beanshell Interpreter not found"); + } catch (SecurityException e) { + log.error("Beanshell Interpreter not found", e); + } catch (NoSuchMethodException e) { + log.error("Beanshell Interpreter not found", e); + } finally { + bshEval = eval; + bshGet = get; + bshSet = set; + bshSource = source; + bshClass = clazz; + } + } + + // This class is not serialised + private Object bshInstance = null; // The interpreter instance for this class + + private final String initFile; // Script file to initialize the Interpreter with + + private final Logger logger; // Logger to use during initialization and script run + + public BeanShellInterpreter() throws ClassNotFoundException { + this(null, null); + } + + /** + * + * @param init initialisation file + * @param _log logger to pass to interpreter + * @throws ClassNotFoundException when beanshell can not be instantiated + */ + public BeanShellInterpreter(String init, Logger _log) throws ClassNotFoundException { + initFile = init; + logger = _log; + init(); + } + + // Called from ctor, so must be private (or final, but it does not seem useful elsewhere) + private void init() throws ClassNotFoundException { + if (bshClass == null) { + throw new ClassNotFoundException(BSH_INTERPRETER); + } + try { + bshInstance = bshClass.newInstance(); + } catch (InstantiationException e) { + log.error("Can't instantiate BeanShell", e); + throw new ClassNotFoundException("Can't instantiate BeanShell", e); + } catch (IllegalAccessException e) { + log.error("Can't instantiate BeanShell", e); + throw new ClassNotFoundException("Can't instantiate BeanShell", e); + } + if (logger != null) {// Do this before starting the script + try { + set("log", logger);//$NON-NLS-1$ + } catch (JMeterException e) { + log.warn("Can't set logger variable", e); + } + } + if (initFile != null && initFile.length() > 0) { + String fileToUse=initFile; + // Check file so we can distinguish file error from script error + File in = new File(fileToUse); + if (!in.exists()){// Cannot find the file locally, so try the bin directory + fileToUse=JMeterUtils.getJMeterHome() + +File.separator+"bin" // $NON-NLS-1$ + +File.separator+initFile; + in = new File(fileToUse); + if (!in.exists()) { + log.warn("Cannot find init file: "+initFile); + } + } + if (!in.canRead()) { + log.warn("Cannot read init file: "+fileToUse); + } + try { + source(fileToUse); + } catch (JMeterException e) { + log.warn("Cannot source init file: "+fileToUse,e); + } + } + } + + /** + * Resets the BeanShell interpreter. + * + * @throws ClassNotFoundException if interpreter cannot be instantiated + */ + public void reset() throws ClassNotFoundException { + init(); + } + + private Object bshInvoke(Method m, Object[] o, boolean shouldLog) throws JMeterException { + Object r = null; + final String errorString = "Error invoking bsh method: "; + try { + r = m.invoke(bshInstance, o); + } catch (IllegalArgumentException e) { // Programming error + final String message = errorString + m.getName(); + log.error(message); + throw new JMeterError(message, e); + } catch (IllegalAccessException e) { // Also programming error + final String message = errorString + m.getName(); + log.error(message); + throw new JMeterError(message, e); + } catch (InvocationTargetException e) { // Can occur at run-time + // could be caused by the bsh Exceptions: + // EvalError, ParseException or TargetError + String message = errorString + m.getName(); + Throwable cause = e.getCause(); + if (cause != null) { + message += "\t" + cause.getLocalizedMessage(); + } + + if (shouldLog) { + log.error(message); + } + throw new JMeterException(message, e); + } + return r; + } + + public Object eval(String s) throws JMeterException { + return bshInvoke(bshEval, new Object[] { s }, true); + } + + public Object evalNoLog(String s) throws JMeterException { + return bshInvoke(bshEval, new Object[] { s }, false); + } + + public Object set(String s, Object o) throws JMeterException { + return bshInvoke(bshSet, new Object[] { s, o }, true); + } + + public Object set(String s, boolean b) throws JMeterException { + return bshInvoke(bshSet, new Object[] { s, Boolean.valueOf(b) }, true); + } + + public Object source(String s) throws JMeterException { + return bshInvoke(bshSource, new Object[] { s }, true); + } + + public Object get(String s) throws JMeterException { + return bshInvoke(bshGet, new Object[] { s }, true); + } + + // For use by Unit Tests + public static boolean isInterpreterPresent(){ + return bshClass != null; + } +} diff --git a/src/core/org/apache/jmeter/util/BeanShellServer.java b/src/core/org/apache/jmeter/util/BeanShellServer.java new file mode 100644 index 00000000000..f81be4b70b2 --- /dev/null +++ b/src/core/org/apache/jmeter/util/BeanShellServer.java @@ -0,0 +1,111 @@ +/* + * 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.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements a BeanShell server to allow access to JMeter variables and + * methods. + * + * To enable, define the JMeter property: beanshell.server.port (see + * JMeter.java) beanshell.server.file (optional, startup file) + * + */ +public class BeanShellServer implements Runnable { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final int serverport; + + private final String serverfile; + + /** + * Constructor which sets the port for this server and the path to an + * optional init file + * + * @param port + * the port for the server to use + * @param file + * the path to an init file, or an empty string, if no init file + * should be used + */ + public BeanShellServer(int port, String file) { + super(); + serverfile = file;// can be the empty string + serverport = port; + } + + // For use by the server script + static String getprop(String s) { + return JMeterUtils.getPropDefault(s, s); + } + + // For use by the server script + static void setprop(String s, String v) { + JMeterUtils.getJMeterProperties().setProperty(s, v); + } + + @Override + public void run() { + + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + + try { + Class Interpreter = loader.loadClass("bsh.Interpreter");//$NON-NLS-1$ + Object instance = Interpreter.newInstance(); + Class string = String.class; + Class object = Object.class; + + Method eval = Interpreter.getMethod("eval", new Class[] { string });//$NON-NLS-1$ + Method setObj = Interpreter.getMethod("set", new Class[] { string, object });//$NON-NLS-1$ + Method setInt = Interpreter.getMethod("set", new Class[] { string, int.class });//$NON-NLS-1$ + Method source = Interpreter.getMethod("source", new Class[] { string });//$NON-NLS-1$ + + setObj.invoke(instance, new Object[] { "t", this });//$NON-NLS-1$ + setInt.invoke(instance, new Object[] { "portnum", Integer.valueOf(serverport) });//$NON-NLS-1$ + + if (serverfile.length() > 0) { + try { + source.invoke(instance, new Object[] { serverfile }); + } catch (InvocationTargetException e1) { + log.warn("Could not source " + serverfile); + Throwable t= e1.getCause(); + if (t != null) { + log.warn(t.toString()); + if(t instanceof Error) { + throw (Error)t; + } + } + } + } + eval.invoke(instance, new Object[] { "setAccessibility(true);" });//$NON-NLS-1$ + eval.invoke(instance, new Object[] { "server(portnum);" });//$NON-NLS-1$ + + } catch (ClassNotFoundException e) { + log.error("Beanshell Interpreter not found"); + } catch (Exception e) { + log.error("Problem starting BeanShell server ", e); + } + } +} diff --git a/src/core/org/apache/jmeter/util/BeanShellTestElement.java b/src/core/org/apache/jmeter/util/BeanShellTestElement.java new file mode 100644 index 00000000000..b42275f3399 --- /dev/null +++ b/src/core/org/apache/jmeter/util/BeanShellTestElement.java @@ -0,0 +1,281 @@ +/* + * 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.util; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public abstract class BeanShellTestElement extends AbstractTestElement + implements Serializable, Cloneable, ThreadListener, TestStateListener +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + //++ For TestBean implementations only + private String parameters; // passed to file or script + + private String filename; // file to source (overrides script) + + private String script; // script (if file not provided) + + private boolean resetInterpreter = false; + //-- For TestBean implementations only + + + private transient BeanShellInterpreter bshInterpreter = null; + + private transient boolean hasInitFile = false; + + public BeanShellTestElement() { + super(); + init(); + } + + protected abstract String getInitFileProperty(); + + /** + * Get the interpreter and set up standard script variables. + *

+ * Sets the following script variables: + *

    + *
  • ctx
  • + *
  • Label
  • + *
  • prev
  • + *
  • props
  • + *
  • vars
  • + *
+ * @return the interpreter + */ + protected BeanShellInterpreter getBeanShellInterpreter() { + if (isResetInterpreter()) { + try { + bshInterpreter.reset(); + } catch (ClassNotFoundException e) { + log.error("Cannot reset BeanShell: "+e.toString()); + } + } + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + try { + bshInterpreter.set("ctx", jmctx);//$NON-NLS-1$ + bshInterpreter.set("Label", getName()); //$NON-NLS-1$ + bshInterpreter.set("prev", jmctx.getPreviousResult());//$NON-NLS-1$ + bshInterpreter.set("props", JMeterUtils.getJMeterProperties()); + bshInterpreter.set("vars", vars);//$NON-NLS-1$ + } catch (JMeterException e) { + log.warn("Problem setting one or more BeanShell variables "+e); + } + return bshInterpreter; + } + + private void init() { + parameters=""; // ensure variables are not null + filename=""; + script=""; + try { + String initFileName = JMeterUtils.getProperty(getInitFileProperty()); + hasInitFile = initFileName != null; + bshInterpreter = new BeanShellInterpreter(initFileName, log); + } catch (ClassNotFoundException e) { + log.error("Cannot find BeanShell: "+e.toString()); + } + } + + protected Object readResolve() { + init(); + return this; + } + + @Override + public Object clone() { + BeanShellTestElement o = (BeanShellTestElement) super.clone(); + o.init(); + return o; + } + + /** + * Process the file or script from the test element. + *

+ * Sets the following script variables: + *

    + *
  • FileName
  • + *
  • Parameters
  • + *
  • bsh.args
  • + *
+ * @param bsh the interpreter, not {@code null} + * @return the result of the script, may be {@code null} + * + * @throws JMeterException when working with the bsh fails + */ + protected Object processFileOrScript(BeanShellInterpreter bsh) throws JMeterException{ + String fileName = getFilename(); + String params = getParameters(); + + bsh.set("FileName", fileName);//$NON-NLS-1$ + // Set params as a single line + bsh.set("Parameters", params); // $NON-NLS-1$ + // and set as an array + bsh.set("bsh.args",//$NON-NLS-1$ + JOrphanUtils.split(params, " "));//$NON-NLS-1$ + + if (fileName.length() == 0) { + return bsh.eval(getScript()); + } + return bsh.source(fileName); + } + + /** + * Return the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @return the script to execute + */ + public String getScript(){ + return script; + } + + /** + * Set the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @param s the script to execute (may be blank) + */ + public void setScript(String s){ + script=s; + } + + @Override + public void threadStarted() { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.evalNoLog("threadStarted()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + @Override + public void threadFinished() { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.evalNoLog("threadFinished()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + @Override + public void testEnded() { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.evalNoLog("testEnded()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + @Override + public void testEnded(String host) { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.eval((new StringBuilder("testEnded(\"")) // $NON-NLS-1$ + .append(host) + .append("\")") // $NON-NLS-1$ + .toString()); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + @Override + public void testStarted() { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.evalNoLog("testStarted()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + @Override + public void testStarted(String host) { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.eval((new StringBuilder("testStarted(\"")) // $NON-NLS-1$ + .append(host) + .append("\")") // $NON-NLS-1$ + .toString()); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + // Overridden by non-TestBean implementations to return the property value instead + public String getParameters() { + return parameters; + } + + public void setParameters(String s) { + parameters = s; + } + + // Overridden by non-TestBean implementations to return the property value instead + public String getFilename() { + return filename; + } + + public void setFilename(String s) { + filename = s; + } + + public boolean isResetInterpreter() { + return resetInterpreter; + } + + public void setResetInterpreter(boolean b) { + resetInterpreter = b; + } +} diff --git a/src/core/org/apache/jmeter/util/CPSPauser.java b/src/core/org/apache/jmeter/util/CPSPauser.java new file mode 100644 index 00000000000..a6067a9a491 --- /dev/null +++ b/src/core/org/apache/jmeter/util/CPSPauser.java @@ -0,0 +1,60 @@ +/* + * 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.util; + +/** + * + * Generate appropriate pauses for a given CPS (characters per second) + */ +public class CPSPauser{ + private final int CPS; // Characters per second to emulate + + // Conversions for milli and nano seconds + private static final int MS_PER_SEC = 1000; + private static final int NS_PER_SEC = 1000000000; + private static final int NS_PER_MS = NS_PER_SEC/MS_PER_SEC; + + /** + * Create a pauser with the appropriate speed settings. + * + * @param cps CPS to emulate + */ + public CPSPauser(int cps){ + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + CPS=cps; + } + + /** + * Pause for an appropriate time according to the number of bytes being transferred. + * + * @param bytes number of bytes being transferred + */ + public void pause(int bytes){ + long sleepMS = (bytes*MS_PER_SEC)/CPS; + int sleepNS = ((bytes*MS_PER_SEC)/CPS) % NS_PER_MS; + try { + if(sleepMS>0 || sleepNS>0) { + Thread.sleep(sleepMS,sleepNS); + } + } catch (InterruptedException ignored) { + } + } +} diff --git a/src/core/org/apache/jmeter/util/Calculator.java b/src/core/org/apache/jmeter/util/Calculator.java new file mode 100644 index 00000000000..e13948d2c3f --- /dev/null +++ b/src/core/org/apache/jmeter/util/Calculator.java @@ -0,0 +1,237 @@ +/* + * 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.util; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * Class to calculate various items that don't require all previous results to be saved: + *
    + *
  • mean = average
  • + *
  • standard deviation
  • + *
  • minimum
  • + *
  • maximum
  • + *
+ */ +public class Calculator { + + private double sum = 0; + + private double sumOfSquares = 0; + + private double mean = 0; + + private double deviation = 0; + + private int count = 0; + + private long bytes = 0; + + private long maximum = Long.MIN_VALUE; + + private long minimum = Long.MAX_VALUE; + + private int errors = 0; + + private final String label; + + public Calculator() { + this(""); + } + + public Calculator(String label) { + this.label = label; + } + + public void clear() { + maximum = Long.MIN_VALUE; + minimum = Long.MAX_VALUE; + sum = 0; + sumOfSquares = 0; + mean = 0; + deviation = 0; + count = 0; + } + + /** + * Add the value for a single sample. + * + * @param newValue the value for the new sample + * + * @deprecated Use {@link #addSample(SampleResult)} instead + */ + @Deprecated + public void addValue(long newValue) { + addValue(newValue, 1); + } + + /** + * Add the value for (possibly multiple) samples. + * Updates the count, sum, min, max, sumOfSqaures, mean and deviation. + * + * @param newValue the total value for all the samples. + * @param sampleCount number of samples included in the value + */ + private void addValue(long newValue, int sampleCount) { + count += sampleCount; + double currentVal = newValue; + sum += currentVal; + if (sampleCount > 1){ + minimum=Math.min(newValue/sampleCount, minimum); + maximum=Math.max(newValue/sampleCount, maximum); + // For n values in an aggregate sample the average value = (val/n) + // So need to add n * (val/n) * (val/n) = val * val / n + sumOfSquares += (currentVal * currentVal) / (sampleCount); + } else { // no point dividing by 1 + minimum=Math.min(newValue, minimum); + maximum=Math.max(newValue, maximum); + sumOfSquares += currentVal * currentVal; + } + // Calculate each time, as likely to be called for each add + mean = sum / count; + deviation = Math.sqrt((sumOfSquares / count) - (mean * mean)); + } + + + public void addBytes(long newValue) { + bytes += newValue; + } + + private long startTime = 0; + private long elapsedTime = 0; + + /** + * Add details for a sample result, which may consist of multiple samples. + * Updates the number of bytes read, error count, startTime and elapsedTime + * @param res the sample result; might represent multiple values + */ + public void addSample(SampleResult res) { + addBytes(res.getBytes()); + addValue(res.getTime(),res.getSampleCount()); + errors+=res.getErrorCount(); // account for multiple samples + if (startTime == 0){ // not yet intialised + startTime=res.getStartTime(); + } else { + startTime = Math.min(startTime, res.getStartTime()); + } + elapsedTime = Math.max(elapsedTime, res.getEndTime()-startTime); + } + + + public long getTotalBytes() { + return bytes; + } + + + public double getMean() { + return mean; + } + + public Number getMeanAsNumber() { + return Long.valueOf((long) mean); + } + + public double getStandardDeviation() { + return deviation; + } + + public long getMin() { + return minimum; + } + + public long getMax() { + return maximum; + } + + public int getCount() { + return count; + } + + public String getLabel() { + return label; + } + + /** + * Returns the raw double value of the percentage of samples with errors + * that were recorded. (Between 0.0 and 1.0) + * + * @return the raw double value of the percentage of samples with errors + * that were recorded. + */ + public double getErrorPercentage() { + double rval = 0.0; + + if (count == 0) { + return (rval); + } + rval = (double) errors / (double) count; + return (rval); + } + + /** + * Returns the throughput associated to this sampler in requests per second. + * May be slightly skewed because it takes the timestamps of the first and + * last samples as the total time passed, and the test may actually have + * started before that start time and ended after that end time. + * + * @return throughput associated to this sampler in requests per second + */ + public double getRate() { + if (elapsedTime == 0) { + return 0.0; + } + + return ((double) count / (double) elapsedTime ) * 1000; + } + + /** + * calculates the average page size, which means divide the bytes by number + * of samples. + * + * @return average page size in bytes + */ + public double getAvgPageBytes() { + if (count > 0 && bytes > 0) { + return (double) bytes / count; + } + return 0.0; + } + + /** + * Throughput in bytes / second + * + * @return throughput in bytes/second + */ + public double getBytesPerSecond() { + if (elapsedTime > 0) { + return bytes / ((double) elapsedTime / 1000); // 1000 = millisecs/sec + } + return 0.0; + } + + /** + * Throughput in kilobytes / second + * + * @return Throughput in kilobytes / second + */ + public double getKBPerSecond() { + return getBytesPerSecond() / 1024; // 1024=bytes per kb + } + +} diff --git a/src/core/org/apache/jmeter/util/ColorHelper.java b/src/core/org/apache/jmeter/util/ColorHelper.java new file mode 100644 index 00000000000..96965b8f7a1 --- /dev/null +++ b/src/core/org/apache/jmeter/util/ColorHelper.java @@ -0,0 +1,66 @@ +/* + * 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.util; + +import java.awt.Color; + +/** + * This class contains the static utility methods to manipulate colors. + * + * @version $Revision$ + */ +public final class ColorHelper { + /** + * Private constructor to prevent instantiation. + */ + private ColorHelper() { + } + + /** + * Given the Color, get the red, green and blue components. + * Increment the lowest of the components by the indicated increment value. + * If all the components are the same value increment in the order of red, + * green and blue. + * + * @param col + * {@link Color} to start with + * @param inc + * value to increment the color components + * @return the color after change + */ + public static Color changeColorCyclicIncrement(Color col, int inc) { + int red = col.getRed(); + int green = col.getGreen(); + int blue = col.getBlue(); + int temp1 = Math.min(red, green); + int temp2 = Math.min(temp1, blue); + // now temp2 has the lowest of the three components + if (red == temp2) { + red += inc; + red %= 256; + } else if (green == temp2) { + green += inc; + green %= 256; + } else if (blue == temp2) { + blue += inc; + blue %= 256; + } + return new Color(red, green, blue); + } +} diff --git a/src/core/org/apache/jmeter/util/CustomX509TrustManager.java b/src/core/org/apache/jmeter/util/CustomX509TrustManager.java new file mode 100644 index 00000000000..e6b93397118 --- /dev/null +++ b/src/core/org/apache/jmeter/util/CustomX509TrustManager.java @@ -0,0 +1,103 @@ +/* + * 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.util; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Custom TrustManager ignores all certificate errors + * + * TODO: implement conditional checking and logging + * + * (Derived from AuthSSLX509TrustManager in HttpClient contrib directory) + */ + +public class CustomX509TrustManager implements X509TrustManager +{ + private final X509TrustManager defaultTrustManager; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public CustomX509TrustManager(final X509TrustManager defaultTrustManager) { + super(); + if (defaultTrustManager == null) { + throw new IllegalArgumentException("Trust manager may not be null"); + } + this.defaultTrustManager = defaultTrustManager; + } + + /** + * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],String) + */ + @Override + public void checkClientTrusted(X509Certificate[] certificates,String authType) throws CertificateException { + if (certificates != null && log.isDebugEnabled()) { + for (int c = 0; c < certificates.length; c++) { + X509Certificate cert = certificates[c]; + log.debug(" Client certificate " + (c + 1) + ":"); + log.debug(" Subject DN: " + cert.getSubjectDN()); + log.debug(" Signature Algorithm: " + cert.getSigAlgName()); + log.debug(" Valid from: " + cert.getNotBefore() ); + log.debug(" Valid until: " + cert.getNotAfter()); + log.debug(" Issuer: " + cert.getIssuerDN()); + } + } +// try { +// defaultTrustManager.checkClientTrusted(certificates,authType); +// } catch (CertificateException e){ +// log.warn("Ignoring failed Client trust check: "+e.getMessage()); +// } + } + + /** + * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],String) + */ + @Override + public void checkServerTrusted(X509Certificate[] certificates,String authType) throws CertificateException { + if (certificates != null && log.isDebugEnabled()) { + for (int c = 0; c < certificates.length; c++) { + X509Certificate cert = certificates[c]; + log.debug(" Server certificate " + (c + 1) + ":"); + log.debug(" Subject DN: " + cert.getSubjectDN()); + log.debug(" Signature Algorithm: " + cert.getSigAlgName()); + log.debug(" Valid from: " + cert.getNotBefore() ); + log.debug(" Valid until: " + cert.getNotAfter()); + log.debug(" Issuer: " + cert.getIssuerDN()); + } + } +// try{ +// defaultTrustManager.checkServerTrusted(certificates,authType); +// } catch (CertificateException e){ +// log.warn("Ignoring failed Server trust check: "+e.getMessage()); +// } + } + + /** + * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() + */ + @Override + public X509Certificate[] getAcceptedIssuers() { + return this.defaultTrustManager.getAcceptedIssuers(); + } +} diff --git a/src/core/org/apache/jmeter/util/Document.java b/src/core/org/apache/jmeter/util/Document.java new file mode 100644 index 00000000000..aaa02293cd4 --- /dev/null +++ b/src/core/org/apache/jmeter/util/Document.java @@ -0,0 +1,86 @@ +/* + * 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.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.parser.AutoDetectParser; +import org.apache.tika.parser.ParseContext; +import org.apache.tika.parser.Parser; +import org.apache.tika.sax.BodyContentHandler; +import org.xml.sax.ContentHandler; + +public class Document { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Maximum size to convert a document to text (default 10Mb) + private static final int MAX_DOCUMENT_SIZE = + JMeterUtils.getPropDefault("document.max_size", 10 * 1024 * 1024); // $NON-NLS-1$ + + /** + * Convert to text plain a lot of kind of document (like odt, ods, odp, + * doc(x), xls(x), ppt(x), pdf, mp3, mp4, etc.) with Apache Tika + * + * @param document + * binary representation of the document + * @return text from document without format + */ + public static String getTextFromDocument(byte[] document) { + String errMissingTika = JMeterUtils.getResString("view_results_response_missing_tika"); // $NON-NLS-1$ + String response = errMissingTika; + Parser parser = new AutoDetectParser(); + ContentHandler handler = new BodyContentHandler(MAX_DOCUMENT_SIZE > 0 ? MAX_DOCUMENT_SIZE : -1); // -1 to disable the write limit + Metadata metadata = new Metadata(); + ParseContext context = new ParseContext(); + InputStream stream = new ByteArrayInputStream(document); // open the stream + try { + parser.parse(stream, handler, metadata, context); + response = handler.toString(); + } catch (Exception e) { + response = e.toString(); + log.warn("Error document parsing:", e); + } catch (NoClassDefFoundError e) { + // put a warning if tika-app.jar missing (or some dependencies in only tika-core|parsers packages are using) + if (!System.getProperty("java.class.path").contains("tika-app")) { // $NON-NLS-1$ $NON-NLS-2$ + log.warn(errMissingTika); + } else { + log.warn(errMissingTika, e); + } + } finally { + try { + stream.close(); // close the stream + } catch (IOException ioe) { + log.warn("Error closing document stream", ioe);// $NON-NLS-1$ + } + } + + if (response.length() == 0 && document.length > 0) { + log.warn("Probably: " + errMissingTika);// $NON-NLS-1$ + response = errMissingTika; + } + return response; + } +} diff --git a/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java b/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java new file mode 100644 index 00000000000..65afb9d6961 --- /dev/null +++ b/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java @@ -0,0 +1,267 @@ +/* + * 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.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Derived from EasySSLProtocolFactory + * + * Used by JsseSSLManager to set up the Commons HttpClient and Java https socket handling + */ + +public class HttpSSLProtocolSocketFactory + extends SSLSocketFactory // for java sockets + implements SecureProtocolSocketFactory { // for Commons Httpclient sockets + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final JsseSSLManager sslManager; + + private final int CPS; // Characters per second to emulate + + public HttpSSLProtocolSocketFactory(JsseSSLManager sslManager) { + this(sslManager, 0); + } + + public HttpSSLProtocolSocketFactory(JsseSSLManager sslManager, int cps) { + super(); + this.sslManager = sslManager; + CPS=cps; + } + + private static final String protocolList = + JMeterUtils.getPropDefault("https.socket.protocols", ""); // $NON-NLS-1$ $NON-NLS-2$ + + static { + if (protocolList.length()>0){ + log.info("Using protocol list: "+protocolList); + } + } + + private static final String[] protocols = protocolList.split(" "); // $NON-NLS-1$ + + private void setSocket(Socket socket){ + if (!(socket instanceof SSLSocket)) { + throw new IllegalArgumentException("Expected SSLSocket"); + } + SSLSocket sock = (SSLSocket) socket; + if (protocolList.length() > 0) { + try { + sock.setEnabledProtocols(protocols); + } catch (IllegalArgumentException e) { + log.warn("Could not set protocol list: " + protocolList + "."); + log.warn("Valid protocols are: " + join(sock.getSupportedProtocols())); + } + } + } + + private String join(String[] strings) { + StringBuilder sb = new StringBuilder(); + for (int i=0;i0) { + sb.append(" "); + } + sb.append(strings[i]); + } + return sb.toString(); + } + + private SSLSocketFactory getSSLSocketFactory() throws IOException { + try { + SSLContext sslContext = this.sslManager.getContext(); + return sslContext.getSocketFactory(); + } catch (GeneralSecurityException ex) { + throw new IOException("Rethrown as IOE", ex); + } + } + + /* + * Wraps the socket in a slow SSL socket if necessary + */ + private Socket wrapSocket(Socket sock){ + if (CPS>0) { + return new SlowSSLSocket((SSLSocket) sock, CPS); + } + return sock; + } + + /** + * Attempts to get a new socket connection to the given host within the given time limit. + * + * @param host the host name/IP + * @param port the port on the host + * @param localAddress the local host name/IP to bind the socket to + * @param localPort the port on the local machine + * @param params {@link HttpConnectionParams Http connection parameters} + * + * @return Socket a new socket + * + * @throws IOException if an I/O error occurs while creating the socket + * @throws UnknownHostException if the IP address of the host cannot be + * determined + */ + @Override + public Socket createSocket( + final String host, + final int port, + final InetAddress localAddress, + final int localPort, + final HttpConnectionParams params + ) throws IOException, UnknownHostException, ConnectTimeoutException { + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null"); + } + int timeout = params.getConnectionTimeout(); + + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket socket; + if (timeout == 0) { + socket = sslfac.createSocket(host, port, localAddress, localPort); + } else { + socket = sslfac.createSocket(); + SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); + SocketAddress remoteaddr = new InetSocketAddress(host, port); + socket.bind(localaddr); + socket.connect(remoteaddr, timeout); + } + setSocket(socket); + return wrapSocket(socket); + } + + /** + * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int) + */ + @Override + public Socket createSocket(String host, int port) + throws IOException, UnknownHostException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock = sslfac.createSocket( + host, + port + ); + setSocket(sock); + return wrapSocket(sock); + } + + /** + * @see javax.net.SocketFactory#createSocket() + */ + @Override + public Socket createSocket() throws IOException, UnknownHostException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock = sslfac.createSocket(); + setSocket(sock); + return wrapSocket(sock); + } + + /** + * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean) + */ + @Override + public Socket createSocket( + Socket socket, + String host, + int port, + boolean autoClose) + throws IOException, UnknownHostException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock = sslfac.createSocket( + socket, + host, + port, + autoClose + ); + setSocket(sock); + return wrapSocket(sock); + } + + /** + * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int) + */ + @Override + public Socket createSocket( + String host, + int port, + InetAddress clientHost, + int clientPort) + throws IOException, UnknownHostException { + + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock = sslfac.createSocket( + host, + port, + clientHost, + clientPort + ); + setSocket(sock); + return wrapSocket(sock); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock=sslfac.createSocket(host,port); + setSocket(sock); + return wrapSocket(sock); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock=sslfac.createSocket(address, port, localAddress, localPort); + setSocket(sock); + return wrapSocket(sock); + } + + @Override + public String[] getDefaultCipherSuites() { + try { + SSLSocketFactory sslfac = getSSLSocketFactory(); + return sslfac.getDefaultCipherSuites(); + } catch (IOException ex) { + return new String[] {}; + } + } + + @Override + public String[] getSupportedCipherSuites() { + try { + SSLSocketFactory sslfac = getSSLSocketFactory(); + return sslfac.getSupportedCipherSuites(); + } catch (IOException ex) { + return new String[] {}; + } + } +} diff --git a/src/core/org/apache/jmeter/util/JMeterTreeNodeTransferable.java b/src/core/org/apache/jmeter/util/JMeterTreeNodeTransferable.java new file mode 100644 index 00000000000..ede55e74ee5 --- /dev/null +++ b/src/core/org/apache/jmeter/util/JMeterTreeNodeTransferable.java @@ -0,0 +1,97 @@ +/* + * 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.util; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.jmeter.gui.tree.JMeterTreeNode; + +/** + * Simple implementation of a transferable for {@link JMeterTreeNode} arrays based on serialization. + * @since 2.9 + */ +public class JMeterTreeNodeTransferable implements Transferable { + + public final static DataFlavor JMETER_TREE_NODE_ARRAY_DATA_FLAVOR = new DataFlavor(JMeterTreeNode[].class, JMeterTreeNode[].class.getName()); + + private final static DataFlavor[] DATA_FLAVORS = new DataFlavor[]{JMETER_TREE_NODE_ARRAY_DATA_FLAVOR}; + + private byte[] data = null; + + @Override + public DataFlavor[] getTransferDataFlavors() { + return DATA_FLAVORS; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavor.match(JMETER_TREE_NODE_ARRAY_DATA_FLAVOR); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + if(!isDataFlavorSupported(flavor)) { + throw new UnsupportedFlavorException(flavor); + } + if(data != null) { + ObjectInput ois = null; + try { + ois = new ObjectInputStream(new ByteArrayInputStream(data)); + JMeterTreeNode[] nodes = (JMeterTreeNode[]) ois.readObject(); + return nodes; + } catch (ClassNotFoundException cnfe) { + throw new IOException("Failed to read object stream.", cnfe); + } finally { + if(ois != null) { + try { + ois.close(); + } catch (Exception e) { + // NOOP + } + } + } + } + return null; + } + + public void setTransferData(JMeterTreeNode[] nodes) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(bos); + oos.writeObject(nodes); + data = bos.toByteArray(); + } finally { + if(oos != null) { + try { + oos.close(); + } catch (Exception e) { + // NOOP + } + } + } + } +} diff --git a/src/core/org/apache/jmeter/util/JMeterUtils.java b/src/core/org/apache/jmeter/util/JMeterUtils.java new file mode 100644 index 00000000000..158d3d064ce --- /dev/null +++ b/src/core/org/apache/jmeter/util/JMeterUtils.java @@ -0,0 +1,1354 @@ +/* + * 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.util; + +import java.awt.Dimension; +import java.awt.HeadlessException; +import java.awt.event.ActionListener; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.Properties; +import java.util.Random; +import java.util.ResourceBundle; +import java.util.Vector; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.test.UnitTestManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.PatternCacheLRU; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.xml.sax.XMLReader; + +/** + * This class contains the static utility methods used by JMeter. + * + */ +public class JMeterUtils implements UnitTestManager { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Note: cannot use a static variable here, because that would be processed before the JMeter properties + // have been defined (Bug 52783) + private static class LazyPatternCacheHolder { + public static final PatternCacheLRU INSTANCE = new PatternCacheLRU( + getPropDefault("oro.patterncache.size",1000), // $NON-NLS-1$ + new Perl5Compiler()); + } + + private static final String EXPERT_MODE_PROPERTY = "jmeter.expertMode"; // $NON-NLS-1$ + + private static final String ENGLISH_LANGUAGE = Locale.ENGLISH.getLanguage(); + + private static volatile Properties appProperties; + + private static final Vector localeChangeListeners = new Vector(); + + private static volatile Locale locale; + + private static volatile ResourceBundle resources; + + // What host am I running on? + + //@GuardedBy("this") + private static String localHostIP = null; + //@GuardedBy("this") + private static String localHostName = null; + //@GuardedBy("this") + private static String localHostFullName = null; + + private static volatile boolean ignoreResorces = false; // Special flag for use in debugging resources + + private static final ThreadLocal localMatcher = new ThreadLocal() { + @Override + protected Perl5Matcher initialValue() { + return new Perl5Matcher(); + } + }; + + // Provide Random numbers to whomever wants one + private static final Random rand = new Random(); + + /** + * Gets Perl5Matcher for this thread. + * @return the {@link Perl5Matcher} for this thread + */ + public static Perl5Matcher getMatcher() { + return localMatcher.get(); + } + + /** + * This method is used by the init method to load the property file that may + * even reside in the user space, or in the classpath under + * org.apache.jmeter.jmeter.properties. + * + * The method also initialises logging and sets up the default Locale + * + * TODO - perhaps remove? + * [still used + * + * @param file + * the file to load + * @return the Properties from the file + * @see #getJMeterProperties() + * @see #loadJMeterProperties(String) + * @see #initLogging() + * @see #initLocale() + */ + public static Properties getProperties(String file) { + loadJMeterProperties(file); + initLogging(); + initLocale(); + return appProperties; + } + + /** + * Initialise JMeter logging + */ + public static void initLogging() { + LoggingManager.initializeLogging(appProperties); + } + + /** + * Initialise the JMeter Locale + */ + public static void initLocale() { + String loc = appProperties.getProperty("language"); // $NON-NLS-1$ + if (loc != null) { + String []parts = JOrphanUtils.split(loc,"_");// $NON-NLS-1$ + if (parts.length==2) { + setLocale(new Locale(parts[0], parts[1])); + } else { + setLocale(new Locale(loc, "")); // $NON-NLS-1$ + } + + } else { + setLocale(Locale.getDefault()); + } + } + + + /** + * Load the JMeter properties file; if not found, then + * default to "org/apache/jmeter/jmeter.properties" from the classpath + * + *

+ * c.f. loadProperties + * + * @param file Name of the file from which the JMeter properties should be loaded + */ + public static void loadJMeterProperties(String file) { + Properties p = new Properties(System.getProperties()); + InputStream is = null; + try { + File f = new File(file); + is = new FileInputStream(f); + p.load(is); + } catch (IOException e) { + try { + is = + ClassLoader.getSystemResourceAsStream("org/apache/jmeter/jmeter.properties"); // $NON-NLS-1$ + if (is == null) { + throw new RuntimeException("Could not read JMeter properties file:"+file); + } + p.load(is); + } catch (IOException ex) { + // JMeter.fail("Could not read internal resource. " + + // "Archive is broken."); + } + } finally { + JOrphanUtils.closeQuietly(is); + } + appProperties = p; + } + + /** + * This method loads a property file that may reside in the user space, or + * in the classpath + * + * @param file + * the file to load + * @return the Properties from the file, may be null (e.g. file not found) + */ + public static Properties loadProperties(String file) { + return loadProperties(file, null); + } + + /** + * This method loads a property file that may reside in the user space, or + * in the classpath + * + * @param file + * the file to load + * @param defaultProps a set of default properties + * @return the Properties from the file; if it could not be processed, the defaultProps are returned. + */ + public static Properties loadProperties(String file, Properties defaultProps) { + Properties p = new Properties(defaultProps); + InputStream is = null; + try { + File f = new File(file); + is = new FileInputStream(f); + p.load(is); + } catch (IOException e) { + try { + final URL resource = JMeterUtils.class.getClassLoader().getResource(file); + if (resource == null) { + log.warn("Cannot find " + file); + return defaultProps; + } + is = resource.openStream(); + if (is == null) { + log.warn("Cannot open " + file); + return defaultProps; + } + p.load(is); + } catch (IOException ex) { + log.warn("Error reading " + file + " " + ex.toString()); + return defaultProps; + } + } finally { + JOrphanUtils.closeQuietly(is); + } + return p; + } + + public static PatternCacheLRU getPatternCache() { + return LazyPatternCacheHolder.INSTANCE; + } + + /** + * Get a compiled expression from the pattern cache (READ_ONLY). + * + * @param expression regular expression to be looked up + * @return compiled pattern + * + * @throws MalformedCachePatternException (Runtime) + * This should be caught for expressions that may vary (e.g. user input) + * + */ + public static Pattern getPattern(String expression) throws MalformedCachePatternException { + return getPattern(expression, Perl5Compiler.READ_ONLY_MASK); + } + + /** + * Get a compiled expression from the pattern cache. + * + * @param expression RE + * @param options e.g. {@link Perl5Compiler#READ_ONLY_MASK READ_ONLY_MASK} + * @return compiled pattern + * + * @throws MalformedCachePatternException (Runtime) + * This should be caught for expressions that may vary (e.g. user input) + * + */ + public static Pattern getPattern(String expression, int options) throws MalformedCachePatternException { + return LazyPatternCacheHolder.INSTANCE.getPattern(expression, options); + } + + @Override + public void initializeProperties(String file) { + System.out.println("Initializing Properties: " + file); + getProperties(file); + } + + /** + * Convenience method for + * {@link ClassFinder#findClassesThatExtend(String[], Class[], boolean)} + * with the option to include inner classes in the search set to false + * and the path list is derived from JMeterUtils.getSearchPaths(). + * + * @param superClass - single class to search for + * @return List of Strings containing discovered class names. + * @throws IOException when the used {@link ClassFinder} throws one while searching for the class + */ + public static List findClassesThatExtend(Class superClass) + throws IOException { + return ClassFinder.findClassesThatExtend(getSearchPaths(), new Class[]{superClass}, false); + } + + /** + * Generate a list of paths to search. + * The output array always starts with + * JMETER_HOME/lib/ext + * and is followed by any paths obtained from the "search_paths" JMeter property. + * + * @return array of path strings + */ + public static String[] getSearchPaths() { + String p = JMeterUtils.getPropDefault("search_paths", null); // $NON-NLS-1$ + String[] result = new String[1]; + + if (p != null) { + String[] paths = p.split(";"); // $NON-NLS-1$ + result = new String[paths.length + 1]; + System.arraycopy(paths, 0, result, 1, paths.length); + } + result[0] = getJMeterHome() + "/lib/ext"; // $NON-NLS-1$ + return result; + } + + /** + * Provide random numbers + * + * @param r - + * the upper bound (exclusive) + * @return a random int + */ + public static int getRandomInt(int r) { + return rand.nextInt(r); + } + + /** + * Changes the current locale: re-reads resource strings and notifies + * listeners. + * + * @param loc - + * new locale + */ + public static void setLocale(Locale loc) { + log.info("Setting Locale to " + loc.toString()); + /* + * See bug 29920. getBundle() defaults to the property file for the + * default Locale before it defaults to the base property file, so we + * need to change the default Locale to ensure the base property file is + * found. + */ + Locale def = null; + boolean isDefault = false; // Are we the default language? + if (loc.getLanguage().equals(ENGLISH_LANGUAGE)) { + isDefault = true; + def = Locale.getDefault(); + // Don't change locale from en_GB to en + if (!def.getLanguage().equals(ENGLISH_LANGUAGE)) { + Locale.setDefault(Locale.ENGLISH); + } else { + def = null; // no need to reset Locale + } + } + if (loc.toString().equals("ignoreResources")){ // $NON-NLS-1$ + log.warn("Resource bundles will be ignored"); + ignoreResorces = true; + // Keep existing settings + } else { + ignoreResorces = false; + ResourceBundle resBund = ResourceBundle.getBundle("org.apache.jmeter.resources.messages", loc); // $NON-NLS-1$ + resources = resBund; + locale = loc; + final Locale resBundLocale = resBund.getLocale(); + if (isDefault || resBundLocale.equals(loc)) {// language change worked + // Check if we at least found the correct language: + } else if (resBundLocale.getLanguage().equals(loc.getLanguage())) { + log.info("Could not find resources for '"+loc.toString()+"', using '"+resBundLocale.toString()+"'"); + } else { + log.error("Could not find resources for '"+loc.toString()+"'"); + } + } + notifyLocaleChangeListeners(); + /* + * Reset Locale if necessary so other locales are properly handled + */ + if (def != null) { + Locale.setDefault(def); + } + } + + /** + * Gets the current locale. + * + * @return current locale + */ + public static Locale getLocale() { + return locale; + } + + public static void addLocaleChangeListener(LocaleChangeListener listener) { + localeChangeListeners.add(listener); + } + + public static void removeLocaleChangeListener(LocaleChangeListener listener) { + localeChangeListeners.remove(listener); + } + + /** + * Notify all listeners interested in locale changes. + * + */ + private static void notifyLocaleChangeListeners() { + LocaleChangeEvent event = new LocaleChangeEvent(JMeterUtils.class, locale); + @SuppressWarnings("unchecked") // clone will produce correct type + // TODO but why do we need to clone the list? + // ANS: to avoid possible ConcurrentUpdateException when unsubscribing + // Could perhaps avoid need to clone by using a modern concurrent list + Vector listeners = (Vector) localeChangeListeners.clone(); + for (LocaleChangeListener listener : listeners) { + listener.localeChanged(event); + } + } + + /** + * Gets the resource string for this key. + * + * If the resource is not found, a warning is logged + * + * @param key + * the key in the resource file + * @return the resource string if the key is found; otherwise, return + * "[res_key="+key+"]" + */ + public static String getResString(String key) { + return getResStringDefault(key, RES_KEY_PFX + key + "]"); // $NON-NLS-1$ + } + + /** + * Gets the resource string for this key in Locale. + * + * If the resource is not found, a warning is logged + * + * @param key + * the key in the resource file + * @param forcedLocale Force a particular locale + * @return the resource string if the key is found; otherwise, return + * "[res_key="+key+"]" + * @since 2.7 + */ + public static String getResString(String key, Locale forcedLocale) { + return getResStringDefault(key, RES_KEY_PFX + key + "]", // $NON-NLS-1$ + forcedLocale); + } + + public static final String RES_KEY_PFX = "[res_key="; // $NON-NLS-1$ + + /** + * Gets the resource string for this key. + * + * If the resource is not found, a warning is logged + * + * @param key + * the key in the resource file + * @param defaultValue - + * the default value + * + * @return the resource string if the key is found; otherwise, return the + * default + * @deprecated Only intended for use in development; use + * getResString(String) normally + */ + @Deprecated + public static String getResString(String key, String defaultValue) { + return getResStringDefault(key, defaultValue); + } + + /* + * Helper method to do the actual work of fetching resources; allows + * getResString(S,S) to be deprecated without affecting getResString(S); + */ + private static String getResStringDefault(String key, String defaultValue) { + return getResStringDefault(key, defaultValue, null); + } + /* + * Helper method to do the actual work of fetching resources; allows + * getResString(S,S) to be deprecated without affecting getResString(S); + */ + private static String getResStringDefault(String key, String defaultValue, Locale forcedLocale) { + if (key == null) { + return null; + } + // Resource keys cannot contain spaces, and are forced to lower case + String resKey = key.replace(' ', '_'); // $NON-NLS-1$ // $NON-NLS-2$ + resKey = resKey.toLowerCase(java.util.Locale.ENGLISH); + String resString = null; + try { + ResourceBundle bundle = resources; + if(forcedLocale != null) { + bundle = ResourceBundle.getBundle("org.apache.jmeter.resources.messages", forcedLocale); // $NON-NLS-1$ + } + if (bundle.containsKey(resKey)) { + resString = bundle.getString(resKey); + } else { + log.warn("ERROR! Resource string not found: [" + resKey + "]"); + resString = defaultValue; + } + if (ignoreResorces ){ // Special mode for debugging resource handling + return "["+key+"]"; + } + } catch (MissingResourceException mre) { + if (ignoreResorces ){ // Special mode for debugging resource handling + return "[?"+key+"?]"; + } + log.warn("ERROR! Resource string not found: [" + resKey + "]", mre); + resString = defaultValue; + } + return resString; + } + + /** + * To get I18N label from properties file + * + * @param key + * in messages.properties + * @return I18N label without (if exists) last colon ':' and spaces + */ + public static String getParsedLabel(String key) { + String value = JMeterUtils.getResString(key); + return value.replaceFirst("(?m)\\s*?:\\s*$", ""); // $NON-NLS-1$ $NON-NLS-2$ + } + + /** + * Get the locale name as a resource. + * Does not log an error if the resource does not exist. + * This is needed to support additional locales, as they won't be in existing messages files. + * + * @param locale name + * @return the locale display name as defined in the current Locale or the original string if not present + */ + public static String getLocaleString(String locale){ + // All keys in messages.properties are lowercase (historical reasons?) + String resKey = locale.toLowerCase(java.util.Locale.ENGLISH); + if (resources.containsKey(resKey)) { + return resources.getString(resKey); + } + return locale; + } + /** + * This gets the currently defined appProperties. It can only be called + * after the {@link #getProperties(String)} or {@link #loadJMeterProperties(String)} + * method has been called. + * + * @return The JMeterProperties value, + * may be null if {@link #loadJMeterProperties(String)} has not been called + * @see #getProperties(String) + * @see #loadJMeterProperties(String) + */ + public static Properties getJMeterProperties() { + return appProperties; + } + + /** + * This looks for the requested image in the classpath under + * org.apache.jmeter.images.<name> + * + * @param name + * Description of Parameter + * @return The Image value + */ + public static ImageIcon getImage(String name) { + try { + URL url = JMeterUtils.class.getClassLoader().getResource( + "org/apache/jmeter/images/" + name.trim()); + if(url != null) { + return new ImageIcon(url); // $NON-NLS-1$ + } else { + log.warn("no icon for " + name); + return null; + } + } catch (NoClassDefFoundError e) {// Can be returned by headless hosts + log.info("no icon for " + name + " " + e.getMessage()); + return null; + } catch (InternalError e) {// Can be returned by headless hosts + log.info("no icon for " + name + " " + e.getMessage()); + return null; + } + } + + /** + * This looks for the requested image in the classpath under + * org.apache.jmeter.images.<name>, and also sets the description + * of the image, which is useful if the icon is going to be placed + * on the clipboard. + * + * @param name + * the name of the image + * @param description + * the description of the image + * @return The Image value + */ + public static ImageIcon getImage(String name, String description) { + ImageIcon icon = getImage(name); + if(icon != null) { + icon.setDescription(description); + } + return icon; + } + + public static String getResourceFileAsText(String name) { + BufferedReader fileReader = null; + try { + String lineEnd = System.getProperty("line.separator"); // $NON-NLS-1$ + InputStream is = JMeterUtils.class.getClassLoader().getResourceAsStream(name); + if(is != null) { + fileReader = new BufferedReader(new InputStreamReader(is)); + StringBuilder text = new StringBuilder(); + String line = "NOTNULL"; // $NON-NLS-1$ + while (line != null) { + line = fileReader.readLine(); + if (line != null) { + text.append(line); + text.append(lineEnd); + } + } + // Done by finally block: fileReader.close(); + return text.toString(); + } else { + return ""; // $NON-NLS-1$ + } + } catch (IOException e) { + return ""; // $NON-NLS-1$ + } finally { + IOUtils.closeQuietly(fileReader); + } + } + + /** + * Creates the vector of Timers plugins. + * + * @param properties + * Description of Parameter + * @return The Timers value + */ + public static Vector getTimers(Properties properties) { + return instantiate(getVector(properties, "timer."), // $NON-NLS-1$ + "org.apache.jmeter.timers.Timer"); // $NON-NLS-1$ + } + + /** + * Creates the vector of visualizer plugins. + * + * @param properties + * Description of Parameter + * @return The Visualizers value + */ + public static Vector getVisualizers(Properties properties) { + return instantiate(getVector(properties, "visualizer."), // $NON-NLS-1$ + "org.apache.jmeter.visualizers.Visualizer"); // $NON-NLS-1$ + } + + /** + * Creates a vector of SampleController plugins. + * + * @param properties + * The properties with information about the samplers + * @return The Controllers value + */ + // TODO - does not appear to be called directly + public static Vector getControllers(Properties properties) { + String name = "controller."; // $NON-NLS-1$ + Vector v = new Vector(); + Enumeration names = properties.keys(); + while (names.hasMoreElements()) { + String prop = (String) names.nextElement(); + if (prop.startsWith(name)) { + Object o = instantiate(properties.getProperty(prop), + "org.apache.jmeter.control.SamplerController"); // $NON-NLS-1$ + v.addElement(o); + } + } + return v; + } + + /** + * Create a string of class names for a particular SamplerController + * + * @param properties + * The properties with info about the samples. + * @param name + * The name of the sampler controller. + * @return The TestSamples value + */ + public static String[] getTestSamples(Properties properties, String name) { + Vector vector = getVector(properties, name + ".testsample"); // $NON-NLS-1$ + return vector.toArray(new String[vector.size()]); + } + + /** + * Create an instance of an org.xml.sax.Parser based on the default props. + * + * @return The XMLParser value + */ + // TODO only called by UserParameterXMLParser.getXMLParameters which is a deprecated class + public static XMLReader getXMLParser() { + final String parserName = getPropDefault("xml.parser", // $NON-NLS-1$ + "org.apache.xerces.parsers.SAXParser"); // $NON-NLS-1$ + return (XMLReader) instantiate(parserName, + "org.xml.sax.XMLReader"); // $NON-NLS-1$ + } + + /** + * Creates the vector of alias strings. + *

+ * The properties will be filtered by all values starting with + * alias.. The matching entries will be used for the new + * {@link Hashtable} while the prefix alias. will be stripped + * of the keys. + * + * @param properties + * the input values + * @return The Alias value + */ + public static Hashtable getAlias(Properties properties) { + return getHashtable(properties, "alias."); // $NON-NLS-1$ + } + + /** + * Creates a vector of strings for all the properties that start with a + * common prefix. + * + * @param properties + * Description of Parameter + * @param name + * Description of Parameter + * @return The Vector value + */ + public static Vector getVector(Properties properties, String name) { + Vector v = new Vector(); + Enumeration names = properties.keys(); + while (names.hasMoreElements()) { + String prop = (String) names.nextElement(); + if (prop.startsWith(name)) { + v.addElement(properties.getProperty(prop)); + } + } + return v; + } + + /** + * Creates a table of strings for all the properties that start with a + * common prefix. + *

+ * So if you have {@link Properties} prop with two entries, say + *

    + *
  • this.test
  • + *
  • that.something
  • + *
+ * And would call this method with a prefix this, the + * result would be a new {@link Hashtable} with one entry, which key would + * be test. + * + * @param properties + * input to search + * @param prefix + * to match against properties + * @return a Hashtable where the keys are the original matching keys with + * the prefix removed + */ + public static Hashtable getHashtable(Properties properties, String prefix) { + Hashtable t = new Hashtable(); + Enumeration names = properties.keys(); + final int length = prefix.length(); + while (names.hasMoreElements()) { + String prop = (String) names.nextElement(); + if (prop.startsWith(prefix)) { + t.put(prop.substring(length), properties.getProperty(prop)); + } + } + return t; + } + + /** + * Get a int value with default if not present. + * + * @param propName + * the name of the property. + * @param defaultVal + * the default value. + * @return The PropDefault value + */ + public static int getPropDefault(String propName, int defaultVal) { + int ans; + try { + ans = Integer.parseInt(appProperties.getProperty(propName, Integer.toString(defaultVal)).trim()); + } catch (Exception e) { + log.warn("Unexpected value set for int property:'"+propName+"', defaulting to:"+defaultVal); + ans = defaultVal; + } + return ans; + } + + /** + * Get a boolean value with default if not present. + * + * @param propName + * the name of the property. + * @param defaultVal + * the default value. + * @return The PropDefault value + */ + public static boolean getPropDefault(String propName, boolean defaultVal) { + boolean ans; + try { + String strVal = appProperties.getProperty(propName, Boolean.toString(defaultVal)).trim(); + if (strVal.equalsIgnoreCase("true") || strVal.equalsIgnoreCase("t")) { // $NON-NLS-1$ // $NON-NLS-2$ + ans = true; + } else if (strVal.equalsIgnoreCase("false") || strVal.equalsIgnoreCase("f")) { // $NON-NLS-1$ // $NON-NLS-2$ + ans = false; + } else { + ans = Integer.parseInt(strVal) == 1; + } + } catch (Exception e) { + log.warn("Unexpected value set for boolean property:'"+propName+"', defaulting to:"+defaultVal); + ans = defaultVal; + } + return ans; + } + + /** + * Get a long value with default if not present. + * + * @param propName + * the name of the property. + * @param defaultVal + * the default value. + * @return The PropDefault value + */ + public static long getPropDefault(String propName, long defaultVal) { + long ans; + try { + ans = Long.parseLong(appProperties.getProperty(propName, Long.toString(defaultVal)).trim()); + } catch (Exception e) { + log.warn("Unexpected value set for long property:'"+propName+"', defaulting to:"+defaultVal); + ans = defaultVal; + } + return ans; + } + + /** + * Get a String value with default if not present. + * + * @param propName + * the name of the property. + * @param defaultVal + * the default value. + * @return The PropDefault value + */ + public static String getPropDefault(String propName, String defaultVal) { + String ans = defaultVal; + try + { + String value = appProperties.getProperty(propName, defaultVal); + if(value != null) { + ans = value.trim(); + } + } catch (Exception e) { + // TODO Can this happen ? + ans = defaultVal; + } + return ans; + } + + /** + * Get the value of a JMeter property. + * + * @param propName + * the name of the property. + * @return the value of the JMeter property, or null if not defined + */ + public static String getProperty(String propName) { + String ans = null; + try { + ans = appProperties.getProperty(propName); + } catch (Exception e) { + // TODO Can this happen ? + ans = null; + } + return ans; + } + + /** + * Set a String value + * + * @param propName + * the name of the property. + * @param propValue + * the value of the property + * @return the previous value of the property + */ + public static Object setProperty(String propName, String propValue) { + return appProperties.setProperty(propName, propValue); + } + + /** + * Sets the selection of the JComboBox to the Object 'name' from the list in + * namVec. + * NOTUSED? + * @param properties not used at the moment + * @param combo {@link JComboBox} to work on + * @param namVec List of names, which are displayed in combo + * @param name Name, that is to be selected. It has to be in namVec + */ + public static void selJComboBoxItem(Properties properties, JComboBox combo, Vector namVec, String name) { + int idx = namVec.indexOf(name); + combo.setSelectedIndex(idx); + // Redisplay. + combo.updateUI(); + } + + /** + * Instatiate an object and guarantee its class. + * + * @param className + * The name of the class to instantiate. + * @param impls + * The name of the class it must be an instance of + * @return an instance of the class, or null if instantiation failed or the class did not implement/extend as required + */ + // TODO probably not needed + public static Object instantiate(String className, String impls) { + if (className != null) { + className = className.trim(); + } + + if (impls != null) { + impls = impls.trim(); + } + + try { + Class c = Class.forName(impls); + try { + Class o = Class.forName(className); + Object res = o.newInstance(); + if (c.isInstance(res)) { + return res; + } + throw new IllegalArgumentException(className + " is not an instance of " + impls); + } catch (ClassNotFoundException e) { + log.error("Error loading class " + className + ": class is not found"); + } catch (IllegalAccessException e) { + log.error("Error loading class " + className + ": does not have access"); + } catch (InstantiationException e) { + log.error("Error loading class " + className + ": could not instantiate"); + } catch (NoClassDefFoundError e) { + log.error("Error loading class " + className + ": couldn't find class " + e.getMessage()); + } + } catch (ClassNotFoundException e) { + log.error("Error loading class " + impls + ": was not found."); + } + return null; + } + + /** + * Instantiate a vector of classes + * + * @param v + * Description of Parameter + * @param className + * Description of Parameter + * @return Description of the Returned Value + */ + public static Vector instantiate(Vector v, String className) { + Vector i = new Vector(); + try { + Class c = Class.forName(className); + Enumeration elements = v.elements(); + while (elements.hasMoreElements()) { + String name = elements.nextElement(); + try { + Object o = Class.forName(name).newInstance(); + if (c.isInstance(o)) { + i.addElement(o); + } + } catch (ClassNotFoundException e) { + log.error("Error loading class " + name + ": class is not found"); + } catch (IllegalAccessException e) { + log.error("Error loading class " + name + ": does not have access"); + } catch (InstantiationException e) { + log.error("Error loading class " + name + ": could not instantiate"); + } catch (NoClassDefFoundError e) { + log.error("Error loading class " + name + ": couldn't find class " + e.getMessage()); + } + } + } catch (ClassNotFoundException e) { + log.error("Error loading class " + className + ": class is not found"); + } + return i; + } + + /** + * Create a button with the netscape style + * + * @param name + * Description of Parameter + * @param listener + * Description of Parameter + * @return Description of the Returned Value + */ + public static JButton createButton(String name, ActionListener listener) { + JButton button = new JButton(getImage(name + ".on.gif")); // $NON-NLS-1$ + button.setDisabledIcon(getImage(name + ".off.gif")); // $NON-NLS-1$ + button.setRolloverIcon(getImage(name + ".over.gif")); // $NON-NLS-1$ + button.setPressedIcon(getImage(name + ".down.gif")); // $NON-NLS-1$ + button.setActionCommand(name); + button.addActionListener(listener); + button.setRolloverEnabled(true); + button.setFocusPainted(false); + button.setBorderPainted(false); + button.setOpaque(false); + button.setPreferredSize(new Dimension(24, 24)); + return button; + } + + /** + * Create a button with the netscape style + * + * @param name + * Description of Parameter + * @param listener + * Description of Parameter + * @return Description of the Returned Value + */ + public static JButton createSimpleButton(String name, ActionListener listener) { + JButton button = new JButton(getImage(name + ".gif")); // $NON-NLS-1$ + button.setActionCommand(name); + button.addActionListener(listener); + button.setFocusPainted(false); + button.setBorderPainted(false); + button.setOpaque(false); + button.setPreferredSize(new Dimension(25, 25)); + return button; + } + + + /** + * Report an error through a dialog box. + * Title defaults to "error_title" resource string + * @param errorMsg - the error message. + */ + public static void reportErrorToUser(String errorMsg) { + reportErrorToUser(errorMsg, JMeterUtils.getResString("error_title")); // $NON-NLS-1$ + } + + /** + * Report an error through a dialog box. + * + * @param errorMsg - the error message. + * @param titleMsg - title string + */ + public static void reportErrorToUser(String errorMsg, String titleMsg) { + if (errorMsg == null) { + errorMsg = "Unknown error - see log file"; + log.warn("Unknown error", new Throwable("errorMsg == null")); + } + GuiPackage instance = GuiPackage.getInstance(); + if (instance == null) { + System.out.println(errorMsg); + return; // Done + } + try { + JOptionPane.showMessageDialog(instance.getMainFrame(), + errorMsg, + titleMsg, + JOptionPane.ERROR_MESSAGE); + } catch (HeadlessException e) { + log.warn("reportErrorToUser(\"" + errorMsg + "\") caused", e); + } + } + + /** + * Finds a string in an array of strings and returns the + * + * @param array + * Array of strings. + * @param value + * String to compare to array values. + * @return Index of value in array, or -1 if not in array. + */ + //TODO - move to JOrphanUtils? + public static int findInArray(String[] array, String value) { + int count = -1; + int index = -1; + if (array != null && value != null) { + while (++count < array.length) { + if (array[count] != null && array[count].equals(value)) { + index = count; + break; + } + } + } + return index; + } + + /** + * Takes an array of strings and a tokenizer character, and returns a string + * of all the strings concatenated with the tokenizer string in between each + * one. + * + * @param splittee + * Array of Objects to be concatenated. + * @param splitChar + * Object to unsplit the strings with. + * @return Array of all the tokens. + */ + //TODO - move to JOrphanUtils? + public static String unsplit(Object[] splittee, Object splitChar) { + StringBuilder retVal = new StringBuilder(); + int count = -1; + while (++count < splittee.length) { + if (splittee[count] != null) { + retVal.append(splittee[count]); + } + if (count + 1 < splittee.length && splittee[count + 1] != null) { + retVal.append(splitChar); + } + } + return retVal.toString(); + } + + // End Method + + /** + * Takes an array of strings and a tokenizer character, and returns a string + * of all the strings concatenated with the tokenizer string in between each + * one. + * + * @param splittee + * Array of Objects to be concatenated. + * @param splitChar + * Object to unsplit the strings with. + * @param def + * Default value to replace null values in array. + * @return Array of all the tokens. + */ + //TODO - move to JOrphanUtils? + public static String unsplit(Object[] splittee, Object splitChar, String def) { + StringBuilder retVal = new StringBuilder(); + int count = -1; + while (++count < splittee.length) { + if (splittee[count] != null) { + retVal.append(splittee[count]); + } else { + retVal.append(def); + } + if (count + 1 < splittee.length) { + retVal.append(splitChar); + } + } + return retVal.toString(); + } + + /** + * Get the JMeter home directory - does not include the trailing separator. + * + * @return the home directory + */ + public static String getJMeterHome() { + return jmDir; + } + + /** + * Get the JMeter bin directory - does not include the trailing separator. + * + * @return the bin directory + */ + public static String getJMeterBinDir() { + return jmBin; + } + + public static void setJMeterHome(String home) { + jmDir = home; + jmBin = jmDir + File.separator + "bin"; // $NON-NLS-1$ + } + + // TODO needs to be synch? Probably not changed after threads have started + private static String jmDir; // JMeter Home directory (excludes trailing separator) + private static String jmBin; // JMeter bin directory (excludes trailing separator) + + + /** + * Gets the JMeter Version. + * + * @return the JMeter version string + */ + public static String getJMeterVersion() { + return JMeterVersion.getVERSION(); + } + + /** + * Gets the JMeter copyright. + * + * @return the JMeter copyright string + */ + public static String getJMeterCopyright() { + return JMeterVersion.getCopyRight(); + } + + /** + * Determine whether we are in 'expert' mode. Certain features may be hidden + * from user's view unless in expert mode. + * + * @return true iif we're in expert mode + */ + public static boolean isExpertMode() { + return JMeterUtils.getPropDefault(EXPERT_MODE_PROPERTY, false); + } + + /** + * Find a file in the current directory or in the JMeter bin directory. + * + * @param fileName the name of the file to find + * @return File object + */ + public static File findFile(String fileName){ + File f =new File(fileName); + if (!f.exists()){ + f=new File(getJMeterBinDir(),fileName); + } + return f; + } + + /** + * Returns the cached result from calling + * InetAddress.getLocalHost().getHostAddress() + * + * @return String representation of local IP address + */ + public static synchronized String getLocalHostIP(){ + if (localHostIP == null) { + getLocalHostDetails(); + } + return localHostIP; + } + + /** + * Returns the cached result from calling + * InetAddress.getLocalHost().getHostName() + * + * @return local host name + */ + public static synchronized String getLocalHostName(){ + if (localHostName == null) { + getLocalHostDetails(); + } + return localHostName; + } + + /** + * Returns the cached result from calling + * InetAddress.getLocalHost().getCanonicalHostName() + * + * @return local host name in canonical form + */ + public static synchronized String getLocalHostFullName(){ + if (localHostFullName == null) { + getLocalHostDetails(); + } + return localHostFullName; + } + + private static void getLocalHostDetails(){ + InetAddress localHost=null; + try { + localHost = InetAddress.getLocalHost(); + } catch (UnknownHostException e1) { + log.error("Unable to get local host IP address."); + return; // TODO - perhaps this should be a fatal error? + } + localHostIP=localHost.getHostAddress(); + localHostName=localHost.getHostName(); + localHostFullName=localHost.getCanonicalHostName(); + } + + /** + * Split line into name/value pairs and remove colon ':' + * + * @param headers + * multi-line string headers + * @return a map name/value for each header + */ + public static LinkedHashMap parseHeaders(String headers) { + LinkedHashMap linkedHeaders = new LinkedHashMap(); + String[] list = headers.split("\n"); // $NON-NLS-1$ + for (String header : list) { + int colon = header.indexOf(':'); // $NON-NLS-1$ + if (colon <= 0) { + linkedHeaders.put(header, ""); // Empty value // $NON-NLS-1$ + } else { + linkedHeaders.put(header.substring(0, colon).trim(), header + .substring(colon + 1).trim()); + } + } + return linkedHeaders; + } + + /** + * Run the runnable in AWT Thread if current thread is not AWT thread + * otherwise runs call {@link SwingUtilities#invokeAndWait(Runnable)} + * @param runnable {@link Runnable} + */ + public static final void runSafe(Runnable runnable) { + if(SwingUtilities.isEventDispatchThread()) { + runnable.run(); + } else { + try { + SwingUtilities.invokeAndWait(runnable); + } catch (InterruptedException e) { + log.warn("Interrupted in thread "+Thread.currentThread().getName(), e); + } catch (InvocationTargetException e) { + throw new Error(e); + } + } + } + + /** + * Help GC by triggering GC and finalization + */ + public static final void helpGC() { + System.gc(); + System.runFinalization(); + } + + /** + * Hack to make matcher clean the two internal buffers it keeps in memory which size is equivalent to + * the unzipped page size + * @param matcher {@link Perl5Matcher} + * @param pattern Pattern + */ + public static final void clearMatcherMemory(Perl5Matcher matcher, Pattern pattern) { + try { + if(pattern != null) { + matcher.matches("", pattern); // $NON-NLS-1$ + } + } catch (Exception e) { + // NOOP + } + } +} diff --git a/src/core/org/apache/jmeter/util/JMeterVersion.java b/src/core/org/apache/jmeter/util/JMeterVersion.java new file mode 100644 index 00000000000..c2db7b5ca60 --- /dev/null +++ b/src/core/org/apache/jmeter/util/JMeterVersion.java @@ -0,0 +1,106 @@ +/* + * 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. + * + */ + +/* + * Created on 02-Oct-2003 + * + * This class defines the JMeter version only (moved from JMeterUtils) + * + * Version changes no longer change the JMeterUtils source file + * - easier to spot when JMeterUtils really changes + * - much smaller to download when the version changes + * + */ +package org.apache.jmeter.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; + +/** + * Utility class to define the JMeter Version string + * + */ +public final class JMeterVersion { + + /* + * + * The string is made private so the compiler can't propagate it into + * JMeterUtils. (Java compilers may make copies of final variables) + * + * This ensures that JMeterUtils always gets the correct + * version, even if JMeterUtils is not re-compiled during the build. + */ + private static final String VERSION = "2.13"; + + private static final String IMPLEMENTATION; + + // Same applies to copyright string + private static final String COPYRIGHT = "Copyright (c) 1998-2015 The Apache Software Foundation"; + + static { + String impl=null; + final Class myClass = JMeterVersion.class; + // This assumes that the JMV treats a class file as a resource (not all do). + URL resource = myClass.getResource("JMeterVersion.class"); + // For example: + // jar:file:/JMeter/lib/ext/ApacheJMeter_core.jar!/org/apache/jmeter/util/JMeterVersion.class + // or if using an IDE + // file:/workspaces/JMeter/build/core/org/apache/jmeter/util/JMeterVersion.class + + + try { + // Convert to URL for manifest + String url = resource.toString().replaceFirst("!/.+", "!/META-INF/MANIFEST.MF"); + resource=new URL(url); + InputStream inputStream = resource.openStream(); + if (inputStream != null) { + Properties props = new Properties(); + try { + props.load(inputStream); + impl = props.getProperty("Implementation-Version"); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + } catch (IOException ioe) { + // Ignored + } + if (impl == null) { + IMPLEMENTATION = VERSION; // default to plain version + } else { + IMPLEMENTATION = impl; + } + } + + private JMeterVersion() // Not instantiable + { + super(); + } + + static String getVERSION() { + return IMPLEMENTATION; + } + + public static String getCopyRight() { + return COPYRIGHT; + } +} diff --git a/src/core/org/apache/jmeter/util/JSR223BeanInfoSupport.java b/src/core/org/apache/jmeter/util/JSR223BeanInfoSupport.java new file mode 100644 index 00000000000..a8cf64988bb --- /dev/null +++ b/src/core/org/apache/jmeter/util/JSR223BeanInfoSupport.java @@ -0,0 +1,81 @@ +/* + * 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.util; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.ListResourceBundle; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.ResourceBundle; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptEngineManager; + +import org.apache.jmeter.testbeans.TestBean; + +/** + * Parent class to handle common GUI design for JSR223 test elements + */ +public abstract class JSR223BeanInfoSupport extends ScriptingBeanInfoSupport { + + private static final String[] LANGUAGE_TAGS; + + public static final String[][] LANGUAGE_NAMES; + + static { + Map nameMap = new HashMap(); + ScriptEngineManager sem = new ScriptEngineManager(); + final List engineFactories = sem.getEngineFactories(); + for(ScriptEngineFactory fact : engineFactories){ + List names = fact.getNames(); + for(String shortName : names) { + nameMap.put(shortName.toLowerCase(Locale.ENGLISH), fact); + } + } + LANGUAGE_TAGS = nameMap.keySet().toArray(new String[nameMap.size()]); + Arrays.sort(LANGUAGE_TAGS); + LANGUAGE_NAMES = new String[nameMap.size()][2]; + int i = 0; + for(Entry me : nameMap.entrySet()) { + final String key = me.getKey(); + LANGUAGE_NAMES[i][0] = key; + final ScriptEngineFactory fact = me.getValue(); + LANGUAGE_NAMES[i++][1] = key + + " (" // $NON-NLS-1$ + + fact.getLanguageName() + " " + fact.getLanguageVersion() // $NON-NLS-1$ + + " / " // $NON-NLS-1$ + + fact.getEngineName() + " " + fact.getEngineVersion() // $NON-NLS-1$ + + ")"; // $NON-NLS-1$ + } + } + + private static final ResourceBundle NAME_BUNDLE = new ListResourceBundle() { + @Override + protected Object[][] getContents() { + return LANGUAGE_NAMES; + } + }; + + protected JSR223BeanInfoSupport(Class beanClass) { + super(beanClass, LANGUAGE_TAGS, NAME_BUNDLE); + } + +} diff --git a/src/core/org/apache/jmeter/util/JSR223TestElement.java b/src/core/org/apache/jmeter/util/JSR223TestElement.java new file mode 100644 index 00000000000..389fb20bd23 --- /dev/null +++ b/src/core/org/apache/jmeter/util/JSR223TestElement.java @@ -0,0 +1,262 @@ +/* + * 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.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; + +import javax.script.Bindings; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public abstract class JSR223TestElement extends ScriptingTestElement + implements Serializable, TestStateListener +{ + /** + * Initialization On Demand Holder pattern + */ + private static class LazyHolder { + public static final ScriptEngineManager INSTANCE = new ScriptEngineManager(); + } + + /** + * @return ScriptEngineManager singleton + */ + public static ScriptEngineManager getInstance() { + return LazyHolder.INSTANCE; + } + + private static final long serialVersionUID = 233L; + + private String cacheKey = ""; // If not empty then script in ScriptText will be compiled and cached + + /** + * Cache of compiled scripts + */ + @SuppressWarnings("unchecked") // LRUMap does not support generics (yet) + private static final Map compiledScriptsCache = + Collections.synchronizedMap( + new LRUMap(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100))); + + public JSR223TestElement() { + super(); + } + + protected ScriptEngine getScriptEngine() throws ScriptException { + final String lang = getScriptLanguage(); + + ScriptEngine scriptEngine = getInstance().getEngineByName(lang); + if (scriptEngine == null) { + throw new ScriptException("Cannot find engine named: '"+lang+"', ensure you set language field in JSR223 Test Element:"+getName()); + } + + return scriptEngine; + } + + /** + * Populate variables to be passed to scripts + * @param bindings Bindings + */ + protected void populateBindings(Bindings bindings) { + final String label = getName(); + final String fileName = getFilename(); + final String scriptParameters = getParameters(); + // Use actual class name for log + final Logger logger = LoggingManager.getLoggerForShortName(getClass().getName()); + bindings.put("log", logger); // $NON-NLS-1$ (this name is fixed) + bindings.put("Label", label); // $NON-NLS-1$ (this name is fixed) + bindings.put("FileName", fileName); // $NON-NLS-1$ (this name is fixed) + bindings.put("Parameters", scriptParameters); // $NON-NLS-1$ (this name is fixed) + String [] args=JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$ + bindings.put("args", args); // $NON-NLS-1$ (this name is fixed) + // Add variables for access to context and variables + JMeterContext jmctx = JMeterContextService.getContext(); + bindings.put("ctx", jmctx); // $NON-NLS-1$ (this name is fixed) + JMeterVariables vars = jmctx.getVariables(); + bindings.put("vars", vars); // $NON-NLS-1$ (this name is fixed) + Properties props = JMeterUtils.getJMeterProperties(); + bindings.put("props", props); // $NON-NLS-1$ (this name is fixed) + // For use in debugging: + bindings.put("OUT", System.out); // $NON-NLS-1$ (this name is fixed) + + // Most subclasses will need these: + Sampler sampler = jmctx.getCurrentSampler(); + bindings.put("sampler", sampler); // $NON-NLS-1$ (this name is fixed) + SampleResult prev = jmctx.getPreviousResult(); + bindings.put("prev", prev); // $NON-NLS-1$ (this name is fixed) + } + + + /** + * This method will run inline script or file script with special behaviour for file script: + * - If ScriptEngine implements Compilable script will be compiled and cached + * - If not if will be run + * @param scriptEngine ScriptEngine + * @param bindings {@link Bindings} might be null + * @return Object returned by script + * @throws IOException when reading the script fails + * @throws ScriptException when compiling or evaluation of the script fails + */ + protected Object processFileOrScript(ScriptEngine scriptEngine, Bindings bindings) throws IOException, ScriptException { + if (bindings == null) { + bindings = scriptEngine.createBindings(); + } + populateBindings(bindings); + File scriptFile = new File(getFilename()); + // Hack: bsh-2.0b5.jar BshScriptEngine implements Compilable but throws "java.lang.Error: unimplemented" + boolean supportsCompilable = scriptEngine instanceof Compilable + && !(scriptEngine.getClass().getName().equals("bsh.engine.BshScriptEngine")); // $NON-NLS-1$ + if (!StringUtils.isEmpty(getFilename())) { + if (scriptFile.exists() && scriptFile.canRead()) { + BufferedReader fileReader = null; + try { + if (supportsCompilable) { + String cacheKey = + getScriptLanguage()+"#"+ // $NON-NLS-1$ + scriptFile.getAbsolutePath()+"#"+ // $NON-NLS-1$ + scriptFile.lastModified(); + CompiledScript compiledScript = + compiledScriptsCache.get(cacheKey); + if (compiledScript==null) { + synchronized (compiledScriptsCache) { + compiledScript = + compiledScriptsCache.get(cacheKey); + if (compiledScript==null) { + // TODO Charset ? + fileReader = new BufferedReader(new FileReader(scriptFile), + (int)scriptFile.length()); + compiledScript = + ((Compilable) scriptEngine).compile(fileReader); + compiledScriptsCache.put(cacheKey, compiledScript); + } + } + } + return compiledScript.eval(bindings); + } else { + // TODO Charset ? + fileReader = new BufferedReader(new FileReader(scriptFile), + (int)scriptFile.length()); + return scriptEngine.eval(fileReader, bindings); + } + } finally { + IOUtils.closeQuietly(fileReader); + } + } else { + throw new ScriptException("Script file '"+scriptFile.getAbsolutePath()+"' does not exist or is unreadable for element:"+getName()); + } + } else if (!StringUtils.isEmpty(getScript())){ + if (supportsCompilable && !StringUtils.isEmpty(cacheKey)) { + CompiledScript compiledScript = + compiledScriptsCache.get(cacheKey); + if (compiledScript==null) { + synchronized (compiledScriptsCache) { + compiledScript = + compiledScriptsCache.get(cacheKey); + if (compiledScript==null) { + compiledScript = + ((Compilable) scriptEngine).compile(getScript()); + compiledScriptsCache.put(cacheKey, compiledScript); + } + } + } + return compiledScript.eval(bindings); + } else { + return scriptEngine.eval(getScript(), bindings); + } + } else { + throw new ScriptException("Both script file and script text are empty for element:"+getName()); + } + } + + + /** + * @return the cacheKey + */ + public String getCacheKey() { + return cacheKey; + } + + /** + * @param cacheKey the cacheKey to set + */ + public void setCacheKey(String cacheKey) { + this.cacheKey = cacheKey; + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testStarted() + */ + @Override + public void testStarted() { + // NOOP + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testStarted(java.lang.String) + */ + @Override + public void testStarted(String host) { + // NOOP + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testEnded() + */ + @Override + public void testEnded() { + testEnded(""); + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testEnded(java.lang.String) + */ + @Override + public void testEnded(String host) { + compiledScriptsCache.clear(); + } + public String getScriptLanguage() { + return scriptLanguage; + } + + public void setScriptLanguage(String s) { + scriptLanguage = s; + } +} diff --git a/src/core/org/apache/jmeter/util/JsseSSLManager.java b/src/core/org/apache/jmeter/util/JsseSSLManager.java new file mode 100644 index 00000000000..510db5a8104 --- /dev/null +++ b/src/core/org/apache/jmeter/util/JsseSSLManager.java @@ -0,0 +1,418 @@ +/* + * 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.util; + +import java.net.HttpURLConnection; +import java.net.Socket; +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.jmeter.util.keystore.JmeterKeyStore; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The SSLManager handles the KeyStore information for JMeter. Basically, it + * handles all the logic for loading and initializing all the JSSE parameters + * and selecting the alias to authenticate against if it is available. + * SSLManager will try to automatically select the client certificate for you, + * but if it can't make a decision, it will pop open a dialog asking you for + * more information. + * + * TODO: does not actually prompt + * + */ +public class JsseSSLManager extends SSLManager { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String HTTPS = "https"; // $NON-NLS-1$ + + // Temporary fix to allow default protocol to be changed + private static final String DEFAULT_SSL_PROTOCOL = + JMeterUtils.getPropDefault("https.default.protocol","TLS"); // $NON-NLS-1$ // $NON-NLS-2$ + + // Allow reversion to original shared session context + private static final boolean SHARED_SESSION_CONTEXT = + JMeterUtils.getPropDefault("https.sessioncontext.shared",false); // $NON-NLS-1$ + + /** + * Characters per second, used to slow down sockets + */ + public static final int CPS = JMeterUtils.getPropDefault("httpclient.socket.https.cps", 0); // $NON-NLS-1$ + + static { + log.info("Using default SSL protocol: "+DEFAULT_SSL_PROTOCOL); + log.info("SSL session context: "+(SHARED_SESSION_CONTEXT ? "shared" : "per-thread")); + + if (CPS > 0) { + log.info("Setting up HTTPS SlowProtocol, cps="+CPS); + } + + } + + /** + * Cache the SecureRandom instance because it takes a long time to create + */ + private SecureRandom rand; + + private Provider pro = null; // TODO why not use the super class value? + + private SSLContext defaultContext; // If we are using a single session + private ThreadLocal threadlocal; // Otherwise + + /** + * Create the SSLContext, and wrap all the X509KeyManagers with + * our X509KeyManager so that we can choose our alias. + * + * @param provider + * Description of Parameter + */ + public JsseSSLManager(Provider provider) { + log.debug("ssl Provider = " + provider); + setProvider(provider); + if (null == this.rand) { // Surely this is always null in the constructor? + this.rand = new SecureRandom(); + } + try { + if (SHARED_SESSION_CONTEXT) { + log.debug("Creating shared context"); + this.defaultContext = createContext(); + } else { + this.threadlocal = new ThreadLocal(); + } + + HttpsURLConnection.setDefaultSSLSocketFactory(new HttpSSLProtocolSocketFactory(this, CPS)); + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + + /* + * Also set up HttpClient defaults + */ + Protocol protocol = new Protocol( + JsseSSLManager.HTTPS, + (ProtocolSocketFactory) new HttpSSLProtocolSocketFactory(this, CPS), + 443); + Protocol.registerProtocol(JsseSSLManager.HTTPS, protocol); + log.debug("SSL stuff all set"); + } catch (GeneralSecurityException ex) { + log.error("Could not set up SSLContext", ex); + } + log.debug("JsseSSLManager installed"); + } + + /** + * Sets the Context attribute of the JsseSSLManager object + * + * @param conn + * The new Context value + */ + @Override + public void setContext(HttpURLConnection conn) { + if (conn instanceof HttpsURLConnection) { +/* + * No point doing this on a per-connection basis, as there is currently no way to configure it. + * So we leave it to the defaults set up in the SSL Context + * + */ +// HttpsURLConnection secureConn = (HttpsURLConnection) conn; +// secureConn.setSSLSocketFactory(this.getContext().getSocketFactory()); + } else { + log.warn("Unexpected HttpURLConnection class: "+conn.getClass().getName()); + } + } + + /** + * Sets the Provider attribute of the JsseSSLManager object + * + * @param p + * The new Provider value + */ + @Override + protected final void setProvider(Provider p) { + super.setProvider(p); + if (null == this.pro) { + this.pro = p; + } + } + + /** + * Returns the SSLContext we are using. This is either a context per thread, + * or, for backwards compatibility, a single shared context. + * + * @return The Context value + * @throws GeneralSecurityException + * when constructing the context fails + */ + public SSLContext getContext() throws GeneralSecurityException { + if (SHARED_SESSION_CONTEXT) { + if (log.isDebugEnabled()){ + log.debug("Using shared SSL context for: "+Thread.currentThread().getName()); + } + return this.defaultContext; + } + + SSLContext sslContext = this.threadlocal.get(); + if (sslContext == null) { + if (log.isDebugEnabled()){ + log.debug("Creating threadLocal SSL context for: "+Thread.currentThread().getName()); + } + sslContext = createContext(); + this.threadlocal.set(sslContext); + } + if (log.isDebugEnabled()){ + log.debug("Using threadLocal SSL context for: "+Thread.currentThread().getName()); + } + return sslContext; + } + + /** + * Resets the SSLContext if using per-thread contexts. + * + */ + public void resetContext() { + if (!SHARED_SESSION_CONTEXT) { + log.debug("Clearing session context for current thread"); + this.threadlocal.set(null); + } + } + + /* + * + * Creates new SSL context + * + * @return SSL context + * + * @throws GeneralSecurityException when the algorithm for the context can + * not be found or the keys have problems + */ + private SSLContext createContext() throws GeneralSecurityException { + SSLContext context; + if (pro != null) { + context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL, pro); // $NON-NLS-1$ + } else { + context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); // $NON-NLS-1$ + } + KeyManagerFactory managerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + JmeterKeyStore keys = this.getKeyStore(); + managerFactory.init(null, defaultpw == null ? new char[]{} : defaultpw.toCharArray()); + KeyManager[] managers = managerFactory.getKeyManagers(); + KeyManager[] newManagers = new KeyManager[managers.length]; + + log.debug(keys.getClass().toString()); + + // Now wrap the default managers with our key manager + for (int i = 0; i < managers.length; i++) { + if (managers[i] instanceof X509KeyManager) { + X509KeyManager manager = (X509KeyManager) managers[i]; + newManagers[i] = new WrappedX509KeyManager(manager, keys); + } else { + newManagers[i] = managers[i]; + } + } + + // Get the default trust managers + TrustManagerFactory tmfactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + tmfactory.init(this.getTrustStore()); + + // Wrap the defaults in our custom trust manager + TrustManager[] trustmanagers = tmfactory.getTrustManagers(); + for (int i = 0; i < trustmanagers.length; i++) { + if (trustmanagers[i] instanceof X509TrustManager) { + trustmanagers[i] = new CustomX509TrustManager( + (X509TrustManager)trustmanagers[i]); + } + } + context.init(newManagers, trustmanagers, this.rand); + if (log.isDebugEnabled()){ + String[] dCiphers = context.getSocketFactory().getDefaultCipherSuites(); + String[] sCiphers = context.getSocketFactory().getSupportedCipherSuites(); + int len = (dCiphers.length > sCiphers.length) ? dCiphers.length : sCiphers.length; + for (int i = 0; i < len; i++) { + if (i < dCiphers.length) { + log.debug("Default Cipher: " + dCiphers[i]); + } + if (i < sCiphers.length) { + log.debug("Supported Cipher: " + sCiphers[i]); + } + } + } + return context; + } + + /** + * This is the X509KeyManager we have defined for the sole purpose of + * selecting the proper key and certificate based on the keystore available. + * + */ + private static class WrappedX509KeyManager implements X509KeyManager { + + /** + * The parent X509KeyManager. + * This is used for the methods {@link #getServerAliases(String, Principal[])} + * and {@link #chooseServerAlias(String, Principal[], Socket)} + */ + private final X509KeyManager manager; + + /** + * The KeyStore this KeyManager uses. + * This is used for the remaining X509KeyManager methods: + * {@link #getClientAliases(String, Principal[])}, + * {@link #getCertificateChain(String)}, + * {@link #getPrivateKey(String)} and + * {@link #chooseClientAlias(String[], Principal[], Socket)} + */ + private final JmeterKeyStore store; + + /** + * Instantiate a new WrappedX509KeyManager. + * + * @param parent + * The parent X509KeyManager + * @param ks + * The KeyStore we derive our client certs and keys from + */ + public WrappedX509KeyManager(X509KeyManager parent, JmeterKeyStore ks) { + this.manager = parent; + this.store = ks; + } + + /** + * Compiles the list of all client aliases with a private key. + * + * @param keyType the key algorithm type name (RSA, DSA, etc.) + * @param issuers the CA certificates we are narrowing our selection on. + * + * @return the array of aliases; may be empty + */ + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + log.debug("WrappedX509Manager: getClientAliases: "); + // implementation moved to JmeterKeystore as only that has the keyType info + return this.store.getClientAliases(keyType, issuers); + } + + /** + * Get the list of server aliases for the SSLServerSockets. This is not + * used in JMeter. + * + * @param keyType + * the type of private key the server expects (RSA, DSA, + * etc.) + * @param issuers + * the CA certificates we are narrowing our selection on. + * @return the ServerAliases value + */ + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + log.debug("WrappedX509Manager: getServerAliases: "); + return this.manager.getServerAliases(keyType, issuers); + } + + /** + * Get the Certificate chain for a particular alias + * + * @param alias + * The client alias + * @return The CertificateChain value + */ + @Override + public X509Certificate[] getCertificateChain(String alias) { + log.debug("WrappedX509Manager: getCertificateChain(" + alias + ")"); + return this.store.getCertificateChain(alias); + } + + /** + * Get the Private Key for a particular alias + * + * @param alias + * The client alias + * @return The PrivateKey value + */ + @Override + public PrivateKey getPrivateKey(String alias) { + PrivateKey privateKey = this.store.getPrivateKey(alias); + log.debug("WrappedX509Manager: getPrivateKey: " + privateKey); + return privateKey; + } + + /** + * Select the Alias we will authenticate as if Client authentication is + * required by the server we are connecting to. We get the list of + * aliases, and if there is only one alias we automatically select it. + * If there are more than one alias that has a private key, we prompt + * the user to choose which alias using a combo box. Otherwise, we + * simply provide a text box, which may or may not work. The alias does + * have to match one in the keystore. + * + * TODO? - does not actually allow the user to choose an alias at present + * + * @param keyType the key algorithm type name(s), ordered with the most-preferred key type first. + * @param issuers the list of acceptable CA issuer subject names or null if it does not matter which issuers are used. + * @param socket the socket to be used for this connection. + * This parameter can be null, which indicates that implementations are free to select an alias applicable to any socket. + * + * @see javax.net.ssl.X509KeyManager#chooseClientAlias(String[], Principal[], Socket) + */ + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { + if(log.isDebugEnabled()) { + log.debug("keyType: " + keyType[0]); + } + String alias = this.store.getAlias(); + if(log.isDebugEnabled()) { + log.debug("Client alias:'"+alias+"'"); + } + return alias; + } + + /** + * Choose the server alias for the SSLServerSockets. This are not used + * in JMeter. + * + * @see javax.net.ssl.X509KeyManager#chooseServerAlias(String, Principal[], Socket) + */ + @Override + public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) { + return this.manager.chooseServerAlias(arg0, arg1, arg2); + } + } +} diff --git a/src/core/org/apache/jmeter/util/LocaleChangeEvent.java b/src/core/org/apache/jmeter/util/LocaleChangeEvent.java new file mode 100644 index 00000000000..61a8769a5c5 --- /dev/null +++ b/src/core/org/apache/jmeter/util/LocaleChangeEvent.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import java.util.EventObject; +import java.util.Locale; + +/** + * @version $Revision$ + */ +public class LocaleChangeEvent extends EventObject { + + private static final long serialVersionUID = 240L; + + private Locale locale; + + public LocaleChangeEvent(Object source) { + super(source); + } + + public LocaleChangeEvent(Object source, Locale locale) { + super(source); + this.locale = locale; + } + + public Locale getLocale() { + return locale; + } +} diff --git a/src/core/org/apache/jmeter/util/LocaleChangeListener.java b/src/core/org/apache/jmeter/util/LocaleChangeListener.java new file mode 100644 index 00000000000..40b1b49d96a --- /dev/null +++ b/src/core/org/apache/jmeter/util/LocaleChangeListener.java @@ -0,0 +1,23 @@ +/* + * 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.util; + +public interface LocaleChangeListener { + void localeChanged(LocaleChangeEvent event); +} diff --git a/src/core/org/apache/jmeter/util/NameUpdater.java b/src/core/org/apache/jmeter/util/NameUpdater.java new file mode 100644 index 00000000000..e0dbbcbfec0 --- /dev/null +++ b/src/core/org/apache/jmeter/util/NameUpdater.java @@ -0,0 +1,190 @@ +/* + * 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. + * + */ + +/* + * Created on Jun 13, 2003 + */ +package org.apache.jmeter.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Properties; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public final class NameUpdater { + private static final Properties nameMap; + // Read-only access after class has been initialised + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String NAME_UPDATER_PROPERTIES = + "META-INF/resources/org.apache.jmeter.nameupdater.properties"; // $NON-NLS-1$ + + static { + nameMap = new Properties(); + FileInputStream fis = null; + File f = new File(JMeterUtils.getJMeterHome(), + JMeterUtils.getPropDefault("upgrade_properties", // $NON-NLS-1$ + "/bin/upgrade.properties")); // $NON-NLS-1$ + try { + fis = new FileInputStream(f); + nameMap.load(fis); + } catch (FileNotFoundException e) { + log.error("Could not find upgrade file: ", e); + } catch (IOException e) { + log.error("Error processing upgrade file: "+f.getPath(), e); + } finally { + JOrphanUtils.closeQuietly(fis); + } + + //load additionnal name conversion rules from plugins + Enumeration enu = null; + + try { + enu = JMeterUtils.class.getClassLoader().getResources(NAME_UPDATER_PROPERTIES); + } catch (IOException e) { + log.error("Error in finding additional nameupdater.properties files: ", e); + } + + if(enu != null) { + while(enu.hasMoreElements()) { + URL ressourceUrl = enu.nextElement(); + log.info("Processing "+ressourceUrl.toString()); + Properties prop = new Properties(); + InputStream is = null; + try { + is = ressourceUrl.openStream(); + prop.load(is); + } catch (IOException e) { + log.error("Error processing upgrade file: " + ressourceUrl.getPath(), e); + } finally { + JOrphanUtils.closeQuietly(is); + } + + @SuppressWarnings("unchecked") // names are Strings + Enumeration propertyNames = (Enumeration) prop.propertyNames(); + while (propertyNames.hasMoreElements()) { + String key = propertyNames.nextElement(); + if (!nameMap.contains(key)) { + nameMap.put(key, prop.get(key)); + log.info("Added additional nameMap entry: " + key); + } else { + log.warn("Additional nameMap entry: '" + key + "' rejected as already defined."); + } + } + } + } + } + + /** + * Looks up the class name; if that does not exist in the map, + * then defaults to the input name. + * + * @param className the classname from the script file + * @return the class name to use, possibly updated. + */ + public static String getCurrentName(String className) { + if (nameMap.containsKey(className)) { + String newName = nameMap.getProperty(className); + log.info("Upgrading class " + className + " to " + newName); + return newName; + } + return className; + } + + /** + * Looks up test element / gui class combination; if that + * does not exist in the map, then defaults to getCurrentName(testClassName). + * + * @param testClassName - test element class name + * @param guiClassName - associated gui class name + * @return new test class name + */ + public static String getCurrentTestName(String testClassName, String guiClassName) { + String key = testClassName + "|" + guiClassName; + if (nameMap.containsKey(key)) { + String newName = nameMap.getProperty(key); + log.info("Upgrading " + key + " to " + newName); + return newName; + } + return getCurrentName(testClassName); + } + + /** + * Looks up class name / property name combination; if that + * does not exist in the map, then defaults to input property name. + * + * @param propertyName - property name to check + * @param className - class name containing the property + * @return possibly updated property name + */ + public static String getCurrentName(String propertyName, String className) { + String key = className + "/" + propertyName; + if (nameMap.containsKey(key)) { + String newName = nameMap.getProperty(key); + log.info("Upgrading property " + propertyName + " to " + newName); + return newName; + } + return propertyName; + } + + /** + * Looks up class name . property name / value combination; + * if that does not exist in the map, returns the original value. + * + * @param value the value to be checked + * @param propertyName the name of the property + * @param className the class containing the propery. + * @return the value, updated if necessary + */ + public static String getCurrentName(String value, String propertyName, String className) { + String key = className + "." + propertyName + "/" + value; + if (nameMap.containsKey(key)) { + String newValue = nameMap.getProperty(key); + log.info("Upgrading value " + value + " to " + newValue); + return newValue; + } + return value; + } + + /** + * Private constructor to prevent instantiation. + */ + private NameUpdater() { + } + + /** + * Check if a key is in the map; intended for use by + * {@link org.apache.jmeter.save.SaveService#checkClasses() SaveService#checkClasses()} + * only. + * + * @param key name of the key to check + * @return true if the key is in the map + */ + public static boolean isMapped(String key) { + return nameMap.containsKey(key); + } +} diff --git a/src/core/org/apache/jmeter/util/NamedObject.java b/src/core/org/apache/jmeter/util/NamedObject.java new file mode 100644 index 00000000000..424ff224681 --- /dev/null +++ b/src/core/org/apache/jmeter/util/NamedObject.java @@ -0,0 +1,24 @@ +/* + * 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.util; + +// TODO no reference to this interface, is it really useful ? +public interface NamedObject { + String getName(); +} diff --git a/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java b/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java new file mode 100644 index 00000000000..a465977f5ed --- /dev/null +++ b/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java @@ -0,0 +1,99 @@ +/* + * 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.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.xml.utils.PrefixResolver; +import org.apache.xml.utils.PrefixResolverDefault; +import org.w3c.dom.Node; + +/** + * {@link PrefixResolver} implementation that loads prefix configuration from jmeter property xpath.namespace.config + */ +public class PropertiesBasedPrefixResolver extends PrefixResolverDefault { + private static final Logger logger = LoggingManager.getLoggerForClass(); + private static final String XPATH_NAMESPACE_CONFIG = "xpath.namespace.config"; + private static final Map NAMESPACE_MAP = new HashMap(); + static { + String pathToNamespaceConfig = JMeterUtils.getPropDefault(XPATH_NAMESPACE_CONFIG, ""); + if(!StringUtils.isEmpty(pathToNamespaceConfig)) { + Properties properties = new Properties(); + InputStream inputStream = null; + try { + File pathToNamespaceConfigFile = JMeterUtils.findFile(pathToNamespaceConfig); + if(!pathToNamespaceConfigFile.exists()) { + logger.error("Cannot find configured file:'"+ + pathToNamespaceConfig+"' in property:'"+XPATH_NAMESPACE_CONFIG+"', file does not exist"); + } else { + if(!pathToNamespaceConfigFile.canRead()) { + logger.error("Cannot read configured file:'"+ + pathToNamespaceConfig+"' in property:'"+XPATH_NAMESPACE_CONFIG+"'"); + } else { + inputStream = new BufferedInputStream(new FileInputStream(pathToNamespaceConfigFile)); + properties.load(inputStream); + properties.entrySet(); + for (Map.Entry entry : properties.entrySet()) { + NAMESPACE_MAP.put((String) entry.getKey(), (String) entry.getValue()); + } + logger.info("Read following XPath namespace configuration "+ + NAMESPACE_MAP); + } + } + } catch(IOException e) { + logger.error("Error loading namespaces from file:'"+ + pathToNamespaceConfig+"', message:"+e.getMessage(),e); + } finally { + JOrphanUtils.closeQuietly(inputStream); + } + } + } + /** + * @param xpathExpressionContext Node + */ + public PropertiesBasedPrefixResolver(Node xpathExpressionContext) { + super(xpathExpressionContext); + } + + /** + * Searches prefix in NAMESPACE_MAP, if it fails to find it defaults to parent implementation + * @param prefix Prefix + * @param namespaceContext Node + */ + @Override + public String getNamespaceForPrefix(String prefix, Node namespaceContext) { + String namespace = NAMESPACE_MAP.get(prefix); + if(namespace==null) { + return super.getNamespaceForPrefix(prefix, namespaceContext); + } else { + return namespace; + } + } +} diff --git a/src/core/org/apache/jmeter/util/SSLManager.java b/src/core/org/apache/jmeter/util/SSLManager.java new file mode 100644 index 00000000000..4c348ba8f4e --- /dev/null +++ b/src/core/org/apache/jmeter/util/SSLManager.java @@ -0,0 +1,313 @@ +/* + * 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.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import java.util.Locale; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.keystore.JmeterKeyStore; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * The SSLManager handles the KeyStore information for JMeter. Basically, it + * handles all the logic for loading and initializing all the JSSE parameters + * and selecting the alias to authenticate against if it is available. + * SSLManager will try to automatically select the client certificate for you, + * but if it can't make a decision, it will pop open a dialog asking you for + * more information. + * + * TODO? - N.B. does not currently allow the selection of a client certificate. + * + */ +public abstract class SSLManager { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$ + + private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$ + + public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$ + + private static final String JAVAX_NET_SSL_KEY_STORE_TYPE = "javax.net.ssl.keyStoreType"; // $NON-NLS-1$ + + private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$ + + /** Singleton instance of the manager */ + //@GuardedBy("this") + private static SSLManager manager; + + private static final boolean isSSLSupported = true; + + /** Cache the KeyStore instance */ + private volatile JmeterKeyStore keyStore; + + /** Cache the TrustStore instance - null if no truststore name was provided */ + private KeyStore trustStore = null; + // Have we yet tried to load the truststore? + private volatile boolean truststore_loaded=false; + + /** Have the password available */ + protected String defaultpw = System.getProperty(KEY_STORE_PASSWORD); + + private int keystoreAliasStartIndex; + + private int keystoreAliasEndIndex; + + private String clientCertAliasVarName; + + /** + * Resets the SSLManager so that we can create a new one with a new keystore + */ + public static synchronized void reset() { + SSLManager.manager = null; + } + + public abstract void setContext(HttpURLConnection conn); + + /** + * Default implementation of setting the Provider + * + * @param provider + * the provider to use + */ + protected void setProvider(Provider provider) { + if (null != provider) { + Security.addProvider(provider); + } + } + + /** + * Opens and initializes the KeyStore. If the password for the KeyStore is + * not set, this method will prompt you to enter it. Unfortunately, there is + * no PasswordEntryField available from JOptionPane. + * + * @return the configured {@link JmeterKeyStore} + */ + protected synchronized JmeterKeyStore getKeyStore() { + if (null == this.keyStore) { + String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided + String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type + fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name + log.info("JmeterKeyStore Location: " + fileName + " type " + fileType); + try { + this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName); + log.info("KeyStore created OK"); + } catch (Exception e) { + this.keyStore = null; + throw new RuntimeException("Could not create keystore: "+e.getMessage(), e); + } + InputStream fileInputStream = null; + try { + File initStore = new File(fileName); + + if (fileName.length() >0 && initStore.exists()) { + fileInputStream = new BufferedInputStream(new FileInputStream(initStore)); + this.keyStore.load(fileInputStream, getPassword()); + if (log.isInfoEnabled()) { + log.info("Total of " + keyStore.getAliasCount() + " aliases loaded OK from keystore"); + } + } else { + log.warn("Keystore file not found, loading empty keystore"); + this.defaultpw = ""; // Ensure not null + this.keyStore.load(null, ""); + } + } catch (Exception e) { + log.error("Problem loading keystore: " +e.getMessage(), e); + } finally { + JOrphanUtils.closeQuietly(fileInputStream); + } + + log.debug("JmeterKeyStore type: " + this.keyStore.getClass().toString()); + } + + return this.keyStore; + } + + /* + * The password can be defined as a property; this dialogue is provided to allow it + * to be entered at run-time. + * + * However, this does not gain much, as the dialogue does not (yet) support hidden input ... + * + */ + private String getPassword() { + String password = this.defaultpw; + if (null == password) { + final GuiPackage guiInstance = GuiPackage.getInstance(); + if (guiInstance != null) { + synchronized (this) { // TODO is sync really needed? + this.defaultpw = JOptionPane.showInputDialog( + guiInstance.getMainFrame(), + JMeterUtils.getResString("ssl_pass_prompt"), // $NON-NLS-1$ + JMeterUtils.getResString("ssl_pass_title"), // $NON-NLS-1$ + JOptionPane.QUESTION_MESSAGE); + System.setProperty(KEY_STORE_PASSWORD, this.defaultpw); + password = this.defaultpw; + } + } else { + log.warn("No password provided, and no GUI present so cannot prompt"); + } + } + return password; + } + + /** + * Opens and initializes the TrustStore. + * + * There are 3 possibilities: + * - no truststore name provided, in which case the default Java truststore should be used + * - truststore name is provided, and loads OK + * - truststore name is provided, but is not found or does not load OK, in which case an empty + * truststore is created + * + * If the KeyStore object cannot be created, then this is currently treated the same + * as if no truststore name was provided. + * + * @return truststore + * - null: use Java truststore + * - otherwise, the truststore, which may be empty if the file could not be loaded. + * + */ + protected KeyStore getTrustStore() { + if (!truststore_loaded) { + + truststore_loaded=true;// we've tried ... + + String fileName = System.getProperty(SSL_TRUST_STORE); + if (fileName == null) { + return null; + } + log.info("TrustStore Location: " + fileName); + + try { + this.trustStore = KeyStore.getInstance("JKS"); + log.info("TrustStore created OK, Type: JKS"); + } catch (Exception e) { + this.trustStore = null; + throw new RuntimeException("Problem creating truststore: "+e.getMessage(), e); + } + + InputStream fileInputStream = null; + try { + File initStore = new File(fileName); + + if (initStore.exists()) { + fileInputStream = new BufferedInputStream(new FileInputStream(initStore)); + this.trustStore.load(fileInputStream, null); + log.info("Truststore loaded OK from file"); + } else { + log.info("Truststore file not found, loading empty truststore"); + this.trustStore.load(null, null); + } + } catch (Exception e) { + throw new RuntimeException("Can't load TrustStore: " + e.getMessage(), e); + } finally { + JOrphanUtils.closeQuietly(fileInputStream); + } + } + + return this.trustStore; + } + + /** + * Protected Constructor to remove the possibility of directly instantiating + * this object. Create the SSLContext, and wrap all the X509KeyManagers with + * our X509KeyManager so that we can choose our alias. + */ + protected SSLManager() { + } + + /** + * Static accessor for the SSLManager object. The SSLManager is a singleton. + * + * @return the singleton {@link SSLManager} + */ + public static final synchronized SSLManager getInstance() { + if (null == SSLManager.manager) { + SSLManager.manager = new JsseSSLManager(null); +// if (SSLManager.isSSLSupported) { +// String classname = null; +// classname = "org.apache.jmeter.util.JsseSSLManager"; // $NON-NLS-1$ +// +// try { +// Class clazz = Class.forName(classname); +// Constructor con = clazz.getConstructor(new Class[] { Provider.class }); +// SSLManager.manager = (SSLManager) con.newInstance(new Object[] { SSLManager.sslProvider }); +// } catch (Exception e) { +// log.error("Could not create SSLManager instance", e); // $NON-NLS-1$ +// SSLManager.isSSLSupported = false; +// return null; +// } +// } + } + + return SSLManager.manager; + } + + /** + * Test whether SSL is supported or not. + * + * @return flag whether SSL is supported + */ + public static final boolean isSSLSupported() { + return SSLManager.isSSLSupported; + } + + /** + * Configure Keystore + * + * @param preload + * flag whether the keystore should be opened within this method, + * or the opening should be delayed + * @param startIndex + * first index to consider for a key + * @param endIndex + * last index to consider for a key + * @param clientCertAliasVarName + * name of the default key, if empty the first key will be used + * as default key + */ + public void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName) { + this.keystoreAliasStartIndex = startIndex; + this.keystoreAliasEndIndex = endIndex; + this.clientCertAliasVarName = clientCertAliasVarName; + if(preload) { + keyStore = getKeyStore(); + } + } + + /** + * Destroy Keystore + */ + public void destroyKeystore() { + keyStore=null; + } +} diff --git a/src/core/org/apache/jmeter/util/ScopePanel.java b/src/core/org/apache/jmeter/util/ScopePanel.java new file mode 100644 index 00000000000..e2487b3d62a --- /dev/null +++ b/src/core/org/apache/jmeter/util/ScopePanel.java @@ -0,0 +1,182 @@ +/* + * 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.util; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.gui.util.HorizontalPanel; + +/** + * Scope panel so users can choose whether + * to apply the test element to the parent sample, the child samples or both. + * + */ +public class ScopePanel extends JPanel implements ActionListener { + + private static final long serialVersionUID = 240L; + + private final JRadioButton parentButton; + private final JRadioButton childButton; + private final JRadioButton allButton; + private final JRadioButton variableButton; + private final JTextField variableName; + + public ScopePanel(){ + this(false); + } + + public ScopePanel(boolean enableVariableButton) { + this(enableVariableButton, true, true); + } + + public ScopePanel(boolean enableVariableButton, boolean enableParentAndSubsamples, boolean enableSubsamplesOnly) { + parentButton = new JRadioButton(JMeterUtils.getResString("sample_scope_parent")); //$NON-NLS-1$ + if(enableParentAndSubsamples) { + allButton = new JRadioButton(JMeterUtils.getResString("sample_scope_all")); //$NON-NLS-1$ + } else { + allButton = null; + } + if(enableSubsamplesOnly) { + childButton = new JRadioButton(JMeterUtils.getResString("sample_scope_children")); //$NON-NLS-1$ + } else { + childButton = null; + } + if (enableVariableButton) { + variableButton = new JRadioButton(JMeterUtils.getResString("sample_scope_variable")); //$NON-NLS-1$ + variableName = new JTextField(10); + } else { + variableButton = null; + variableName = null; + } + init(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("sample_scope"))); //$NON-NLS-1$ + + parentButton.setSelected(true); + + JPanel buttonPanel = new HorizontalPanel(); + ButtonGroup group = new ButtonGroup(); + if(allButton != null) { + group.add(allButton); + buttonPanel.add(allButton); + } + group.add(parentButton); + buttonPanel.add(parentButton); + if(childButton != null) { + group.add(childButton); + buttonPanel.add(childButton); + } + + if (variableButton != null){ + variableButton.addActionListener(this); + group.add(variableButton); + buttonPanel.add(variableButton); + buttonPanel.add(variableName); + } + add(buttonPanel); + } + + public void clearGui() { + parentButton.setSelected(true); + } + + public int getSelection(){ + if (parentButton.isSelected()){ + return 0; + } + return 1; + } + + public void setScopeAll() { + setScopeAll(false); + } + + public void setScopeAll(boolean enableVariableButton) { + allButton.setSelected(true); + if (enableVariableButton) { + variableName.setText(""); //$NON-NLS-1$ + } + } + + public void setScopeChildren() { + setScopeChildren(false); + } + + public void setScopeChildren(boolean enableVariableButton) { + childButton.setSelected(true); + if (enableVariableButton) { + variableName.setText(""); //$NON-NLS-1$ + } + } + + public void setScopeParent() { + setScopeParent(false); + } + + public void setScopeParent(boolean enableVariableButton) { + parentButton.setSelected(true); + if (enableVariableButton) { + variableName.setText(""); //$NON-NLS-1$ + } + } + + public void setScopeVariable(String value){ + variableButton.setSelected(true); + variableName.setText(value); + } + + public boolean isScopeParent() { + return parentButton.isSelected(); + } + + public boolean isScopeChildren() { + return childButton != null && childButton.isSelected(); + } + + public boolean isScopeAll() { + return allButton != null && allButton.isSelected(); + } + + public boolean isScopeVariable() { + return variableButton != null && variableButton.isSelected(); + } + + @Override + public void actionPerformed(ActionEvent e) { + variableName.setEnabled(variableButton.isSelected()); + } + + public String getVariable() { + return variableName.getText(); + } +} diff --git a/src/core/org/apache/jmeter/util/ScriptingBeanInfoSupport.java b/src/core/org/apache/jmeter/util/ScriptingBeanInfoSupport.java new file mode 100644 index 00000000000..0959dc879b7 --- /dev/null +++ b/src/core/org/apache/jmeter/util/ScriptingBeanInfoSupport.java @@ -0,0 +1,109 @@ +/* + * 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.util; + +import java.beans.PropertyDescriptor; +import java.util.ResourceBundle; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.FileEditor; +import org.apache.jmeter.testbeans.gui.TextAreaEditor; + +/** + * Parent class to define common GUI parameters for BSF and JSR223 test elements + */ +public abstract class ScriptingBeanInfoSupport extends BeanInfoSupport { + + public ScriptingBeanInfoSupport(Class beanClass, String[] languageTags) { + this(beanClass, languageTags, null); + } + + protected ScriptingBeanInfoSupport(Class beanClass, String[] LANGUAGE_TAGS, ResourceBundle rb) { + super(beanClass); + PropertyDescriptor p; + + p = property("scriptLanguage"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + if (rb != null) { + p.setValue(RESOURCE_BUNDLE, rb); + } + p.setValue(TAGS, LANGUAGE_TAGS); + + createPropertyGroup("scriptingLanguage", // $NON-NLS-1$ + new String[] { "scriptLanguage" }); // $NON-NLS-1$ + + p = property("parameters"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + createPropertyGroup("parameterGroup", // $NON-NLS-1$ + new String[] { "parameters" }); // $NON-NLS-1$ + + p = property("filename"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + p.setPropertyEditorClass(FileEditor.class); + + createPropertyGroup("filenameGroup", // $NON-NLS-1$ + new String[] { "filename" }); // $NON-NLS-1$ + + /* + * If we are creating a JSR223 element, add the cache key property. + * + * Note that this cannot be done in the JSR223BeanInfoSupport class + * because that causes problems with the group; its properties are + * not always set up before they are needed. This cause various + * issues with the GUI: + * - wrong field attributes (should not allow null) + * - sometimes GUI is completely mangled + * - field appears at start rather than at end. + * - the following warning is logged: + * jmeter.testbeans.gui.GenericTestBeanCustomizer: + * org.apache.jmeter.util.JSR223TestElement#cacheKey does not appear to have been configured + * + * Adding the group here solves these issues, and it's also + * possible to add the key just before the script panel + * to which it relates. + * + * It's not yet clear why this should be, but it looks as though + * createPropertyGroup does not work properly if it is called from + * any subclasses of this class. + * + */ + if (JSR223TestElement.class.isAssignableFrom(beanClass) ) { + p = property("cacheKey"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + createPropertyGroup("cacheKey_group", // $NON-NLS-1$ + new String[] { "cacheKey" }); // $NON-NLS-1$ + } + + p = property("script"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + p.setPropertyEditorClass(TextAreaEditor.class); + + createPropertyGroup("scripting", // $NON-NLS-1$ + new String[] { "script" }); // $NON-NLS-1$ + } + +} diff --git a/src/core/org/apache/jmeter/util/ScriptingTestElement.java b/src/core/org/apache/jmeter/util/ScriptingTestElement.java new file mode 100644 index 00000000000..45d7387a5b4 --- /dev/null +++ b/src/core/org/apache/jmeter/util/ScriptingTestElement.java @@ -0,0 +1,84 @@ +/* + * 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.util; + +import org.apache.jmeter.testelement.AbstractTestElement; + +/** + * Common parent class for the {@link BSFTestElement} and {@link JSR223TestElement} scripting test elements. + * These also share the {@link ScriptingBeanInfoSupport} class for configuration. + */ +public abstract class ScriptingTestElement extends AbstractTestElement { + + private static final long serialVersionUID = 281L; + + //++ For TestBean implementations only + private String parameters = ""; // passed to file or script + + private String filename = ""; // file to source (overrides script) + + private String script = ""; // script (if file not provided) + + protected String scriptLanguage = ""; // BSF/JSR223 language to use + //-- For TestBean implementations only + + public ScriptingTestElement() { + super(); + } + + /** + * Return the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @return the script to execute + */ + public String getScript(){ + return script; + } + + /** + * Set the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @param s the script to execute (may be blank) + */ + public void setScript(String s){ + script=s; + } + + public String getParameters() { + return parameters; + } + + public void setParameters(String s) { + parameters = s; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String s) { + filename = s; + } + + +} diff --git a/src/core/org/apache/jmeter/util/ShutdownClient.java b/src/core/org/apache/jmeter/util/ShutdownClient.java new file mode 100644 index 00000000000..40c5254c53f --- /dev/null +++ b/src/core/org/apache/jmeter/util/ShutdownClient.java @@ -0,0 +1,49 @@ +/* + * 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.util; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +import org.apache.jmeter.JMeter; + + +/** + * Simple utility to send a shutdown message to a non-GUI instance of JMeter + */ +public class ShutdownClient { + public static void main(String[] args) throws IOException { + int port = JMeter.UDP_PORT_DEFAULT; + if (args.length > 1){ + port = Integer.parseInt(args[1]); + } else if (args.length == 0) { + throw new RuntimeException("Usage: command [port]"); + } + String command = args[0]; + System.out.println("Sending "+command+" request to port "+port); + DatagramSocket socket = new DatagramSocket(); + byte[] buf = command.getBytes("ASCII"); + InetAddress address = InetAddress.getByName("localhost"); + DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port); + socket.send(packet); + socket.close(); + } +} diff --git a/src/core/org/apache/jmeter/util/SlowInputStream.java b/src/core/org/apache/jmeter/util/SlowInputStream.java new file mode 100644 index 00000000000..927d21404ae --- /dev/null +++ b/src/core/org/apache/jmeter/util/SlowInputStream.java @@ -0,0 +1,56 @@ +/* + * 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.util; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStream wrapper to emulate a slow device, e.g. modem + * + */ +public class SlowInputStream extends FilterInputStream { + + private final CPSPauser pauser; + + /** + * Wraps the input stream to emulate a slow device + * @param in input stream + * @param cps characters per second to emulate + */ + public SlowInputStream(InputStream in, int cps) { + super(in); + pauser = new CPSPauser(cps); + } + + @Override + public int read() throws IOException { + pauser.pause(1); + return in.read(); + } + + // Also handles read(byte[]) + @Override + public int read(byte[] b, int off, int len) throws IOException { + pauser.pause(len); + return in.read(b, off, len); + } + +} diff --git a/src/core/org/apache/jmeter/util/SlowOutputStream.java b/src/core/org/apache/jmeter/util/SlowOutputStream.java new file mode 100644 index 00000000000..5b00fa40ebf --- /dev/null +++ b/src/core/org/apache/jmeter/util/SlowOutputStream.java @@ -0,0 +1,55 @@ +/* + * 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.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * OutputStream filter to emulate a slow device, e.g. modem + * + */ +public class SlowOutputStream extends FilterOutputStream { + + private final CPSPauser pauser; + + /** + * Create wrapped Output Stream toe emulate the requested CPS. + * @param out OutputStream + * @param cps characters per second + */ + public SlowOutputStream(OutputStream out, int cps) { + super(out); + pauser = new CPSPauser(cps); + } + + // Also handles write(byte[]) + @Override + public void write(byte[] b, int off, int len) throws IOException { + pauser.pause(len); + out.write(b, off, len); + } + + @Override + public void write(int b) throws IOException { + pauser.pause(1); + out.write(b); + } +} diff --git a/src/core/org/apache/jmeter/util/SlowSSLSocket.java b/src/core/org/apache/jmeter/util/SlowSSLSocket.java new file mode 100644 index 00000000000..f837004d0ad --- /dev/null +++ b/src/core/org/apache/jmeter/util/SlowSSLSocket.java @@ -0,0 +1,354 @@ +/* + * 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.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +/** + * "Slow" SSLsocket implementation to emulate dial-up modems etc + * + * WARNING: the class relies on overriding all superclass methods in order to apply them to the input socket. + * Any missing methods will access the superclass socket, which will probably be in the wrong state. + * + */ +public class SlowSSLSocket extends SSLSocket { + + private final int CPS; // Characters per second to emulate + + private final SSLSocket sslSock; // Save the actual socket + + /** + * Wrap an SSLSocket with slow input and output streams + * @param sock SSLSocket to be wrapped + * @param cps characters per second to emulate + */ + public SlowSSLSocket(final SSLSocket sock, final int cps){ + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + sslSock=sock; + CPS=cps; + } + + // Override so we can intercept the stream + @Override + public OutputStream getOutputStream() throws IOException { + return new SlowOutputStream(sslSock.getOutputStream(), CPS); + } + + // Override so we can intercept the stream + @Override + public InputStream getInputStream() throws IOException { + return new SlowInputStream(sslSock.getInputStream(), CPS); + } + + // Forward all the SSLSocket methods to the input socket + + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener arg0) { + sslSock.addHandshakeCompletedListener(arg0); + } + + @Override + public boolean getEnableSessionCreation() { + return sslSock.getEnableSessionCreation(); + } + + @Override + public String[] getEnabledCipherSuites() { + return sslSock.getEnabledCipherSuites(); + } + + @Override + public String[] getEnabledProtocols() { + return sslSock.getEnabledProtocols(); + } + + @Override + public boolean getNeedClientAuth() { + return sslSock.getNeedClientAuth(); + } + + @Override + public SSLSession getSession() { + return sslSock.getSession(); + } + + @Override + public String[] getSupportedCipherSuites() { + return sslSock.getSupportedCipherSuites(); + } + + @Override + public String[] getSupportedProtocols() { + return sslSock.getSupportedProtocols(); + } + + @Override + public boolean getUseClientMode() { + return sslSock.getUseClientMode(); + } + + @Override + public boolean getWantClientAuth() { + return sslSock.getWantClientAuth(); + } + + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener arg0) { + sslSock.removeHandshakeCompletedListener(arg0); + } + + @Override + public void setEnableSessionCreation(boolean arg0) { + sslSock.setEnableSessionCreation(arg0); + } + + @Override + public void setEnabledCipherSuites(String[] arg0) { + sslSock.setEnabledCipherSuites(arg0); + } + + @Override + public void setEnabledProtocols(String[] arg0) { + sslSock.setEnabledProtocols(arg0); + } + + @Override + public void setNeedClientAuth(boolean arg0) { + sslSock.setNeedClientAuth(arg0); + } + + @Override + public void setUseClientMode(boolean arg0) { + sslSock.setUseClientMode(arg0); + } + + @Override + public void setWantClientAuth(boolean arg0) { + sslSock.setWantClientAuth(arg0); + } + + @Override + public void startHandshake() throws IOException { + sslSock.startHandshake(); + } + + // Also forward all the Socket methods. + + @Override + public void bind(SocketAddress bindpoint) throws IOException { + sslSock.bind(bindpoint); + } + + @Override + public synchronized void close() throws IOException { + sslSock.close(); + } + + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException { + sslSock.connect(endpoint, timeout); + } + + @Override + public void connect(SocketAddress endpoint) throws IOException { + sslSock.connect(endpoint); + } + + @Override + public SocketChannel getChannel() { + return sslSock.getChannel(); + } + + @Override + public InetAddress getInetAddress() { + return sslSock.getInetAddress(); + } + + @Override + public boolean getKeepAlive() throws SocketException { + return sslSock.getKeepAlive(); + } + + @Override + public InetAddress getLocalAddress() { + return sslSock.getLocalAddress(); + } + + @Override + public int getLocalPort() { + return sslSock.getLocalPort(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return sslSock.getLocalSocketAddress(); + } + + @Override + public boolean getOOBInline() throws SocketException { + return sslSock.getOOBInline(); + } + + @Override + public int getPort() { + return sslSock.getPort(); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + return sslSock.getReceiveBufferSize(); + } + + @Override + public SocketAddress getRemoteSocketAddress() { + return sslSock.getRemoteSocketAddress(); + } + + @Override + public boolean getReuseAddress() throws SocketException { + return sslSock.getReuseAddress(); + } + + @Override + public synchronized int getSendBufferSize() throws SocketException { + return sslSock.getSendBufferSize(); + } + + @Override + public int getSoLinger() throws SocketException { + return sslSock.getSoLinger(); + } + + @Override + public synchronized int getSoTimeout() throws SocketException { + return sslSock.getSoTimeout(); + } + + @Override + public boolean getTcpNoDelay() throws SocketException { + return sslSock.getTcpNoDelay(); + } + + @Override + public int getTrafficClass() throws SocketException { + return sslSock.getTrafficClass(); + } + + @Override + public boolean isBound() { + return sslSock.isBound(); + } + + @Override + public boolean isClosed() { + return sslSock.isClosed(); + } + + @Override + public boolean isConnected() { + return sslSock.isConnected(); + } + + @Override + public boolean isInputShutdown() { + return sslSock.isInputShutdown(); + } + + @Override + public boolean isOutputShutdown() { + return sslSock.isOutputShutdown(); + } + + @Override + public void sendUrgentData(int data) throws IOException { + sslSock.sendUrgentData(data); + } + + @Override + public void setKeepAlive(boolean on) throws SocketException { + sslSock.setKeepAlive(on); + } + + @Override + public void setOOBInline(boolean on) throws SocketException { + sslSock.setOOBInline(on); + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + sslSock.setReceiveBufferSize(size); + } + + @Override + public void setReuseAddress(boolean on) throws SocketException { + sslSock.setReuseAddress(on); + } + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException { + sslSock.setSendBufferSize(size); + } + + @Override + public void setSoLinger(boolean on, int linger) throws SocketException { + sslSock.setSoLinger(on, linger); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + sslSock.setSoTimeout(timeout); + } + + @Override + public void setTcpNoDelay(boolean on) throws SocketException { + sslSock.setTcpNoDelay(on); + } + + @Override + public void setTrafficClass(int tc) throws SocketException { + sslSock.setTrafficClass(tc); + } + + @Override + public void shutdownInput() throws IOException { + sslSock.shutdownInput(); + } + + @Override + public void shutdownOutput() throws IOException { + sslSock.shutdownOutput(); + } + + @Override + public String toString() { + return sslSock.toString(); + } +} diff --git a/src/core/org/apache/jmeter/util/SlowSocket.java b/src/core/org/apache/jmeter/util/SlowSocket.java new file mode 100644 index 00000000000..8e242572223 --- /dev/null +++ b/src/core/org/apache/jmeter/util/SlowSocket.java @@ -0,0 +1,127 @@ +/* + * 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.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.UnknownHostException; + +/** + * "Slow" (non-SSL) socket implementation to emulate dial-up modems etc + */ +public class SlowSocket extends Socket { + + private final int CPS; // Characters per second to emulate + + public SlowSocket(final int cps, String host, int port, InetAddress localAddress, int localPort, int timeout) throws IOException { + super(); + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + CPS=cps; + // This sequence is borrowed from: + // org.apache.commons.httpclient.protocol.ReflectionSocketFactory.createSocket + SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); + SocketAddress remoteaddr = new InetSocketAddress(host, port); + bind(localaddr); + connect(remoteaddr, timeout); + } + + /** + * + * @param cps + * characters per second + * @param host + * hostname + * @param port + * port + * @param localAddr + * local address + * @param localPort + * local port + * + * @throws IOException + * if an I/O error occurs during initialization + * @throws IllegalArgumentException + * if cps <= 0, or if the port or + * localPort values lie outside of the allowed + * range between 0 and 65535 + */ + public SlowSocket(int cps, String host, int port, InetAddress localAddr, int localPort) throws IOException { + super(host, port, localAddr, localPort); + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + CPS=cps; + } + + /** + * + * @param cps + * characters per second + * @param host + * hostname + * @param port + * port + * + * @throws UnknownHostException + * if the name of the host can not be determined automatically + * @throws IOException + * if an I/O error occurs during initialization + * @throws IllegalArgumentException + * if cps <= 0, or if the port or + * localPort values lie outside of the allowed + * range between 0 and 65535 + */ + public SlowSocket(int cps, String host, int port) throws UnknownHostException, IOException { + super(host, port); + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + CPS=cps; + } + + /** + * Added for use by SlowHC4SocketFactory. + * + * @param cps characters per second + */ + public SlowSocket(int cps) { + super(); + CPS = cps; + } + + // Override so we can intercept the stream + @Override + public OutputStream getOutputStream() throws IOException { + return new SlowOutputStream(super.getOutputStream(), CPS); + } + + // Override so we can intercept the stream + @Override + public InputStream getInputStream() throws IOException { + return new SlowInputStream(super.getInputStream(), CPS); + } + +} diff --git a/src/core/org/apache/jmeter/util/StringUtilities.java b/src/core/org/apache/jmeter/util/StringUtilities.java new file mode 100644 index 00000000000..1c6545b9ddb --- /dev/null +++ b/src/core/org/apache/jmeter/util/StringUtilities.java @@ -0,0 +1,53 @@ +/* + * 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.util; + +public final class StringUtilities { + + /** + * Private constructor to prevent instantiation. + */ + private StringUtilities() { + } + + /** + * Replace all patterns in a String + * + * @see String#replaceAll(String,String) + * - JDK1.4 only + * + * @param input - string to be transformed + * @param pattern - pattern to replace + * @param sub - replacement + * @return the updated string + */ + public static String substitute(final String input, final String pattern, final String sub) { + StringBuilder ret = new StringBuilder(input.length()); + int start = 0; + int index = -1; + final int length = pattern.length(); + while ((index = input.indexOf(pattern, start)) >= start) { + ret.append(input.substring(start, index)); + ret.append(sub); + start = index + length; + } + ret.append(input.substring(start)); + return ret.toString(); + } +} diff --git a/src/core/org/apache/jmeter/util/ThreadLocalRandom.java b/src/core/org/apache/jmeter/util/ThreadLocalRandom.java new file mode 100644 index 00000000000..ce39b830a26 --- /dev/null +++ b/src/core/org/apache/jmeter/util/ThreadLocalRandom.java @@ -0,0 +1,204 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.16 version + +package org.apache.jmeter.util; + +import java.util.Random; + +/** + * A random number generator isolated to the current thread. Like the + * global {@link java.util.Random} generator used by the {@link + * java.lang.Math} class, a {@code ThreadLocalRandom} is initialized + * with an internally generated seed that may not otherwise be + * modified. When applicable, use of {@code ThreadLocalRandom} rather + * than shared {@code Random} objects in concurrent programs will + * typically encounter much less overhead and contention. Use of + * {@code ThreadLocalRandom} is particularly appropriate when multiple + * tasks (for example, each a java.util.concurrent.ForkJoinTask (JDK 1.7)) + * use random numbers in parallel in thread pools. + * + *

Usages of this class should typically be of the form: + * {@code ThreadLocalRandom.current().nextX(...)} (where + * {@code X} is {@code Int}, {@code Long}, etc). + * When all usages are of this form, it is never possible to + * accidently share a {@code ThreadLocalRandom} across multiple threads. + * + *

This class also provides additional commonly used bounded random + * generation methods. + * + * @author Doug Lea + * TODO Remove when minimum Java Version become Java 7 + * @since 2.12 + */ +public class ThreadLocalRandom extends Random { + // same constants as Random, but must be redeclared because private + private static final long multiplier = 0x5DEECE66DL; + private static final long addend = 0xBL; + private static final long mask = (1L << 48) - 1; + + /** + * The random seed. We can't use super.seed. + */ + private long rnd; + + /** + * Initialization flag to permit calls to setSeed to succeed only + * while executing the Random constructor. We can't allow others + * since it would cause setting seed in one part of a program to + * unintentionally impact other usages by the thread. + */ + boolean initialized; + + // Padding to help avoid memory contention among seed updates in + // different TLRs in the common case that they are located near + // each other. + @SuppressWarnings("unused") + private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + /** + * The actual ThreadLocal + */ + private static final ThreadLocal localRandom = + new ThreadLocal() { + @Override + protected ThreadLocalRandom initialValue() { + return new ThreadLocalRandom(); + } + }; + + + /** + * Constructor called only by localRandom.initialValue. + */ + ThreadLocalRandom() { + super(); + initialized = true; + } + + /** + * Returns the current thread's {@code ThreadLocalRandom}. + * + * @return the current thread's {@code ThreadLocalRandom} + */ + public static ThreadLocalRandom current() { + return localRandom.get(); + } + + /** + * Throws {@code UnsupportedOperationException}. Setting seeds in + * this generator is not supported. + * + * @throws UnsupportedOperationException always + */ + @Override + public void setSeed(long seed) { + if (initialized) + throw new UnsupportedOperationException(); + rnd = (seed ^ multiplier) & mask; + } + + @Override + protected int next(int bits) { + rnd = (rnd * multiplier + addend) & mask; + return (int) (rnd >>> (48-bits)); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @throws IllegalArgumentException if least greater than or equal + * to bound + * @return the next value + */ + public int nextInt(int least, int bound) { + if (least >= bound) + throw new IllegalArgumentException(); + return nextInt(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed value + * between 0 (inclusive) and the specified value (exclusive). + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return the next value + * @throws IllegalArgumentException if n is not positive + */ + public long nextLong(long n) { + if (n <= 0) + throw new IllegalArgumentException("n must be positive"); + // Divide n by two until small enough for nextInt. On each + // iteration (at most 31 of them but usually much less), + // randomly choose both whether to include high bit in result + // (offset) and whether to continue with the lower vs upper + // half (which makes a difference only if odd). + long offset = 0; + while (n >= Integer.MAX_VALUE) { + int bits = next(2); + long half = n >>> 1; + long nextn = ((bits & 2) == 0) ? half : n - half; + if ((bits & 1) == 0) + offset += n - nextn; + n = nextn; + } + return offset + nextInt((int) n); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException if least greater than or equal + * to bound + */ + public long nextLong(long least, long bound) { + if (least >= bound) + throw new IllegalArgumentException(); + return nextLong(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed {@code double} value + * between 0 (inclusive) and the specified value (exclusive). + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return the next value + * @throws IllegalArgumentException if n is not positive + */ + public double nextDouble(double n) { + if (n <= 0) + throw new IllegalArgumentException("n must be positive"); + return nextDouble() * n; + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException if least greater than or equal + * to bound + */ + public double nextDouble(double least, double bound) { + if (least >= bound) + throw new IllegalArgumentException(); + return nextDouble() * (bound - least) + least; + } + + private static final long serialVersionUID = -5851777807851030925L; +} diff --git a/src/core/org/apache/jmeter/util/TidyException.java b/src/core/org/apache/jmeter/util/TidyException.java new file mode 100644 index 00000000000..4bc330117c2 --- /dev/null +++ b/src/core/org/apache/jmeter/util/TidyException.java @@ -0,0 +1,35 @@ +/* + * 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.util; + +/** + * Class for reporting errors when running Tidy. + */ +public class TidyException extends Exception { + + private static final long serialVersionUID = 240L; + + public TidyException() { + this(0,0); + } + + public TidyException(int errors, int warnings){ + super("tidy: " + errors + " errors, " + warnings + " warnings"); + } +} diff --git a/src/core/org/apache/jmeter/util/XPathUtil.java b/src/core/org/apache/jmeter/util/XPathUtil.java new file mode 100644 index 00000000000..441b5ea09d2 --- /dev/null +++ b/src/core/org/apache/jmeter/util/XPathUtil.java @@ -0,0 +1,451 @@ +/* + * 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.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.xml.utils.PrefixResolver; +import org.apache.xpath.XPathAPI; +import org.apache.xpath.objects.XObject; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.tidy.Tidy; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * This class provides a few utility methods for dealing with XML/XPath. + */ +public class XPathUtil { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private XPathUtil() { + super(); + } + + //@GuardedBy("this") + private static DocumentBuilderFactory documentBuilderFactory; + + /** + * Returns a suitable document builder factory. + * Caches the factory in case the next caller wants the same options. + * + * @param validate should the parser validate documents? + * @param whitespace should the parser eliminate whitespace in element content? + * @param namespace should the parser be namespace aware? + * + * @return javax.xml.parsers.DocumentBuilderFactory + */ + private static synchronized DocumentBuilderFactory makeDocumentBuilderFactory(boolean validate, boolean whitespace, + boolean namespace) { + if (XPathUtil.documentBuilderFactory == null || documentBuilderFactory.isValidating() != validate + || documentBuilderFactory.isNamespaceAware() != namespace + || documentBuilderFactory.isIgnoringElementContentWhitespace() != whitespace) { + // configure the document builder factory + documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setValidating(validate); + documentBuilderFactory.setNamespaceAware(namespace); + documentBuilderFactory.setIgnoringElementContentWhitespace(whitespace); + } + return XPathUtil.documentBuilderFactory; + } + + /** + * Create a DocumentBuilder using the makeDocumentFactory func. + * + * @param validate should the parser validate documents? + * @param whitespace should the parser eliminate whitespace in element content? + * @param namespace should the parser be namespace aware? + * @param downloadDTDs if true, parser should attempt to resolve external entities + * @return document builder + * @throws ParserConfigurationException if {@link DocumentBuilder} can not be created for the wanted configuration + */ + public static DocumentBuilder makeDocumentBuilder(boolean validate, boolean whitespace, boolean namespace, boolean downloadDTDs) + throws ParserConfigurationException { + DocumentBuilder builder = makeDocumentBuilderFactory(validate, whitespace, namespace).newDocumentBuilder(); + builder.setErrorHandler(new MyErrorHandler(validate, false)); + if (!downloadDTDs){ + EntityResolver er = new EntityResolver(){ + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + return new InputSource(new ByteArrayInputStream(new byte[]{})); + } + }; + builder.setEntityResolver(er); + } + return builder; + } + + /** + * Utility function to get new Document + * + * @param stream - Document Input stream + * @param validate - Validate Document (not Tidy) + * @param whitespace - Element Whitespace (not Tidy) + * @param namespace - Is Namespace aware. (not Tidy) + * @param tolerant - Is tolerant - i.e. use the Tidy parser + * @param quiet - set Tidy quiet + * @param showWarnings - set Tidy warnings + * @param report_errors - throw TidyException if Tidy detects an error + * @param isXml - is document already XML (Tidy only) + * @param downloadDTDs - if true, try to download external DTDs + * @return document + * @throws ParserConfigurationException when no {@link DocumentBuilder} can be constructed for the wanted configuration + * @throws SAXException if parsing fails + * @throws IOException if an I/O error occurs while parsing + * @throws TidyException if a ParseError is detected and report_errors is true + */ + public static Document makeDocument(InputStream stream, boolean validate, boolean whitespace, boolean namespace, + boolean tolerant, boolean quiet, boolean showWarnings, boolean report_errors, boolean isXml, boolean downloadDTDs) + throws ParserConfigurationException, SAXException, IOException, TidyException { + return makeDocument(stream, validate, whitespace, namespace, + tolerant, quiet, showWarnings, report_errors, isXml, downloadDTDs, null); + } + + /** + * Utility function to get new Document + * + * @param stream - Document Input stream + * @param validate - Validate Document (not Tidy) + * @param whitespace - Element Whitespace (not Tidy) + * @param namespace - Is Namespace aware. (not Tidy) + * @param tolerant - Is tolerant - i.e. use the Tidy parser + * @param quiet - set Tidy quiet + * @param showWarnings - set Tidy warnings + * @param report_errors - throw TidyException if Tidy detects an error + * @param isXml - is document already XML (Tidy only) + * @param downloadDTDs - if true, try to download external DTDs + * @param tidyOut OutputStream for Tidy pretty-printing + * @return document + * @throws ParserConfigurationException if {@link DocumentBuilder} can not be created for the wanted configuration + * @throws SAXException if parsing fails + * @throws IOException if I/O error occurs while parsing + * @throws TidyException if a ParseError is detected and report_errors is true + */ + public static Document makeDocument(InputStream stream, boolean validate, boolean whitespace, boolean namespace, + boolean tolerant, boolean quiet, boolean showWarnings, boolean report_errors, boolean isXml, boolean downloadDTDs, + OutputStream tidyOut) + throws ParserConfigurationException, SAXException, IOException, TidyException { + Document doc; + if (tolerant) { + doc = tidyDoc(stream, quiet, showWarnings, report_errors, isXml, tidyOut); + } else { + doc = makeDocumentBuilder(validate, whitespace, namespace, downloadDTDs).parse(stream); + } + return doc; + } + + /** + * Create a document using Tidy + * + * @param stream - input + * @param quiet - set Tidy quiet? + * @param showWarnings - show Tidy warnings? + * @param report_errors - log errors and throw TidyException? + * @param isXML - treat document as XML? + * @param out OutputStream, null if no output required + * @return the document + * + * @throws TidyException if a ParseError is detected and report_errors is true + */ + private static Document tidyDoc(InputStream stream, boolean quiet, boolean showWarnings, boolean report_errors, + boolean isXML, OutputStream out) throws TidyException { + StringWriter sw = new StringWriter(); + Tidy tidy = makeTidyParser(quiet, showWarnings, isXML, sw); + Document doc = tidy.parseDOM(stream, out); + doc.normalize(); + if (tidy.getParseErrors() > 0) { + if (report_errors) { + log.error("TidyException: " + sw.toString()); + throw new TidyException(tidy.getParseErrors(),tidy.getParseWarnings()); + } + log.warn("Tidy errors: " + sw.toString()); + } + return doc; + } + + /** + * Create a Tidy parser with the specified settings. + * + * @param quiet - set the Tidy quiet flag? + * @param showWarnings - show Tidy warnings? + * @param isXml - treat the content as XML? + * @param stringWriter - if non-null, use this for Tidy errorOutput + * @return the Tidy parser + */ + public static Tidy makeTidyParser(boolean quiet, boolean showWarnings, boolean isXml, StringWriter stringWriter) { + Tidy tidy = new Tidy(); + tidy.setInputEncoding("UTF8"); + tidy.setOutputEncoding("UTF8"); + tidy.setQuiet(quiet); + tidy.setShowWarnings(showWarnings); + tidy.setMakeClean(true); + tidy.setXmlTags(isXml); + if (stringWriter != null) { + tidy.setErrout(new PrintWriter(stringWriter)); + } + return tidy; + } + + static class MyErrorHandler implements ErrorHandler { + private final boolean val, tol; + + private final String type; + + MyErrorHandler(boolean validate, boolean tolerate) { + val = validate; + tol = tolerate; + type = "Val=" + val + " Tol=" + tol; + } + + @Override + public void warning(SAXParseException ex) throws SAXException { + log.info("Type=" + type + " " + ex); + if (val && !tol){ + throw new SAXException(ex); + } + } + + @Override + public void error(SAXParseException ex) throws SAXException { + log.warn("Type=" + type + " " + ex); + if (val && !tol) { + throw new SAXException(ex); + } + } + + @Override + public void fatalError(SAXParseException ex) throws SAXException { + log.error("Type=" + type + " " + ex); + if (val && !tol) { + throw new SAXException(ex); + } + } + } + + /** + * Return value for node + * @param node Node + * @return String + */ + private static String getValueForNode(Node node) { + StringWriter sw = new StringWriter(); + try { + Transformer t = TransformerFactory.newInstance().newTransformer(); + t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + t.transform(new DOMSource(node), new StreamResult(sw)); + } catch (TransformerException e) { + sw.write(e.getMessageAndLocation()); + } + return sw.toString(); + } + + /** + * Extract NodeList using expression + * @param document {@link Document} + * @param xPathExpression XPath expression + * @return {@link NodeList} + * @throws TransformerException when the internally used xpath engine fails + */ + public static NodeList selectNodeList(Document document, String xPathExpression) throws TransformerException { + XObject xObject = XPathAPI.eval(document, xPathExpression, getPrefixResolver(document)); + return xObject.nodelist(); + } + + /** + * Put in matchStrings results of evaluation + * @param document XML document + * @param xPathQuery XPath Query + * @param matchStrings List of strings that will be filled + * @param fragment return fragment + * @throws TransformerException when the internally used xpath engine fails + */ + public static void putValuesForXPathInList(Document document, + String xPathQuery, + List matchStrings, boolean fragment) throws TransformerException { + String val = null; + XObject xObject = XPathAPI.eval(document, xPathQuery, getPrefixResolver(document)); + final int objectType = xObject.getType(); + if (objectType == XObject.CLASS_NODESET) { + NodeList matches = xObject.nodelist(); + int length = matches.getLength(); + for (int i = 0 ; i < length; i++) { + Node match = matches.item(i); + if ( match instanceof Element){ + if (fragment){ + val = getValueForNode(match); + } else { + // elements have empty nodeValue, but we are usually interested in their content + final Node firstChild = match.getFirstChild(); + if (firstChild != null) { + val = firstChild.getNodeValue(); + } else { + val = match.getNodeValue(); // TODO is this correct? + } + } + } else { + val = match.getNodeValue(); + } + matchStrings.add(val); + } + } else if (objectType == XObject.CLASS_NULL + || objectType == XObject.CLASS_UNKNOWN + || objectType == XObject.CLASS_UNRESOLVEDVARIABLE) { + log.warn("Unexpected object type: "+xObject.getTypeString()+" returned for: "+xPathQuery); + } else { + val = xObject.toString(); + matchStrings.add(val); + } + } + + /** + * + * @param document XML Document + * @return {@link PrefixResolver} + */ + private static PrefixResolver getPrefixResolver(Document document) { + PropertiesBasedPrefixResolver propertiesBasedPrefixResolver = + new PropertiesBasedPrefixResolver(document.getDocumentElement()); + return propertiesBasedPrefixResolver; + } + + /** + * Validate xpathString is a valid XPath expression + * @param document XML Document + * @param xpathString XPATH String + * @throws TransformerException if expression fails to evaluate + */ + public static void validateXPath(Document document, String xpathString) throws TransformerException { + if (XPathAPI.eval(document, xpathString, getPrefixResolver(document)) == null) { + // We really should never get here + // because eval will throw an exception + // if xpath is invalid, but whatever, better + // safe + throw new IllegalArgumentException("xpath eval of '" + xpathString + "' was null"); + } + } + + /** + * Fills result + * @param result {@link AssertionResult} + * @param doc XML Document + * @param xPathExpression XPath expression + * @param isNegated flag whether a non-match should be considered a success + */ + public static void computeAssertionResult(AssertionResult result, + Document doc, + String xPathExpression, + boolean isNegated) { + try { + XObject xObject = XPathAPI.eval(doc, xPathExpression, getPrefixResolver(doc)); + switch (xObject.getType()) { + case XObject.CLASS_NODESET: + NodeList nodeList = xObject.nodelist(); + if (nodeList == null || nodeList.getLength() == 0) { + if (log.isDebugEnabled()) { + log.debug(new StringBuilder("nodeList null no match ").append(xPathExpression).toString()); + } + result.setFailure(!isNegated); + result.setFailureMessage("No Nodes Matched " + xPathExpression); + return; + } + if (log.isDebugEnabled()) { + log.debug("nodeList length " + nodeList.getLength()); + if (!isNegated) { + for (int i = 0; i < nodeList.getLength(); i++){ + log.debug(new StringBuilder("nodeList[").append(i).append("] ").append(nodeList.item(i)).toString()); + } + } + } + result.setFailure(isNegated); + if (isNegated) { + result.setFailureMessage("Specified XPath was found... Turn off negate if this is not desired"); + } + return; + case XObject.CLASS_BOOLEAN: + if (!xObject.bool()){ + result.setFailure(!isNegated); + result.setFailureMessage("No Nodes Matched " + xPathExpression); + } + return; + default: + result.setFailure(true); + result.setFailureMessage("Cannot understand: " + xPathExpression); + return; + } + } catch (TransformerException e) { + result.setError(true); + result.setFailureMessage( + new StringBuilder("TransformerException: ") + .append(e.getMessage()) + .append(" for:") + .append(xPathExpression) + .toString()); + } + } + + /** + * Formats XML + * @param xml string to format + * @return String formatted XML + */ + public static final String formatXml(String xml){ + try { + Transformer serializer= TransformerFactory.newInstance().newTransformer(); + serializer.setOutputProperty(OutputKeys.INDENT, "yes"); + serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + Source xmlSource=new SAXSource(new InputSource(new StringReader(xml))); + StringWriter stringWriter = new StringWriter(); + StreamResult res = new StreamResult(stringWriter); + serializer.transform(xmlSource, res); + return stringWriter.toString(); + } catch (Exception e) { + return xml; + } + } + +} diff --git a/src/core/org/apache/jmeter/util/keystore/JmeterKeyStore.java b/src/core/org/apache/jmeter/util/keystore/JmeterKeyStore.java new file mode 100644 index 00000000000..0189361882b --- /dev/null +++ b/src/core/org/apache/jmeter/util/keystore/JmeterKeyStore.java @@ -0,0 +1,318 @@ +/* + * 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.util.keystore; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Use this Keystore for JMeter specific KeyStores. + * + */ +public final class JmeterKeyStore { + + private static final Logger LOG = LoggingManager.getLoggerForClass(); + + private final KeyStore store; + + /** first index to consider for a key */ + private final int startIndex; + + /** last index to consider for a key */ + private final int endIndex; + + /** name of the default alias */ + private String clientCertAliasVarName; + + private String[] names = new String[0]; // default empty array to prevent NPEs + private Map privateKeyByAlias = new HashMap(); + private Map certsByAlias = new HashMap(); + + //@GuardedBy("this") + private int last_user; + + + /** + * @param type + * type of the {@link KeyStore} + * @param startIndex which keys should be considered starting from 0 + * @param endIndex which keys should be considered up to count - 1 + * @param clientCertAliasVarName name for the default key, if empty use the first key available + * @throws KeyStoreException + * when the type of the keystore is not supported + * @throws IllegalArgumentException + * when startIndex < 0, endIndex + * < 0 or endIndex < startIndex + */ + private JmeterKeyStore(String type, int startIndex, int endIndex, String clientCertAliasVarName) throws KeyStoreException { + if (startIndex < 0 || endIndex < 0 || endIndex < startIndex) { + throw new IllegalArgumentException("Invalid index(es). Start="+startIndex+", end="+endIndex); + } + this.store = KeyStore.getInstance(type); + this.startIndex = startIndex; + this.endIndex = endIndex; + this.clientCertAliasVarName = clientCertAliasVarName; + } + + /** + * Process the input stream and try to read the keys from the store + * + * @param is + * {@link InputStream} from which the store should be loaded + * @param pword + * the password used to check the integrity of the store + * @throws IOException + * if there is a problem decoding or reading the store. A bad + * password might be the cause for this, or an empty store + * @throws CertificateException + * if any of the certificated in the store can not be loaded + * @throws NoSuchAlgorithmException + * if the algorithm to check the integrity of the store can not + * be found + * @throws KeyStoreException + * if the store has not been initialized (should not happen + * here) + * @throws UnrecoverableKeyException + * if the key can not be recovered from the store (should not + * happen here, either) + */ + public void load(InputStream is, String pword) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, UnrecoverableKeyException { + char pw[] = pword==null ? null : pword.toCharArray(); + store.load(is, pw); + + ArrayList v_names = new ArrayList(); + this.privateKeyByAlias = new HashMap(); + this.certsByAlias = new HashMap(); + + if (null != is){ // No point checking an empty keystore + PrivateKey _key = null; + int index = 0; + Enumeration aliases = store.aliases(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + if (store.isKeyEntry(alias)) { + if (index >= startIndex && index <= endIndex) { + _key = (PrivateKey) store.getKey(alias, pw); + if (null == _key) { + throw new IOException("No key found for alias: " + alias); // Should not happen + } + Certificate[] chain = store.getCertificateChain(alias); + if (null == chain) { + throw new IOException("No certificate chain found for alias: " + alias); + } + v_names.add(alias); + X509Certificate[] x509certs = new X509Certificate[chain.length]; + for (int i = 0; i < x509certs.length; i++) { + x509certs[i] = (X509Certificate)chain[i]; + } + + privateKeyByAlias.put(alias, _key); + certsByAlias.put(alias, x509certs); + } + index++; + } + } + + if (null == _key) { + throw new IOException("No key(s) found"); + } + if (index <= endIndex-startIndex) { + LOG.warn("Did not find all requested aliases. Start="+startIndex + +", end="+endIndex+", found="+certsByAlias.size()); + } + } + + /* + * Note: if is == null, the arrays will be empty + */ + this.names = v_names.toArray(new String[v_names.size()]); + } + + + /** + * Get the ordered certificate chain for a specific alias. + * + * @param alias + * the alias for which the certificate chain should be given + * @return the certificate chain for the alias + * @throws IllegalArgumentException + * if no chain could be found for the alias + */ + public X509Certificate[] getCertificateChain(String alias) { + X509Certificate[] result = this.certsByAlias.get(alias); + if(result != null) { + return result; + } + // API expects null not empty array, see http://docs.oracle.com/javase/6/docs/api/javax/net/ssl/X509KeyManager.html + throw new IllegalArgumentException("No certificate found for alias:'"+alias+"'"); + } + + /** + * Get the next or only alias. + * + * @return the next or only alias. + * @throws IllegalArgumentException + * if {@link JmeterKeyStore#clientCertAliasVarName + * clientCertAliasVarName} is not empty and no key for this + * alias could be found + */ + public String getAlias() { + if(!StringUtils.isEmpty(clientCertAliasVarName)) { + // We return even if result is null + String aliasName = JMeterContextService.getContext().getVariables().get(clientCertAliasVarName); + if(StringUtils.isEmpty(aliasName)) { + LOG.error("No var called '"+clientCertAliasVarName+"' found"); + throw new IllegalArgumentException("No var called '"+clientCertAliasVarName+"' found"); + } + return aliasName; + } + int length = this.names.length; + if (length == 0) { // i.e. is == null + return null; + } + return this.names[getIndexAndIncrement(length)]; + } + + public int getAliasCount() { + return this.names.length; + } + + public String getAlias(int index) { + int length = this.names.length; + if (length == 0 && index == 0) { // i.e. is == null + return null; + } + if (index >= length || index < 0) { + throw new ArrayIndexOutOfBoundsException(index); + } + return this.names[index]; + } + + /** + * Return the private Key for a specific alias + * + * @param alias + * the name of the alias for the private key + * @return the private key for the given alias + * @throws IllegalArgumentException + * when no private key could be found + */ + public PrivateKey getPrivateKey(String alias) { + PrivateKey pk = this.privateKeyByAlias.get(alias); + if(pk != null) { + return pk; + } + throw new IllegalArgumentException("No PrivateKey found for alias:'"+alias+"'"); + } + + /** + * Create a keystore which returns a range of aliases (if available) + * + * @param type + * store type (e.g. JKS) + * @param startIndex + * first index (from 0) + * @param endIndex + * last index (to count -1) + * @param clientCertAliasVarName + * name of the default key to, if empty the first key will be + * used as default key + * @return the keystore + * @throws KeyStoreException + * when the type of the store is not supported + * @throws IllegalArgumentException + * when startIndex < 0, endIndex + * < 0, or endIndex < startIndex + */ + public static JmeterKeyStore getInstance(String type, int startIndex, int endIndex, String clientCertAliasVarName) throws KeyStoreException { + return new JmeterKeyStore(type, startIndex, endIndex, clientCertAliasVarName); + } + + /** + * Create a keystore which returns the first alias only. + * + * @param type + * of the store e.g. JKS + * @return the keystore + * @throws KeyStoreException + * when the type of the store is not supported + */ + public static JmeterKeyStore getInstance(String type) throws KeyStoreException { + return getInstance(type, 0, 0, null); + } + + /** + * Gets current index and increment by rolling if index is equal to length + * @param length Number of keys to roll + */ + private int getIndexAndIncrement(int length) { + synchronized(this) { + int result = last_user++; + if (last_user >= length) { + last_user = 0; + } + return result; + } + } + + /** + * Compiles the list of all client aliases with a private key. + * TODO Currently, keyType and issuers are both ignored. + * + * @param keyType the key algorithm type name (RSA, DSA, etc.) + * @param issuers the CA certificates we are narrowing our selection on. + * + * @return the array of aliases; may be empty + */ + public String[] getClientAliases(String keyType, Principal[] issuers) { + int count = getAliasCount(); + String[] aliases = new String[count]; + for(int i = 0; i < aliases.length; i++) { +// if (keys[i].getAlgorithm().equals(keyType)){ +// +// } + aliases[i] = this.names[i]; + } + if(aliases.length>0) { + return aliases; + } else { + // API expects null not empty array, see http://docs.oracle.com/javase/6/docs/api/javax/net/ssl/X509KeyManager.html + return null; + } + } + +} diff --git a/src/core/org/apache/jmeter/visualizers/CachingStatCalculator.java b/src/core/org/apache/jmeter/visualizers/CachingStatCalculator.java new file mode 100644 index 00000000000..f700d15335e --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/CachingStatCalculator.java @@ -0,0 +1,66 @@ +/* + * 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.visualizers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * Provides storage of samples in addition to calculations + */ +public class CachingStatCalculator extends SamplingStatCalculator { + + private final List storedValues = Collections.synchronizedList(new ArrayList()); + + public CachingStatCalculator(String string) { + super(string); + } + + public List getSamples() { + return storedValues; + } + + public Sample getSample(int index) { + synchronized( storedValues ){ + if (index < storedValues.size()) { + return storedValues.get(index); + } + } + return null; + } + + @Override + public void clear() { + super.clear(); + storedValues.clear(); + } + /** + * Records a sample. + * + */ + @Override + public Sample addSample(SampleResult res) { + final Sample sample = super.addSample(res); + storedValues.add(sample); + return sample; + } +} diff --git a/src/core/org/apache/jmeter/visualizers/ImageVisualizer.java b/src/core/org/apache/jmeter/visualizers/ImageVisualizer.java new file mode 100644 index 00000000000..cdabc4de67a --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/ImageVisualizer.java @@ -0,0 +1,30 @@ +/* + * 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.visualizers; + +import java.awt.Image; + +/** + * TODO - interface is used but getImage() does not appear to be used + * + * @version $Revision$ + */ +public interface ImageVisualizer { + Image getImage(); +} diff --git a/src/core/org/apache/jmeter/visualizers/Printable.java b/src/core/org/apache/jmeter/visualizers/Printable.java new file mode 100644 index 00000000000..e3dc5506e06 --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/Printable.java @@ -0,0 +1,30 @@ +/* + * 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.visualizers; + +import javax.swing.JComponent; + +/** + * Printable is used by components that can be saved to an external file. It is + * up to the visualizers to get the right component containing the JPanel or + * JComponent to save. + */ +public interface Printable { + JComponent getPrintableComponent(); +} diff --git a/src/core/org/apache/jmeter/visualizers/RunningSample.java b/src/core/org/apache/jmeter/visualizers/RunningSample.java new file mode 100644 index 00000000000..6bc55b35c63 --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/RunningSample.java @@ -0,0 +1,375 @@ +/* + * 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.visualizers; + +import java.text.DecimalFormat; + +import org.apache.jmeter.samplers.SampleResult; + +/** + *

+ * Running sample data container. Just instantiate a new instance of this + * class, and then call {@link #addSample(SampleResult)} a few times, and pull + * the stats out with whatever methods you prefer. + *

+ *

+ * Please note that this class is not thread-safe. + * The calling class is responsible for ensuring thread safety if required. + * Versions prior to 2.3.2 appeared to be thread-safe but weren't as label and index were not final. + * Also the caller needs to synchronize access in order to ensure that variables are consistent. + *

+ * + */ +public class RunningSample { + + private final DecimalFormat rateFormatter = new DecimalFormat("#.0"); // $NON-NLS-1$ + + private final DecimalFormat errorFormatter = new DecimalFormat("#0.00%"); // $NON-NLS-1$ + + private long counter; + + private long runningSum; + + private long max, min; + + private long errorCount; + + private long firstTime; + + private long lastTime; + + private final String label; + + private final int index; + + /** + * Use this constructor to create the initial instance + * + * @param label the label for this component + * @param index the index of this component + */ + public RunningSample(String label, int index) { + this.label = label; + this.index = index; + init(); + } + + /** + * Copy constructor to create a duplicate of existing instance (without the + * disadvantages of clone() + * + * @param src existing RunningSample to be copied + */ + public RunningSample(RunningSample src) { + this.counter = src.counter; + this.errorCount = src.errorCount; + this.firstTime = src.firstTime; + this.index = src.index; + this.label = src.label; + this.lastTime = src.lastTime; + this.max = src.max; + this.min = src.min; + this.runningSum = src.runningSum; + } + + private void init() { + counter = 0L; + runningSum = 0L; + max = Long.MIN_VALUE; + min = Long.MAX_VALUE; + errorCount = 0L; + firstTime = Long.MAX_VALUE; + lastTime = 0L; + } + + /** + * Clear the counters (useful for differential stats) + * + */ + public void clear() { + init(); + } + + /** + * Get the elapsed time for the samples + * + * @return how long the samples took + */ + public long getElapsed() { + if (lastTime == 0) { + return 0;// No samples collected ... + } + return lastTime - firstTime; + } + + /** + * Returns the throughput associated to this sampler in requests per second. + * May be slightly skewed because it takes the timestamps of the first and + * last samples as the total time passed, and the test may actually have + * started before that start time and ended after that end time. + * + * @return throughput associated with this sampler per second + */ + public double getRate() { + if (counter == 0) { + return 0.0; // Better behaviour when howLong=0 or lastTime=0 + } + + long howLongRunning = lastTime - firstTime; + + if (howLongRunning == 0) { + return Double.MAX_VALUE; + } + + return (double) counter / howLongRunning * 1000.0; + } + + /** + * Returns the throughput associated to this sampler in requests per min. + * May be slightly skewed because it takes the timestamps of the first and + * last samples as the total time passed, and the test may actually have + * started before that start time and ended after that end time. + * + * @return throughput associated with this sampler per minute + */ + public double getRatePerMin() { + if (counter == 0) { + return 0.0; // Better behaviour when howLong=0 or lastTime=0 + } + + long howLongRunning = lastTime - firstTime; + + if (howLongRunning == 0) { + return Double.MAX_VALUE; + } + return (double) counter / howLongRunning * 60000.0; + } + + /** + * Returns a String that represents the throughput associated for this + * sampler, in units appropriate to its dimension: + *

+ * The number is represented in requests/second or requests/minute or + * requests/hour. + *

+ * Examples: "34.2/sec" "0.1/sec" "43.0/hour" "15.9/min" + * + * @return a String representation of the rate the samples are being taken + * at. + */ + public String getRateString() { + double rate = getRate(); + + if (rate == Double.MAX_VALUE) { + return "N/A"; + } + + String unit = "sec"; + + if (rate < 1.0) { + rate *= 60.0; + unit = "min"; + } + if (rate < 1.0) { + rate *= 60.0; + unit = "hour"; + } + + return rateFormatter.format(rate) + "/" + unit; + } + + /** + * @return the label for this component + */ + public String getLabel() { + return label; + } + + /** + * @return the index of this component + */ + public int getIndex() { + return index; + } + + /** + * Records a sample. + * + * @param res sample to record + */ + public void addSample(SampleResult res) { + long aTimeInMillis = res.getTime(); + + counter+=res.getSampleCount(); + errorCount += res.getErrorCount(); + + long startTime = res.getStartTime(); + long endTime = res.getEndTime(); + + if (firstTime > startTime) { + // this is our first sample, set the start time to current timestamp + firstTime = startTime; + } + + // Always update the end time + if (lastTime < endTime) { + lastTime = endTime; + } + runningSum += aTimeInMillis; + + if (aTimeInMillis > max) { + max = aTimeInMillis; + } + + if (aTimeInMillis < min) { + min = aTimeInMillis; + } + + } + + /** + * Adds another RunningSample to this one. + * Does not check if it has the same label and index. + * + * @param rs sample to add + */ + public void addSample(RunningSample rs) { + this.counter += rs.counter; + this.errorCount += rs.errorCount; + this.runningSum += rs.runningSum; + if (this.firstTime > rs.firstTime) { + this.firstTime = rs.firstTime; + } + if (this.lastTime < rs.lastTime) { + this.lastTime = rs.lastTime; + } + if (this.max < rs.max) { + this.max = rs.max; + } + if (this.min > rs.min) { + this.min = rs.min; + } + } + + /** + * Returns the time in milliseconds of the quickest sample. + * + * @return the time in milliseconds of the quickest sample. + */ + public long getMin() { + long rval = 0; + + if (min != Long.MAX_VALUE) { + rval = min; + } + return rval; + } + + /** + * Returns the time in milliseconds of the slowest sample. + * + * @return the time in milliseconds of the slowest sample. + */ + public long getMax() { + long rval = 0; + + if (max != Long.MIN_VALUE) { + rval = max; + } + return rval; + } + + /** + * Returns the average time in milliseconds that samples ran in. + * + * @return the average time in milliseconds that samples ran in. + */ + public long getAverage() { + if (counter == 0) { + return 0; + } + return runningSum / counter; + } + + /** + * Returns the number of samples that have been recorded by this instance of + * the RunningSample class. + * + * @return the number of samples that have been recorded by this instance of + * the RunningSample class. + */ + public long getNumSamples() { + return counter; + } + + /** + * Returns the raw double value of the percentage of samples with errors + * that were recorded. (Between 0.0 and 1.0) If you want a nicer return + * format, see {@link #getErrorPercentageString()}. + * + * @return the raw double value of the percentage of samples with errors + * that were recorded. + */ + public double getErrorPercentage() { + double rval = 0.0; + + if (counter == 0) { + return rval; + } + rval = (double) errorCount / (double) counter; + return rval; + } + + /** + * Returns a String which represents the percentage of sample errors that + * have occurred. ("0.00%" through "100.00%") + * + * @return a String which represents the percentage of sample errors that + * have occurred. + */ + public String getErrorPercentageString() { + double myErrorPercentage = this.getErrorPercentage(); + + return errorFormatter.format(myErrorPercentage); + } + + /** + * For debugging purposes, mainly. + */ + @Override + public String toString() { + StringBuilder mySB = new StringBuilder(); + + mySB.append("Samples: " + this.getNumSamples() + " "); + mySB.append("Avg: " + this.getAverage() + " "); + mySB.append("Min: " + this.getMin() + " "); + mySB.append("Max: " + this.getMax() + " "); + mySB.append("Error Rate: " + this.getErrorPercentageString() + " "); + mySB.append("Sample Rate: " + this.getRateString()); + return mySB.toString(); + } + + /** + * @return errorCount + */ + public long getErrorCount() { + return errorCount; + } + +} diff --git a/src/core/org/apache/jmeter/visualizers/Sample.java b/src/core/org/apache/jmeter/visualizers/Sample.java new file mode 100644 index 00000000000..3a78c3cf888 --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/Sample.java @@ -0,0 +1,218 @@ +/* + * 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.visualizers; + +import java.io.Serializable; +import java.text.Format; +import java.util.Date; + +public class Sample implements Serializable, Comparable { + private static final long serialVersionUID = 240L; + + private final long data; // = elapsed + + private final long average; + + private final long median; + + private final long distributionLine; // TODO: what is this for? + + private final long deviation; + + private final double throughput; + + private final long errorCount; + + private final boolean success; + + private final String label; + + private final String threadName; + + private final long count; + + private final long endTime; + + private final int bytes; + + public Sample(String name, long data, long average, long deviation, long median, long distributionLine, + double throughput, long errorCount, boolean success, long num, long endTime) { + this.data = data; + this.average = average; + this.deviation = deviation; + this.throughput = throughput; + this.success = success; + this.median = median; + this.distributionLine = distributionLine; + this.label = name; + this.errorCount = errorCount; + this.count = num; + this.endTime = endTime; + this.bytes = 0; + this.threadName = ""; + } + + public Sample(String name, long data, long average, long deviation, long median, long distributionLine, + double throughput, long errorCount, boolean success, long num, long endTime, int bytes, String threadName) { + this.data = data; + this.average = average; + this.deviation = deviation; + this.throughput = throughput; + this.success = success; + this.median = median; + this.distributionLine = distributionLine; + this.label = name; + this.errorCount = errorCount; + this.count = num; + this.endTime = endTime; + this.bytes = bytes; + this.threadName = threadName; + } + + public Sample() { + this(null, 0, 0, 0, 0, 0, 0, 0, true, 0, 0); + } + + // Appears not to be used - however it is invoked via the Functor class + public int getBytes() { + return bytes; + } + + /** + * @return Returns the average. + */ + public long getAverage() { + return average; + } + + /** + * @return Returns the count. + */ + public long getCount() { + return count; + } + + /** + * @return Returns the data (usually elapsed time) + */ + public long getData() { + return data; + } + + /** + * @return Returns the deviation. + */ + public long getDeviation() { + return deviation; + } + + /** + * @return Returns the distributionLine. + */ + public long getDistributionLine() { + return distributionLine; + } + + /** + * @return Returns the error. + */ + public boolean isSuccess() { + return success; + } + + /** + * @return Returns the errorCount. + */ + public long getErrorCount() { + return errorCount; + } + + /** + * @return Returns the label. + */ + public String getLabel() { + return label; + } + + /** + * @return Returns the threadName. + */ + public String getThreadName() { + return threadName; + } + + /** + * @return Returns the median. + */ + public long getMedian() { + return median; + } + + /** + * @return Returns the throughput. + */ + public double getThroughput() { + return throughput; + } + + /** {@inheritDoc} */ + @Override + public int compareTo(Sample o) { + Sample oo = o; + return ((count - oo.count) < 0 ? -1 : (count == oo.count ? 0 : 1)); + } + + // TODO should equals and hashCode depend on field other than count? + + @Override + public boolean equals(Object o){ + return ( + (o instanceof Sample) && + (this.compareTo((Sample) o) == 0) + ); + } + + @Override + public int hashCode(){ + return (int)(count ^ (count >>> 32)); + } + + /** + * @return Returns the endTime. + */ + public long getEndTime() { + return endTime; + } + + /** + * @return Returns the (calculated) startTime, assuming Data is the elapsed time. + */ + public long getStartTime() { + return endTime-data; + } + + /** + * @param format the format of the time to be used + * @return the start time using the specified format + * Intended for use from Functors + */ + public String getStartTimeFormatted(Format format) { + return format.format(new Date(getStartTime())); + } +} diff --git a/src/core/org/apache/jmeter/visualizers/SamplingStatCalculator.java b/src/core/org/apache/jmeter/visualizers/SamplingStatCalculator.java new file mode 100644 index 00000000000..468ac593bcd --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/SamplingStatCalculator.java @@ -0,0 +1,296 @@ +/* + * 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.visualizers; + +import java.util.Map; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.math.StatCalculatorLong; + +/** + * Aggregate sample data container. Just instantiate a new instance of this + * class, and then call {@link #addSample(SampleResult)} a few times, and pull + * the stats out with whatever methods you prefer. + * + */ +public class SamplingStatCalculator { + private final StatCalculatorLong calculator = new StatCalculatorLong(); + + private double maxThroughput; + + private long firstTime; + + private String label; + + private volatile Sample currentSample; + + public SamplingStatCalculator(){ // Only for use by test code + this(""); + } + + public SamplingStatCalculator(String label) { + this.label = label; + init(); + } + + private void init() { + firstTime = Long.MAX_VALUE; + calculator.clear(); + maxThroughput = Double.MIN_VALUE; + currentSample = new Sample(); + } + + /** + * Clear the counters (useful for differential stats) + * + */ + public synchronized void clear() { + init(); + } + + public Sample getCurrentSample() { + return currentSample; + } + + /** + * Get the elapsed time for the samples + * + * @return how long the samples took + */ + public long getElapsed() { + if (getCurrentSample().getEndTime() == 0) { + return 0;// No samples collected ... + } + return getCurrentSample().getEndTime() - firstTime; + } + + /** + * Returns the throughput associated to this sampler in requests per second. + * May be slightly skewed because it takes the timestamps of the first and + * last samples as the total time passed, and the test may actually have + * started before that start time and ended after that end time. + * + * @return throughput associated with this sampler per second + */ + public double getRate() { + if (calculator.getCount() == 0) { + return 0.0; // Better behaviour when howLong=0 or lastTime=0 + } + + return getCurrentSample().getThroughput(); + } + + /** + * Throughput in bytes / second + * + * @return throughput in bytes/second + */ + public double getBytesPerSecond() { + // Code duplicated from getPageSize() + double rate = 0; + if (this.getElapsed() > 0 && calculator.getTotalBytes() > 0) { + rate = calculator.getTotalBytes() / ((double) this.getElapsed() / 1000); + } + if (rate < 0) { + rate = 0; + } + return rate; + } + + /** + * Throughput in kilobytes / second + * + * @return Throughput in kilobytes / second + */ + public double getKBPerSecond() { + return getBytesPerSecond() / 1024; // 1024=bytes per kb + } + + /** + * calculates the average page size, which means divide the bytes by number + * of samples. + * + * @return average page size in bytes (0 if sample count is zero) + */ + public double getAvgPageBytes() { + long count = calculator.getCount(); + if (count == 0) { + return 0; + } + return calculator.getTotalBytes() / (double) count; + } + + /** + * @return the label of this component + */ + public String getLabel() { + return label; + } + + /** + * Records a sample. + * + * @param res + * the sample to record + * @return newly created sample with current statistics + * + */ + public Sample addSample(SampleResult res) { + long rtime, cmean, cstdv, cmedian, cpercent, eCount, endTime; + double throughput; + boolean rbool; + synchronized (calculator) { + calculator.addValue(res.getTime(), res.getSampleCount()); + calculator.addBytes(res.getBytes()); + setStartTime(res); + eCount = getCurrentSample().getErrorCount(); + eCount += res.getErrorCount(); + endTime = getEndTime(res); + long howLongRunning = endTime - firstTime; + throughput = ((double) calculator.getCount() / (double) howLongRunning) * 1000.0; + if (throughput > maxThroughput) { + maxThroughput = throughput; + } + + rtime = res.getTime(); + cmean = (long)calculator.getMean(); + cstdv = (long)calculator.getStandardDeviation(); + cmedian = calculator.getMedian().longValue(); + cpercent = calculator.getPercentPoint( 0.500 ).longValue(); +// TODO cpercent is the same as cmedian here - why? and why pass it to "distributionLine"? + rbool = res.isSuccessful(); + } + + long count = calculator.getCount(); + Sample s = + new Sample( null, rtime, cmean, cstdv, cmedian, cpercent, throughput, eCount, rbool, count, endTime ); + currentSample = s; + return s; + } + + private long getEndTime(SampleResult res) { + long endTime = res.getEndTime(); + long lastTime = getCurrentSample().getEndTime(); + if (lastTime < endTime) { + lastTime = endTime; + } + return lastTime; + } + + /** + * @param res + */ + private void setStartTime(SampleResult res) { + long startTime = res.getStartTime(); + if (firstTime > startTime) { + // this is our first sample, set the start time to current timestamp + firstTime = startTime; + } + } + + /** + * Returns the raw double value of the percentage of samples with errors + * that were recorded. (Between 0.0 and 1.0) + * + * @return the raw double value of the percentage of samples with errors + * that were recorded. + */ + public double getErrorPercentage() { + double rval = 0.0; + + if (calculator.getCount() == 0) { + return rval; + } + rval = (double) getCurrentSample().getErrorCount() / (double) calculator.getCount(); + return rval; + } + + /** + * For debugging purposes, only. + */ + @Override + public String toString() { + StringBuilder mySB = new StringBuilder(); + + mySB.append("Samples: " + this.getCount() + " "); + mySB.append("Avg: " + this.getMean() + " "); + mySB.append("Min: " + this.getMin() + " "); + mySB.append("Max: " + this.getMax() + " "); + mySB.append("Error Rate: " + this.getErrorPercentage() + " "); + mySB.append("Sample Rate: " + this.getRate()); + return mySB.toString(); + } + + /** + * @return errorCount + */ + public long getErrorCount() { + return getCurrentSample().getErrorCount(); + } + + /** + * @return Returns the maxThroughput. + */ + public double getMaxThroughput() { + return maxThroughput; + } + + public Map getDistribution() { + return calculator.getDistribution(); + } + + public Number getPercentPoint(double percent) { + return calculator.getPercentPoint(percent); + } + + public long getCount() { + return calculator.getCount(); + } + + public Number getMax() { + return calculator.getMax(); + } + + public double getMean() { + return calculator.getMean(); + } + + public Number getMeanAsNumber() { + return Long.valueOf((long) calculator.getMean()); + } + + public Number getMedian() { + return calculator.getMedian(); + } + + public Number getMin() { + if (calculator.getMin().longValue() < 0) { + return Long.valueOf(0); + } + return calculator.getMin(); + } + + public Number getPercentPoint(float percent) { + return calculator.getPercentPoint(percent); + } + + public double getStandardDeviation() { + return calculator.getStandardDeviation(); + } +} diff --git a/src/core/org/apache/jmeter/visualizers/TableSample.java b/src/core/org/apache/jmeter/visualizers/TableSample.java new file mode 100644 index 00000000000..fac3e817613 --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/TableSample.java @@ -0,0 +1,154 @@ +/* + * 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.visualizers; + +import java.io.Serializable; +import java.text.Format; +import java.util.Date; + +/** + * Class to hold data for the TableVisualiser. + */ +public class TableSample implements Serializable, Comparable { + private static final long serialVersionUID = 240L; + + private final long totalSamples; + + private final int sampleCount; // number of samples in this entry + + private final long startTime; + + private final String threadName; + + private final String label; + + private final long elapsed; + + private final boolean success; + + private final long bytes; + + private final long latency; + + private final long connect; + + /** + * @deprecated for unit test code only + */ + @Deprecated + public TableSample() { + this(0, 1, 0, "", "", 0, true, 0, 0, 0); + } + + public TableSample(long totalSamples, int sampleCount, long startTime, String threadName, + String label, + long elapsed, boolean success, long bytes, long latency, long connect) { + this.totalSamples = totalSamples; + this.sampleCount = sampleCount; + this.startTime = startTime; + this.threadName = threadName; + this.label = label; + // SampleCount can be equal to 0, see SubscriberSampler#sample + this.elapsed = (sampleCount > 0) ? elapsed/sampleCount : 0; + this.bytes = (sampleCount > 0) ? bytes/sampleCount : 0; + this.success = success; + this.latency = latency; + this.connect = connect; + } + + // The following getters may appear not to be used - however they are invoked via the Functor class + + public long getBytes() { + return bytes; + } + + public String getSampleNumberString(){ + StringBuilder sb = new StringBuilder(); + if (sampleCount > 1) { + sb.append(totalSamples-sampleCount+1); + sb.append('-'); + } + sb.append(totalSamples); + return sb.toString(); + } + + public long getElapsed() { + return elapsed; + } + + public boolean isSuccess() { + return success; + } + + public long getStartTime() { + return startTime; + } + + /** + * @param format the format to be used on the time + * @return the start time using the specified format + * Intended for use from Functors + */ + public String getStartTimeFormatted(Format format) { + return format.format(new Date(getStartTime())); + } + + public String getThreadName() { + return threadName; + } + + public String getLabel() { + return label; + } + + @Override + public int compareTo(TableSample o) { + TableSample oo = o; + return ((totalSamples - oo.totalSamples) < 0 ? -1 : (totalSamples == oo.totalSamples ? 0 : 1)); + } + + // TODO should equals and hashCode depend on field other than count? + + @Override + public boolean equals(Object o){ + return ( + (o instanceof TableSample) && + (this.compareTo((TableSample) o) == 0) + ); + } + + @Override + public int hashCode(){ + return (int)(totalSamples ^ (totalSamples >>> 32)); + } + + /** + * @return the latency + */ + public long getLatency() { + return latency; + } + + /** + * @return the conneect time + */ + public long getConnectTime() { + return connect; + } +} diff --git a/src/core/org/apache/jmeter/visualizers/Visualizer.java b/src/core/org/apache/jmeter/visualizers/Visualizer.java new file mode 100644 index 00000000000..e44d7296b0a --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/Visualizer.java @@ -0,0 +1,48 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * Implement this method to be a Visualizer for JMeter. This interface defines a + * single method, "add()", that provides the means by which + * {@link org.apache.jmeter.samplers.SampleResult SampleResults} are passed to + * the implementing visualizer for display/logging. The easiest way to create + * the visualizer is to extend the + * {@link org.apache.jmeter.visualizers.gui.AbstractVisualizer} class. + * + */ +public interface Visualizer { + /** + * This method is called by sampling thread to inform the visualizer about + * the arrival of a new sample. + * + * @param sample + * the newly arrived sample + */ + void add(SampleResult sample); + + /** + * This method is used to indicate a visualizer generates statistics. + * + * @return true if visualiser generates statistics + */ + boolean isStats(); +} diff --git a/src/core/org/apache/jmeter/visualizers/gui/AbstractListenerGui.java b/src/core/org/apache/jmeter/visualizers/gui/AbstractListenerGui.java new file mode 100644 index 00000000000..bf76f1686e3 --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/gui/AbstractListenerGui.java @@ -0,0 +1,66 @@ +/* + * 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.visualizers.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * Basic Listener/Visualiser Gui class to correspond with AbstractPreProcessorGui etc. + */ +public abstract class AbstractListenerGui extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most visualizer + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + @Override + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultVisualizerMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#LISTENERS}, which is + * appropriate for most visualizer components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.LISTENERS }); + } + +} diff --git a/src/core/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java b/src/core/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java new file mode 100644 index 00000000000..80d06015a6a --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java @@ -0,0 +1,359 @@ +/* + * 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.visualizers.gui; + +import java.awt.Component; +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.SavePropertyDialog; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.reporters.AbstractListenerElement; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This is the base class for JMeter GUI components which can display test + * results in some way. It provides the following conveniences to developers: + *

    + *
  • Implements the + * {@link org.apache.jmeter.gui.JMeterGUIComponent JMeterGUIComponent} interface + * that allows your Gui visualizer to "plug-in" to the JMeter GUI environment. + * Provides implementations for the following methods: + *
      + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) configure(TestElement)}. + * Any additional parameters of your Visualizer need to be handled by you.
    • + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() createTestElement()}. + * For most purposes, the default + * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} created + * by this method is sufficient.
    • + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#getMenuCategories getMenuCategories()}. + * To control where in the GUI your visualizer can be added.
    • + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) modifyTestElement(TestElement)}. + * Again, additional parameters you require have to be handled by you.
    • + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#createPopupMenu() createPopupMenu()}.
    • + *
    + *
  • + *
  • Provides convenience methods to help you make a JMeter-compatible GUI: + *
      + *
    • {@link #makeTitlePanel()}. Returns a panel that includes the name of + * the component, and a FilePanel that allows users to control what file samples + * are logged to.
    • + *
    • {@link #getModel()} and {@link #setModel(ResultCollector)} methods for + * setting and getting the model class that handles the receiving and logging of + * sample results.
    • + *
    + *
  • + *
+ * For most developers, making a new visualizer is primarly for the purpose of + * either calculating new statistics on the sample results that other + * visualizers don't calculate, or displaying the results visually in a new and + * interesting way. Making a new visualizer for either of these purposes is easy - + * just extend this class and implement the + * {@link org.apache.jmeter.visualizers.Visualizer#add add(SampleResult)} + * method and display the results as you see fit. This AbstractVisualizer and + * the default + * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} handle + * logging and registering to receive SampleEvents for you - all you need to do + * is include the JPanel created by makeTitlePanel somewhere in your gui to + * allow users set the log file. + *

+ * If you are doing more than that, you may need to extend + * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} as well + * and modify the {@link #configure(TestElement)}, + * {@link #modifyTestElement(TestElement)}, and {@link #createTestElement()} + * methods to create and modify your alternate ResultCollector. For an example + * of this, see the + * {@link org.apache.jmeter.visualizers.MailerVisualizer MailerVisualizer}. + */ +public abstract class AbstractVisualizer + extends AbstractListenerGui + implements Visualizer, ChangeListener, UnsharedComponent, Clearable + { + private static final long serialVersionUID = 240L; + + /** Logging. */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** File Extensions */ + private static final String[] EXTS = { ".jtl", ".csv" }; // $NON-NLS-1$ $NON-NLS-2$ + + /** A panel allowing results to be saved. */ + private final FilePanel filePanel; + + /** A checkbox choosing whether or not only errors should be logged. */ + private final JCheckBox errorLogging; + + /* A checkbox choosing whether or not only successes should be logged. */ + private final JCheckBox successOnlyLogging; + + protected ResultCollector collector = new ResultCollector(); + + protected boolean isStats = false; + + public AbstractVisualizer() { + super(); + + // errorLogging and successOnlyLogging are mutually exclusive + errorLogging = new JCheckBox(JMeterUtils.getResString("log_errors_only")); // $NON-NLS-1$ + errorLogging.addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent e) { + if (errorLogging.isSelected()) { + successOnlyLogging.setSelected(false); + } + } + }); + successOnlyLogging = new JCheckBox(JMeterUtils.getResString("log_success_only")); // $NON-NLS-1$ + successOnlyLogging.addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent e) { + if (successOnlyLogging.isSelected()) { + errorLogging.setSelected(false); + } + } + }); + JButton saveConfigButton = new JButton(JMeterUtils.getResString("config_save_settings")); // $NON-NLS-1$ + saveConfigButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SavePropertyDialog d = new SavePropertyDialog( + GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("sample_result_save_configuration"), // $NON-NLS-1$ + true, collector.getSaveConfig()); + d.pack(); + ComponentUtil.centerComponentInComponent(GuiPackage.getInstance().getMainFrame(), d); + d.setVisible(true); + } + }); + + filePanel = new FilePanel(JMeterUtils.getResString("file_visualizer_output_file"), EXTS); // $NON-NLS-1$ + filePanel.addChangeListener(this); + filePanel.add(new JLabel(JMeterUtils.getResString("log_only"))); // $NON-NLS-1$ + filePanel.add(errorLogging); + filePanel.add(successOnlyLogging); + filePanel.add(saveConfigButton); + + } + + @Override + public boolean isStats() { + return isStats; + } + + /** + * Gets the checkbox which selects whether or not only errors should be + * logged. Subclasses don't normally need to worry about this checkbox, + * because it is automatically added to the GUI in {@link #makeTitlePanel()}, + * and the behavior is handled in this base class. + * + * @return the error logging checkbox + */ + protected JCheckBox getErrorLoggingCheckbox() { + return errorLogging; + } + + /** + * Provides access to the ResultCollector model class for extending + * implementations. Using this method and setModel(ResultCollector) is only + * necessary if your visualizer requires a differently behaving + * ResultCollector. Using these methods will allow maximum reuse of the + * methods provided by AbstractVisualizer in this event. + * + * @return the associated collector + */ + protected ResultCollector getModel() { + return collector; + } + + /** + * Gets the file panel which allows the user to save results to a file. + * Subclasses don't normally need to worry about this panel, because it is + * automatically added to the GUI in {@link #makeTitlePanel()}, and the + * behavior is handled in this base class. + * + * @return the file panel allowing users to save results + */ + protected Component getFilePanel() { + return filePanel; + } + + /** + * Sets the filename which results will be saved to. This will set the + * filename in the FilePanel. Subclasses don't normally need to call this + * method, because configuration of the FilePanel is handled in this base + * class. + * + * @param filename + * the new filename + * + * @see #getFilePanel() + */ + public void setFile(String filename) { + // TODO: Does this method need to be public? It isn't currently + // called outside of this class. + filePanel.setFilename(filename); + } + + /** + * Gets the filename which has been entered in the FilePanel. Subclasses + * don't normally need to call this method, because configuration of the + * FilePanel is handled in this base class. + * + * @return the current filename + * + * @see #getFilePanel() + */ + public String getFile() { + // TODO: Does this method need to be public? It isn't currently + // called outside of this class. + return filePanel.getFilename(); + } + + /** + * Invoked when the target of the listener has changed its state. This + * implementation assumes that the target is the FilePanel, and will update + * the result collector for the new filename. + * + * @param e + * the event that has occurred + */ + @Override + public void stateChanged(ChangeEvent e) { + log.debug("getting new collector"); + collector = (ResultCollector) createTestElement(); + collector.loadExistingFile(); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + if (collector == null) { + collector = new ResultCollector(); + } + modifyTestElement(collector); + return (TestElement) collector.clone(); + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement c) { + configureTestElement((AbstractListenerElement) c); + if (c instanceof ResultCollector) { + ResultCollector rc = (ResultCollector) c; + rc.setErrorLogging(errorLogging.isSelected()); + rc.setSuccessOnlyLogging(successOnlyLogging.isSelected()); + rc.setFilename(getFile()); + collector = rc; + } + } + + /* Overrides AbstractJMeterGuiComponent.configure(TestElement) */ + @Override + public void configure(TestElement el) { + super.configure(el); + setFile(el.getPropertyAsString(ResultCollector.FILENAME)); + ResultCollector rc = (ResultCollector) el; + errorLogging.setSelected(rc.isErrorLogging()); + successOnlyLogging.setSelected(rc.isSuccessOnlyLogging()); + if (collector == null) { + collector = new ResultCollector(); + } + collector.setSaveConfig((SampleSaveConfiguration) rc.getSaveConfig().clone()); + } + + /** + * This provides a convenience for extenders when they implement the + * {@link org.apache.jmeter.gui.JMeterGUIComponent#createTestElement()} + * method. This method will set the name, gui class, and test class for the + * created Test Element. It should be called by every extending class when + * creating Test Elements, as that will best assure consistent behavior. + * + * @param mc + * the TestElement being created. + */ + protected void configureTestElement(AbstractListenerElement mc) { + // TODO: Should the method signature of this method be changed to + // match the super-implementation (using a TestElement parameter + // instead of AbstractListenerElement)? This would require an + // instanceof check before adding the listener (below), but would + // also make the behavior a bit more obvious for sub-classes -- the + // Java rules dealing with this situation aren't always intuitive, + // and a subclass may think it is calling this version of the method + // when it is really calling the superclass version instead. + super.configureTestElement(mc); + mc.setListener(this); + } + + /** + * Create a standard title section for JMeter components. This includes the + * title for the component and the Name Panel allowing the user to change + * the name for the component. The AbstractVisualizer also adds the + * FilePanel allowing the user to save the results, and the error logging + * checkbox, allowing the user to choose whether or not only errors should + * be logged. + *

+ * This method is typically added to the top of the component at the + * beginning of the component's init method. + * + * @return a panel containing the component title, name panel, file panel, + * and error logging checkbox + */ + @Override + protected Container makeTitlePanel() { + Container panel = super.makeTitlePanel(); + // Note: the file panel already includes the error logging checkbox, + // so we don't have to add it explicitly. + panel.add(getFilePanel()); + return panel; + } + + /** + * Provides extending classes the opportunity to set the ResultCollector + * model for the Visualizer. This is useful to allow maximum reuse of the + * methods from AbstractVisualizer. + * + * @param collector {@link ResultCollector} for the visualizer + */ + protected void setModel(ResultCollector collector) { + this.collector = collector; + } + + @Override + public void clearGui(){ + super.clearGui(); + filePanel.clearGui(); + } +} diff --git a/src/core/org/apache/jmeter/visualizers/package.html b/src/core/org/apache/jmeter/visualizers/package.html new file mode 100644 index 00000000000..9f2f37dd223 --- /dev/null +++ b/src/core/org/apache/jmeter/visualizers/package.html @@ -0,0 +1,33 @@ + + + + + + + + +This package contains the interfaces that have to be implemented by +any class wishing to display or present data collected in SampleResults. +

+The primary classes/interfaces to be concerned with for implementers is the {@link org.apache.jmeter.visualizers.Visualizer Visualizer} interface, and the {@link org.apache.jmeter.visualizers.gui.AbstractVisualizer AbstractVisualizer} abstract class. + + + diff --git a/src/examples/org/apache/jmeter/examples/sampler/ExampleSampler.java b/src/examples/org/apache/jmeter/examples/sampler/ExampleSampler.java new file mode 100644 index 00000000000..fcc29e889dd --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/sampler/ExampleSampler.java @@ -0,0 +1,128 @@ +/* + * 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.examples.sampler; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Example Sampler (non-Bean version) + * + * JMeter creates an instance of a sampler class for every occurrence of the + * element in every thread. [some additional copies may be created before the + * test run starts] + * + * Thus each sampler is guaranteed to be called by a single thread - there is no + * need to synchronize access to instance variables. + * + * However, access to class fields must be synchronized. + * + * @version $Revision$ + */ +public class ExampleSampler extends AbstractSampler { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // The name of the property used to hold our data + public static final String DATA = "ExampleSampler.data"; //$NON-NLS-1$ + + private static AtomicInteger classCount = new AtomicInteger(0); // keep track of classes created + + // (for instructional purposes only!) + + public ExampleSampler() { + classCount.incrementAndGet(); + trace("ExampleSampler()"); + } + + /** + * {@inheritDoc} + */ + @Override + public SampleResult sample(Entry e) { + trace("sample()"); + SampleResult res = new SampleResult(); + boolean isOK = false; // Did sample succeed? + String data = getData(); // Sampler data + String response = null; + + res.setSampleLabel(getTitle()); + /* + * Perform the sampling + */ + res.sampleStart(); // Start timing + try { + + // Do something here ... + + response = Thread.currentThread().getName(); + + /* + * Set up the sample result details + */ + res.setSamplerData(data); + res.setResponseData(response, null); + res.setDataType(SampleResult.TEXT); + + res.setResponseCodeOK(); + res.setResponseMessage("OK");// $NON-NLS-1$ + isOK = true; + } catch (Exception ex) { + log.debug("", ex); + res.setResponseCode("500");// $NON-NLS-1$ + res.setResponseMessage(ex.toString()); + } + res.sampleEnd(); // End timimg + + res.setSuccessful(isOK); + + return res; + } + + /** + * @return a string for the sampleResult Title + */ + private String getTitle() { + return this.getName(); + } + + /** + * @return the data for the sample + */ + public String getData() { + return getPropertyAsString(DATA); + } + + /* + * Helper method + */ + private void trace(String s) { + String tl = getTitle(); + String tn = Thread.currentThread().getName(); + String th = this.toString(); + log.debug(tn + " (" + classCount.get() + ") " + tl + " " + s + " " + th); + } +} diff --git a/src/examples/org/apache/jmeter/examples/sampler/gui/ExampleSamplerGui.java b/src/examples/org/apache/jmeter/examples/sampler/gui/ExampleSamplerGui.java new file mode 100644 index 00000000000..fa32b3c700e --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/sampler/gui/ExampleSamplerGui.java @@ -0,0 +1,134 @@ +/* + * 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. + * + */ + +/* + * Example Sampler GUI (non-beans version) + */ + +package org.apache.jmeter.examples.sampler.gui; + +import java.awt.BorderLayout; +import java.awt.Component; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import org.apache.jmeter.examples.sampler.ExampleSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Example Sampler (non-Bean version) + * + * This class is responsible for ensuring that the Sampler data is kept in step + * with the GUI. + * + * The GUI class is not invoked in non-GUI mode, so it should not perform any + * additional setup that a test would need at run-time + * + */ +public class ExampleSamplerGui extends AbstractSamplerGui { + + private static final long serialVersionUID = 240L; + + private JTextArea data; + + public ExampleSamplerGui() { + init(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getLabelResource() { + return "example_title"; // $NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement element) { + data.setText(element.getPropertyAsString(ExampleSampler.DATA)); + super.configure(element); + } + + /** + * {@inheritDoc} + */ + @Override + public TestElement createTestElement() { + ExampleSampler sampler = new ExampleSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement te) { + te.clear(); + configureTestElement(te); + te.setProperty(ExampleSampler.DATA, data.getText()); + } + + /* + * Helper method to set up the GUI screen + */ + private void init() { + // Standard setup + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); // Add the standard title + + // Specific setup + add(createDataPanel(), BorderLayout.CENTER); + } + + /* + * Create a data input text field + * + * @return the panel for entering the data + */ + private Component createDataPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("example_data")); //$NON-NLS-1$ + + data = new JTextArea(); + data.setName(ExampleSampler.DATA); + label.setLabelFor(data); + + JPanel dataPanel = new JPanel(new BorderLayout(5, 0)); + dataPanel.add(label, BorderLayout.WEST); + dataPanel.add(data, BorderLayout.CENTER); + + return dataPanel; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + data.setText(""); // $NON-NLS-1$ + + } +} diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example1/Example1.java b/src/examples/org/apache/jmeter/examples/testbeans/example1/Example1.java new file mode 100644 index 00000000000..57bda46f827 --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example1/Example1.java @@ -0,0 +1,62 @@ +/* + * 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.examples.testbeans.example1; + +import java.util.Locale; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; + +/** + * This TestBean is just an example about how to write testbeans. The intent is + * to demonstrate usage of the TestBean features to podential TestBean + * developers. Note that only the class's introspector view matters: the methods + * do nothing -- nothing useful, in any case. + */ +public class Example1 extends AbstractSampler implements TestBean { + + private static final long serialVersionUID = 240L; + + @Override + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.setSamplerData(myStringProperty); + res.sampleStart(); + // Do something ... + res.setResponseData(myStringProperty.toUpperCase(Locale.ENGLISH), null); + res.setDataType(SampleResult.TEXT); + res.sampleEnd(); + res.setSuccessful(true); + return res; + } + private String myStringProperty; + + // A String property: + public void setMyStringProperty(String s) { + myStringProperty=s; + } + + public String getMyStringProperty() { + return myStringProperty; + } + +} diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2.java b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2.java new file mode 100644 index 00000000000..bde1dca392d --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2.java @@ -0,0 +1,64 @@ +/* + * 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.examples.testbeans.example2; + +import java.util.Locale; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; + +/** + * This TestBean is just an example about how to write testbeans. The intent is + * to demonstrate usage of the TestBean features to podential TestBean + * developers. Note that only the class's introspector view matters: the methods + * do nothing -- nothing useful, in any case. + */ +public class Example2 extends AbstractSampler implements TestBean { + + private static final long serialVersionUID = 240L; + + @Override + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.setSamplerData(myStringProperty); + res.sampleStart(); + // Do something ... + res.setResponseData(myStringProperty.toLowerCase(Locale.ENGLISH), null); + res.setDataType(SampleResult.TEXT); + res.sampleEnd(); + res.setSuccessful(true); + return res; + } + + private String myStringProperty; + + // A TestBean is a Java Bean. Just define some properties and they will + // automagically show up in the GUI. + // A String property: + public void setMyStringProperty(String s) { + myStringProperty=s; + } + + public String getMyStringProperty() { + return myStringProperty; + } +} diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2BeanInfo.java b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2BeanInfo.java new file mode 100644 index 00000000000..51dcb412215 --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2BeanInfo.java @@ -0,0 +1,28 @@ +/* + * 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.examples.testbeans.example2; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class Example2BeanInfo extends BeanInfoSupport { + public Example2BeanInfo() { + super(Example2.class); + property("myStringProperty").setValue(DEFAULT, ""); + } +} diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources.properties b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources.properties new file mode 100644 index 00000000000..38654b9d23b --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources.properties @@ -0,0 +1,17 @@ +# 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. + +displayName=Example2 +myStringProperty.displayName=A String \ No newline at end of file diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_es.properties b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_es.properties new file mode 100644 index 00000000000..94a2c8b741c --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_es.properties @@ -0,0 +1,18 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Ejemplo2 +myStringProperty.displayName=Una Cadena diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_pt_BR.properties b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_pt_BR.properties new file mode 100644 index 00000000000..d46e9648a06 --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_pt_BR.properties @@ -0,0 +1,17 @@ +# 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. + +displayName=Exemplo 2 +myStringProperty.displayName=Uma String (cadeia de caracteres) diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_tr.properties b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_tr.properties new file mode 100644 index 00000000000..717ed173240 --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_tr.properties @@ -0,0 +1,18 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=\u00D6rnek2 +myStringProperty.displayName=Bir Metin diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_zh_TW.properties b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_zh_TW.properties new file mode 100644 index 00000000000..2ee13a99a0d --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_zh_TW.properties @@ -0,0 +1,18 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=\u7BC4\u4F8B 2 +myStringProperty.displayName=\u5B57\u4E32 diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3.java b/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3.java new file mode 100644 index 00000000000..c7cce4ef2ae --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3.java @@ -0,0 +1,157 @@ +/* + * 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.examples.testbeans.example3; + +import java.io.File; +import java.lang.reflect.Field; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; + +/** + * This TestBean is just an example of the use of different TestBean types. + */ +public class Example3 extends AbstractSampler implements TestBean { + + private static final long serialVersionUID = 240L; + + private boolean mybool; + private Boolean myBoolean1, myBoolean2; + private int myInt; + private Integer myInteger1, myInteger2; + private long mylong; + private Long myLong1, myLong2; + private String myString1, myString2; + private File myFile1; + private String myFile2; + + @Override + public SampleResult sample(Entry ignored) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.sampleStart(); + StringBuilder bld = new StringBuilder(); + for (Field field : this.getClass().getDeclaredFields()) { + try { + String name = field.getName(); + if (name.startsWith("my")) { + Object value = field.get(this); + bld.append(name).append('='); + bld.append(value); + bld.append(" ("); + bld.append(field.getType().getCanonicalName()); + bld.append(")\n"); + } + } catch (IllegalAccessException e) { + bld.append(e.toString()); + } + } + res.setResponseData(bld.toString(), null); + res.setDataType(SampleResult.TEXT); + res.sampleEnd(); + res.setSuccessful(true); + return res; + } + + public boolean isMybool() { + return mybool; + } + public void setMybool(boolean mybool) { + this.mybool = mybool; + } + public Boolean getMyBoolean1() { + return myBoolean1; + } + public void setMyBoolean1(Boolean myBoolean1) { + this.myBoolean1 = myBoolean1; + } + public Boolean getMyBoolean2() { + return myBoolean2; + } + public void setMyBoolean2(Boolean myBoolean2) { + this.myBoolean2 = myBoolean2; + } + public int getMyInt() { + return myInt; + } + public void setMyInt(int myInt) { + this.myInt = myInt; + } + public Integer getMyInteger1() { + return myInteger1; + } + public void setMyInteger1(Integer myInteger1) { + this.myInteger1 = myInteger1; + } + public Integer getMyInteger2() { + return myInteger2; + } + public void setMyInteger2(Integer myInteger2) { + this.myInteger2 = myInteger2; + } + public long getMylong() { + return mylong; + } + public void setMylong(long mylong) { + this.mylong = mylong; + } + public Long getMyLong1() { + return myLong1; + } + public void setMyLong1(Long myLong1) { + this.myLong1 = myLong1; + } + public Long getMyLong2() { + return myLong2; + } + public void setMyLong2(Long myLong2) { + this.myLong2 = myLong2; + } + public String getMyString1() { + return myString1; + } + public void setMyString1(String myString1) { + this.myString1 = myString1; + } + public String getMyString2() { + return myString2; + } + public void setMyString2(String myString2) { + this.myString2 = myString2; + } + + public File getMyFile1() { + return myFile1; + } + + public void setMyFile1(File myFile) { + this.myFile1 = myFile; + } + + public String getMyFile2() { + return myFile2; + } + + public void setMyFile2(String myFile) { + this.myFile2 = myFile; + } + +} diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3BeanInfo.java b/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3BeanInfo.java new file mode 100644 index 00000000000..ab638653cf6 --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3BeanInfo.java @@ -0,0 +1,57 @@ +/* + * 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.examples.testbeans.example3; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TypeEditor; + +public class Example3BeanInfo extends BeanInfoSupport { + + private PropertyDescriptor getprop(String name) { + final PropertyDescriptor property = property(name); + property.setValue(NOT_UNDEFINED, Boolean.FALSE); // Ensure it is not flagged as 'unconfigured' + return property; + } + + private PropertyDescriptor getprop(String name, Object deflt) { + PropertyDescriptor p = property(name); + p.setValue(DEFAULT, deflt); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + return p; + } + + public Example3BeanInfo() { + super(Example3.class); + getprop("mybool", Boolean.TRUE); // Must use defaults for primitive types + getprop("myBoolean1"); + getprop("myBoolean2", Boolean.TRUE); + getprop("myInt", Integer.valueOf(77)); // Must use defaults for primitive types + getprop("myInteger1"); + getprop("myInteger2", Integer.valueOf(123)); + getprop("mylong", Long.valueOf(99)); // Must use defaults for primitive types + getprop("myLong1"); + getprop("myLong2", Long.valueOf(456)); + getprop("myString1"); + getprop("myString2","abcd"); + getprop("myFile1"); + property("myFile2", TypeEditor.FileEditor); + } +} diff --git a/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3Resources.properties b/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3Resources.properties new file mode 100644 index 00000000000..8caff8ae314 --- /dev/null +++ b/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3Resources.properties @@ -0,0 +1,29 @@ +# 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. + +displayName=Example3 +mybool.displayName=boolean +myBoolean1.displayName=Boolean 1 +myBoolean2.displayName=Boolean 2 (default true) +myInt.displayName=int (77) +myInteger1.displayName=Integer 1 +myInteger2.displayName=Integer 2 (123) +mylong.displayName=long (99) +myLong1.displayName=Long 1 +myLong2.displayName=Long 2 (456) +myString1.displayName=String 1 +myString2.displayName=String 2 (abcd) +myFile1.displayName=File 1 +myFile2.displayName=File 2 (String, FileEditor type) diff --git a/src/functions/org/apache/jmeter/functions/AbstractHostIPName.java b/src/functions/org/apache/jmeter/functions/AbstractHostIPName.java new file mode 100644 index 00000000000..f807ed3c993 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/AbstractHostIPName.java @@ -0,0 +1,84 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +abstract class AbstractHostIPName extends AbstractFunction { + + private static final List desc = new LinkedList(); + + static { + // desc.add("Use fully qualified host name: TRUE/FALSE (Default FALSE)"); + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + public AbstractHostIPName() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + /* + * boolean fullHostName = false; if (((CompoundFunction) values[0]) + * .execute() .toLowerCase() .equals("true")) { fullHostName = true; } + */ + + String value = compute(); + + if (values.length >= 1){// we have a variable name + JMeterVariables vars = getVariables(); + if (vars != null) {// May be null if function is used on TestPlan + String varName = ((CompoundVariable) values[0]).execute().trim(); + if (varName.length() > 0) { + vars.put(varName, value); + } + } + } + return value; + + } + + abstract protected String compute(); + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 0, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/BeanShell.java b/src/functions/org/apache/jmeter/functions/BeanShell.java new file mode 100644 index 00000000000..28f12eebb1c --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/BeanShell.java @@ -0,0 +1,155 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A function which understands BeanShell + * @since 1.X + */ +public class BeanShell extends AbstractFunction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__BeanShell"; //$NON-NLS-1$ + + public static final String INIT_FILE = "beanshell.function.init"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("bsh_function_expression"));// $NON-NLS1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));// $NON-NLS1$ + } + + private Object[] values; + + private BeanShellInterpreter bshInterpreter = null; + + public BeanShell() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + if (bshInterpreter == null) // did we find BeanShell? + { + throw new InvalidVariableException("BeanShell not found"); + } + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + String script = ((CompoundVariable) values[0]).execute(); + String varName = ""; //$NON-NLS-1$ + if (values.length > 1) { + varName = ((CompoundVariable) values[1]).execute().trim(); + } + + String resultStr = ""; //$NON-NLS-1$ + + log.debug("Script=" + script); + + try { + + // Pass in some variables + if (currentSampler != null) { + bshInterpreter.set("Sampler", currentSampler); //$NON-NLS-1$ + } + + if (previousResult != null) { + bshInterpreter.set("SampleResult", previousResult); //$NON-NLS-1$ + } + + // Allow access to context and variables directly + bshInterpreter.set("ctx", jmctx); //$NON-NLS-1$ + bshInterpreter.set("vars", vars); //$NON-NLS-1$ + bshInterpreter.set("props", JMeterUtils.getJMeterProperties()); //$NON-NLS-1$ + bshInterpreter.set("threadName", Thread.currentThread().getName()); //$NON-NLS-1$ + + // Execute the script + Object bshOut = bshInterpreter.eval(script); + if (bshOut != null) { + resultStr = bshOut.toString(); + } + if (vars != null && varName.length() > 0) {// vars will be null on TestPlan + vars.put(varName, resultStr); + } + } catch (Exception ex) // Mainly for bsh.EvalError + { + log.warn("Error running BSH script", ex); + } + + log.debug("Output=" + resultStr); + return resultStr; + + } + + /* + * Helper method for use by scripts + * + */ + public void log_info(String s) { + log.info(s); + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + + checkParameterCount(parameters, 1, 2); + + values = parameters.toArray(); + + try { + bshInterpreter = new BeanShellInterpreter(JMeterUtils.getProperty(INIT_FILE), log); + } catch (ClassNotFoundException e) { + throw new InvalidVariableException("BeanShell not found", e); + } + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/CSVRead.java b/src/functions/org/apache/jmeter/functions/CSVRead.java new file mode 100644 index 00000000000..62367fadee0 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/CSVRead.java @@ -0,0 +1,162 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The function represented by this class allows data to be read from CSV files. + * Syntax is similar to StringFromFile function. The function allows the test to + * line-thru the data in the CSV file - one line per each test. E.g. inserting + * the following in the test scripts : + * + * ${_CSVRead(c:/BOF/abcd.csv,0)} // read (first) line of 'c:/BOF/abcd.csv' , + * return the 1st column ( represented by the '0'), + * ${_CSVRead(c:/BOF/abcd.csv,1)} // read (first) line of 'c:/BOF/abcd.csv' , + * return the 2nd column ( represented by the '1'), + * ${_CSVRead(c:/BOF/abcd.csv,next())} // Go to next line of 'c:/BOF/abcd.csv' + * + * NOTE: A single instance of each different file is opened and used for all + * threads. + * + * To open the same file twice, use the alias function: __CSVRead(abc.csv,*ONE); + * __CSVRead(abc.csv,*TWO); + * + * __CSVRead(*ONE,1); etc + * @since 1.9 + */ +public class CSVRead extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY = "__CSVRead"; // Function name //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + private Object[] values; // Parameter list + + static { + desc.add(JMeterUtils.getResString("csvread_file_file_name")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("column_number")); //$NON-NLS-1$ + } + + public CSVRead() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String myValue = ""; //$NON-NLS-1$ + + String fileName = ((org.apache.jmeter.engine.util.CompoundVariable) values[0]).execute(); + String columnOrNext = ((org.apache.jmeter.engine.util.CompoundVariable) values[1]).execute(); + + if (log.isDebugEnabled()) { + log.debug("execute (" + fileName + " , " + columnOrNext + ") "); + } + + // Process __CSVRead(filename,*ALIAS) + if (columnOrNext.startsWith("*")) { //$NON-NLS-1$ + FileWrapper.open(fileName, columnOrNext); + /* + * All done, so return + */ + return ""; //$NON-NLS-1$ + } + + // if argument is 'next' - go to the next line + if (columnOrNext.equals("next()") || columnOrNext.equals("next")) { //$NON-NLS-1$ //$NON-NLS-2$ + FileWrapper.endRow(fileName); + + /* + * All done now ,so return the empty string - this allows the caller + * to append __CSVRead(file,next) to the last instance of + * __CSVRead(file,col) + * + * N.B. It is important not to read any further lines at this point, + * otherwise the wrong line can be retrieved when using multiple + * threads. + */ + return ""; //$NON-NLS-1$ + } + + try { + int columnIndex = Integer.parseInt(columnOrNext); // what column + // is wanted? + myValue = FileWrapper.getColumn(fileName, columnIndex); + } catch (NumberFormatException e) { + log.warn(Thread.currentThread().getName() + " - can't parse column number: " + columnOrNext + " " + + e.toString()); + } catch (IndexOutOfBoundsException e) { + log.warn(Thread.currentThread().getName() + " - invalid column number: " + columnOrNext + " at row " + + FileWrapper.getCurrentRow(fileName) + " " + e.toString()); + } + + if (log.isDebugEnabled()) { + log.debug("execute value: " + myValue); + } + + return myValue; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + log.debug("setParameter - Collection.size=" + parameters.size()); + + values = parameters.toArray(); + + if (log.isDebugEnabled()) { + for (int i = 0; i < parameters.size(); i++) { + log.debug("i:" + ((CompoundVariable) values[i]).execute()); + } + } + + checkParameterCount(parameters, 2); + + /* + * Need to reset the containers for repeated runs; about the only way + * for functions to detect that a run is starting seems to be the + * setParameters() call. + */ + FileWrapper.clearAll();// TODO only clear the relevant entry - if possible... + + } +} diff --git a/src/functions/org/apache/jmeter/functions/CharFunction.java b/src/functions/org/apache/jmeter/functions/CharFunction.java new file mode 100644 index 00000000000..813e04783f0 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/CharFunction.java @@ -0,0 +1,91 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Function to generate chars from a list of decimal or hex values + * @since 2.3.3 + */ +public class CharFunction extends AbstractFunction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__char"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("char_value")); //$NON-NLS-1$ + } + + private Object[] values; + + public CharFunction() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + StringBuilder sb = new StringBuilder(values.length); + for (int i=0; i < values.length; i++){ + String numberString = ((CompoundVariable) values[i]).execute().trim(); + try { + long value=Long.decode(numberString).longValue(); + char ch = (char) value; + sb.append(ch); + } catch (NumberFormatException e){ + log.warn("Could not parse "+numberString+" : "+e); + } + } + return sb.toString(); + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkMinParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/EscapeHtml.java b/src/functions/org/apache/jmeter/functions/EscapeHtml.java new file mode 100644 index 00000000000..460040d6f82 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/EscapeHtml.java @@ -0,0 +1,93 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.StringEscapeUtils; +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; + +/** + *

Function which escapes the characters in a String using HTML entities.

+ * + *

+ * For example: + *

+ *

"bread" & "butter"

+ * becomes: + *

+ * &quot;bread&quot; &amp; &quot;butter&quot;. + *

+ * + *

Supports all known HTML 4.0 entities. + * Note that the commonly used apostrophe escape character (&apos;) + * is not a legal entity and so is not supported).

+ * + * @see StringEscapeUtils#escapeHtml4(String) (Commons Lang) + * @since 2.3.3 + */ +public class EscapeHtml extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__escapeHtml"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("escape_html_string")); //$NON-NLS-1$ + } + + private Object[] values; + + public EscapeHtml() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + String rawString = ((CompoundVariable) values[0]).execute(); + return StringEscapeUtils.escapeHtml4(rawString); + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/EscapeOroRegexpChars.java b/src/functions/org/apache/jmeter/functions/EscapeOroRegexpChars.java new file mode 100644 index 00000000000..dae8b0e4fa8 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/EscapeOroRegexpChars.java @@ -0,0 +1,113 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.regex.Perl5Compiler; + +/** + * Escape ORO meta characters + * @since 2.9 + */ +public class EscapeOroRegexpChars extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__escapeOroRegexpChars"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("value_to_quote_meta")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private CompoundVariable[] values; + + private static final int MAX_PARAM_COUNT = 2; + + private static final int MIN_PARAM_COUNT = 1; + + private static final int PARAM_NAME = 2; + + /** + * No-arg constructor. + */ + public EscapeOroRegexpChars() { + super(); + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + String valueToEscape = values[0].execute(); + + String varName = "";//$NON-NLS-1$ + if (values.length >= PARAM_NAME) { + varName = values[PARAM_NAME - 1].execute().trim(); + } + + String escapedValue = Perl5Compiler.quotemeta(valueToEscape); + + if (varName.length() > 0) { + JMeterVariables vars = getVariables(); + if (vars != null) {// Can be null if called from Config item testEnded() method + vars.put(varName, escapedValue); + } + } + + if (log.isDebugEnabled()) { + String tn = Thread.currentThread().getName(); + log.debug(tn + " name:" //$NON-NLS-1$ + + varName + " value:" + escapedValue);//$NON-NLS-1$ + } + + return escapedValue; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAM_COUNT, MAX_PARAM_COUNT); + values = parameters.toArray(new CompoundVariable[parameters.size()]); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/EvalFunction.java b/src/functions/org/apache/jmeter/functions/EvalFunction.java new file mode 100644 index 00000000000..5f88250b5c5 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/EvalFunction.java @@ -0,0 +1,87 @@ +/* + * 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; + +// @see PackageTest for unit tests + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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; + +/** + * Function to evaluate a string which may contain variable or function references. + * + * Parameter: string to be evaluated + * + * Returns: the evaluated value + * @since 2.3.1 + */ +public class EvalFunction extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__eval"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + private static final int MAX_PARAMETER_COUNT = 1; + + static { + desc.add(JMeterUtils.getResString("eval_name_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public EvalFunction() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String parameter = ((CompoundVariable) values[0]).execute(); + CompoundVariable cv = new CompoundVariable(parameter); + return cv.execute(); + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/EvalVarFunction.java b/src/functions/org/apache/jmeter/functions/EvalVarFunction.java new file mode 100644 index 00000000000..dfff314b2b5 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/EvalVarFunction.java @@ -0,0 +1,98 @@ +/* + * 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; + +// @see PackageTest for unit tests + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Function to evaluate a string which may contain variable or function references. + * + * Parameter: string to be evaluated + * + * Returns: the evaluated value + * @since 2.3.1 + */ +public class EvalVarFunction extends AbstractFunction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__evalVar"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + private static final int MAX_PARAMETER_COUNT = 1; + + static { + desc.add(JMeterUtils.getResString("evalvar_name_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public EvalVarFunction() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String variableName = ((CompoundVariable) values[0]).execute(); + final JMeterVariables vars = getVariables(); + if (vars == null){ + log.error("Variables have not yet been defined"); + return "**ERROR - see log file**"; + } + String variableValue = vars.get(variableName); + CompoundVariable cv = new CompoundVariable(variableValue); + return cv.execute(); + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/FileRowColContainer.java b/src/functions/org/apache/jmeter/functions/FileRowColContainer.java new file mode 100644 index 00000000000..6ed8ffcef46 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/FileRowColContainer.java @@ -0,0 +1,192 @@ +/* + * 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.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * File data container for CSV (and similar delimited) files Data is accessible + * via row and column number + * + */ +public class FileRowColContainer { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final List> fileData; // Lines in the file, split into columns + + private final String fileName; // name of the file + + public static final String DELIMITER + = JMeterUtils.getPropDefault("csvread.delimiter", // $NON-NLS-1$ + ","); // $NON-NLS-1$ + + /** Keeping track of which row is next to be read. */ + private int nextRow; + + /** Delimiter for this file */ + private final String delimiter; + + public FileRowColContainer(String file, String delim) throws IOException, FileNotFoundException { + log.debug("FRCC(" + file + "," + delim + ")"); + fileName = file; + delimiter = delim; + nextRow = 0; + fileData = new ArrayList>(); + load(); + } + + public FileRowColContainer(String file) throws IOException, FileNotFoundException { + log.debug("FRCC(" + file + ")[" + DELIMITER + "]"); + fileName = file; + delimiter = DELIMITER; + nextRow = 0; + fileData = new ArrayList>(); + load(); + } + + private void load() throws IOException, FileNotFoundException { + + BufferedReader myBread = null; + try { + FileReader fis = new FileReader(fileName); + myBread = new BufferedReader(fis); + String line = myBread.readLine(); + /* + * N.B. Stop reading the file if we get a blank line: This allows + * for trailing comments in the file + */ + while (line != null && line.length() > 0) { + fileData.add(splitLine(line, delimiter)); + line = myBread.readLine(); + } + } catch (FileNotFoundException e) { + fileData.clear(); + log.warn(e.toString()); + throw e; + } catch (IOException e) { + fileData.clear(); + log.warn(e.toString()); + throw e; + } finally { + if (myBread != null) { + myBread.close(); + } + } + } + + /** + * Get the string for the column from the current row + * + * @param row + * row number (from 0) + * @param col + * column number (from 0) + * @return the string (empty if out of bounds) + * @throws IndexOutOfBoundsException + * if the column number is out of bounds + */ + public String getColumn(int row, int col) throws IndexOutOfBoundsException { + String colData; + colData = fileData.get(row).get(col); + log.debug(fileName + "(" + row + "," + col + "): " + colData); + return colData; + } + + /** + * Returns the next row to the caller, and updates it, allowing for wrap + * round + * + * @return the first free (unread) row + * + */ + public int nextRow() { + int row = nextRow; + nextRow++; + if (nextRow >= fileData.size())// 0-based + { + nextRow = 0; + } + log.debug("Row: " + row); + return row; + } + + /** + * Splits the line according to the specified delimiter + * + * @return an ArrayList of Strings containing one element for each value in + * the line + */ + private static List splitLine(String theLine, String delim) { + ArrayList result = new ArrayList(); + StringTokenizer tokener = new StringTokenizer(theLine, delim, true); + /* + * the beginning of the line is a "delimiter" so that ,a,b,c returns "" + * "a" "b" "c" + */ + boolean lastWasDelim = true; + while (tokener.hasMoreTokens()) { + String token = tokener.nextToken(); + if (token.equals(delim)) { + if (lastWasDelim) { + // two delimiters in a row; add an empty String + result.add(""); + } + lastWasDelim = true; + } else { + lastWasDelim = false; + result.add(token); + } + } + if (lastWasDelim) // Catch the trailing delimiter + { + result.add(""); // $NON-NLS-1$ + } + return result; + } + + /** + * @return the file name for this class + */ + public String getFileName() { + return fileName; + } + + /** + * @return Returns the delimiter. + */ + final String getDelimiter() { + return delimiter; + } + + // Added to support external testing + public int getSize(){ + return fileData.size(); + } +} diff --git a/src/functions/org/apache/jmeter/functions/FileToString.java b/src/functions/org/apache/jmeter/functions/FileToString.java new file mode 100644 index 00000000000..26493808cef --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/FileToString.java @@ -0,0 +1,143 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.log.Logger; + +/** + * FileToString Function to read a complete file into a String. + * + * Parameters: + * - file name + * - file encoding (optional) + * - variable name (optional) + * + * Returns: + * - the whole text from a file + * - or **ERR** if an error occurs + * - value is also optionally saved in the variable for later re-use. + * @since 2.4 + */ +public class FileToString extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__FileToString";//$NON-NLS-1$ + + static final String ERR_IND = "**ERR**";//$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("string_from_file_file_name"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("string_from_file_encoding"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));//$NON-NLS-1$ + } + + private static final int MIN_PARAM_COUNT = 1; + + private static final int MAX_PARAM_COUNT = 3; + + private static final int ENCODING = 2; + + private static final int PARAM_NAME = 3; + + private Object[] values; + + public FileToString() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + String fileName = ((CompoundVariable) values[0]).execute(); + + String encoding = null;//means platform default + if (values.length >= ENCODING) { + encoding = ((CompoundVariable) values[ENCODING - 1]).execute().trim(); + if (encoding.length() <= 0) { // empty encoding, return to platorm default + encoding = null; + } + } + + String myName = "";//$NON-NLS-1$ + if (values.length >= PARAM_NAME) { + myName = ((CompoundVariable) values[PARAM_NAME - 1]).execute().trim(); + } + + String myValue = ERR_IND; + + try { + myValue = FileUtils.readFileToString(new File(fileName), encoding); + } catch (IOException e) { + log.warn("Could not read file: "+fileName+" "+e.getMessage(), e); + throw new JMeterStopThreadException("End of sequence", e); + } + + if (myName.length() > 0) { + JMeterVariables vars = getVariables(); + if (vars != null) {// Can be null if called from Config item testEnded() method + vars.put(myName, myValue); + } + } + + if (log.isDebugEnabled()) { + String tn = Thread.currentThread().getName(); + log.debug(tn + " name:" //$NON-NLS-1$ + + myName + " value:" + myValue);//$NON-NLS-1$ + } + + return myValue; + } + + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAM_COUNT, MAX_PARAM_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/FileWrapper.java b/src/functions/org/apache/jmeter/functions/FileWrapper.java new file mode 100644 index 00000000000..6b5cbd5c81b --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/FileWrapper.java @@ -0,0 +1,211 @@ +/* + * 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.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class wraps the FileRowColContainer for use across multiple threads. + * + * It does this by maintaining a list of open files, keyed by file name (or + * alias, if used). A list of open files is also maintained for each thread, + * together with the current line number. + * + */ +public final class FileWrapper { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int NO_LINE = -1; + + private static volatile String defaultFile = ""; // for omitted file names //$NON-NLS-1$ + + /* + * This Map serves two purposes: + * - maps file names to containers + * - ensures only one container per file across all threads + */ + private static final Map fileContainers = + new HashMap(); + + /* The cache of file packs - used to improve thread access */ + private static final ThreadLocal> filePacks = + new ThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + private final FileRowColContainer container; + + private int currentRow; + + /* + * Only needed locally + */ + private FileWrapper(FileRowColContainer fdc) { + super(); + container = fdc; + currentRow = -1; + } + + private static String checkDefault(String file) { + if (file.length() == 0) { + if (fileContainers.size() == 1 && defaultFile.length() > 0) { + log.warn("Using default: " + defaultFile); + file = defaultFile; + } else { + log.error("Cannot determine default file name"); + } + } + return file; + } + + /* + * called by CSVRead(file,alias) + */ + public static synchronized void open(String file, String alias) { + log.info("Opening " + file + " as " + alias); + file = checkDefault(file); + if (alias.length() == 0) { + log.error("Alias cannot be empty"); + return; + } + Map m = filePacks.get(); + if (m.get(alias) == null) { + FileRowColContainer frcc; + try { + frcc = getFile(file, alias); + log.info("Stored " + file + " as " + alias); + m.put(alias, new FileWrapper(frcc)); + } catch (FileNotFoundException e) { + // Already logged + } catch (IOException e) { + // Already logged + } + } + } + + private static FileRowColContainer getFile(String file, String alias) throws FileNotFoundException, IOException { + FileRowColContainer frcc; + if ((frcc = fileContainers.get(alias)) == null) { + frcc = new FileRowColContainer(file); + fileContainers.put(alias, frcc); + log.info("Saved " + file + " as " + alias + " delimiter=<" + frcc.getDelimiter() + ">"); + if (defaultFile.length() == 0) { + defaultFile = file;// Save in case needed later + } + } + return frcc; + } + + /* + * Called by CSVRead(x,next) - sets the row to nil so the next row will be + * picked up the next time round + * + */ + public static void endRow(String file) { + file = checkDefault(file); + Map my = filePacks.get(); + FileWrapper fw = my.get(file); + if (fw == null) { + log.warn("endRow(): no entry for " + file); + } else { + fw.endRow(); + } + } + + private void endRow() { + if (currentRow == NO_LINE) { + log.warn("endRow() called twice in succession"); + } + currentRow = NO_LINE; + } + + public static String getColumn(String file, int col) { + Map my = filePacks.get(); + FileWrapper fw = my.get(file); + if (fw == null) // First call + { + if (file.startsWith("*")) { //$NON-NLS-1$ + log.warn("Cannot perform initial open using alias " + file); + } else { + file = checkDefault(file); + log.info("Attaching " + file); + open(file, file); + fw = my.get(file); + } + // TODO improve the error handling + if (fw == null) { + return ""; //$NON-NLS-1$ + } + } + return fw.getColumn(col); + } + + private String getColumn(int col) { + if (currentRow == NO_LINE) { + currentRow = container.nextRow(); + + } + return container.getColumn(currentRow, col); + } + + /** + * Gets the current row number (mainly for error reporting) + * + * @param file + * name of the file for which the row number is asked + * @return the current row number for this thread, or -1 if + * file was not opened yet + */ + public static int getCurrentRow(String file) { + + Map my = filePacks.get(); + FileWrapper fw = my.get(file); + if (fw == null) // Not yet open + { + return -1; + } + return fw.currentRow; + } + + /** + * + */ + public static void clearAll() { + log.debug("clearAll()"); + Map my = filePacks.get(); + for (Iterator> i = my.entrySet().iterator(); i.hasNext();) { + Map.Entry fw = i.next(); + log.info("Removing " + fw.toString()); + i.remove(); + } + fileContainers.clear(); + defaultFile = ""; //$NON-NLS-1$ + } +} diff --git a/src/functions/org/apache/jmeter/functions/IntSum.java b/src/functions/org/apache/jmeter/functions/IntSum.java new file mode 100644 index 00000000000..7cc6447844f --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/IntSum.java @@ -0,0 +1,106 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Provides an intSum function that adds two or more integer values. + * + * @see LongSum + * @since 1.8.1 + */ +public class IntSum extends AbstractFunction { + private static final List desc = new LinkedList(); + + private static final String KEY = "__intSum"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("intsum_param_1")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("intsum_param_2")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + /** + * No-arg constructor. + */ + public IntSum() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + JMeterVariables vars = getVariables(); + + int sum = 0; + String varName = ((CompoundVariable) values[values.length - 1]).execute().trim(); // trim() see bug 55871 + + for (int i = 0; i < values.length - 1; i++) { + sum += Integer.parseInt(((CompoundVariable) values[i]).execute()); + } + + try { + // Has chances to be a var + sum += Integer.parseInt(varName); + varName = null; // there is no variable name + } catch(NumberFormatException ignored) { + // varName keeps its value and sum has not taken + // into account non numeric or overflowing number + } + + String totalString = Integer.toString(sum); + if (vars != null && varName != null){// vars will be null on TestPlan + vars.put(varName.trim(), totalString); + } + + return totalString; + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkMinParameterCount(parameters, 2); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/IterationCounter.java b/src/functions/org/apache/jmeter/functions/IterationCounter.java new file mode 100644 index 00000000000..b22f56a8d77 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/IterationCounter.java @@ -0,0 +1,122 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Counter that can be referenced anywhere in the Thread Group. It can be configured per User (Thread Local) + * or globally. + * @since 1.X + */ +public class IterationCounter extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__counter"; //$NON-NLS-1$ + + private ThreadLocal perThreadInt; + + private Object[] variables; + + private int globalCounter;//MAXINT = 2,147,483,647 + + private void init(){ + synchronized(this){ + globalCounter=0; + } + perThreadInt = new ThreadLocal(){ + @Override + protected Integer initialValue() { + return Integer.valueOf(0); + } + }; + } + + static { + desc.add(JMeterUtils.getResString("iteration_counter_arg_1")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + public IterationCounter() { + init(); + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + JMeterVariables vars = getVariables(); + + boolean perThread = Boolean.parseBoolean(((CompoundVariable) variables[0]).execute()); + + String varName = ""; //$NON-NLS-1$ + if (variables.length >=2) {// Ensure variable has been provided + varName = ((CompoundVariable) variables[1]).execute().trim(); + } + + String counterString = ""; //$NON-NLS-1$ + + if (perThread) { + int threadCounter; + threadCounter = perThreadInt.get().intValue() + 1; + perThreadInt.set(Integer.valueOf(threadCounter)); + counterString = String.valueOf(threadCounter); + } else { + synchronized (this) { + globalCounter++; + counterString = String.valueOf(globalCounter); + } + } + + // vars will be null on Test Plan + if (vars != null && varName.length() > 0) { + vars.put(varName, counterString); + } + return counterString; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1, 2); + variables = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/JavaScript.java b/src/functions/org/apache/jmeter/functions/JavaScript.java new file mode 100644 index 00000000000..a6563456ced --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/JavaScript.java @@ -0,0 +1,125 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +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.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.Scriptable; + +/** + * javaScript function implementation that executes a piece of JavaScript (not Java!) code and returns its value + * @since 1.9 + */ +public class JavaScript extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__javaScript"; //$NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + static { + desc.add(JMeterUtils.getResString("javascript_expression"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + public JavaScript() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + String script = ((CompoundVariable) values[0]).execute(); + // Allow variable to be omitted + String varName = values.length < 2 ? null : ((CompoundVariable) values[1]).execute().trim(); + String resultStr = ""; + + Context cx = Context.enter(); + try { + + Scriptable scope = cx.initStandardObjects(null); + + // Set up some objects for the script to play with + scope.put("log", scope, log); //$NON-NLS-1$ + scope.put("ctx", scope, jmctx); //$NON-NLS-1$ + scope.put("vars", scope, vars); //$NON-NLS-1$ + scope.put("props", scope, JMeterUtils.getJMeterProperties()); //$NON-NLS-1$ + // Previously mis-spelt as theadName + scope.put("threadName", scope, Thread.currentThread().getName()); //$NON-NLS-1$ + scope.put("sampler", scope, currentSampler); //$NON-NLS-1$ + scope.put("sampleResult", scope, previousResult); //$NON-NLS-1$ + + Object result = cx.evaluateString(scope, script, "", 1, null); //$NON-NLS-1$ + + resultStr = Context.toString(result); + if (varName != null && vars != null) {// vars can be null if run from TestPlan + vars.put(varName, resultStr); + } + + } catch (RhinoException e) { + log.error("Error processing Javascript: [" + script + "]\n", e); + throw new InvalidVariableException("Error processing Javascript: [" + script + "]", e); + } finally { + Context.exit(); + } + + return resultStr; + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1, 2); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/Jexl2Function.java b/src/functions/org/apache/jmeter/functions/Jexl2Function.java new file mode 100644 index 00000000000..dcbfd6c596c --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/Jexl2Function.java @@ -0,0 +1,162 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.jexl2.Expression; +import org.apache.commons.jexl2.JexlContext; +import org.apache.commons.jexl2.JexlEngine; +import org.apache.commons.jexl2.MapContext; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.ThreadListener; +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.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A function which understands Commons JEXL2 + * @since 2.6 + */ +// For unit tests, see TestJexlFunction +public class Jexl2Function extends AbstractFunction implements ThreadListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY = "__jexl2"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + private static final ThreadLocal threadLocalJexl = new ThreadLocal(); + + static + { + desc.add(JMeterUtils.getResString("jexl_expression")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));// $NON-NLS1$ + } + + private Object[] values; + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException + { + String str = ""; //$NON-NLS-1$ + + CompoundVariable var = (CompoundVariable) values[0]; + String exp = var.execute(); + + String varName = ""; //$NON-NLS-1$ + if (values.length > 1) { + varName = ((CompoundVariable) values[1]).execute().trim(); + } + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + try + { + JexlContext jc = new MapContext(); + jc.set("log", log); //$NON-NLS-1$ + jc.set("ctx", jmctx); //$NON-NLS-1$ + jc.set("vars", vars); //$NON-NLS-1$ + jc.set("props", JMeterUtils.getJMeterProperties()); //$NON-NLS-1$ + // Previously mis-spelt as theadName + jc.set("threadName", Thread.currentThread().getName()); //$NON-NLS-1$ + jc.set("sampler", currentSampler); //$NON-NLS-1$ (may be null) + jc.set("sampleResult", previousResult); //$NON-NLS-1$ (may be null) + jc.set("OUT", System.out);//$NON-NLS-1$ + + // Now evaluate the script, getting the result + Expression e = getJexlEngine().createExpression( exp ); + Object o = e.evaluate(jc); + if (o != null) + { + str = o.toString(); + } + if (vars != null && varName.length() > 0) {// vars will be null on TestPlan + vars.put(varName, str); + } + } catch (Exception e) + { + log.error("An error occurred while evaluating the expression \"" + + exp + "\"\n",e); + } + return str; + } + + /** + * Get JexlEngine from ThreadLocal + * @return JexlEngine + */ + private static JexlEngine getJexlEngine() { + JexlEngine engine = threadLocalJexl.get(); + if(engine == null) { + engine = new JexlEngine(); + engine.setCache(512); + engine.setLenient(false); + engine.setSilent(false); + threadLocalJexl.set(engine); + } + return engine; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() + { + return desc; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() + { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) + throws InvalidVariableException + { + checkParameterCount(parameters, 1, 2); + values = parameters.toArray(); + } + + @Override + public void threadStarted() { + } + + @Override + public void threadFinished() { + JexlEngine engine = threadLocalJexl.get(); + if(engine != null) { + engine.clearCache(); + threadLocalJexl.remove(); + } + } + +} diff --git a/src/functions/org/apache/jmeter/functions/JexlFunction.java b/src/functions/org/apache/jmeter/functions/JexlFunction.java new file mode 100644 index 00000000000..f403a9f20b6 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/JexlFunction.java @@ -0,0 +1,133 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.jexl.JexlContext; +import org.apache.commons.jexl.JexlHelper; +import org.apache.commons.jexl.Script; +import org.apache.commons.jexl.ScriptFactory; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +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.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A function which understands Commons JEXL + * @since 2.2 + */ +// For unit tests, see TestJexlFunction +public class JexlFunction extends AbstractFunction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY = "__jexl"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + static + { + desc.add(JMeterUtils.getResString("jexl_expression")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));// $NON-NLS1$ + } + + private Object[] values; + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException + { + String str = ""; //$NON-NLS-1$ + + CompoundVariable var = (CompoundVariable) values[0]; + String exp = var.execute(); + + String varName = ""; //$NON-NLS-1$ + if (values.length > 1) { + varName = ((CompoundVariable) values[1]).execute().trim(); + } + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + try + { + Script script = ScriptFactory.createScript(exp); + JexlContext jc = JexlHelper.createContext(); + @SuppressWarnings("unchecked") + final Map jexlVars = jc.getVars(); + jexlVars.put("log", log); //$NON-NLS-1$ + jexlVars.put("ctx", jmctx); //$NON-NLS-1$ + jexlVars.put("vars", vars); //$NON-NLS-1$ + jexlVars.put("props", JMeterUtils.getJMeterProperties()); //$NON-NLS-1$ + // Previously mis-spelt as theadName + jexlVars.put("threadName", Thread.currentThread().getName()); //$NON-NLS-1$ + jexlVars.put("sampler", currentSampler); //$NON-NLS-1$ (may be null) + jexlVars.put("sampleResult", previousResult); //$NON-NLS-1$ (may be null) + jexlVars.put("OUT", System.out);//$NON-NLS-1$ + + // Now evaluate the script, getting the result + Object o = script.execute(jc); + if (o != null) + { + str = o.toString(); + } + if (vars != null && varName.length() > 0) {// vars will be null on TestPlan + vars.put(varName, str); + } + } catch (Exception e) + { + log.error("An error occurred while evaluating the expression \"" + + exp + "\"\n",e); + } + return str; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() + { + return desc; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() + { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) + throws InvalidVariableException + { + checkParameterCount(parameters, 1, 2); + values = parameters.toArray(); + } + +} diff --git a/src/functions/org/apache/jmeter/functions/LogFunction.java b/src/functions/org/apache/jmeter/functions/LogFunction.java new file mode 100644 index 00000000000..38ead95c097 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/LogFunction.java @@ -0,0 +1,182 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.log.Priority; + +/** + *

+ * Function to log a message. + *

+ * + *

+ * Parameters: + *

    + *
  • string value
  • + *
  • log level (optional; defaults to INFO; or DEBUG if unrecognised; or can use OUT or ERR)
  • + *
  • throwable message (optional)
  • + *
  • comment (optional)
  • + *
+ * Returns: - the input string + * @since 2.2 + */ +public class LogFunction extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__log"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + + private static final int MAX_PARAMETER_COUNT = 4; + static { + desc.add(JMeterUtils.getResString("log_function_string_ret")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_level")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_throwable")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_comment")); //$NON-NLS-1$ + } + + private static final String DEFAULT_PRIORITY = "INFO"; //$NON-NLS-1$ + + private static final String DEFAULT_SEPARATOR = " : "; //$NON-NLS-1$ + + private Object[] values; + + public LogFunction() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + // The method is synchronized to avoid interference of messages from multiple threads + String stringToLog = ((CompoundVariable) values[0]).execute(); + + String priorityString; + if (values.length > 1) { // We have a default + priorityString = ((CompoundVariable) values[1]).execute(); + if (priorityString.length() == 0) { + priorityString = DEFAULT_PRIORITY; + } + } else { + priorityString = DEFAULT_PRIORITY; + } + + Throwable t = null; + if (values.length > 2) { // Throwable wanted + String value = ((CompoundVariable) values[2]).execute(); + if (value.length() > 0) { + t = new Throwable(value); + } + } + + String comment = ""; + if (values.length > 3) { // Comment wanted + comment = ((CompoundVariable) values[3]).execute(); + } + + logDetails(log, stringToLog, priorityString, t, comment); + + return stringToLog; + + } + + // Common output function + private static void printDetails(java.io.PrintStream ps, String s, Throwable t, String c) { + String tn = Thread.currentThread().getName(); + + StringBuilder sb = new StringBuilder(80); + sb.append("Log: "); + sb.append(tn); + if (c.length()>0){ + sb.append(" "); + sb.append(c); + } else { + sb.append(DEFAULT_SEPARATOR); + } + sb.append(s); + if (t != null) { + sb.append(" "); + ps.print(sb.toString()); + t.printStackTrace(ps); + } else { + ps.println(sb.toString()); + } + } + + // Routine to perform the output (also used by __logn() function) + static synchronized void logDetails(Logger l, String s, String prio, Throwable t, String c) { + if (prio.equalsIgnoreCase("OUT")) //$NON-NLS-1 + { + printDetails(System.out, s, t, c); + } else if (prio.equalsIgnoreCase("ERR")) //$NON-NLS-1 + { + printDetails(System.err, s, t, c); + } else { + // N.B. if the string is not recognised, DEBUG is assumed + Priority p = Priority.getPriorityForName(prio); + if (log.isPriorityEnabled(p)) {// Thread method is potentially expensive + String tn = Thread.currentThread().getName(); + StringBuilder sb = new StringBuilder(40); + sb.append(tn); + if (c.length()>0){ + sb.append(" "); + sb.append(c); + } else { + sb.append(DEFAULT_SEPARATOR); + } + sb.append(s); + log.log(p, sb.toString(), t); + } + } + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/LogFunction2.java b/src/functions/org/apache/jmeter/functions/LogFunction2.java new file mode 100644 index 00000000000..81faf8965a5 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/LogFunction2.java @@ -0,0 +1,117 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + *

+ * Function to log a message. + *

+ * + *

+ * Parameters: + *

    + *
  • string value
  • + *
  • log level (optional; defaults to INFO; or DEBUG if unrecognised; or can use OUT or ERR)
  • + *
  • throwable message (optional)
  • + *
+ * Returns: - Empty String (so can be used where return value would be a nuisance) + * @since 2.2 + */ +public class LogFunction2 extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__logn"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + + private static final int MAX_PARAMETER_COUNT = 3; + static { + desc.add(JMeterUtils.getResString("log_function_string")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_level")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_throwable")); //$NON-NLS-1$ + } + + private static final String DEFAULT_PRIORITY = "INFO"; //$NON-NLS-1$ + + private Object[] values; + + public LogFunction2() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String stringToLog = ((CompoundVariable) values[0]).execute(); + + String priorityString; + if (values.length > 1) { // We have a default + priorityString = ((CompoundVariable) values[1]).execute(); + if (priorityString.length() == 0) { + priorityString = DEFAULT_PRIORITY; + } + } else { + priorityString = DEFAULT_PRIORITY; + } + + Throwable t = null; + if (values.length > 2) { // Throwable wanted + t = new Throwable(((CompoundVariable) values[2]).execute()); + } + + LogFunction.logDetails(log, stringToLog, priorityString, t, ""); + + return ""; + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/LongSum.java b/src/functions/org/apache/jmeter/functions/LongSum.java new file mode 100644 index 00000000000..f68cdc4ad4c --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/LongSum.java @@ -0,0 +1,106 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Provides a longSum function that adds two or more long values. + * @see IntSum + * @since 2.3.2 + */ +public class LongSum extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__longSum"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("longsum_param_1")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("longsum_param_2")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + /** + * No-arg constructor. + */ + public LongSum() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + JMeterVariables vars = getVariables(); + + long sum = 0; + String varName = ((CompoundVariable) values[values.length - 1]).execute().trim(); + + for (int i = 0; i < values.length - 1; i++) { + sum += Long.parseLong(((CompoundVariable) values[i]).execute()); + } + + try { + // Has chances to be a var + sum += Long.parseLong(varName); + varName = null; // there is no variable name + } catch(NumberFormatException ignored) { + // varName keeps its value and sum has not taken + // into account non numeric or overflowing number + } + + String totalString = Long.toString(sum); + if (vars != null && varName != null && varName.length() > 0){// vars will be null on TestPlan + vars.put(varName, totalString); + } + + return totalString; + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkMinParameterCount(parameters, 2); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/MachineIP.java b/src/functions/org/apache/jmeter/functions/MachineIP.java new file mode 100644 index 00000000000..a4ab9c4d7f2 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/MachineIP.java @@ -0,0 +1,44 @@ +/* + * 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 org.apache.jmeter.util.JMeterUtils; + +/** + * Return Machine IP + * @since 2.6 + */ +public class MachineIP extends AbstractHostIPName { + + private static final String KEY = "__machineIP"; //$NON-NLS-1$ + + public MachineIP() { + } + + @Override + protected String compute() { + return JMeterUtils.getLocalHostIP(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } +} diff --git a/src/functions/org/apache/jmeter/functions/MachineName.java b/src/functions/org/apache/jmeter/functions/MachineName.java new file mode 100644 index 00000000000..d21548e3e2d --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/MachineName.java @@ -0,0 +1,44 @@ +/* + * 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 org.apache.jmeter.util.JMeterUtils; + +/** + * Return Machine Host + * @since 1.X + */ +public class MachineName extends AbstractHostIPName { + + private static final String KEY = "__machineName"; //$NON-NLS-1$ + + public MachineName() { + } + + @Override + protected String compute() { + return JMeterUtils.getLocalHostName(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } +} diff --git a/src/functions/org/apache/jmeter/functions/Property.java b/src/functions/org/apache/jmeter/functions/Property.java new file mode 100644 index 00000000000..b99359340a6 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/Property.java @@ -0,0 +1,108 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Function to get a JMeter property, and optionally store it + * + * Parameters: + * - property name + * - variable name (optional) + * - default value (optional) + * + * Returns: + * - the property value, but if not found: + * - the default value, but if not defined: + * - the property name itself + * @since 2.0 + */ +public class Property extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__property"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + private static final int MAX_PARAMETER_COUNT = 3; + + static { + desc.add(JMeterUtils.getResString("property_name_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("property_default_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public Property() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String propertyName = ((CompoundVariable) values[0]).execute(); + String propertyDefault = propertyName; + if (values.length > 2) { // We have a 3rd parameter + propertyDefault = ((CompoundVariable) values[2]).execute(); + } + String propertyValue = JMeterUtils.getPropDefault(propertyName, propertyDefault); + if (values.length > 1) { + String variableName = ((CompoundVariable) values[1]).execute(); + if (variableName.length() > 0) {// Allow for empty name + final JMeterVariables variables = getVariables(); + if (variables != null) { + variables.put(variableName, propertyValue); + } + } + } + return propertyValue; + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/Property2.java b/src/functions/org/apache/jmeter/functions/Property2.java new file mode 100644 index 00000000000..eab945b40cf --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/Property2.java @@ -0,0 +1,106 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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; + +/** + * Function to get a JMeter property, or a default. Does not offer the option to + * store the value, as it is just as easy to refetch it. This is a + * specialisation of the __property() function to make it simpler to use for + * ThreadGroup GUI etc. The name is also shorter. + * + * Parameters: - property name - default value (optional; defaults to "1") + * + * Usage: + * + * Define the property in jmeter.properties, or on the command-line: java ... + * -Jpropname=value + * + * Retrieve the value in the appropriate GUI by using the string: + * ${__P(propname)} $(__P(propname,default)} + * + * Returns: - the property value, but if not found - the default value, but if + * not present - "1" (suitable for use in ThreadGroup GUI) + * @since 2.0 + */ +public class Property2 extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__P"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + + private static final int MAX_PARAMETER_COUNT = 2; + static { + desc.add(JMeterUtils.getResString("property_name_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("property_default_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public Property2() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String propertyName = ((CompoundVariable) values[0]).execute(); + + String propertyDefault = "1"; //$NON-NLS-1$ + if (values.length > 1) { // We have a default + propertyDefault = ((CompoundVariable) values[1]).execute(); + } + + String propertyValue = JMeterUtils.getPropDefault(propertyName, propertyDefault); + + return propertyValue; + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/Random.java b/src/functions/org/apache/jmeter/functions/Random.java new file mode 100644 index 00000000000..cd2c41c6cd8 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/Random.java @@ -0,0 +1,109 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.ThreadLocalRandom; + +/** + * Provides a Random function which returns a random long integer between a min + * (first argument) and a max (second argument). + * @since 1.9 + */ +public class Random extends AbstractFunction { + + private static final List desc = new LinkedList(); + private static final String KEY = "__Random"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("minimum_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("maximum_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private CompoundVariable varName, minimum, maximum; + + /** + * No-arg constructor. + */ + public Random() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + + long min = Long.parseLong(minimum.execute().trim()); + long max = Long.parseLong(maximum.execute().trim()); + + long rand = ThreadLocalRandom.current().nextLong(min, max+1); + + String randString = Long.toString(rand); + + if (varName != null) { + JMeterVariables vars = getVariables(); + final String varTrim = varName.execute().trim(); + if (vars != null && varTrim.length() > 0){// vars will be null on TestPlan + vars.put(varTrim, randString); + } + } + + return randString; + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 2, 3); + Object[] values = parameters.toArray(); + + minimum = (CompoundVariable) values[0]; + maximum = (CompoundVariable) values[1]; + if (values.length>2){ + varName = (CompoundVariable) values[2]; + } else { + varName = null; + } + + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/RandomString.java b/src/functions/org/apache/jmeter/functions/RandomString.java new file mode 100644 index 00000000000..1c8bdc6d69b --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/RandomString.java @@ -0,0 +1,131 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.RandomStringUtils; +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.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Provides a RandomString function which returns a random String of length (first argument) + * using characters (second argument) + * @since 2.6 + */ +public class RandomString extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__RandomString"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("random_string_length")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("random_string_chars_to_use")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private CompoundVariable[] values; + + private static final int MAX_PARAM_COUNT = 3; + + private static final int MIN_PARAM_COUNT = 1; + + private static final int CHARS = 2; + + private static final int PARAM_NAME = 3; + + /** + * No-arg constructor. + */ + public RandomString() { + super(); + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + int length = Integer.parseInt(values[0].execute()); + + String charsToUse = null;//means no restriction + if (values.length >= CHARS) { + charsToUse = (values[CHARS - 1]).execute().trim(); + if (charsToUse.length() <= 0) { // empty chars, return to null + charsToUse = null; + } + } + + String myName = "";//$NON-NLS-1$ + if (values.length >= PARAM_NAME) { + myName = (values[PARAM_NAME - 1]).execute().trim(); + } + + String myValue = null; + if(StringUtils.isEmpty(charsToUse)) { + myValue = RandomStringUtils.random(length); + } else { + myValue = RandomStringUtils.random(length, charsToUse); + } + + if (myName.length() > 0) { + JMeterVariables vars = getVariables(); + if (vars != null) {// Can be null if called from Config item testEnded() method + vars.put(myName, myValue); + } + } + + if (log.isDebugEnabled()) { + String tn = Thread.currentThread().getName(); + log.debug(tn + " name:" //$NON-NLS-1$ + + myName + " value:" + myValue);//$NON-NLS-1$ + } + + return myValue; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAM_COUNT, MAX_PARAM_COUNT); + values = parameters.toArray(new CompoundVariable[parameters.size()]); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/RegexFunction.java b/src/functions/org/apache/jmeter/functions/RegexFunction.java new file mode 100644 index 00000000000..79ff6fe9819 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/RegexFunction.java @@ -0,0 +1,290 @@ +/* + * 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.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcher; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Util; +/** + * Implements regular expression parsing of sample results and variables + * @since 1.X + */ + +// @see TestRegexFunction for unit tests + +public class RegexFunction extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String ALL = "ALL"; //$NON-NLS-1$ + + public static final String RAND = "RAND"; //$NON-NLS-1$ + + public static final String KEY = "__regexFunction"; //$NON-NLS-1$ + + private Object[] values;// Parameters are stored here + + // Using the same Random across threads might result in pool performance + // It might make sense to use ThreadLocalRandom or ThreadLocal + private static final Random rand = new Random(); + + private static final List desc = new LinkedList(); + + private static final String TEMPLATE_PATTERN = "\\$(\\d+)\\$"; //$NON-NLS-1$ + /** initialised to the regex \$(\d+)\$ */ + private final Pattern templatePattern; + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 2; + + private static final int MAX_PARAMETER_COUNT = 7; + static { + desc.add(JMeterUtils.getResString("regexfunc_param_1"));// regex //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_2"));// template //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_3"));// which match //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_4"));// between text //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_5"));// default text //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); // output variable name //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_7"));// input variable //$NON-NLS-1$ + } + + public RegexFunction() { + templatePattern = JMeterUtils.getPatternCache().getPattern(TEMPLATE_PATTERN, + Perl5Compiler.READ_ONLY_MASK); + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String valueIndex = ""; //$NON-NLS-1$ + String defaultValue = ""; //$NON-NLS-1$ + String between = ""; //$NON-NLS-1$ + String name = ""; //$NON-NLS-1$ + String inputVariable = ""; //$NON-NLS-1$ + Pattern searchPattern; + Object[] tmplt; + try { + searchPattern = JMeterUtils.getPatternCache().getPattern(((CompoundVariable) values[0]).execute(), + Perl5Compiler.READ_ONLY_MASK); + tmplt = generateTemplate(((CompoundVariable) values[1]).execute()); + + if (values.length > 2) { + valueIndex = ((CompoundVariable) values[2]).execute(); + } + if (valueIndex.length() == 0) { + valueIndex = "1"; //$NON-NLS-1$ + } + + if (values.length > 3) { + between = ((CompoundVariable) values[3]).execute(); + } + + if (values.length > 4) { + String dv = ((CompoundVariable) values[4]).execute(); + if (dv.length() != 0) { + defaultValue = dv; + } + } + + if (values.length > 5) { + name = ((CompoundVariable) values[5]).execute(); + } + + if (values.length > 6) { + inputVariable = ((CompoundVariable) values[6]).execute(); + } + } catch (MalformedCachePatternException e) { + log.error("Malformed cache pattern:"+values[0], e); + throw new InvalidVariableException("Malformed cache pattern:"+values[0], e); + } + + // Relatively expensive operation, so do it once + JMeterVariables vars = getVariables(); + + if (vars == null){// Can happen if called during test closedown + return defaultValue; + } + + if (name.length() > 0) { + vars.put(name, defaultValue); + } + + String textToMatch=null; + + if (inputVariable.length() > 0){ + textToMatch=vars.get(inputVariable); + } else if (previousResult != null){ + textToMatch = previousResult.getResponseDataAsString(); + } + + if (textToMatch == null || textToMatch.length() == 0) { + return defaultValue; + } + + List collectAllMatches = new ArrayList(); + try { + PatternMatcher matcher = JMeterUtils.getMatcher(); + PatternMatcherInput input = new PatternMatcherInput(textToMatch); + while (matcher.contains(input, searchPattern)) { + MatchResult match = matcher.getMatch(); + collectAllMatches.add(match); + } + } finally { + if (name.length() > 0){ + vars.put(name + "_matchNr", Integer.toString(collectAllMatches.size())); //$NON-NLS-1$ + } + } + + if (collectAllMatches.size() == 0) { + return defaultValue; + } + + if (valueIndex.equals(ALL)) { + StringBuilder value = new StringBuilder(); + Iterator it = collectAllMatches.iterator(); + boolean first = true; + while (it.hasNext()) { + if (!first) { + value.append(between); + } else { + first = false; + } + value.append(generateResult(it.next(), name, tmplt, vars)); + } + return value.toString(); + } else if (valueIndex.equals(RAND)) { + MatchResult result = collectAllMatches.get(rand.nextInt(collectAllMatches.size())); + return generateResult(result, name, tmplt, vars); + } else { + try { + int index = Integer.parseInt(valueIndex) - 1; + MatchResult result = collectAllMatches.get(index); + return generateResult(result, name, tmplt, vars); + } catch (NumberFormatException e) { + float ratio = Float.parseFloat(valueIndex); + MatchResult result = collectAllMatches + .get((int) (collectAllMatches.size() * ratio + .5) - 1); + return generateResult(result, name, tmplt, vars); + } catch (IndexOutOfBoundsException e) { + return defaultValue; + } + } + + } + + private void saveGroups(MatchResult result, String namep, JMeterVariables vars) { + if (result != null) { + for (int x = 0; x < result.groups(); x++) { + vars.put(namep + "_g" + x, result.group(x)); //$NON-NLS-1$ + } + } + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + + private String generateResult(MatchResult match, String namep, Object[] template, JMeterVariables vars) { + saveGroups(match, namep, vars); + StringBuilder result = new StringBuilder(); + for (int a = 0; a < template.length; a++) { + if (template[a] instanceof String) { + result.append(template[a]); + } else { + result.append(match.group(((Integer) template[a]).intValue())); + } + } + if (namep.length() > 0){ + vars.put(namep, result.toString()); + } + return result.toString(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + private Object[] generateTemplate(String rawTemplate) { + List pieces = new ArrayList(); + // String or Integer + List combined = new LinkedList(); + PatternMatcher matcher = JMeterUtils.getMatcher(); + Util.split(pieces, matcher, templatePattern, rawTemplate); + PatternMatcherInput input = new PatternMatcherInput(rawTemplate); + boolean startsWith = isFirstElementGroup(rawTemplate); + if (startsWith) { + pieces.remove(0);// Remove initial empty entry + } + Iterator iter = pieces.iterator(); + while (iter.hasNext()) { + boolean matchExists = matcher.contains(input, templatePattern); + if (startsWith) { + if (matchExists) { + combined.add(Integer.valueOf(matcher.getMatch().group(1))); + } + combined.add(iter.next()); + } else { + combined.add(iter.next()); + if (matchExists) { + combined.add(Integer.valueOf(matcher.getMatch().group(1))); + } + } + } + if (matcher.contains(input, templatePattern)) { + combined.add(Integer.valueOf(matcher.getMatch().group(1))); + } + return combined.toArray(); + } + + private boolean isFirstElementGroup(String rawData) { + Pattern pattern = JMeterUtils.getPatternCache().getPattern("^\\$\\d+\\$", //$NON-NLS-1$ + Perl5Compiler.READ_ONLY_MASK); + return JMeterUtils.getMatcher().contains(rawData, pattern); + } + +} diff --git a/src/functions/org/apache/jmeter/functions/SamplerName.java b/src/functions/org/apache/jmeter/functions/SamplerName.java new file mode 100644 index 00000000000..ab22158baed --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/SamplerName.java @@ -0,0 +1,88 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Function to return the name of the current sampler. + * @since 2.5 + */ +public class SamplerName extends AbstractFunction { + + private static final String KEY = "__samplerName"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + static { + // desc.add("Use fully qualified host name: TRUE/FALSE (Default FALSE)"); + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + // return JMeterContextService.getContext().getCurrentSampler().getName(); + String name = ""; + if (currentSampler != null) { // will be null if function is used on TestPlan + name = currentSampler.getName(); + } + if (values.length > 0){ + JMeterVariables vars = getVariables(); + if (vars != null) {// May be null if function is used on TestPlan + String varName = ((CompoundVariable) values[0]).execute().trim(); + if (varName.length() > 0) { + vars.put(varName, name); + } + } + } + return name; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) + throws InvalidVariableException { + checkParameterCount(parameters, 0, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/SetProperty.java b/src/functions/org/apache/jmeter/functions/SetProperty.java new file mode 100644 index 00000000000..a31fb3d1aa3 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/SetProperty.java @@ -0,0 +1,105 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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; + +/** + * Function to set a JMeter property + * + * Parameters: - property name - value + * + * Usage: + * + * Set the property value in the appropriate GUI by using the string: + * ${__setProperty(propname,propvalue[,returnvalue?])} + * + * Returns: nothing or original value if the 3rd parameter is true + * @since 2.1 + */ +public class SetProperty extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__setProperty"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 2; + + private static final int MAX_PARAMETER_COUNT = 3; + static { + desc.add(JMeterUtils.getResString("property_name_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("property_value_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("property_returnvalue_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public SetProperty() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String propertyName = ((CompoundVariable) values[0]).execute(); + + String propertyValue = ((CompoundVariable) values[1]).execute(); + + boolean returnValue = false;// should we return original value? + if (values.length > 2) { + returnValue = ((CompoundVariable) values[2]).execute().equalsIgnoreCase("true"); //$NON-NLS-1$ + } + + if (returnValue) { // Only obtain and cast the return if needed + return (String) JMeterUtils.setProperty(propertyName, propertyValue); + } else { + JMeterUtils.setProperty(propertyName, propertyValue); + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/SplitFunction.java b/src/functions/org/apache/jmeter/functions/SplitFunction.java new file mode 100644 index 00000000000..59853a6fdab --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/SplitFunction.java @@ -0,0 +1,129 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +// @see org.apache.jmeter.functions.PackageTest for unit tests + +/** + * Function to split a string into variables + *

+ * Parameters: + *

    + *
  • String to split
  • + *
  • Variable name prefix
  • + *
  • String to split on (optional, default is comma)
  • + *
+ *

+ * Returns: the input string + *

+ * Also sets the variables: + *
    + *
  • VARNAME - the input string
  • + *
  • VARNAME_n - number of fields found
  • + *
  • VARNAME_1..n - fields
  • + *
+ * @since 2.0.2 + */ +public class SplitFunction extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__split";// $NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 2; + + private static final int MAX_PARAMETER_COUNT = 3; + static { + desc.add(JMeterUtils.getResString("split_function_string")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("split_function_separator"));//$NON-NLS-1$ + } + + private Object[] values; + + public SplitFunction() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + JMeterVariables vars = getVariables(); + + String stringToSplit = ((CompoundVariable) values[0]).execute(); + String varNamePrefix = ((CompoundVariable) values[1]).execute().trim(); + String splitString = ","; + + if (values.length > 2) { // Split string provided + splitString = ((CompoundVariable) values[2]).execute(); + } + if (log.isDebugEnabled()){ + log.debug("Split "+stringToSplit+ " using "+ splitString+ " into "+varNamePrefix); + } + String parts[] = JOrphanUtils.split(stringToSplit, splitString, "?");// $NON-NLS-1$ + + vars.put(varNamePrefix, stringToSplit); + vars.put(varNamePrefix + "_n", Integer.toString(parts.length));// $NON-NLS-1$ + for (int i = 1; i <= parts.length; i++) { + if (log.isDebugEnabled()){ + log.debug(parts[i-1]); + } + vars.put(varNamePrefix + "_" + i, parts[i - 1]);// $NON-NLS-1$ + } + vars.remove(varNamePrefix + "_" + (parts.length+1)); + return stringToSplit; + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/StringFromFile.java b/src/functions/org/apache/jmeter/functions/StringFromFile.java new file mode 100644 index 00000000000..ad526420a2d --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/StringFromFile.java @@ -0,0 +1,339 @@ +/* + * 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.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.log.Logger; + +/** + * StringFromFile Function to read a String from a text file. + * + * Parameters: + * - file name + * - variable name (optional - defaults to StringFromFile_) + * + * Returns: + * - the next line from the file + * - or **ERR** if an error occurs + * - value is also saved in the variable for later re-use. + * + * Ensure that different variable names are used for each call to the function + * + * + * Notes: + *
    + *
  • JMeter instantiates a single copy of each function for every reference in the test plan
  • + *
  • Function instances are shared between threads.
  • + *
  • Each StringFromFile instance reads the file independently. The output variable can be used to save the + * value for later use in the same thread.
  • + *
  • The file name is resolved at file (re-)open time; the file is initially opened on first execution (which could be any thread)
  • + *
  • the output variable name is resolved every time the function is invoked
  • + *
+ * Because function instances are shared, it does not make sense to use the thread number as part of the file name. + * @since 1.9 + */ +public class StringFromFile extends AbstractFunction implements TestStateListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Only modified by static block so no need to synchronize subsequent read-only access + private static final List desc = new LinkedList(); + + private static final String KEY = "__StringFromFile";//$NON-NLS-1$ + + static final String ERR_IND = "**ERR**";//$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("string_from_file_file_name"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("string_from_file_seq_start"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("string_from_file_seq_final"));//$NON-NLS-1$ + } + + private static final int MIN_PARAM_COUNT = 1; + + private static final int PARAM_NAME = 2; + + private static final int PARAM_START = 3; + + private static final int PARAM_END = 4; + + private static final int MAX_PARAM_COUNT = 4; + + private static final int COUNT_UNUSED = -2; + + // @GuardedBy("this") + private Object[] values; + + // @GuardedBy("this") + private BufferedReader myBread = null; // Buffered reader + + // @GuardedBy("this") + private boolean firstTime = false; // should we try to open the file? + + // @GuardedBy("this") + private String fileName; // needed for error messages + + // @GuardedBy("this") + private int myStart = COUNT_UNUSED; + + // @GuardedBy("this") + private int myCurrent = COUNT_UNUSED; + + // @GuardedBy("this") + private int myEnd = COUNT_UNUSED; + + public StringFromFile() { + if (log.isDebugEnabled()) { + log.debug("++++++++ Construct " + this); + } + } + + /** + * Close file and log + */ + private synchronized void closeFile() { + if (myBread == null) { + return; + } + String tn = Thread.currentThread().getName(); + log.info(tn + " closing file " + fileName);//$NON-NLS-1$ + try { + myBread.close(); + } catch (IOException e) { + log.error("closeFile() error: " + e.toString(), e);//$NON-NLS-1$ + } + } + + private synchronized void openFile() { + String tn = Thread.currentThread().getName(); + fileName = ((CompoundVariable) values[0]).execute(); + + String start = ""; + if (values.length >= PARAM_START) { + start = ((CompoundVariable) values[PARAM_START - 1]).execute(); + try { + // Low chances to be non numeric, we parse + myStart = Integer.parseInt(start); + } catch(NumberFormatException e) { + myStart = COUNT_UNUSED;// Don't process invalid numbers + log.warn("Exception parsing "+start + " as int, value will not be considered as Start Number sequence"); + } + } + // Have we used myCurrent yet? + // Set to 1 if start number is missing (to allow for end without start) + if (myCurrent == COUNT_UNUSED) { + myCurrent = myStart == COUNT_UNUSED ? 1 : myStart; + } + + if (values.length >= PARAM_END) { + String tmp = ((CompoundVariable) values[PARAM_END - 1]).execute(); + try { + // Low chances to be non numeric, we parse + myEnd = Integer.parseInt(tmp); + } catch(NumberFormatException e) { + myEnd = COUNT_UNUSED;// Don't process invalid numbers (including "") + log.warn("Exception parsing "+tmp + " as int, value will not be considered as End Number sequence"); + } + } + + if (values.length >= PARAM_START) { + log.info(tn + " Start = " + myStart + " Current = " + myCurrent + " End = " + myEnd);//$NON-NLS-1$ + if (myEnd != COUNT_UNUSED) { + if (myCurrent > myEnd) { + log.info(tn + " No more files to process, " + myCurrent + " > " + myEnd);//$NON-NLS-1$ + myBread = null; + return; + } + } + /* + * DecimalFormat adds the number to the end of the format if there + * are no formatting characters, so we need a way to prevent this + * from messing up the file name. + * + */ + if (myStart != COUNT_UNUSED) // Only try to format if there is a + // number + { + log.info(tn + " using format " + fileName); + try { + DecimalFormat myFormatter = new DecimalFormat(fileName); + fileName = myFormatter.format(myCurrent); + } catch (NumberFormatException e) { + log.warn("Bad file name format ", e); + } + } + myCurrent++;// for next time + } + + log.info(tn + " opening file " + fileName);//$NON-NLS-1$ + try { + myBread = new BufferedReader(new FileReader(fileName)); + } catch (Exception e) { + log.error("openFile() error: " + e.toString());//$NON-NLS-1$ + myBread = null; + } + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String myValue = ERR_IND; + String myName = "StringFromFile_";//$NON-NLS-1$ + if (values.length >= PARAM_NAME) { + myName = ((CompoundVariable) values[PARAM_NAME - 1]).execute().trim(); + } + + /* + * To avoid re-opening the file repeatedly after an error, only try to + * open it in the first execute() call (It may be re=opened at EOF, but + * that will cause at most one failure.) + */ + if (firstTime) { + openFile(); + firstTime = false; + } + + if (null != myBread) { // Did we open the file? + try { + String line = myBread.readLine(); + if (line == null) { // EOF, re-open file + String tn = Thread.currentThread().getName(); + log.info(tn + " EOF on file " + fileName);//$NON-NLS-1$ + closeFile(); + openFile(); + if (myBread != null) { + line = myBread.readLine(); + } else { + line = ERR_IND; + if (myEnd != COUNT_UNUSED) {// Are we processing a file + // sequence? + log.info(tn + " Detected end of sequence."); + throw new JMeterStopThreadException("End of sequence"); + } + } + } + myValue = line; + } catch (IOException e) { + String tn = Thread.currentThread().getName(); + log.error(tn + " error reading file " + e.toString());//$NON-NLS-1$ + } + } else { // File was not opened successfully + if (myEnd != COUNT_UNUSED) {// Are we processing a file sequence? + String tn = Thread.currentThread().getName(); + log.info(tn + " Detected end of sequence."); + throw new JMeterStopThreadException("End of sequence"); + } + } + + if (myName.length() > 0) { + JMeterVariables vars = getVariables(); + if (vars != null) {// Can be null if called from Config item testEnded() method + vars.put(myName, myValue); + } + } + + if (log.isDebugEnabled()) { + String tn = Thread.currentThread().getName(); + log.debug(tn + " name:" //$NON-NLS-1$ + + myName + " value:" + myValue);//$NON-NLS-1$ + } + + return myValue; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + + log.debug(this + "::StringFromFile.setParameters()");//$NON-NLS-1$ + checkParameterCount(parameters, MIN_PARAM_COUNT, MAX_PARAM_COUNT); + values = parameters.toArray(); + + StringBuilder sb = new StringBuilder(40); + sb.append("setParameters(");//$NON-NLS-1$ + for (int i = 0; i < values.length; i++) { + if (i > 0) { + sb.append(","); + } + sb.append(((CompoundVariable) values[i]).getRawParameters()); + } + sb.append(")");//$NON-NLS-1$ + log.info(sb.toString()); + + // N.B. setParameters is called before the test proper is started, + // and thus variables are not interpreted at this point + // So defer the file open until later to allow variable file names to be + // used. + firstTime = true; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + + /** {@inheritDoc} */ + @Override + public void testStarted() { + // + } + + /** {@inheritDoc} */ + @Override + public void testStarted(String host) { + // + } + + /** {@inheritDoc} */ + @Override + public void testEnded() { + this.testEnded(""); //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void testEnded(String host) { + closeFile(); + } + +} diff --git a/src/functions/org/apache/jmeter/functions/TestPlanName.java b/src/functions/org/apache/jmeter/functions/TestPlanName.java new file mode 100644 index 00000000000..4958c6c07f6 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/TestPlanName.java @@ -0,0 +1,71 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.services.FileServer; + +/** + * Returns Test Plan name + * @since 2.6 + */ +public class TestPlanName extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__TestPlanName"; //$NON-NLS-1$ + + /** + * No-arg constructor. + */ + public TestPlanName() { + super(); + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + return FileServer.getFileServer().getScriptName(); + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 0); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/ThreadNumber.java b/src/functions/org/apache/jmeter/functions/ThreadNumber.java new file mode 100644 index 00000000000..70227a600b3 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/ThreadNumber.java @@ -0,0 +1,63 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; + +/** + * Function to return the current thread number. + * @since 1.X + */ +public class ThreadNumber extends AbstractFunction { + + private static final String KEY = "__threadNum"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException { + String threadName = Thread.currentThread().getName(); + return threadName.substring(threadName.lastIndexOf('-') + 1); + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters,0,0); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/TimeFunction.java b/src/functions/org/apache/jmeter/functions/TimeFunction.java new file mode 100644 index 00000000000..8179f057e4f --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/TimeFunction.java @@ -0,0 +1,137 @@ +/* + * 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.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +// See org.apache.jmeter.functions.TestTimeFunction for unit tests + +/** + * __time() function - returns the current time in milliseconds + * @since 2.2 + */ +public class TimeFunction extends AbstractFunction { + + private static final String KEY = "__time"; // $NON-NLS-1$ + + private static final List desc = new LinkedList(); + + // Only modified in class init + private static final Map aliases = new HashMap(); + + static { + desc.add(JMeterUtils.getResString("time_format")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + aliases.put("YMD", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.YMD", //$NON-NLS-1$ + "yyyyMMdd")); //$NON-NLS-1$ + aliases.put("HMS", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.HMS", //$NON-NLS-1$ + "HHmmss")); //$NON-NLS-1$ + aliases.put("YMDHMS", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.YMDHMS", //$NON-NLS-1$ + "yyyyMMdd-HHmmss")); //$NON-NLS-1$ + aliases.put("USER1", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.USER1","")); //$NON-NLS-1$ + aliases.put("USER2", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.USER2","")); //$NON-NLS-1$ + } + + // Ensure that these are set, even if no paramters are provided + private String format = ""; //$NON-NLS-1$ + private String variable = ""; //$NON-NLS-1$ + + public TimeFunction(){ + super(); + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException { + String datetime; + if (format.length() == 0){// Default to milliseconds + datetime = Long.toString(System.currentTimeMillis()); + } else { + // Resolve any aliases + String fmt = aliases.get(format); + if (fmt == null) { + fmt = format;// Not found + } + // TODO: avoid regexp parsing in loop + if (fmt.matches("/\\d+")) { // divisor is a positive number + long div = Long.parseLong(fmt.substring(1)); // should never case NFE + datetime = Long.toString((System.currentTimeMillis() / div)); + } else { + SimpleDateFormat df = new SimpleDateFormat(fmt);// Not synchronised, so can't be shared + datetime = df.format(new Date()); + } + } + + if (variable.length() > 0) { + JMeterVariables vars = getVariables(); + if (vars != null){// vars will be null on TestPlan + vars.put(variable, datetime); + } + } + return datetime; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + + checkParameterCount(parameters, 0, 2); + + Object []values = parameters.toArray(); + int count = values.length; + + if (count > 0) { + format = ((CompoundVariable) values[0]).execute(); + } + + if (count > 1) { + variable = ((CompoundVariable)values[1]).execute().trim(); + } + + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/UnEscape.java b/src/functions/org/apache/jmeter/functions/UnEscape.java new file mode 100644 index 00000000000..ac148a9d4dd --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/UnEscape.java @@ -0,0 +1,83 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.StringEscapeUtils; +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; + +/** + * Function to unescape any Java literals found in the String. + * For example, it will turn a sequence of '\' and 'n' into a newline character, + * unless the '\' is preceded by another '\'. + * + * @see StringEscapeUtils#unescapeJava(String) + * @since 2.3.3 + */ +public class UnEscape extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__unescape"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("unescape_string")); //$NON-NLS-1$ + } + + private Object[] values; + + public UnEscape() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + String rawString = ((CompoundVariable) values[0]).execute(); + return StringEscapeUtils.unescapeJava(rawString); + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/UnEscapeHtml.java b/src/functions/org/apache/jmeter/functions/UnEscapeHtml.java new file mode 100644 index 00000000000..3449a206960 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/UnEscapeHtml.java @@ -0,0 +1,89 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.StringEscapeUtils; +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; + +/** + * Function to unescape a string containing entity escapes + * to a string containing the actual Unicode characters corresponding to the escapes. + * Supports HTML 4.0 entities. + *

+ * For example, the string "&lt;Fran&ccedil;ais&gt;" will become "<Français>" + *

+ *

+ * If an entity is unrecognized, it is left alone, and inserted verbatim into the result string. + * e.g. "&gt;&zzzz;x" will become ">&zzzz;x". + *

+ * @see org.apache.commons.lang3.StringEscapeUtils#unescapeHtml4(String) + * @since 2.3.3 + */ +public class UnEscapeHtml extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__unescapeHtml"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("unescape_html_string")); //$NON-NLS-1$ + } + + private Object[] values; + + public UnEscapeHtml() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + String escapedString = ((CompoundVariable) values[0]).execute(); + return StringEscapeUtils.unescapeHtml4(escapedString); + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/UrlDecode.java b/src/functions/org/apache/jmeter/functions/UrlDecode.java new file mode 100644 index 00000000000..9478a41055c --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/UrlDecode.java @@ -0,0 +1,87 @@ +/* + * 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.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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; + +/** + * Function to decode a application/x-www-form-urlencoded string. + * + * @since 2.10 + */ +public class UrlDecode extends AbstractFunction { + + private static final String CHARSET_ENCODING = "UTF-8"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + private static final String KEY = "__urldecode"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("urldecode_string")); //$NON-NLS-1$ + } + + private Object[] values; + + public UrlDecode() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String decodeString = ""; //$NON-NLS-1$ + try { + String rawString = ((CompoundVariable) values[0]).execute(); + decodeString = URLDecoder.decode(rawString, CHARSET_ENCODING); + } catch (UnsupportedEncodingException uee) { + return null; + } + return decodeString; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/UrlEncode.java b/src/functions/org/apache/jmeter/functions/UrlEncode.java new file mode 100644 index 00000000000..0e3e2da71bf --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/UrlEncode.java @@ -0,0 +1,87 @@ +/* + * 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.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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; + +/** + * Function to encode a string to a application/x-www-form-urlencoded string. + * + * @since 2.10 + */ +public class UrlEncode extends AbstractFunction { + + private static final String CHARSET_ENCODING = "UTF-8"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + private static final String KEY = "__urlencode"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("urlencode_string")); //$NON-NLS-1$ + } + + private Object[] values; + + public UrlEncode() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String decodeString = ""; //$NON-NLS-1$ + try { + String encodedString = ((CompoundVariable) values[0]).execute(); + decodeString = URLEncoder.encode(encodedString, CHARSET_ENCODING); + } catch (UnsupportedEncodingException uee) { + return null; + } + return decodeString; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } +} diff --git a/src/functions/org/apache/jmeter/functions/Uuid.java b/src/functions/org/apache/jmeter/functions/Uuid.java new file mode 100644 index 00000000000..77f6718bb91 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/Uuid.java @@ -0,0 +1,69 @@ +/* + * 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 org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +/** + * Function to create a UUID + * + * Parameters: + * - None + * + * Returns: + * - A pseudo random UUID 4 + * @since 2.9 + */ +public class Uuid extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__UUID"; //$NON-NLS-1$ + + public Uuid() { + } + + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException { + return UUID.randomUUID().toString(); + } + + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 0, 0); + } + + @Override + public String getReferenceKey() { + return KEY; + } + + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/Variable.java b/src/functions/org/apache/jmeter/functions/Variable.java new file mode 100644 index 00000000000..8d27d48a8d5 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/Variable.java @@ -0,0 +1,90 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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; + +/** + * Function to get a JMeter Variable + * + * Parameters: + * - variable name + * + * Returns: + * - the variable value, but if not found + * - the variable name itself + * @since 2.3RC3 + */ +public class Variable extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__V"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + private static final int MAX_PARAMETER_COUNT = 1; + + static { + desc.add(JMeterUtils.getResString("variable_name_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public Variable() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String variableName = ((CompoundVariable) values[0]).execute(); + String variableValue = getVariables().get(variableName); + return variableValue == null? variableName : variableValue; + + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/XPath.java b/src/functions/org/apache/jmeter/functions/XPath.java new file mode 100644 index 00000000000..a1359dc82c3 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/XPath.java @@ -0,0 +1,126 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.List; + +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.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// @see org.apache.jmeter.functions.PackageTest for unit tests + +/** + * The function represented by this class allows data to be read from XML files. + * Syntax is similar to the CVSRead function. The function allows the test to + * line-thru the nodes in the XML file - one node per each test. E.g. inserting + * the following in the test scripts : + * + * ${_XPath(c:/BOF/abcd.xml,/xpath/)} // match the (first) node + * ${_XPath(c:/BOF/abcd.xml,/xpath/)} // Go to next match of '/xpath/' expression + * + * NOTE: A single instance of each different file/expression combination + * is opened and used for all threads. + * @since 2.0.3 + */ +public class XPath extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // static { + // LoggingManager.setPriority("DEBUG","jmeter"); + // LoggingManager.setTarget(new java.io.PrintWriter(System.out)); + // } + private static final String KEY = "__XPath"; // Function name //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + private Object[] values; // Parameter list + + static { + desc.add(JMeterUtils.getResString("xpath_file_file_name")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("xpath_expression")); //$NON-NLS-1$ + } + + public XPath() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String myValue = ""; //$NON-NLS-1$ + + String fileName = ((CompoundVariable) values[0]).execute(); + String xpathString = ((CompoundVariable) values[1]).execute(); + + if (log.isDebugEnabled()){ + log.debug("execute (" + fileName + " " + xpathString + ") "); + } + + myValue = XPathWrapper.getXPathString(fileName, xpathString); + + if (log.isDebugEnabled()){ + log.debug("execute value: " + myValue); + } + + return myValue; + } + + /** {@inheritDoc} */ + @Override + public List getArgumentDesc() { + return desc; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + log.debug("setParameter - Collection.size=" + parameters.size()); + + values = parameters.toArray(); + + if (log.isDebugEnabled()) { + for (int i = 0; i < parameters.size(); i++) { + log.debug("i:" + ((CompoundVariable) values[i]).execute()); + } + } + + checkParameterCount(parameters, 2); + + /* + * Need to reset the containers for repeated runs; about the only way + * for functions to detect that a run is starting seems to be the + * setParameters() call. + */ + XPathWrapper.clearAll();// TODO only clear the relevant entry - if possible... + + } +} diff --git a/src/functions/org/apache/jmeter/functions/XPathFileContainer.java b/src/functions/org/apache/jmeter/functions/XPathFileContainer.java new file mode 100644 index 00000000000..4a815965e4a --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/XPathFileContainer.java @@ -0,0 +1,138 @@ +/* + * 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.BufferedInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +//@see org.apache.jmeter.functions.PackageTest for unit tests + +/** + * File data container for XML files Data is accessible via XPath + * + */ +public class XPathFileContainer { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final NodeList nodeList; + + private final String fileName; // name of the file + + private final String xpath; + + /** Keeping track of which row is next to be read. */ + private int nextRow;// probably does not need to be synch (always accessed through ThreadLocal?) + int getNextRow(){// give access to Test code + return nextRow; + } + + public XPathFileContainer(String file, String xpath) throws FileNotFoundException, IOException, + ParserConfigurationException, SAXException, TransformerException { + if(log.isDebugEnabled()) { + log.debug("XPath(" + file + ") xpath " + xpath); + } + fileName = file; + this.xpath = xpath; + nextRow = 0; + nodeList=load(); + } + + private NodeList load() throws IOException, FileNotFoundException, ParserConfigurationException, SAXException, + TransformerException { + InputStream fis = null; + NodeList nl = null; + try { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + fis = new BufferedInputStream(new FileInputStream(fileName)); + nl = XPathUtil.selectNodeList(builder.parse(fis), xpath); + if(log.isDebugEnabled()) { + log.debug("found " + nl.getLength()); + } + } catch (FileNotFoundException e) { + log.warn(e.toString()); + throw e; + } catch (IOException e) { + log.warn(e.toString()); + throw e; + } catch (ParserConfigurationException e) { + log.warn(e.toString()); + throw e; + } catch (SAXException e) { + log.warn(e.toString()); + throw e; + } catch (TransformerException e) { + log.warn(e.toString()); + throw e; + } finally { + JOrphanUtils.closeQuietly(fis); + } + return nl; + } + + public String getXPathString(int num) { + return nodeList.item(num).getNodeValue(); + } + + /** + * Returns the next row to the caller, and updates it, allowing for wrap + * round + * + * @return the first free (unread) row + * + */ + public int nextRow() { + int row = nextRow; + nextRow++; + if (nextRow >= size())// 0-based + { + nextRow = 0; + } + log.debug(new StringBuilder("Row: ").append(row).toString()); + return row; + } + + public int size() { + return (nodeList == null) ? -1 : nodeList.getLength(); + } + + /** + * @return the file name for this class + */ + public String getFileName() { + return fileName; + } + +} diff --git a/src/functions/org/apache/jmeter/functions/XPathWrapper.java b/src/functions/org/apache/jmeter/functions/XPathWrapper.java new file mode 100644 index 00000000000..47c33833280 --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/XPathWrapper.java @@ -0,0 +1,135 @@ +/* + * 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.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xml.sax.SAXException; + +/** + * This class wraps the XPathFileContainer for use across multiple threads. + * + * It maintains a list of nodelist containers, one for each file/xpath combination + * + */ +final class XPathWrapper { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * This Map serves two purposes: + *
    + *
  • maps names to containers
  • + *
  • ensures only one container per file across all threads
  • + *
+ * The key is the concatenation of the file name and the XPath string + */ + //@GuardedBy("fileContainers") + private static final Map fileContainers = + new HashMap(); + + /* The cache of file packs - for faster local access */ + private static final ThreadLocal> filePacks = + new ThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + private XPathWrapper() {// Prevent separate instantiation + super(); + } + + private static XPathFileContainer open(String file, String xpathString) { + String tname = Thread.currentThread().getName(); + log.info(tname+": Opening " + file); + XPathFileContainer frcc=null; + try { + frcc = new XPathFileContainer(file, xpathString); + } catch (FileNotFoundException e) { + log.warn(e.getLocalizedMessage()); + } catch (IOException e) { + log.warn(e.getLocalizedMessage()); + } catch (ParserConfigurationException e) { + log.warn(e.getLocalizedMessage()); + } catch (SAXException e) { + log.warn(e.getLocalizedMessage()); + } catch (TransformerException e) { + log.warn(e.getLocalizedMessage()); + } + return frcc; + } + + /** + * Not thread-safe - must be called from a synchronized method. + * + * @param file name of the file + * @param xpathString xpath to look up in file + * @return the next row from the file container + */ + public static String getXPathString(String file, String xpathString) { + Map my = filePacks.get(); + String key = file+xpathString; + XPathFileContainer xpfc = my.get(key); + if (xpfc == null) // We don't have a local copy + { + synchronized(fileContainers){ + xpfc = fileContainers.get(key); + if (xpfc == null) { // There's no global copy either + xpfc=open(file, xpathString); + } + if (xpfc != null) { + fileContainers.put(key, xpfc);// save the global copy + } + } + // TODO improve the error handling + if (xpfc == null) { + log.error("XPathFileContainer is null!"); + return ""; //$NON-NLS-1$ + } + my.put(key,xpfc); // save our local copy + } + if (xpfc.size()==0){ + log.warn("XPathFileContainer has no nodes: "+file+" "+xpathString); + return ""; //$NON-NLS-1$ + } + int currentRow = xpfc.nextRow(); + log.debug("getting match number " + currentRow); + return xpfc.getXPathString(currentRow); + } + + public static void clearAll() { + log.debug("clearAll()"); + filePacks.get().clear(); + String tname = Thread.currentThread().getName(); + log.info(tname+": clearing container"); + synchronized (fileContainers) { + fileContainers.clear(); + } + } +} diff --git a/src/functions/org/apache/jmeter/functions/package.html b/src/functions/org/apache/jmeter/functions/package.html new file mode 100644 index 00000000000..05a0cd7b79f --- /dev/null +++ b/src/functions/org/apache/jmeter/functions/package.html @@ -0,0 +1,41 @@ + + + +

Functions

+

Methods to be implemented

+ setParameters(Collection) + +

+ execute(prevResult,currentSampler) + Note that either or both of the parameters may be null. + +

Calling sequence

+ When the test plan is prepared for running, one instance of the class is created for each occurrence + of a function call. The setParameters() method is then called on each instance. + Once the test is running, the execute method can be called by any thread, and is + therefore synchronized. + + This is unlike most of (all?) the JMeter test elements, which are created for each thread. + + Any context that needs to be maintained for a thread must be done using ThreadLocal or similar. + + + \ No newline at end of file diff --git a/src/i18nedit.properties b/src/i18nedit.properties new file mode 100644 index 00000000000..9550dd9a7fa --- /dev/null +++ b/src/i18nedit.properties @@ -0,0 +1,25 @@ +# 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. + +#I18NEdit settings for project +# Do not change the default; it must remain as "en" +locale.default=en +locales=de es fr ja no pl pt_BR tr zh_CN zh_TW +main.name=jmeter +# Do not change the sourcelocale unless you are sure what you are doing +personal.User.sourcelocale=en +# Change the target locale as needed +personal.User.targetlocale=xx +personal.User.workmode=directed \ No newline at end of file diff --git a/src/jorphan/org/apache/commons/cli/avalon/AbstractParserControl.java b/src/jorphan/org/apache/commons/cli/avalon/AbstractParserControl.java new file mode 100644 index 00000000000..24dc4791579 --- /dev/null +++ b/src/jorphan/org/apache/commons/cli/avalon/AbstractParserControl.java @@ -0,0 +1,42 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * Class to inherit from so when in future when new controls are added clients + * will no have to implement them. + * + * @see ParserControl + */ +public abstract class AbstractParserControl implements ParserControl { + /** + * By default always continue parsing by returning false. + * + * @param lastOptionCode + * the code of last option parsed + * @return return true to halt, false to continue parsing + * @see ParserControl#isFinished(int) + */ + @Override + public boolean isFinished(int lastOptionCode) { + return false; + } +} diff --git a/src/jorphan/org/apache/commons/cli/avalon/CLArgsParser.java b/src/jorphan/org/apache/commons/cli/avalon/CLArgsParser.java new file mode 100644 index 00000000000..ce734756d03 --- /dev/null +++ b/src/jorphan/org/apache/commons/cli/avalon/CLArgsParser.java @@ -0,0 +1,682 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +import java.text.ParseException; +import java.util.Hashtable; +import java.util.Vector; + +/** + * Parser for command line arguments. + * + * This parses command lines according to the standard (?) of GNU utilities. + * + * Note: This is still used in 1.1 libraries so do not add 1.2+ dependencies. + * + * Note that CLArgs uses a backing hashtable for the options index and so + * duplicate arguments are only returned by getArguments(). + * + * @see ParserControl + * @see CLOption + * @see CLOptionDescriptor + */ +public final class CLArgsParser { + // cached character == Integer.MAX_VALUE when invalid + private static final int INVALID = Integer.MAX_VALUE; + + private static final int STATE_NORMAL = 0; + + private static final int STATE_REQUIRE_2ARGS = 1; + + private static final int STATE_REQUIRE_ARG = 2; + + private static final int STATE_OPTIONAL_ARG = 3; + + private static final int STATE_NO_OPTIONS = 4; + + private static final int STATE_OPTION_MODE = 5; + + // Values for creating tokens + private static final int TOKEN_SEPARATOR = 0; + + private static final int TOKEN_STRING = 1; + + private static final char[] ARG_SEPARATORS = new char[] { (char) 0, '=' }; + + private static final char[] NULL_SEPARATORS = new char[] { (char) 0 }; + + private final CLOptionDescriptor[] m_optionDescriptors; + + private final Vector m_options; + + // Key is String or Integer + private Hashtable m_optionIndex; + + private final ParserControl m_control; + + private String m_errorMessage; + + private String[] m_unparsedArgs = new String[] {}; + + // variables used while parsing options. + private char m_ch; + + private String[] m_args; + + private boolean m_isLong; + + private int m_argIndex; + + private int m_stringIndex; + + private int m_stringLength; + + private int m_lastChar = INVALID; + + private int m_lastOptionId; + + private CLOption m_option; + + private int m_state = STATE_NORMAL; + + /** + * Retrieve an array of arguments that have not been parsed due to the + * parser halting. + * + * @return an array of unparsed args + */ + public final String[] getUnparsedArgs() { + return m_unparsedArgs; + } + + /** + * Retrieve a list of options that were parsed from command list. + * + * @return the list of options + */ + public final Vector getArguments() { + // System.out.println( "Arguments: " + m_options ); + return m_options; + } + + /** + * Retrieve the {@link CLOption} with specified id, or null + * if no command line option is found. + * + * @param id + * the command line option id + * @return the {@link CLOption} with the specified id, or null + * if no CLOption is found. + * @see CLOption + */ + public final CLOption getArgumentById(final int id) { + return m_optionIndex.get(Integer.valueOf(id)); + } + + /** + * Retrieve the {@link CLOption} with specified name, or null + * if no command line option is found. + * + * @param name + * the command line option name + * @return the {@link CLOption} with the specified name, or + * null if no CLOption is found. + * @see CLOption + */ + public final CLOption getArgumentByName(final String name) { + return m_optionIndex.get(name); + } + + /** + * Get Descriptor for option id. + * + * @param id + * the id + * @return the descriptor + */ + private final CLOptionDescriptor getDescriptorFor(final int id) { + for (int i = 0; i < m_optionDescriptors.length; i++) { + if (m_optionDescriptors[i].getId() == id) { + return m_optionDescriptors[i]; + } + } + + return null; + } + + /** + * Retrieve a descriptor by name. + * + * @param name + * the name + * @return the descriptor + */ + private final CLOptionDescriptor getDescriptorFor(final String name) { + for (int i = 0; i < m_optionDescriptors.length; i++) { + if (m_optionDescriptors[i].getName().equals(name)) { + return m_optionDescriptors[i]; + } + } + + return null; + } + + /** + * Retrieve an error message that occured during parsing if one existed. + * + * @return the error string + */ + public final String getErrorString() { + // System.out.println( "ErrorString: " + m_errorMessage ); + return m_errorMessage; + } + + /** + * Require state to be placed in for option. + * + * @param descriptor + * the Option Descriptor + * @return the state + */ + private final int getStateFor(final CLOptionDescriptor descriptor) { + final int flags = descriptor.getFlags(); + if ((flags & CLOptionDescriptor.ARGUMENTS_REQUIRED_2) == CLOptionDescriptor.ARGUMENTS_REQUIRED_2) { + return STATE_REQUIRE_2ARGS; + } else if ((flags & CLOptionDescriptor.ARGUMENT_REQUIRED) == CLOptionDescriptor.ARGUMENT_REQUIRED) { + return STATE_REQUIRE_ARG; + } else if ((flags & CLOptionDescriptor.ARGUMENT_OPTIONAL) == CLOptionDescriptor.ARGUMENT_OPTIONAL) { + return STATE_OPTIONAL_ARG; + } else { + return STATE_NORMAL; + } + } + + /** + * Create a parser that can deal with options and parses certain args. + * + * @param args + * the args, typically that passed to the + * public static void main(String[] args) method. + * @param optionDescriptors + * the option descriptors + * @param control + * the parser control used determine behaviour of parser + */ + public CLArgsParser(final String[] args, final CLOptionDescriptor[] optionDescriptors, final ParserControl control) { + m_optionDescriptors = optionDescriptors; + m_control = control; + m_options = new Vector(); + m_args = args; + + try { + parse(); + checkIncompatibilities(m_options); + buildOptionIndex(); + } catch (final ParseException pe) { + m_errorMessage = pe.getMessage(); + } + + // System.out.println( "Built : " + m_options ); + // System.out.println( "From : " + Arrays.asList( args ) ); + } + + /** + * Check for duplicates of an option. It is an error to have duplicates + * unless appropriate flags is set in descriptor. + * + * @param arguments + * the arguments + */ + private final void checkIncompatibilities(final Vector arguments) throws ParseException { + final int size = arguments.size(); + + for (int i = 0; i < size; i++) { + final CLOption option = arguments.elementAt(i); + final int id = option.getDescriptor().getId(); + final CLOptionDescriptor descriptor = getDescriptorFor(id); + + // this occurs when id == 0 and user has not supplied a descriptor + // for arguments + if (null == descriptor) { + continue; + } + + final int[] incompatible = descriptor.getIncompatible(); + + checkIncompatible(arguments, incompatible, i); + } + } + + private final void checkIncompatible(final Vector arguments, final int[] incompatible, final int original) + throws ParseException { + final int size = arguments.size(); + + for (int i = 0; i < size; i++) { + if (original == i) { + continue; + } + + final CLOption option = arguments.elementAt(i); + final int id = option.getDescriptor().getId(); + + for (int j = 0; j < incompatible.length; j++) { + if (id == incompatible[j]) { + final CLOption originalOption = arguments.elementAt(original); + final int originalId = originalOption.getDescriptor().getId(); + + String message = null; + + if (id == originalId) { + message = "Duplicate options for " + describeDualOption(originalId) + " found."; + } else { + message = "Incompatible options -" + describeDualOption(id) + " and " + + describeDualOption(originalId) + " found."; + } + throw new ParseException(message, 0); + } + } + } + } + + private final String describeDualOption(final int id) { + final CLOptionDescriptor descriptor = getDescriptorFor(id); + if (null == descriptor) { + return ""; + } else { + final StringBuilder sb = new StringBuilder(); + boolean hasCharOption = false; + + if (Character.isLetter((char) id)) { + sb.append('-'); + sb.append((char) id); + hasCharOption = true; + } + + final String longOption = descriptor.getName(); + if (null != longOption) { + if (hasCharOption) { + sb.append('/'); + } + sb.append("--"); + sb.append(longOption); + } + + return sb.toString(); + } + } + + /** + * Create a parser that deals with options and parses certain args. + * + * @param args + * the args + * @param optionDescriptors + * the option descriptors + */ + public CLArgsParser(final String[] args, final CLOptionDescriptor[] optionDescriptors) { + this(args, optionDescriptors, null); + } + + /** + * Create a string array that is subset of input array. The sub-array should + * start at array entry indicated by index. That array element should only + * include characters from charIndex onwards. + * + * @param array + * the original array + * @param index + * the cut-point in array + * @param charIndex + * the cut-point in element of array + * @return the result array + */ + private final String[] subArray(final String[] array, final int index, final int charIndex) { + final int remaining = array.length - index; + final String[] result = new String[remaining]; + + if (remaining > 1) { + System.arraycopy(array, index + 1, result, 1, remaining - 1); + } + + result[0] = array[index].substring(charIndex - 1); + + return result; + } + + /** + * Actually parse arguments + */ + private final void parse() throws ParseException { + if (0 == m_args.length) { + return; + } + + m_stringLength = m_args[m_argIndex].length(); + + while (true) { + m_ch = peekAtChar(); + + if (m_argIndex >= m_args.length) { + break; + } + + if (null != m_control && m_control.isFinished(m_lastOptionId)) { + // this may need mangling due to peeks + m_unparsedArgs = subArray(m_args, m_argIndex, m_stringIndex); + return; + } + + if (STATE_OPTION_MODE == m_state) { + // if get to an arg barrier then return to normal mode + // else continue accumulating options + if (0 == m_ch) { + getChar(); // strip the null + m_state = STATE_NORMAL; + } else { + parseShortOption(); + } + } else if (STATE_NORMAL == m_state) { + parseNormal(); + } else if (STATE_NO_OPTIONS == m_state) { + // should never get to here when stringIndex != 0 + addOption(new CLOption(m_args[m_argIndex++])); + } else { + parseArguments(); + } + } + + // Reached end of input arguments - perform final processing + if (m_option != null) { + if (STATE_OPTIONAL_ARG == m_state) { + m_options.addElement(m_option); + } else if (STATE_REQUIRE_ARG == m_state) { + final CLOptionDescriptor descriptor = getDescriptorFor(m_option.getDescriptor().getId()); + final String message = "Missing argument to option " + getOptionDescription(descriptor); + throw new ParseException(message, 0); + } else if (STATE_REQUIRE_2ARGS == m_state) { + if (1 == m_option.getArgumentCount()) { + m_option.addArgument(""); + m_options.addElement(m_option); + } else { + final CLOptionDescriptor descriptor = getDescriptorFor(m_option.getDescriptor().getId()); + final String message = "Missing argument to option " + getOptionDescription(descriptor); + throw new ParseException(message, 0); + } + } else { + throw new ParseException("IllegalState " + m_state + ": " + m_option, 0); + } + } + } + + private final String getOptionDescription(final CLOptionDescriptor descriptor) { + if (m_isLong) { + return "--" + descriptor.getName(); + } else { + return "-" + (char) descriptor.getId(); + } + } + + private final char peekAtChar() { + if (INVALID == m_lastChar) { + m_lastChar = readChar(); + } + return (char) m_lastChar; + } + + private final char getChar() { + if (INVALID != m_lastChar) { + final char result = (char) m_lastChar; + m_lastChar = INVALID; + return result; + } else { + return readChar(); + } + } + + private final char readChar() { + if (m_stringIndex >= m_stringLength) { + m_argIndex++; + m_stringIndex = 0; + + if (m_argIndex < m_args.length) { + m_stringLength = m_args[m_argIndex].length(); + } else { + m_stringLength = 0; + } + + return 0; + } + + if (m_argIndex >= m_args.length) { + return 0; + } + + return m_args[m_argIndex].charAt(m_stringIndex++); + } + + private char m_tokesep; // Keep track of token separator + + private final Token nextToken(final char[] separators) { + m_ch = getChar(); + + if (isSeparator(m_ch, separators)) { + m_tokesep=m_ch; + m_ch = getChar(); + return new Token(TOKEN_SEPARATOR, null); + } + + final StringBuilder sb = new StringBuilder(); + + do { + sb.append(m_ch); + m_ch = getChar(); + } while (!isSeparator(m_ch, separators)); + + m_tokesep=m_ch; + return new Token(TOKEN_STRING, sb.toString()); + } + + private final boolean isSeparator(final char ch, final char[] separators) { + for (int i = 0; i < separators.length; i++) { + if (ch == separators[i]) { + return true; + } + } + + return false; + } + + private final void addOption(final CLOption option) { + m_options.addElement(option); + m_lastOptionId = option.getDescriptor().getId(); + m_option = null; + } + + private final void parseOption(final CLOptionDescriptor descriptor, final String optionString) + throws ParseException { + if (null == descriptor) { + throw new ParseException("Unknown option " + optionString, 0); + } + + m_state = getStateFor(descriptor); + m_option = new CLOption(descriptor); + + if (STATE_NORMAL == m_state) { + addOption(m_option); + } + } + + private final void parseShortOption() throws ParseException { + m_ch = getChar(); + final CLOptionDescriptor descriptor = getDescriptorFor(m_ch); + m_isLong = false; + parseOption(descriptor, "-" + m_ch); + + if (STATE_NORMAL == m_state) { + m_state = STATE_OPTION_MODE; + } + } + + private final void parseArguments() throws ParseException { + if (STATE_REQUIRE_ARG == m_state) { + if ('=' == m_ch || 0 == m_ch) { + getChar(); + } + + final Token token = nextToken(NULL_SEPARATORS); + m_option.addArgument(token.getValue()); + + addOption(m_option); + m_state = STATE_NORMAL; + } else if (STATE_OPTIONAL_ARG == m_state) { + if ('-' == m_ch || 0 == m_ch) { + getChar(); // consume stray character + addOption(m_option); + m_state = STATE_NORMAL; + return; + } + + if (m_isLong && '=' != m_tokesep){ // Long optional arg must have = as separator + addOption(m_option); + m_state = STATE_NORMAL; + return; + } + + if ('=' == m_ch) { + getChar(); + } + + final Token token = nextToken(NULL_SEPARATORS); + m_option.addArgument(token.getValue()); + + addOption(m_option); + m_state = STATE_NORMAL; + } else if (STATE_REQUIRE_2ARGS == m_state) { + if (0 == m_option.getArgumentCount()) { + /* + * Fix bug: -D arg1=arg2 was causing parse error; however + * --define arg1=arg2 is OK This seems to be because the parser + * skips the terminator for the long options, but was not doing + * so for the short options. + */ + if (!m_isLong) { + if (0 == peekAtChar()) { + getChar(); + } + } + final Token token = nextToken(ARG_SEPARATORS); + + if (TOKEN_SEPARATOR == token.getType()) { + final CLOptionDescriptor descriptor = getDescriptorFor(m_option.getDescriptor().getId()); + final String message = "Unable to parse first argument for option " + + getOptionDescription(descriptor); + throw new ParseException(message, 0); + } else { + m_option.addArgument(token.getValue()); + } + // Are we about to start a new option? + if (0 == m_ch && '-' == peekAtChar()) { + // Yes, so the second argument is missing + m_option.addArgument(""); + m_options.addElement(m_option); + m_state = STATE_NORMAL; + } + } else // 2nd argument + { + final StringBuilder sb = new StringBuilder(); + + m_ch = getChar(); + while (!isSeparator(m_ch, NULL_SEPARATORS)) { + sb.append(m_ch); + m_ch = getChar(); + } + + final String argument = sb.toString(); + + // System.out.println( "Arguement:" + argument ); + + m_option.addArgument(argument); + addOption(m_option); + m_option = null; + m_state = STATE_NORMAL; + } + } + } + + /** + * Parse Options from Normal mode. + */ + private final void parseNormal() throws ParseException { + if ('-' != m_ch) { + // Parse the arguments that are not options + final String argument = nextToken(NULL_SEPARATORS).getValue(); + addOption(new CLOption(argument)); + m_state = STATE_NORMAL; + } else { + getChar(); // strip the - + + if (0 == peekAtChar()) { + throw new ParseException("Malformed option -", 0); + } else { + m_ch = peekAtChar(); + + // if it is a short option then parse it else ... + if ('-' != m_ch) { + parseShortOption(); + } else { + getChar(); // strip the - + // -- sequence .. it can either mean a change of state + // to STATE_NO_OPTIONS or else a long option + + if (0 == peekAtChar()) { + getChar(); + m_state = STATE_NO_OPTIONS; + } else { + // its a long option + final String optionName = nextToken(ARG_SEPARATORS).getValue(); + final CLOptionDescriptor descriptor = getDescriptorFor(optionName); + m_isLong = true; + parseOption(descriptor, "--" + optionName); + } + } + } + } + } + + /** + * Build the m_optionIndex lookup map for the parsed options. + */ + private final void buildOptionIndex() { + final int size = m_options.size(); + m_optionIndex = new Hashtable(size * 2); + + for (int i = 0; i < size; i++) { + final CLOption option = m_options.get(i); + final CLOptionDescriptor optionDescriptor = getDescriptorFor(option.getDescriptor().getId()); + + m_optionIndex.put(Integer.valueOf(option.getDescriptor().getId()), option); + + if (null != optionDescriptor && null != optionDescriptor.getName()) { + m_optionIndex.put(optionDescriptor.getName(), option); + } + } + } +} diff --git a/src/jorphan/org/apache/commons/cli/avalon/CLOption.java b/src/jorphan/org/apache/commons/cli/avalon/CLOption.java new file mode 100644 index 00000000000..3ec6b5caa51 --- /dev/null +++ b/src/jorphan/org/apache/commons/cli/avalon/CLOption.java @@ -0,0 +1,176 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +import java.util.Arrays; + +/** + * Basic class describing an instance of option. + * + */ +public final class CLOption { + /** + * Value of {@link CLOptionDescriptor#getId} when the option is a text argument. + */ + public static final int TEXT_ARGUMENT = 0; + + /** + * Default descriptor. Required, since code assumes that getDescriptor will + * never return null. + */ + private static final CLOptionDescriptor TEXT_ARGUMENT_DESCRIPTOR = new CLOptionDescriptor(null, + CLOptionDescriptor.ARGUMENT_OPTIONAL, TEXT_ARGUMENT, null); + + private String[] m_arguments; + + private CLOptionDescriptor m_descriptor = TEXT_ARGUMENT_DESCRIPTOR; + + /** + * Retrieve argument to option if it takes arguments. + * + * @return the (first) argument + */ + public final String getArgument() { + return getArgument(0); + } + + /** + * Retrieve indexed argument to option if it takes arguments. + * + * @param index + * The argument index, from 0 to {@link #getArgumentCount()}-1. + * @return the argument + */ + public final String getArgument(final int index) { + if (null == m_arguments || index < 0 || index >= m_arguments.length) { + return null; + } else { + return m_arguments[index]; + } + } + + public final CLOptionDescriptor getDescriptor() { + return m_descriptor; + } + + /** + * Constructor taking an descriptor + * + * @param descriptor + * the descriptor iff null, will default to a "text argument" + * descriptor. + */ + public CLOption(final CLOptionDescriptor descriptor) { + if (descriptor != null) { + m_descriptor = descriptor; + } + } + + /** + * Constructor taking argument for option. + * + * @param argument + * the argument + */ + public CLOption(final String argument) { + this((CLOptionDescriptor) null); + addArgument(argument); + } + + /** + * Mutator of Argument property. + * + * @param argument + * the argument + */ + public final void addArgument(final String argument) { + if (null == m_arguments) { + m_arguments = new String[] { argument }; + } else { + final String[] arguments = new String[m_arguments.length + 1]; + System.arraycopy(m_arguments, 0, arguments, 0, m_arguments.length); + arguments[m_arguments.length] = argument; + m_arguments = arguments; + } + } + + /** + * Get number of arguments. + * + * @return the number of arguments + */ + public final int getArgumentCount() { + if (null == m_arguments) { + return 0; + } else { + return m_arguments.length; + } + } + + /** + * Convert to String. + * + * @return the string value + */ + @Override + public final String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("["); + final char id = (char) m_descriptor.getId(); + if (id == TEXT_ARGUMENT) { + sb.append("TEXT "); + } else { + sb.append("Option "); + sb.append(id); + } + + if (null != m_arguments) { + sb.append(", "); + sb.append(Arrays.asList(m_arguments)); + } + + sb.append(" ]"); + + return sb.toString(); + } + + /* + * Convert to a shorter String for test purposes + * + * @return the string value + */ + final String toShortString() { + final StringBuilder sb = new StringBuilder(); + final char id = (char) m_descriptor.getId(); + if (id != TEXT_ARGUMENT) { + sb.append("-"); + sb.append(id); + } + + if (null != m_arguments) { + if (id != TEXT_ARGUMENT) { + sb.append("="); + } + sb.append(Arrays.asList(m_arguments)); + } + return sb.toString(); + } +} diff --git a/src/jorphan/org/apache/commons/cli/avalon/CLOptionDescriptor.java b/src/jorphan/org/apache/commons/cli/avalon/CLOptionDescriptor.java new file mode 100644 index 00000000000..35dd2d2753c --- /dev/null +++ b/src/jorphan/org/apache/commons/cli/avalon/CLOptionDescriptor.java @@ -0,0 +1,201 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * Basic class describing an type of option. Typically, one creates a static + * array of CLOptionDescriptors, and passes it to + * {@link CLArgsParser#CLArgsParser(String[], CLOptionDescriptor[])}. + * + * @see CLArgsParser + * @see CLUtil + */ +public final class CLOptionDescriptor { + /** Flag to say that one argument is required */ + public static final int ARGUMENT_REQUIRED = 1 << 1; + + /** Flag to say that the argument is optional */ + public static final int ARGUMENT_OPTIONAL = 1 << 2; + + /** Flag to say this option does not take arguments */ + public static final int ARGUMENT_DISALLOWED = 1 << 3; + + /** Flag to say this option requires 2 arguments */ + public static final int ARGUMENTS_REQUIRED_2 = 1 << 4; + + /** Flag to say this option may be repeated on the command line */ + public static final int DUPLICATES_ALLOWED = 1 << 5; + + private final int m_id; + + private final int m_flags; + + private final String m_name; + + private final String m_description; + + private final int[] m_incompatible; + + /** + * Constructor. + * + * @param name + * the name/long option + * @param flags + * the flags + * @param id + * the id/character option + * @param description + * description of option usage + */ + public CLOptionDescriptor(final String name, final int flags, final int id, final String description) { + + checkFlags(flags); + + m_id = id; + m_name = name; + m_flags = flags; + m_description = description; + m_incompatible = ((flags & DUPLICATES_ALLOWED) != 0) ? new int[0] : new int[] { id }; + } + + + /** + * Constructor. + * + * @param name + * the name/long option + * @param flags + * the flags + * @param id + * the id/character option + * @param description + * description of option usage + * @param incompatible + * descriptors for incompatible options + */ + public CLOptionDescriptor(final String name, final int flags, final int id, final String description, + final CLOptionDescriptor[] incompatible) { + + checkFlags(flags); + + m_id = id; + m_name = name; + m_flags = flags; + m_description = description; + + m_incompatible = new int[incompatible.length]; + for (int i = 0; i < incompatible.length; i++) { + m_incompatible[i] = incompatible[i].getId(); + } + } + + private void checkFlags(final int flags) { + int modeCount = 0; + if ((ARGUMENT_REQUIRED & flags) == ARGUMENT_REQUIRED) { + modeCount++; + } + if ((ARGUMENT_OPTIONAL & flags) == ARGUMENT_OPTIONAL) { + modeCount++; + } + if ((ARGUMENT_DISALLOWED & flags) == ARGUMENT_DISALLOWED) { + modeCount++; + } + if ((ARGUMENTS_REQUIRED_2 & flags) == ARGUMENTS_REQUIRED_2) { + modeCount++; + } + + if (0 == modeCount) { + final String message = "No mode specified for option " + this; + throw new IllegalStateException(message); + } else if (1 != modeCount) { + final String message = "Multiple modes specified for option " + this; + throw new IllegalStateException(message); + } + } + + /** + * Get the array of incompatible option ids. + * + * @return the array of incompatible option ids + */ + protected final int[] getIncompatible() { + return m_incompatible; + } + + /** + * Retrieve textual description. + * + * @return the description + */ + public final String getDescription() { + return m_description; + } + + /** + * Retrieve flags about option. Flags include details such as whether it + * allows parameters etc. + * + * @return the flags + */ + public final int getFlags() { + return m_flags; + } + + /** + * Retrieve the id for option. The id is also the character if using single + * character options. + * + * @return the id + */ + public final int getId() { + return m_id; + } + + /** + * Retrieve name of option which is also text for long option. + * + * @return name/long option + */ + public final String getName() { + return m_name; + } + + /** + * Convert to String. + * + * @return the converted value to string. + */ + @Override + public final String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("[OptionDescriptor "); + sb.append(m_name); + sb.append(", "); + sb.append(m_id); + sb.append(", "); + sb.append(m_flags); + sb.append(", "); + sb.append(m_description); + sb.append(" ]"); + return sb.toString(); + } +} diff --git a/src/jorphan/org/apache/commons/cli/avalon/CLUtil.java b/src/jorphan/org/apache/commons/cli/avalon/CLUtil.java new file mode 100644 index 00000000000..472df65113c --- /dev/null +++ b/src/jorphan/org/apache/commons/cli/avalon/CLUtil.java @@ -0,0 +1,108 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * CLUtil offers basic utility operations for use both internal and external to + * package. + * + * @see CLOptionDescriptor + */ +public final class CLUtil { + private static final int MAX_DESCRIPTION_COLUMN_LENGTH = 60; + + /** + * Private Constructor so that no instance can ever be created. + * + */ + private CLUtil() { + } + + /** + * Format options into StringBuilder and return. This is typically used to + * print "Usage" text in response to a "--help" or invalid option. + * + * @param options + * the option descriptors + * @return the formatted description/help for options + */ + public static final StringBuilder describeOptions(final CLOptionDescriptor[] options) { + final String lSep = System.getProperty("line.separator"); + final StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < options.length; i++) { + final char ch = (char) options[i].getId(); + final String name = options[i].getName(); + String description = options[i].getDescription(); + int flags = options[i].getFlags(); + boolean argumentOptional = ((flags & CLOptionDescriptor.ARGUMENT_OPTIONAL) == CLOptionDescriptor.ARGUMENT_OPTIONAL); + boolean argumentRequired = ((flags & CLOptionDescriptor.ARGUMENT_REQUIRED) == CLOptionDescriptor.ARGUMENT_REQUIRED); + boolean twoArgumentsRequired = ((flags & CLOptionDescriptor.ARGUMENTS_REQUIRED_2) == CLOptionDescriptor.ARGUMENTS_REQUIRED_2); + boolean needComma = false; + if (twoArgumentsRequired) { + argumentRequired = true; + } + + sb.append('\t'); + + if (Character.isLetter(ch)) { + sb.append("-"); + sb.append(ch); + needComma = true; + } + + if (null != name) { + if (needComma) { + sb.append(", "); + } + + sb.append("--"); + sb.append(name); + } + + if (argumentOptional) { + sb.append(" []"); + } + if (argumentRequired) { + sb.append(" "); + } + if (twoArgumentsRequired) { + sb.append("="); + } + sb.append(lSep); + + if (null != description) { + while (description.length() > MAX_DESCRIPTION_COLUMN_LENGTH) { + final String descriptionPart = description.substring(0, MAX_DESCRIPTION_COLUMN_LENGTH); + description = description.substring(MAX_DESCRIPTION_COLUMN_LENGTH); + sb.append("\t\t"); + sb.append(descriptionPart); + sb.append(lSep); + } + + sb.append("\t\t"); + sb.append(description); + sb.append(lSep); + } + } + return sb; + } +} diff --git a/src/jorphan/org/apache/commons/cli/avalon/ParserControl.java b/src/jorphan/org/apache/commons/cli/avalon/ParserControl.java new file mode 100644 index 00000000000..a1761848f1a --- /dev/null +++ b/src/jorphan/org/apache/commons/cli/avalon/ParserControl.java @@ -0,0 +1,38 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * ParserControl is used to control particular behaviour of the parser. + * + * @see AbstractParserControl + */ +public interface ParserControl { + /** + * Called by the parser to determine whether it should stop after last + * option parsed. + * + * @param lastOptionCode + * the code of last option parsed + * @return return true to halt, false to continue parsing + */ + boolean isFinished(int lastOptionCode); +} diff --git a/src/jorphan/org/apache/commons/cli/avalon/Token.java b/src/jorphan/org/apache/commons/cli/avalon/Token.java new file mode 100644 index 00000000000..acaeeb7d98e --- /dev/null +++ b/src/jorphan/org/apache/commons/cli/avalon/Token.java @@ -0,0 +1,80 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * Token handles tokenizing the CLI arguments + * + */ +class Token { + /** Type for a separator token */ + public static final int TOKEN_SEPARATOR = 0; + + /** Type for a text token */ + public static final int TOKEN_STRING = 1; + + private final int m_type; + + private final String m_value; + + /** + * New Token object with a type and value + * + * @param type + * type of the token + * @param value + * value of the token + */ + Token(final int type, final String value) { + m_type = type; + m_value = value; + } + + /** + * Get the value of the token + * + * @return value of the token + */ + final String getValue() { + return m_value; + } + + /** + * Get the type of the token + * + * @return type of the token + */ + final int getType() { + return m_type; + } + + /** + * Convert to a string + */ + @Override + public final String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append(m_type); + sb.append(":"); + sb.append(m_value); + return sb.toString(); + } +} diff --git a/src/jorphan/org/apache/commons/cli/avalon/package.html b/src/jorphan/org/apache/commons/cli/avalon/package.html new file mode 100644 index 00000000000..359a04983e9 --- /dev/null +++ b/src/jorphan/org/apache/commons/cli/avalon/package.html @@ -0,0 +1,183 @@ + + + + Package Documentation for org.apache.commons.cli.avalon Package + + + Utility code for parsing command-line options. +

+

+These classes were originally in the Avalon project in the package org.apache.avalon.excalibur.cli +

+ +

Introduction

+

The utilities in org.apache.commons.cli.avalon assist + you in parsing command line options during startup time. It allows you + to associate a short option and a long option to the same command, and + then test for it in a switch statement.

+ +

Usage Example

+
+import java.util.List;
+
+import org.apache.commons.cli.avalon.CLArgsParser;
+import org.apache.commons.cli.avalon.CLOption;
+import org.apache.commons.cli.avalon.CLOptionDescriptor;
+import org.apache.commons.cli.avalon.CLUtil;
+
+/**
+* Demonstrates the excalibur command-line parsing utility.
+*
+*/
+public class CLDemo {
+    // Define our short one-letter option identifiers.
+    protected static final int HELP_OPT = 'h';
+    protected static final int VERSION_OPT = 'v';
+    protected static final int MSG_OPT = 'm';
+
+    /**
+     *  Define the understood options. Each CLOptionDescriptor contains:
+     * - The "long" version of the option. Eg, "help" means that "--help" will
+     * be recognised.
+     * - The option flags, governing the option's argument(s).
+     * - The "short" version of the option. Eg, 'h' means that "-h" will be
+     * recognised.
+     * - A description of the option.
+     */
+    protected static final CLOptionDescriptor [] options = new CLOptionDescriptor [] {
+        new CLOptionDescriptor("help",
+                CLOptionDescriptor.ARGUMENT_DISALLOWED,
+                HELP_OPT,
+                "print this message and exit"),
+        new CLOptionDescriptor("version",
+                CLOptionDescriptor.ARGUMENT_DISALLOWED,
+                VERSION_OPT,
+                "print the version information and exit"),
+        new CLOptionDescriptor("msg",
+                CLOptionDescriptor.ARGUMENT_REQUIRED,
+                MSG_OPT,
+                "the message to print"),
+    };
+
+    public static void main(String args[]) {
+        // Parse the arguments
+        CLArgsParser parser = new CLArgsParser(args, options);
+
+        if( null != parser.getErrorString() ) {
+           System.err.println( "Error: " + parser.getErrorString() );
+           return;
+        }
+
+        // Get a list of parsed options
+        List clOptions = parser.getArguments();
+        int size = clOptions.size();
+
+        for (int i = 0; i < size; i++) {
+            CLOption option = (CLOption) clOptions.get(i);
+
+            switch (option.getId()) {
+                case CLOption.TEXT_ARGUMENT:
+                    System.out.println("Unknown arg: "+option.getArgument());
+                    break;
+
+                case HELP_OPT:
+                    printUsage();
+                    break;
+
+                case VERSION_OPT:
+                    printVersion();
+                    break;
+
+
+                case MSG_OPT:
+                    System.out.println(option.getArgument());
+                    break;
+            }
+        }
+    }
+
+    private static void printVersion() {
+        System.out.println("1.0");
+        System.exit(0);
+    }
+
+    private static void printUsage() {
+        String lSep = System.getProperty("line.separator");
+        StringBuffer msg = new StringBuffer();
+        msg.append("------------------------------------------------------------------------ ").append(lSep);
+        msg.append("Excalibur command-line arg parser demo").append(lSep);
+        msg.append("Usage: java "+CLDemo.class.getName()+" [options]").append(lSep).append(lSep);
+        msg.append("Options: ").append(lSep);
+        msg.append(CLUtil.describeOptions(CLDemo.options).toString());
+        System.out.println(msg.toString());
+        System.exit(0);
+    }
+}
+
+ +

Parsing Rules

+

+ The command line is parsed according to the following rules. There are + two forms of options in this package, the Long form and the Short form. + The long form of an option is preceded by the '--' characters while the + short form is preceded by a single '-'. Some example options would be; + "--an-option", "-a", "--day", "-s -f -a". +

+

+ In the tradition of UNIX programs, the short form of an option can occur + immediately after another short form option. So if 'a', 'b' and 'c' are + short forms of options that take no parameters then the following + command lines are equivalent: "-abc", "-a -bc", "-a -b -c", "-ab -c", etc. +

+

+ Options can also accept arguments if specified. You can specify that an + option requires an argument in which the text immediately following the + option will be considered to be an argument to the option. So if 'a' was an + option that required an argument then the following would be equivalent; + "-abc", "-a bc" (namely the option 'a' with argument 'bc'). +

+

+ Options can also specify optional arguments. In this case if there is any + text immediately following the option character then it is considered an + argument. Otherwise, the option has no arguments. For example if 'a' was an + option that required an optional argument then "-abc" is an option 'a' with + argument "bc" while "-a bc" is an option 'a' with no argument, followed by + the text "bc".

+

It is also possible to place an '=' sign between the option + and its argument. So if we assume that a is an option that + requires an argument then the following are equivalent; + "-a=bc" and "-abc". +

+

+ In the case of a long option with an optional argument, the '=' sign is required. + For example. --optarg=1, not --optarg 1. +

+

+ In some cases it is also necessary to disable command line parsing so that you + can pass a text argument to the program that starts with a '-' character. To do + this insert the sequence '--' onto the command line with no text immediately + following it. This will disable processing for the rest of the command line. + The '--' characters will not be passed to the user program. For instance the + line "-- -b" would result in the program being passed the + text "-b" (ie. not as an option). +

+ + diff --git a/src/jorphan/org/apache/commons/jexl/bsf/JexlEngine.java b/src/jorphan/org/apache/commons/jexl/bsf/JexlEngine.java new file mode 100644 index 00000000000..f1f770a9a47 --- /dev/null +++ b/src/jorphan/org/apache/commons/jexl/bsf/JexlEngine.java @@ -0,0 +1,150 @@ +/* + * 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.commons.jexl.bsf; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.Vector; + +import org.apache.bsf.BSFDeclaredBean; +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.bsf.util.BSFEngineImpl; + +import org.apache.commons.jexl.JexlContext; +import org.apache.commons.jexl.JexlHelper; +import org.apache.commons.jexl.Script; +import org.apache.commons.jexl.ScriptFactory; +//import org.apache.jorphan.logging.LoggingManager; +//import org.apache.log.Logger; + +// See JIRA: JEXL-39 + +/** + * BSFEngine for Commons JEXL. + */ +public class JexlEngine extends BSFEngineImpl { + +// private static final Logger log = LoggingManager.getLoggerForClass(); + + private JexlContext jc; + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") // super-class does not use generics + @Override + public void initialize(BSFManager mgr, String lang, + @SuppressWarnings("rawtypes") Vector declaredBeans) // super-class does not use generics + throws BSFException { + super.initialize(mgr, lang, declaredBeans); + jc = JexlHelper.createContext(); + for (int i = 0; i < declaredBeans.size(); i++) { + BSFDeclaredBean bean = (BSFDeclaredBean) declaredBeans.elementAt(i); + jc.getVars().put(bean.name, bean.bean); + } + } + + /** {@inheritDoc} */ + @Override + public void terminate() { + if (jc != null) { + jc.getVars().clear(); + jc = null; + } + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + public void declareBean(BSFDeclaredBean bean) throws BSFException { + jc.getVars().put(bean.name, bean.bean); + } + + /** {@inheritDoc} */ + @Override + public void undeclareBean(BSFDeclaredBean bean) throws BSFException { + jc.getVars().remove(bean.name); + } + + /** {@inheritDoc} */ + @Override + public Object eval(String fileName, int lineNo, int colNo, Object expr) + throws BSFException { + if (expr == null) { + return null; + } + try { + Script jExpr = null; + if (expr instanceof File) { + jExpr = ScriptFactory.createScript((File) expr); + } else if (expr instanceof URL) { + jExpr = ScriptFactory.createScript((URL) expr); + } else { + jExpr = ScriptFactory.createScript((String) expr); + } + return jExpr.execute(jc); + } catch (Exception e) { + throw new BSFException(BSFException.REASON_OTHER_ERROR, e.getMessage(), e); + } + } + + /** {@inheritDoc} */ + @Override + public void exec(String fileName, int lineNo, int colNo, Object script) + throws BSFException { + if (script == null) { + return; + } + try { + Script jExpr = null; + if (script instanceof File) { + jExpr = ScriptFactory.createScript((File) script); + } else if (script instanceof URL) { + jExpr = ScriptFactory.createScript((URL) script); + } else { + jExpr = ScriptFactory.createScript((String) script); + } + jExpr.execute(jc); + } catch (Exception e) { + throw new BSFException(BSFException.REASON_OTHER_ERROR, e.getMessage(), e); + } + } + + /** {@inheritDoc} */ + @Override + public void iexec(String fileName, int lineNo, int colNo, Object script) + throws BSFException { + exec(fileName, lineNo, colNo, script); + } + + /** {@inheritDoc} */ + @Override + public Object call(Object object, String name, Object[] args) + throws BSFException { + try { + Class[] types = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + types[i] = args[i].getClass(); + } + Method m = object.getClass().getMethod(name, types); + return m.invoke(object, args); + } catch (Exception e) { + throw new BSFException(BSFException.REASON_OTHER_ERROR, e.getMessage(), e); + } + } + +} diff --git a/src/jorphan/org/apache/jorphan/collections/Data.java b/src/jorphan/org/apache/jorphan/collections/Data.java new file mode 100644 index 00000000000..f978ab8e0ef --- /dev/null +++ b/src/jorphan/org/apache/jorphan/collections/Data.java @@ -0,0 +1,698 @@ +/* + * 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.jorphan.collections; + +import java.io.Serializable; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Use this class to store database-like data. This class uses rows and columns + * to organize its data. It has some convenience methods that allow fast loading + * and retrieval of the data into and out of string arrays. It is also handy for + * reading CSV files. + * + * WARNING: the class assumes that column names are unique, but does not enforce this. + * + */ +public class Data implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final Map> data; + + private List header; + + // saves current position in data List + private int currentPos, size; + + /** + * Constructor - takes no arguments. + */ + public Data() { + header = new ArrayList(); + data = new HashMap>(); + currentPos = -1; + size = currentPos + 1; + } + + /** + * Replaces the given header name with a new header name. + * + * @param oldHeader + * Old header name. + * @param newHeader + * New header name. + */ + public void replaceHeader(String oldHeader, String newHeader) { + List tempList; + int index = header.indexOf(oldHeader); + header.set(index, newHeader); + tempList = data.remove(oldHeader); + data.put(newHeader, tempList); + } + + /** + * Adds the rows of the given Data object to this Data object. + * + * @param d + * data object to be appended to this one + */ + public void append(Data d) { + boolean valid = true; + String[] headers = getHeaders(); + String[] dHeaders = d.getHeaders(); + if (headers.length != dHeaders.length) { + valid = false; + } else { + for (int count = 0; count < dHeaders.length; count++) { + if (!header.contains(dHeaders[count])) { + valid = false; + break; + } + } + } + + if (valid) { + currentPos = size; + d.reset(); + while (d.next()) { + for (int count = 0; count < headers.length; count++) { + addColumnValue(headers[count], d.getColumnValue(headers[count])); + } + } + } + } + + /** + * Get the number of the current row. + * + * @return integer representing the current row + */ + public int getCurrentPos() { + return currentPos; + } + + /** + * Removes the current row. + */ + public void removeRow() { + List tempList; + Iterator it = data.keySet().iterator(); + log.debug("removing row, size = " + size); + if (currentPos > -1 && currentPos < size) { + log.debug("got to here"); + while (it.hasNext()) { + tempList = data.get(it.next()); + tempList.remove(currentPos); + } + if (currentPos > 0) { + currentPos--; + } + size--; + } + } + + public void removeRow(int index) { + log.debug("Removing row: " + index); + if (index < size) { + setCurrentPos(index); + log.debug("Setting currentpos to " + index); + removeRow(); + } + } + + public void addRow() { + String[] headers = getHeaders(); + List tempList = new ArrayList(); + for (int i = 0; i < headers.length; i++) { + if ((tempList = data.get(header.get(i))) == null) { + tempList = new ArrayList(); + data.put(headers[i], tempList); + } + tempList.add(""); + } + size = tempList.size(); + setCurrentPos(size - 1); + } + + /** + * Sets the current pos. If value sent to method is not a valid number, the + * current position is set to one higher than the maximum. + * + * @param r + * position to set to. + */ + public void setCurrentPos(int r) { + currentPos = r; + } + + /** + * Sorts the data using a given row as the sorting criteria. A boolean value + * indicates whether to sort ascending or descending. + * + * @param column + * name of column to use as sorting criteria. + * @param asc + * boolean value indicating whether to sort ascending or + * descending. True for asc, false for desc. Currently this + * feature is not enabled and all sorts are asc. + */ + public void sort(String column, boolean asc) { + sortData(column, 0, size); + } + + private void swapRows(int row1, int row2) { + List temp; + Object o; + Iterator it = data.keySet().iterator(); + while (it.hasNext()) { + temp = data.get(it.next()); + o = temp.get(row1); + temp.set(row1, temp.get(row2)); + temp.set(row2, o); + } + } + + /** + * Private method that implements the quicksort algorithm to sort the rows + * of the Data object. + * + * @param column + * name of column to use as sorting criteria. + * @param start + * starting index (for quicksort algorithm). + * @param end + * ending index (for quicksort algorithm). + */ + private void sortData(String column, int start, int end) { + int x = start, y = end - 1; + String basis = ((List) data.get(column)).get((x + y) / 2).toString(); + if (x == y) { + return; + } + + while (x <= y) { + while (x < end && ((List) data.get(column)).get(x).toString().compareTo(basis) < 0) { + x++; + } + + while (y >= (start - 1) && ((List) data.get(column)).get(y).toString().compareTo(basis) > 0) { + y--; + } + + if (x <= y) { + swapRows(x, y); + x++; + y--; + } + } + + if (x == y) { + x++; + } + + y = end - x; + + if (x > 0) { + sortData(column, start, x); + } + + if (y > 0) { + sortData(column, x, end); + } + } + + /** + * Gets the number of rows in the Data object. + * + * @return number of rows in Data object. + */ + public int size() { + return size; + } // end method + + /** + * Adds a value into the Data set at the current row, using a column name to + * find the column in which to insert the new value. + * + * @param column + * the name of the column to set. + * @param value + * value to set into column. + */ + public void addColumnValue(String column, Object value) { + List tempList; + if ((tempList = data.get(column)) == null) { + tempList = new ArrayList(); + data.put(column, tempList); + } + int s = tempList.size(); + if (currentPos == -1) { + currentPos = size; + } + + if (currentPos >= size) { + size = currentPos + 1; + } + + while (currentPos > s) { + s++; + tempList.add(null); + } + + if (currentPos == s) { + tempList.add(value); + } else { + tempList.set(currentPos, value); + } + } + + /** + * Returns the row number where a certain value is. + * + * @param column + * column to be searched for value. + * @param value + * object in Search of. + * @return row # where value exists. + */ + public int findValue(String column, Object value) { + return data.get(column).indexOf(value); + } + + /** + * Sets the value in the Data set at the current row, using a column name to + * find the column in which to insert the new value. + * + * @param column + * the name of the column to set. + * @param value + * value to set into column. + */ + public void setColumnValue(String column, Object value) { + List tempList; + if ((tempList = data.get(column)) == null) { + tempList = new ArrayList(); + data.put(column, tempList); + } + + if (currentPos == -1) { + currentPos = 0; + } + + if (currentPos >= size) { + size++; + tempList.add(value); + } else if (currentPos >= tempList.size()) { + tempList.add(value); + } else { + tempList.set(currentPos, value); + } + } + + /** + * Checks to see if a column exists in the Data object. + * + * @param column + * Name of column header to check for. + * @return True or False depending on whether the column exists. + */ + public boolean hasHeader(String column) { + return data.containsKey(column); + } + + /** + * Sets the current position of the Data set to the next row. + * + * @return True if there is another row. False if there are no more rows. + */ + public boolean next() { + return (++currentPos < size); + } + + /** + * Gets a Data object from a ResultSet. + * + * @param rs + * ResultSet passed in from a database query + * @return a Data object + * @throws java.sql.SQLException when database access errors occur + */ + public static Data getDataFromResultSet(ResultSet rs) throws SQLException { + ResultSetMetaData meta = rs.getMetaData(); + Data data = new Data(); + + int numColumns = meta.getColumnCount(); + String[] dbCols = new String[numColumns]; + for (int i = 0; i < numColumns; i++) { + dbCols[i] = meta.getColumnName(i + 1); + data.addHeader(dbCols[i]); + } + + while (rs.next()) { + data.next(); + for (int i = 0; i < numColumns; i++) { + Object o = rs.getObject(i + 1); + if (o instanceof byte[]) { + o = new String((byte[]) o); // TODO - charset? + } + data.addColumnValue(dbCols[i], o); + } + } + return data; + } + + /** + * Sets the current position of the Data set to the previous row. + * + * @return True if there is another row. False if there are no more rows. + */ + public boolean previous() { + return (--currentPos >= 0); + } + + /** + * Resets the current position of the data set to just before the first + * element. + */ + public void reset() { + currentPos = -1; + } + + /** + * Gets the value in the current row of the given column. + * + * @param column + * name of the column. + * @return an Object which holds the value of the column. + */ + public Object getColumnValue(String column) { + try { + if (currentPos < size) { + return ((List) data.get(column)).get(currentPos); + } else { + return null; + } + } catch (Exception e) { + return null; + } + } + + /** + * Gets the value in the current row of the given column. + * + * @param column + * index of the column (starts at 0). + * @return an Object which holds the value of the column. + */ + public Object getColumnValue(int column) { + String columnName = header.get(column); + try { + if (currentPos < size) { + return ((List) data.get(columnName)).get(currentPos); + } else { + return null; + } + } catch (Exception e) { + return null; + } + } + + public Object getColumnValue(int column, int row) { + setCurrentPos(row); + return getColumnValue(column); + } + + public void removeColumn(int col) { + String columnName = header.get(col); + data.remove(columnName); + header.remove(columnName); + } + + /** + * Sets the headers for the data set. Each header represents a column of + * data. Each row's data can be gotten with the column header name, which + * will always be a string. + * + * @param h + * array of strings representing the column headers. + * these must be distinct - duplicates will cause incorrect behaviour + */ + public void setHeaders(String[] h) { + int x = 0; + header = new ArrayList(h.length); + for (x = 0; x < h.length; x++) { + header.add(h[x]); + data.put(h[x], new ArrayList()); + } + } + + /** + * Returns a String array of the column headers. + * + * @return array of strings of the column headers. + */ + public String[] getHeaders() { + String[] r = new String[header.size()]; + if (r.length > 0) { + r = header.toArray(r); + } + return r; + } + + public int getHeaderCount(){ + return header.size(); + } + + /** + * This method will retrieve every entry in a certain column. It returns an + * array of Objects from the column. + * + * @param columnName + * name of the column. + * @return array of Objects representing the data. + */ + public List getColumnAsObjectArray(String columnName) { + return data.get(columnName); + } + + /** + * This method will retrieve every entry in a certain column. It returns an + * array of strings from the column. Even if the data are not strings, they + * will be returned as strings in this method. + * + * @param columnName + * name of the column. + * @return array of Strings representing the data. + */ + public String[] getColumn(String columnName) { + String[] returnValue; + List temp = data.get(columnName); + if (temp != null) { + returnValue = new String[temp.size()]; + int index = 0; + for (Object o : temp) { + if (o != null) { + if (o instanceof String) { + returnValue[index++] = (String) o; + } else { + returnValue[index++] = o.toString(); + } + } + } + } else { + returnValue = new String[0]; + } + return returnValue; + } + + /** + * Use this method to set the entire data set. It takes an array of strings. + * It uses the first row as the headers, and the next rows as the data + * elements. Delimiter represents the delimiting character(s) that separate + * each item in a data row. + * + * @param contents + * array of strings, the first element is a list of the column + * headers, the next elements each represent a single row of + * data. + * @param delimiter + * the delimiter character that separates columns within the + * string array. + */ + public void setData(String[] contents, String delimiter) { + setHeaders(JOrphanUtils.split(contents[0], delimiter)); + int x = 1; + while (x < contents.length) { + setLine(JOrphanUtils.split(contents[x++], delimiter)); + } + } + + /* + * Deletes a header from the Data object. Takes the column name as input. It + * will delete the entire column. + * + * public void deleteHeader(String s) { + * } + */ + + /** + * Sets the data for every row in the column. + * + * @param colName + * name of the column + * @param value + * value to be set + */ + public void setColumnData(String colName, Object value) { + List list = this.getColumnAsObjectArray(colName); + while (list.size() < size()) { + list.add(value); + } + } + + public void setColumnData(int col, List data) { + reset(); + Iterator iter = data.iterator(); + String columnName = header.get(col); + while (iter.hasNext()) { + next(); + setColumnValue(columnName, iter.next()); + } + } + + /** + * Adds a header name to the Data object. + * + * @param s + * name of header. + */ + public void addHeader(String s) { + header.add(s); + data.put(s, new ArrayList(Math.max(size(), 100))); + } + + /** + * Sets a row of data using an array of strings as input. Each value in the + * array represents a column's value in that row. Assumes the order will be + * the same order in which the headers were added to the data set. + * + * @param line + * array of strings representing column values. + */ + public void setLine(String[] line) { + List tempList; + String[] h = getHeaders(); + for (int count = 0; count < h.length; count++) { + tempList = data.get(h[count]); + if (count < line.length && line[count].length() > 0) { + tempList.add(line[count]); + } else { + tempList.add("N/A"); + } + } + size++; + } + + /** + * Sets a row of data using an array of strings as input. Each value in the + * array represents a column's value in that row. Assumes the order will be + * the same order in which the headers were added to the data set. + * + * @param line + * array of strings representing column values. + * @param deflt + * default value to be placed in data if line is not as long as + * headers. + */ + public void setLine(String[] line, String deflt) { + List tempList; + String[] h = getHeaders(); + for (int count = 0; count < h.length; count++) { + tempList = data.get(h[count]); + if (count < line.length && line[count].length() > 0) { + tempList.add(line[count]); + } else { + tempList.add(deflt); + } + } + size++; + } + + /** + * Returns all the data in the Data set as an array of strings. Each array + * gives a row of data, each column separated by tabs. + * + * @return array of strings. + */ + public String[] getDataAsText() { + StringBuilder temp = new StringBuilder(""); + String[] line = new String[size + 1]; + String[] elements = getHeaders(); + for (int count = 0; count < elements.length; count++) { + temp.append(elements[count]); + if (count + 1 < elements.length) { + temp.append("\t"); + } + } + line[0] = temp.toString(); + reset(); + int index = 1; + temp = new StringBuilder(); + while (next()) { + temp.setLength(0); + for (int count = 0; count < elements.length; count++) { + temp.append(getColumnValue(count)); + if (count + 1 < elements.length) { + temp.append("\t"); + } + } + line[index++] = temp.toString(); + } + return line; + } + + @Override + public String toString() { + String[] contents = getDataAsText(); + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (int x = 0; x < contents.length; x++) { + if (!first) { + sb.append("\n"); + } else { + first = false; + } + sb.append(contents[x]); + } + return sb.toString(); + } +} diff --git a/src/jorphan/org/apache/jorphan/collections/HashTree.java b/src/jorphan/org/apache/jorphan/collections/HashTree.java new file mode 100644 index 00000000000..83353985442 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/collections/HashTree.java @@ -0,0 +1,1103 @@ +/* + * 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.jorphan.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * This class is used to create a tree structure of objects. Each element in the + * tree is also a key to the next node down in the tree. It provides many ways + * to add objects and branches, as well as many ways to retrieve. + *

+ * HashTree implements the Map interface for convenience reasons. The main + * difference between a Map and a HashTree is that the HashTree organizes the + * data into a recursive tree structure, and provides the means to manipulate + * that structure. + *

+ * Of special interest is the {@link #traverse(HashTreeTraverser)} method, which + * provides an expedient way to traverse any HashTree by implementing the + * {@link HashTreeTraverser} interface in order to perform some operation on the + * tree, or to extract information from the tree. + * + * @see HashTreeTraverser + * @see SearchByClass + */ +public class HashTree implements Serializable, Map, Cloneable { + + private static final long serialVersionUID = 240L; + + // Used for the RuntimeException to short-circuit the traversal + private static final String FOUND = "found"; // $NON-NLS-1$ + + // N.B. The keys can be either JMeterTreeNode or TestElement + protected final Map data; + + /** + * Creates an empty new HashTree. + */ + public HashTree() { + this(null, null); + } + + /** + * Allow subclasses to provide their own Map. + * @param _map {@link Map} to use + */ + protected HashTree(Map _map) { + this(_map, null); + } + + /** + * Creates a new HashTree and adds the given object as a top-level node. + * + * @param key + * name of the new top-level node + */ + public HashTree(Object key) { + this(new HashMap(), key); + } + + /** + * Uses the new HashTree if not null and adds the given object as a + * top-level node if not null + * + * @param _map + * the map to be used. If null a new {@link HashMap} + * will be created + * @param key + * the object to be used as the key for the root node (may be + * null, in which case no root node will be created) + */ + private HashTree(Map _map, Object key) { + if(_map != null) { + data = _map; + } else { + data = new HashMap(); + } + if(key != null) { + data.put(key, new HashTree()); + } + } + + /** + * The Map given must also be a HashTree, otherwise an + * UnsupportedOperationException is thrown. If it is a HashTree, this is + * like calling the add(HashTree) method. + * + * @see #add(HashTree) + * @see java.util.Map#putAll(Map) + */ + @Override + public void putAll(Map map) { + if (map instanceof HashTree) { + this.add((HashTree) map); + } else { + throw new UnsupportedOperationException("can only putAll other HashTree objects"); + } + } + + /** + * Exists to satisfy the Map interface. + * + * @see java.util.Map#entrySet() + */ + @Override + public Set> entrySet() { + return data.entrySet(); + } + + /** + * Implemented as required by the Map interface, but is not very useful + * here. All 'values' in a HashTree are HashTree's themselves. + * + * @param value + * Object to be tested as a value. + * @return True if the HashTree contains the value, false otherwise. + * @see java.util.Map#containsValue(Object) + */ + @Override + public boolean containsValue(Object value) { + return data.containsValue(value); + } + + /** + * This is the same as calling HashTree.add(key,value). + * + * @param key + * to use + * @param value + * to store against key + * @see java.util.Map#put(Object, Object) + */ + @Override + public HashTree put(Object key, HashTree value) { + HashTree previous = data.get(key); + add(key, value); + return previous; + } + + /** + * Clears the HashTree of all contents. + * + * @see java.util.Map#clear() + */ + @Override + public void clear() { + data.clear(); + } + + /** + * Returns a collection of all the sub-trees of the current tree. + * + * @see java.util.Map#values() + */ + @Override + public Collection values() { + return data.values(); + } + + /** + * Adds a key as a node at the current level and then adds the given + * HashTree to that new node. + * + * @param key + * key to create in this tree + * @param subTree + * sub tree to add to the node created for the first argument. + */ + public void add(Object key, HashTree subTree) { + add(key).add(subTree); + } + + /** + * Adds all the nodes and branches of the given tree to this tree. Is like + * merging two trees. Duplicates are ignored. + * + * @param newTree the tree to be added + */ + public void add(HashTree newTree) { + for (Object item : newTree.list()) { + add(item).add(newTree.getTree(item)); + } + } + + /** + * Creates a new HashTree and adds all the objects in the given collection + * as top-level nodes in the tree. + * + * @param keys + * a collection of objects to be added to the created HashTree. + */ + public HashTree(Collection keys) { + data = new HashMap(); + for (Object o : keys) { + data.put(o, new HashTree()); + } + } + + /** + * Creates a new HashTree and adds all the objects in the given array as + * top-level nodes in the tree. + * + * @param keys + * array with names for the new top-level nodes + */ + public HashTree(Object[] keys) { + data = new HashMap(); + for (int x = 0; x < keys.length; x++) { + data.put(keys[x], new HashTree()); + } + } + + /** + * If the HashTree contains the given object as a key at the top level, then + * a true result is returned, otherwise false. + * + * @param o + * Object to be tested as a key. + * @return True if the HashTree contains the key, false otherwise. + * @see java.util.Map#containsKey(Object) + */ + @Override + public boolean containsKey(Object o) { + return data.containsKey(o); + } + + /** + * If the HashTree is empty, true is returned, false otherwise. + * + * @return True if HashTree is empty, false otherwise. + */ + @Override + public boolean isEmpty() { + return data.isEmpty(); + } + + /** + * Sets a key and it's value in the HashTree. It actually sets up a key, and + * then creates a node for the key and sets the value to the new node, as a + * key. Any previous nodes that existed under the given key are lost. + * + * @param key + * key to be set up + * @param value + * value to be set up as a key in the secondary node + */ + public void set(Object key, Object value) { + data.put(key, createNewTree(value)); + } + + /** + * Sets a key into the current tree and assigns it a HashTree as its + * subtree. Any previous entries under the given key are removed. + * + * @param key + * key to be set up + * @param t + * HashTree that the key maps to + */ + public void set(Object key, HashTree t) { + data.put(key, t); + } + + /** + * Sets a key and its values in the HashTree. It sets up a key in the + * current node, and then creates a node for that key, and sets all the + * values in the array as keys in the new node. Any keys previously held + * under the given key are lost. + * + * @param key + * Key to be set up + * @param values + * Array of objects to be added as keys in the secondary node + */ + public void set(Object key, Object[] values) { + data.put(key, createNewTree(Arrays.asList(values))); + } + + /** + * Sets a key and its values in the HashTree. It sets up a key in the + * current node, and then creates a node for that key, and set all the + * values in the array as keys in the new node. Any keys previously held + * under the given key are removed. + * + * @param key + * key to be set up + * @param values + * Collection of objects to be added as keys in the secondary + * node + */ + public void set(Object key, Collection values) { + data.put(key, createNewTree(values)); + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key array as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the array. + * Continues recursing in this manner until the end of the first array is + * reached, at which point all the values of the second array are set as + * keys to the bottom-most node. All previous keys of that bottom-most node + * are removed. + * + * @param treePath + * array of keys to put into HashTree + * @param values + * array of values to be added as keys to bottom-most node + */ + public void set(Object[] treePath, Object[] values) { + if (treePath != null && values != null) { + set(Arrays.asList(treePath), Arrays.asList(values)); + } + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key array as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the array. + * Continues recursing in this manner until the end of the first array is + * reached, at which point all the values of the Collection of values are + * set as keys to the bottom-most node. Any keys previously held by the + * bottom-most node are lost. + * + * @param treePath + * array of keys to put into HashTree + * @param values + * Collection of values to be added as keys to bottom-most node + */ + public void set(Object[] treePath, Collection values) { + if (treePath != null) { + set(Arrays.asList(treePath), values); + } + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key list as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the list. + * Continues recursing in this manner until the end of the first list is + * reached, at which point all the values of the array of values are set as + * keys to the bottom-most node. Any previously existing keys of that bottom + * node are removed. + * + * @param treePath + * collection of keys to put into HashTree + * @param values + * array of values to be added as keys to bottom-most node + */ + public void set(Collection treePath, Object[] values) { + HashTree tree = addTreePath(treePath); + tree.set(Arrays.asList(values)); + } + + /** + * Sets the nodes of the current tree to be the objects of the given + * collection. Any nodes previously in the tree are removed. + * + * @param values + * Collection of objects to set as nodes. + */ + public void set(Collection values) { + clear(); + this.add(values); + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key list as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the list. + * Continues recursing in this manner until the end of the first list is + * reached, at which point all the values of the Collection of values are + * set as keys to the bottom-most node. Any previously existing keys of that + * bottom node are lost. + * + * @param treePath + * list of keys to put into HashTree + * @param values + * collection of values to be added as keys to bottom-most node + */ + public void set(Collection treePath, Collection values) { + HashTree tree = addTreePath(treePath); + tree.set(values); + } + + /** + * Adds an key into the HashTree at the current level. If a HashTree exists + * for the key already, no new tree will be added + * + * @param key + * key to be added to HashTree + * @return newly generated tree, if no tree was found for the given key; + * existing key otherwise + */ + public HashTree add(Object key) { + if (!data.containsKey(key)) { + HashTree newTree = createNewTree(); + data.put(key, newTree); + return newTree; + } + return getTree(key); + } + + /** + * Adds all the given objects as nodes at the current level. + * + * @param keys + * Array of Keys to be added to HashTree. + */ + public void add(Object[] keys) { + for (int x = 0; x < keys.length; x++) { + add(keys[x]); + } + } + + /** + * Adds a bunch of keys into the HashTree at the current level. + * + * @param keys + * Collection of Keys to be added to HashTree. + */ + public void add(Collection keys) { + for (Object o : keys) { + add(o); + } + } + + /** + * Adds a key and it's value in the HashTree. The first argument becomes a + * node at the current level, and the second argument becomes a node of it. + * + * @param key + * key to be added + * @param value + * value to be added as a key in the secondary node + * @return HashTree for which value is the key + */ + public HashTree add(Object key, Object value) { + return add(key).add(value); + } + + /** + * Adds a key and it's values in the HashTree. The first argument becomes a + * node at the current level, and adds all the values in the array to the + * new node. + * + * @param key + * key to be added + * @param values + * array of objects to be added as keys in the secondary node + */ + public void add(Object key, Object[] values) { + add(key).add(values); + } + + /** + * Adds a key as a node at the current level and then adds all the objects + * in the second argument as nodes of the new node. + * + * @param key + * key to be added + * @param values + * Collection of objects to be added as keys in the secondary + * node + */ + public void add(Object key, Collection values) { + add(key).add(values); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is an array that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * an array of objects representing a path + * @param values + * array of values to be added as keys to bottom-most node + */ + public void add(Object[] treePath, Object[] values) { + if (treePath != null) { + add(Arrays.asList(treePath), Arrays.asList(values)); + } + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is an array that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * an array of objects representing a path + * @param values + * collection of values to be added as keys to bottom-most node + */ + public void add(Object[] treePath, Collection values) { + if (treePath != null) { + add(Arrays.asList(treePath), values); + } + } + + public HashTree add(Object[] treePath, Object value) { + return add(Arrays.asList(treePath), value); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a List that represents a path to a specific node in the tree. + * If the path doesn't already exist, it is created (the objects are added + * along the way). At the path, all the objects in the second argument are + * added as nodes. + * + * @param treePath + * a list of objects representing a path + * @param values + * array of values to be added as keys to bottom-most node + */ + public void add(Collection treePath, Object[] values) { + HashTree tree = addTreePath(treePath); + tree.add(Arrays.asList(values)); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a List that represents a path to a specific node in the tree. + * If the path doesn't already exist, it is created (the objects are added + * along the way). At the path, the object in the second argument is added + * as a node. + * + * @param treePath + * a list of objects representing a path + * @param value + * Object to add as a node to bottom-most node + * @return HashTree for which value is the key + */ + public HashTree add(Collection treePath, Object value) { + HashTree tree = addTreePath(treePath); + return tree.add(value); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a SortedSet that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * a SortedSet of objects representing a path + * @param values + * Collection of values to be added as keys to bottom-most node + */ + public void add(Collection treePath, Collection values) { + HashTree tree = addTreePath(treePath); + tree.add(values); + } + + protected HashTree addTreePath(Collection treePath) { + HashTree tree = this; + for (Object temp : treePath) { + tree = tree.add(temp); + } + return tree; + } + + /** + * Gets the HashTree mapped to the given key. + * + * @param key + * Key used to find appropriate HashTree() + * @return the HashTree for key + */ + public HashTree getTree(Object key) { + return data.get(key); + } + + /** + * Returns the HashTree object associated with the given key. Same as + * calling {@link #getTree(Object)}. + * + * @see java.util.Map#get(Object) + */ + @Override + public HashTree get(Object key) { + return getTree(key); + } + + /** + * Gets the HashTree object mapped to the last key in the array by recursing + * through the HashTree structure one key at a time. + * + * @param treePath + * array of keys. + * @return HashTree at the end of the recursion. + */ + public HashTree getTree(Object[] treePath) { + if (treePath != null) { + return getTree(Arrays.asList(treePath)); + } + return this; + } + + /** + * Create a clone of this HashTree. This is not a deep clone (ie, the + * contents of the tree are not cloned). + * + */ + @Override + public Object clone() { + HashTree newTree = new HashTree(); + cloneTree(newTree); + return newTree; + } + + protected void cloneTree(HashTree newTree) { + for (Object key : list()) { + newTree.set(key, (HashTree) getTree(key).clone()); + } + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @return HashTree + */ + protected HashTree createNewTree() { + return new HashTree(); + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @param key + * object to use as the key for the top level + * + * @return newly created {@link HashTree} + */ + protected HashTree createNewTree(Object key) { + return new HashTree(key); + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @param values objects to be added to the new {@link HashTree} + * + * @return newly created {@link HashTree} + */ + protected HashTree createNewTree(Collection values) { + return new HashTree(values); + } + + /** + * Gets the HashTree object mapped to the last key in the SortedSet by + * recursing through the HashTree structure one key at a time. + * + * @param treePath + * Collection of keys + * @return HashTree at the end of the recursion + */ + public HashTree getTree(Collection treePath) { + return getTreePath(treePath); + } + + /** + * Gets a Collection of all keys in the current HashTree node. If the + * HashTree represented a file system, this would be like getting a + * collection of all the files in the current folder. + * + * @return Set of all keys in this HashTree + */ + public Collection list() { + return data.keySet(); + } + + /** + * Gets a Set of all keys in the HashTree mapped to the given key of the + * current HashTree object (in other words, one level down. If the HashTree + * represented a file system, this would like getting a list of all files in + * a sub-directory (of the current directory) specified by the key argument. + * + * @param key + * key used to find HashTree to get list of + * @return Set of all keys in found HashTree. + */ + public Collection list(Object key) { + HashTree temp = data.get(key); + if (temp != null) { + return temp.list(); + } + return new HashSet(); + } + + /** + * Removes the entire branch specified by the given key. + * + * @see java.util.Map#remove(Object) + */ + @Override + public HashTree remove(Object key) { + return data.remove(key); + } + + /** + * Recurses down into the HashTree stucture using each subsequent key in the + * array of keys, and returns the Set of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * Array of keys used to recurse into HashTree structure + * @return Set of all keys found in end HashTree + */ + public Collection list(Object[] treePath) { // TODO not used? + if (treePath != null) { + return list(Arrays.asList(treePath)); + } + return list(); + } + + /** + * Recurses down into the HashTree stucture using each subsequent key in the + * List of keys, and returns the Set of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * List of keys used to recurse into HashTree structure + * @return Set of all keys found in end HashTree + */ + public Collection list(Collection treePath) { + HashTree tree = getTreePath(treePath); + if (tree != null) { + return tree.list(); + } + return new HashSet(); + } + + /** + * Finds the given current key, and replaces it with the given new key. Any + * tree structure found under the original key is moved to the new key. + * + * @param currentKey name of the key to be replaced + * @param newKey name of the new key + */ + public void replaceKey(Object currentKey, Object newKey) { + HashTree tree = getTree(currentKey); + data.remove(currentKey); + data.put(newKey, tree); + } + + /** + * Gets an array of all keys in the current HashTree node. If the HashTree + * represented a file system, this would be like getting an array of all the + * files in the current folder. + * + * @return array of all keys in this HashTree. + */ + public Object[] getArray() { + return data.keySet().toArray(); + } + + /** + * Gets an array of all keys in the HashTree mapped to the given key of the + * current HashTree object (in other words, one level down). If the HashTree + * represented a file system, this would like getting a list of all files in + * a sub-directory (of the current directory) specified by the key argument. + * + * @param key + * key used to find HashTree to get list of + * @return array of all keys in found HashTree + */ + public Object[] getArray(Object key) { + HashTree t = getTree(key); + if (t != null) { + return t.getArray(); + } + return null; + } + + /** + * Recurses down into the HashTree stucture using each subsequent key in the + * array of keys, and returns an array of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * array of keys used to recurse into HashTree structure + * @return array of all keys found in end HashTree + */ + public Object[] getArray(Object[] treePath) { + if (treePath != null) { + return getArray(Arrays.asList(treePath)); + } + return getArray(); + } + + /** + * Recurses down into the HashTree structure using each subsequent key in the + * treePath argument, and returns an array of keys of the HashTree object at + * the end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * list of keys used to recurse into HashTree structure + * @return array of all keys found in end HashTree + */ + public Object[] getArray(Collection treePath) { + HashTree tree = getTreePath(treePath); + return (tree != null) ? tree.getArray() : null; + } + + protected HashTree getTreePath(Collection treePath) { + HashTree tree = this; + Iterator iter = treePath.iterator(); + while (iter.hasNext()) { + if (tree == null) { + return null; + } + Object temp = iter.next(); + tree = tree.getTree(temp); + } + return tree; + } + + /** + * Returns a hashcode for this HashTree. + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return data.hashCode() * 7; + } + + /** + * Compares all objects in the tree and verifies that the two trees contain + * the same objects at the same tree levels. Returns true if they do, false + * otherwise. + * + * @param o + * Object to be compared against + * @see java.lang.Object#equals(Object) + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof HashTree)) { + return false; + } + HashTree oo = (HashTree) o; + if (oo.size() != this.size()) { + return false; + } + return data.equals(oo.data); + } + + /** + * Returns a Set of all the keys in the top-level of this HashTree. + * + * @see java.util.Map#keySet() + */ + @Override + public Set keySet() { + return data.keySet(); + } + + /** + * Searches the HashTree structure for the given key. If it finds the key, + * it returns the HashTree mapped to the key. If it finds nothing, it + * returns null. + * + * @param key + * Key to search for + * @return HashTree mapped to key, if found, otherwise null + */ + public HashTree search(Object key) {// TODO does not appear to be used + HashTree result = getTree(key); + if (result != null) { + return result; + } + TreeSearcher searcher = new TreeSearcher(key); + try { + traverse(searcher); + } catch (RuntimeException e) { + if (!e.getMessage().equals(FOUND)){ + throw e; + } + // do nothing - means object is found + } + return searcher.getResult(); + } + + /** + * Method readObject. + * + * @param ois + * the stream to read the objects from + * @throws ClassNotFoundException + * when the class for the deserialization can not be found + * @throws IOException + * when I/O error occurs + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } + + /** + * Returns the number of top-level entries in the HashTree. + * + * @see java.util.Map#size() + */ + @Override + public int size() { + return data.size(); + } + + /** + * Allows any implementation of the HashTreeTraverser interface to easily + * traverse (depth-first) all the nodes of the HashTree. The Traverser + * implementation will be given notification of each node visited. + * + * @see HashTreeTraverser + * @param visitor + * the visitor that wants to traverse the tree + */ + public void traverse(HashTreeTraverser visitor) { + for (Object item : list()) { + visitor.addNode(item, getTree(item)); + getTree(item).traverseInto(visitor); + } + } + + /** + * The recursive method that accomplishes the tree-traversal and performs + * the callbacks to the HashTreeTraverser. + * + * @param visitor + * the {@link HashTreeTraverser} to be notified + */ + private void traverseInto(HashTreeTraverser visitor) { + + if (list().size() == 0) { + visitor.processPath(); + } else { + for (Object item : list()) { + final HashTree treeItem = getTree(item); + visitor.addNode(item, treeItem); + treeItem.traverseInto(visitor); + } + } + visitor.subtractNode(); + } + + /** + * Generate a printable representation of the tree. + * + * @return a representation of the tree + */ + @Override + public String toString() { + ConvertToString converter = new ConvertToString(); + try { + traverse(converter); + } catch (Exception e) { // Just in case + converter.reportError(e); + } + return converter.toString(); + } + + private static class TreeSearcher implements HashTreeTraverser { + + private final Object target; + + private HashTree result; + + public TreeSearcher(Object t) { + target = t; + } + + public HashTree getResult() { + return result; + } + + /** {@inheritDoc} */ + @Override + public void addNode(Object node, HashTree subTree) { + result = subTree.getTree(target); + if (result != null) { + // short circuit traversal when found + throw new RuntimeException(FOUND); + } + } + + /** {@inheritDoc} */ + @Override + public void processPath() { + // Not used + } + + /** {@inheritDoc} */ + @Override + public void subtractNode() { + // Not used + } + } + + private static class ConvertToString implements HashTreeTraverser { + private final StringBuilder string = new StringBuilder(getClass().getName() + "{"); + + private final StringBuilder spaces = new StringBuilder(); + + private int depth = 0; + + @Override + public void addNode(Object key, HashTree subTree) { + depth++; + string.append("\n").append(getSpaces()).append(key); + string.append(" {"); + } + + @Override + public void subtractNode() { + string.append("\n" + getSpaces() + "}"); + depth--; + } + + @Override + public void processPath() { + } + + @Override + public String toString() { + string.append("\n}"); + return string.toString(); + } + + void reportError(Throwable t){ + string.append("Error: ").append(t.toString()); + } + + private String getSpaces() { + if (spaces.length() < depth * 2) { + while (spaces.length() < depth * 2) { + spaces.append(" "); + } + } else if (spaces.length() > depth * 2) { + spaces.setLength(depth * 2); + } + return spaces.toString(); + } + } +} diff --git a/src/jorphan/org/apache/jorphan/collections/HashTreeTraverser.java b/src/jorphan/org/apache/jorphan/collections/HashTreeTraverser.java new file mode 100644 index 00000000000..f376af31e97 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/collections/HashTreeTraverser.java @@ -0,0 +1,76 @@ +/* + * 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.jorphan.collections; + +/** + * By implementing this interface, a class can easily traverse a HashTree + * object, and be notified via callbacks of certain events. There are three such + * events: + *
    + *
  1. When a node is first encountered, the traverser's + * {@link #addNode(Object,HashTree)} method is called. It is handed the object + * at that node, and the entire sub-tree of the node.
  2. + *
  3. When a leaf node is encountered, the traverser is notified that a full + * path has been finished via the {@link #processPath()} method. It is the + * traversing class's responsibility to know the path that has just finished + * (this can be done by keeping a simple stack of all added nodes).
  4. + *
  5. When a node is retraced, the traverser's {@link #subtractNode()} is + * called. Again, it is the traverser's responsibility to know which node has + * been retraced.
  6. + *
+ * To summarize, as the traversal goes down a tree path, nodes are added. When + * the end of the path is reached, the {@link #processPath()} call is sent. As + * the traversal backs up, nodes are subtracted. + *

+ * The traversal is a depth-first traversal. + * + * @see HashTree + * @see SearchByClass + * + * @version $Revision$ + */ +public interface HashTreeTraverser { + /** + * The tree traverses itself depth-first, calling addNode for each object it + * encounters as it goes. This is a callback method, and should not be + * called except by a HashTree during traversal. + * + * @param node + * the node currently encountered + * @param subTree + * the HashTree under the node encountered + */ + void addNode(Object node, HashTree subTree); + + /** + * Indicates traversal has moved up a step, and the visitor should remove + * the top node from its stack structure. This is a callback method, and + * should not be called except by a HashTree during traversal. + */ + void subtractNode(); + + /** + * Process path is called when a leaf is reached. If a visitor wishes to + * generate Lists of path elements to each leaf, it should keep a Stack data + * structure of nodes passed to it with addNode, and removing top items for + * every {@link #subtractNode()} call. This is a callback method, and should + * not be called except by a HashTree during traversal. + */ + void processPath(); +} diff --git a/src/jorphan/org/apache/jorphan/collections/ListedHashTree.java b/src/jorphan/org/apache/jorphan/collections/ListedHashTree.java new file mode 100644 index 00000000000..d4196da432d --- /dev/null +++ b/src/jorphan/org/apache/jorphan/collections/ListedHashTree.java @@ -0,0 +1,225 @@ +/* + * 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.jorphan.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jorphan.util.JMeterError; + +/** + * ListedHashTree is a different implementation of the {@link HashTree} + * collection class. In the ListedHashTree, the order in which values are added + * is preserved (not to be confused with {@link SortedHashTree}, which sorts + * the order of the values using the compare() function). Any listing of nodes + * or iteration through the list of nodes of a ListedHashTree will be given in + * the order in which the nodes were added to the tree. + * + * @see HashTree + */ +public class ListedHashTree extends HashTree implements Serializable, Cloneable { + private static final long serialVersionUID = 240L; + + private final List order; + + public ListedHashTree() { + super(); + order = new LinkedList(); + } + + public ListedHashTree(Object key) { + this(); + data.put(key, new ListedHashTree()); + order.add(key); + } + + public ListedHashTree(Collection keys) { + this(); + for (Object temp : keys) { + data.put(temp, new ListedHashTree()); + order.add(temp); + } + } + + public ListedHashTree(Object[] keys) { + this(); + for (int x = 0; x < keys.length; x++) { + data.put(keys[x], new ListedHashTree()); + order.add(keys[x]); + } + } + + /** {@inheritDoc} */ + @Override + public Object clone() { + ListedHashTree newTree = new ListedHashTree(); + cloneTree(newTree); + return newTree; + } + + /** {@inheritDoc} */ + @Override + public void set(Object key, Object value) { + if (!data.containsKey(key)) { + order.add(key); + } + super.set(key, value); + } + + /** {@inheritDoc} */ + @Override + public void set(Object key, HashTree t) { + if (!data.containsKey(key)) { + order.add(key); + } + super.set(key, t); + } + + /** {@inheritDoc} */ + @Override + public void set(Object key, Object[] values) { + if (!data.containsKey(key)) { + order.add(key); + } + super.set(key, values); + } + + /** {@inheritDoc} */ + @Override + public void set(Object key, Collection values) { + if (!data.containsKey(key)) { + order.add(key); + } + super.set(key, values); + } + + /** {@inheritDoc} */ + @Override + public void replaceKey(Object currentKey, Object newKey) { + HashTree tree = getTree(currentKey); + data.remove(currentKey); + data.put(newKey, tree); + // find order.indexOf(currentKey) using == rather than equals() + // there may be multiple entries which compare equals (Bug 50898) + // This will be slightly slower than the built-in method, + // but replace() is not used frequently. + int entry=-1; + for (int i=0; i < order.size(); i++) { + Object ent = order.get(i); + if (ent == currentKey) { + entry = i; + break; + } + } + if (entry == -1) { + throw new JMeterError("Impossible state, data key not present in order: "+currentKey.getClass()); + } + order.set(entry, newKey); + } + + /** {@inheritDoc} */ + @Override + public HashTree createNewTree() { + return new ListedHashTree(); + } + + /** {@inheritDoc} */ + @Override + public HashTree createNewTree(Object key) { + return new ListedHashTree(key); + } + + /** {@inheritDoc} */ + @Override + public HashTree createNewTree(Collection values) { + return new ListedHashTree(values); + } + + /** {@inheritDoc} */ + @Override + public HashTree add(Object key) { + if (!data.containsKey(key)) { + HashTree newTree = createNewTree(); + data.put(key, newTree); + order.add(key); + return newTree; + } + return getTree(key); + } + + /** {@inheritDoc} */ + @Override + public Collection list() { + return order; + } + + /** {@inheritDoc} */ + @Override + public HashTree remove(Object key) { + order.remove(key); + return data.remove(key); + } + + /** {@inheritDoc} */ + @Override + public Object[] getArray() { + return order.toArray(); + } + + /** {@inheritDoc} */ + // Make sure the hashCode depends on the order as well + @Override + public int hashCode() { + int hc = 17; + hc = hc * 37 + (order == null ? 0 : order.hashCode()); + hc = hc * 37 + super.hashCode(); + return hc; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (!(o instanceof ListedHashTree)) { + return false; + } + ListedHashTree lht = (ListedHashTree) o; + return (super.equals(lht) && order.equals(lht.order)); + } + + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + super.clear(); + order.clear(); + } +} diff --git a/src/jorphan/org/apache/jorphan/collections/SearchByClass.java b/src/jorphan/org/apache/jorphan/collections/SearchByClass.java new file mode 100644 index 00000000000..0ce00d0ab8b --- /dev/null +++ b/src/jorphan/org/apache/jorphan/collections/SearchByClass.java @@ -0,0 +1,117 @@ +/* + * 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.jorphan.collections; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Useful for finding all nodes in the tree that represent objects of a + * particular type. For instance, if your tree contains all strings, and a few + * StringBuilder objects, you can use the SearchByClass traverser to find all + * the StringBuilder objects in your tree. + *

+ * Usage is simple. Given a {@link HashTree} object "tree", and a SearchByClass + * object: + * + *

+ * HashTree tree = new HashTree();
+ * // ... tree gets filled with objects
+ * SearchByClass searcher = new SearchByClass(StringBuilder.class);
+ * tree.traverse(searcher);
+ * Iterator iter = searcher.getSearchResults().iterator();
+ * while (iter.hasNext()) {
+ *     StringBuilder foundNode = (StringBuilder) iter.next();
+ *     HashTree subTreeOfFoundNode = searcher.getSubTree(foundNode);
+ *     // .... do something with node and subTree...
+ * }
+ * 
+ * + * @see HashTree + * @see HashTreeTraverser + * + * @version $Revision$ + * @param + * Class that should be searched for + */ +public class SearchByClass implements HashTreeTraverser { + private final List objectsOfClass = new LinkedList(); + + private final Map subTrees = new HashMap(); + + private final Class searchClass; + + /** + * Creates an instance of SearchByClass, and sets the Class to be searched + * for. + * + * @param searchClass + * class to be searched for + */ + public SearchByClass(Class searchClass) { + this.searchClass = searchClass; + } + + /** + * After traversing the HashTree, call this method to get a collection of + * the nodes that were found. + * + * @return Collection All found nodes of the requested type + */ + public Collection getSearchResults() { // TODO specify collection type without breaking callers + return objectsOfClass; + } + + /** + * Given a specific found node, this method will return the sub tree of that + * node. + * + * @param root + * the node for which the sub tree is requested + * @return HashTree + */ + public HashTree getSubTree(Object root) { + return subTrees.get(root); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + public void addNode(Object node, HashTree subTree) { + if (searchClass.isAssignableFrom(node.getClass())) { + objectsOfClass.add((T) node); + ListedHashTree tree = new ListedHashTree(node); + tree.set(node, subTree); + subTrees.put(node, tree); + } + } + + /** {@inheritDoc} */ + @Override + public void subtractNode() { + } + + /** {@inheritDoc} */ + @Override + public void processPath() { + } +} diff --git a/src/jorphan/org/apache/jorphan/collections/SortedHashTree.java b/src/jorphan/org/apache/jorphan/collections/SortedHashTree.java new file mode 100644 index 00000000000..6192c19ae16 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/collections/SortedHashTree.java @@ -0,0 +1,109 @@ +/* + * 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.jorphan.collections; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; +import java.util.TreeMap; + +/** + * SortedHashTree is a different implementation of the {@link HashTree} + * collection class. In the SortedHashTree, the ordering of values in the tree + * is made explicit via the compare() function of objects added to the tree. + * This works in exactly the same fashion as it does for a SortedSet. + * + * @see HashTree + * @see HashTreeTraverser + * + * TODO does not appear to be used currently + */ +public class SortedHashTree extends HashTree implements Serializable { + + private static final long serialVersionUID = 233L; + + public SortedHashTree() { + super(new TreeMap()); // equivalent to new TreeMap((Comparator)null); + } + + // non-null Comparators don't appear to be used at present + public SortedHashTree(Comparator comper) { + super(new TreeMap(comper)); + } + + public SortedHashTree(Object key) { + this(); + data.put(key, new SortedHashTree()); + } + + public SortedHashTree(Object key, Comparator comper) { + this(comper); + data.put(key, new SortedHashTree(comper)); + } + + public SortedHashTree(Collection keys) { + this(); + for (Object key : keys) { + data.put(key, new SortedHashTree()); + } + } + + public SortedHashTree(Collection keys, Comparator comper) { + this(comper); + for (Object key : keys) { + data.put(key, new SortedHashTree(comper)); + } + } + + public SortedHashTree(Object[] keys) { + this(); + for (int x = 0; x < keys.length; x++) { + data.put(keys[x], new SortedHashTree()); + } + } + + public SortedHashTree(Object[] keys, Comparator comper) { + this(comper); + for (int x = 0; x < keys.length; x++) { + data.put(keys[x], new SortedHashTree(comper)); + } + } + + /** {@inheritDoc} */ + @Override + protected HashTree createNewTree() { + Comparator comparator = ((TreeMap)data).comparator(); + return new SortedHashTree(comparator); + } + + /** {@inheritDoc} */ + @Override + protected HashTree createNewTree(Object key) { + Comparator comparator = ((TreeMap) data).comparator(); + return new SortedHashTree(key, comparator); + } + + /** {@inheritDoc} */ + @Override + protected HashTree createNewTree(Collection values) { + Comparator comparator = ((TreeMap)data).comparator(); + return new SortedHashTree(values, comparator); + } + +} diff --git a/src/jorphan/org/apache/jorphan/exec/KeyToolUtils.java b/src/jorphan/org/apache/jorphan/exec/KeyToolUtils.java new file mode 100644 index 00000000000..df374830625 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/exec/KeyToolUtils.java @@ -0,0 +1,452 @@ +/* + * 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.jorphan.exec; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.JavaVersion; +import org.apache.commons.lang3.SystemUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Utilities for working with Java keytool + */ +public class KeyToolUtils { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // The DNAME which is used if none is provided + private static final String DEFAULT_DNAME = "cn=JMeter Proxy (DO NOT TRUST)"; // $NON-NLS-1$ + + // N.B. It seems that Opera needs a chain in order to accept server keys signed by the intermediate CA + // Opera does not seem to like server keys signed by the root (self-signed) cert. + + private static final String DNAME_ROOT_CA_KEY; + + private static final String KEYTOOL = "keytool"; + + /** Name of property that can be used to override the default keytool location */ + private static final String KEYTOOL_DIRECTORY = "keytool.directory"; // $NON-NLS-1$ + + /** + * Where to find the keytool application. + * If null, then keytool cannot be found. + */ + private static final String KEYTOOL_PATH; + + private static void addElement(StringBuilder sb, String prefix, String value) { + if (value != null) { + sb.append(", "); + sb.append(prefix); + sb.append(value); + } + } + + static { + StringBuilder sb = new StringBuilder(); + sb.append("CN=_ DO NOT INSTALL unless this is your certificate (JMeter root CA)"); // $NON-NLS-1$ + String userName = System.getProperty("user.name"); // $NON-NLS-1$ + userName = userName.replace('\\','/'); // Backslash is special (Bugzilla 56178) + addElement(sb, "OU=Username: ", userName); // $NON-NLS-1$ + addElement(sb, "C=", System.getProperty("user.country")); // $NON-NLS-1$ $NON-NLS-2$ + DNAME_ROOT_CA_KEY = sb.toString(); + + // Try to find keytool application + // N.B. Cannot use JMeter property from jorphan jar. + final String keytoolDir = System.getProperty(KEYTOOL_DIRECTORY); + + String keytoolPath; // work field + if (keytoolDir != null) { + keytoolPath = new File(new File(keytoolDir),KEYTOOL).getPath(); + if (!checkKeytool(keytoolPath)) { + log.error("Cannot find keytool using property " + KEYTOOL_DIRECTORY + "="+keytoolDir); + keytoolPath = null; // don't try anything else if the property is provided + } + } else { + keytoolPath = KEYTOOL; + if (!checkKeytool(keytoolPath)) { // Not found on PATH, check Java Home + File javaHome = SystemUtils.getJavaHome(); + if (javaHome != null) { + keytoolPath = new File(new File(javaHome,"bin"),KEYTOOL).getPath(); // $NON-NLS-1$ + if (!checkKeytool(keytoolPath)) { + keytoolPath = null; + } + } else { + keytoolPath = null; + } + } + } + if (keytoolPath == null) { + log.error("Unable to find keytool application. Check PATH or define system property " + KEYTOOL_DIRECTORY); + } else { + log.info("keytool found at '" + keytoolPath + "'"); + } + KEYTOOL_PATH = keytoolPath; + } + + private static final String DNAME_INTERMEDIATE_CA_KEY = "cn=DO NOT INSTALL THIS CERTIFICATE (JMeter Intermediate CA)"; // $NON-NLS-1$ + + public static final String ROOT_CACERT_CRT_PFX = "ApacheJMeterTemporaryRootCA"; // $NON-NLS-1$ (do not change) + private static final String ROOT_CACERT_CRT = ROOT_CACERT_CRT_PFX + ".crt"; // $NON-NLS-1$ (Firefox and Windows) + private static final String ROOT_CACERT_USR = ROOT_CACERT_CRT_PFX + ".usr"; // $NON-NLS-1$ (Opera) + + private static final String ROOTCA_ALIAS = ":root_ca:"; // $NON-NLS-1$ + private static final String INTERMEDIATE_CA_ALIAS = ":intermediate_ca:"; // $NON-NLS-1$ + + /** Does this class support generation of host certificates? */ + public static final boolean SUPPORTS_HOST_CERT = SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_7); + // i.e. does keytool support -gencert and -ext ? + + private KeyToolUtils() { + // not instantiable + } + + /** + * Generate a self-signed keypair using the algorithm "RSA". + * Requires Java 7 or later if the "ext" parameter is not null. + * + * @param keystore the keystore; if it already contains the alias the command will fail + * @param alias the alias to use, not null + * @param password the password to use for the store and the key + * @param validity the validity period in days, greater than 0 + * @param dname the distinguished name value, if omitted use "cn=JMeter Proxy (DO NOT TRUST)" + * @param ext if not null, the extension (-ext) to add (e.g. "bc:c"). This requires Java 7. + * + * @throws IOException if keytool was not configured or running keytool application fails + */ + public static void genkeypair(final File keystore, String alias, final String password, int validity, String dname, String ext) + throws IOException { + final File workingDir = keystore.getParentFile(); + final SystemCommand nativeCommand = new SystemCommand(workingDir, null); + final List arguments = new ArrayList(); + arguments.add(getKeyToolPath()); + arguments.add("-genkeypair"); // $NON-NLS-1$ + arguments.add("-alias"); // $NON-NLS-1$ + arguments.add(alias); + arguments.add("-dname"); // $NON-NLS-1$ + arguments.add(dname == null ? DEFAULT_DNAME : dname); + arguments.add("-keyalg"); // $NON-NLS-1$ + arguments.add("RSA"); // $NON-NLS-1$ + + arguments.add("-keystore"); // $NON-NLS-1$ + arguments.add(keystore.getName()); + arguments.add("-storepass"); // $NON-NLS-1$ + arguments.add(password); + arguments.add("-keypass"); // $NON-NLS-1$ + arguments.add(password); + arguments.add("-validity"); // $NON-NLS-1$ + arguments.add(Integer.toString(validity)); + if (ext != null) { // Requires Java 7 + arguments.add("-ext"); // $NON-NLS-1$ + arguments.add(ext); + } + try { + int exitVal = nativeCommand.run(arguments); + if (exitVal != 0) { + throw new IOException(" >> " + nativeCommand.getOutResult().trim() + " <<" + + "\nCommand failed, code: " + exitVal + + "\n'" + formatCommand(arguments)+"'"); + } + } catch (InterruptedException e) { + throw new IOException("Command was interrupted\n" + nativeCommand.getOutResult(), e); + } + } + + /** + * Formats arguments + * @param arguments + * @return String command line + */ + private static String formatCommand(List arguments) { + StringBuilder builder = new StringBuilder(); + boolean redact = false; // whether to redact next parameter + for (String string : arguments) { + final boolean quote = string.contains(" "); + if (quote) builder.append("\""); + builder.append(redact? "{redacted}" : string); + if (quote) builder.append("\""); + builder.append(" "); + redact = string.equals("-storepass") || string.equals("-keypass"); + } + if(arguments.size()>0) { + builder.setLength(builder.length()-1); // trim trailing space + } + return builder.toString(); + } + + /** + * Creates a self-signed Root CA certificate and an intermediate CA certificate + * (signed by the Root CA certificate) that can be used to sign server certificates. + * The Root CA certificate file is exported to the same directory as the keystore + * in formats suitable for Firefox/Chrome/IE (.crt) and Opera (.usr). + * Requires Java 7 or later. + * + * @param keystore the keystore in which to store everything + * @param password the password for keystore and keys + * @param validity the validity period in days, must be greater than 0 + * + * @throws IOException if keytool was not configured, running keytool application failed or copying the keys failed + */ + public static void generateProxyCA(File keystore, String password, int validity) throws IOException { + File caCert_crt = new File(ROOT_CACERT_CRT); + File caCert_usr = new File(ROOT_CACERT_USR); + boolean fileExists = false; + if (!keystore.delete() && keystore.exists()) { + log.warn("Problem deleting the keystore '" + keystore + "'"); + fileExists = true; + } + if (!caCert_crt.delete() && caCert_crt.exists()) { + log.warn("Problem deleting the certificate file '" + caCert_crt + "'"); + fileExists = true; + } + if (!caCert_usr.delete() && caCert_usr.exists()) { + log.warn("Problem deleting the certificate file '" + caCert_usr + "'"); + fileExists = true; + } + if (fileExists) { + log.warn("If problems occur when recording SSL, delete the files manually and retry."); + } + // Create the self-signed keypairs (requires Java 7 for -ext flag) + KeyToolUtils.genkeypair(keystore, ROOTCA_ALIAS, password, validity, DNAME_ROOT_CA_KEY, "bc:c"); + KeyToolUtils.genkeypair(keystore, INTERMEDIATE_CA_ALIAS, password, validity, DNAME_INTERMEDIATE_CA_KEY, "bc:c"); + + // Create cert for CA using root (requires Java 7 for gencert) + ByteArrayOutputStream certReqOut = new ByteArrayOutputStream(); + // generate the request + KeyToolUtils.keytool("-certreq", keystore, password, INTERMEDIATE_CA_ALIAS, null, certReqOut); + + // generate the certificate and store in output file + InputStream certReqIn = new ByteArrayInputStream(certReqOut.toByteArray()); + ByteArrayOutputStream genCertOut = new ByteArrayOutputStream(); + KeyToolUtils.keytool("-gencert", keystore, password, ROOTCA_ALIAS, certReqIn, genCertOut, "-ext", "BC:0"); + + // import the signed CA cert into the store (root already there) - both are needed to sign the domain certificates + InputStream genCertIn = new ByteArrayInputStream(genCertOut.toByteArray()); + KeyToolUtils.keytool("-importcert", keystore, password, INTERMEDIATE_CA_ALIAS, genCertIn, null); + + // Export the Root CA for Firefox/Chrome/IE + KeyToolUtils.keytool("-exportcert", keystore, password, ROOTCA_ALIAS, null, null, "-rfc", "-file", ROOT_CACERT_CRT); + // Copy for Opera + if(caCert_crt.exists() && caCert_crt.canRead()) { + FileUtils.copyFile(caCert_crt, caCert_usr); + } else { + log.warn("Failed creating "+caCert_crt.getAbsolutePath()+", check 'keytool' utility in path is available and points to a JDK >= 7"); + } + } + + /** + * Create a host certificate signed with the CA certificate. + * Requires Java 7 or later. + * + * @param keystore the keystore to use + * @param password the password to use for the keystore and keys + * @param host the host, e.g. jmeter.apache.org or *.apache.org; also used as the alias + * @param validity the validity period for the generated keypair + * + * @throws IOException if keytool was not configured or running keytool application failed + * + */ + public static void generateHostCert(File keystore, String password, String host, int validity) throws IOException { + // generate the keypair for the host + generateSignedCert(keystore, password, validity, + host, // alias + host); // subject + } + + private static void generateSignedCert(File keystore, String password, + int validity, String alias, String subject) throws IOException { + String dname = "cn=" + subject + ", o=JMeter Proxy (TEMPORARY TRUST ONLY)"; + KeyToolUtils.genkeypair(keystore, alias, password, validity, dname, null); + //rem generate cert for DOMAIN using CA (requires Java7 for gencert) and import it + + // get the certificate request + ByteArrayOutputStream certReqOut = new ByteArrayOutputStream(); + KeyToolUtils.keytool("-certreq", keystore, password, alias, null, certReqOut); + + // create the certificate + //rem ku:c=dig,keyE means KeyUsage:criticial=digitalSignature,keyEncipherment + InputStream certReqIn = new ByteArrayInputStream(certReqOut.toByteArray()); + ByteArrayOutputStream certOut = new ByteArrayOutputStream(); + KeyToolUtils.keytool("-gencert", keystore, password, INTERMEDIATE_CA_ALIAS, certReqIn, certOut, "-ext", "ku:c=dig,keyE"); + + // inport the certificate + InputStream certIn = new ByteArrayInputStream(certOut.toByteArray()); + KeyToolUtils.keytool("-importcert", keystore, password, alias, certIn, null, "-noprompt"); + } + + /** + * List the contents of a keystore + * + * @param keystore + * the keystore file + * @param storePass + * the keystore password + * @return the output from the command "keytool -list -v" + * @throws IOException + * if keytool was not configured or running keytool application + * failed + */ + public static String list(final File keystore, final String storePass) throws IOException { + final File workingDir = keystore.getParentFile(); + final SystemCommand nativeCommand = new SystemCommand(workingDir, null); + final List arguments = new ArrayList(); + arguments.add(getKeyToolPath()); + arguments.add("-list"); // $NON-NLS-1$ + arguments.add("-v"); // $NON-NLS-1$ + + arguments.add("-keystore"); // $NON-NLS-1$ + arguments.add(keystore.getName()); + arguments.add("-storepass"); // $NON-NLS-1$ + arguments.add(storePass); + try { + int exitVal = nativeCommand.run(arguments); + if (exitVal != 0) { + throw new IOException("Command failed, code: " + exitVal + "\n" + nativeCommand.getOutResult()); + } + } catch (InterruptedException e) { + throw new IOException("Command was interrupted\n" + nativeCommand.getOutResult(), e); + } + return nativeCommand.getOutResult(); + } + + /** + * Returns a list of the CA aliases that should be in the keystore. + * + * @return the aliases that are used for the keystore + */ + public static String[] getCAaliases() { + return new String[]{ROOTCA_ALIAS, INTERMEDIATE_CA_ALIAS}; + } + + /** + * Get the root CA alias; needed to check the serial number and fingerprint + * + * @return the alias + */ + public static String getRootCAalias() { + return ROOTCA_ALIAS; + } + + /** + * Helper method to simplify chaining keytool commands. + * + * @param command + * the command, not null + * @param keystore + * the keystore, not nill + * @param password + * the password used for keystore and key, not null + * @param alias + * the alias, not null + * @param input + * where to source input, may be null + * @param output + * where to send output, may be null + * @param parameters + * additional parameters to the command, may be null + * @throws IOException + * if keytool is not configured or running it failed + */ + private static void keytool(String command, File keystore, String password, String alias, + InputStream input, OutputStream output, String ... parameters) + throws IOException { + final File workingDir = keystore.getParentFile(); + final SystemCommand nativeCommand = new SystemCommand(workingDir, 0L, 0, null, input, output, null); + final List arguments = new ArrayList(); + arguments.add(getKeyToolPath()); + arguments.add(command); + arguments.add("-keystore"); // $NON-NLS-1$ + arguments.add(keystore.getName()); + arguments.add("-storepass"); // $NON-NLS-1$ + arguments.add(password); + arguments.add("-keypass"); // $NON-NLS-1$ + arguments.add(password); + arguments.add("-alias"); // $NON-NLS-1$ + arguments.add(alias); + for (String parameter : parameters) { + arguments.add(parameter); + } + + try { + int exitVal = nativeCommand.run(arguments); + if (exitVal != 0) { + throw new IOException("Command failed, code: " + exitVal + "\n" + nativeCommand.getOutResult()); + } + } catch (InterruptedException e) { + throw new IOException("Command was interrupted\n" + nativeCommand.getOutResult(), e); + } + } + + /** + * @return flag whether {@link KeyToolUtils#KEYTOOL_PATH KEYTOOL_PATH} is + * configured (is not null) + */ + public static boolean haveKeytool() { + return KEYTOOL_PATH != null; + } + + /** + * @return path to keytool binary + * @throws IOException + * when {@link KeyToolUtils#KEYTOOL_PATH KEYTOOL_PATH} is + * null + */ + private static String getKeyToolPath() throws IOException { + if (KEYTOOL_PATH == null) { + throw new IOException("keytool application cannot be found"); + } + return KEYTOOL_PATH; + } + + /** + * Check if keytool can be found + * @param keytoolPath the path to check + */ + private static boolean checkKeytool(String keytoolPath) { + final SystemCommand nativeCommand = new SystemCommand(null, null); + final List arguments = new ArrayList(); + arguments.add(keytoolPath); + arguments.add("-help"); // $NON-NLS-1$ + try { + int status = nativeCommand.run(arguments); + if (log.isDebugEnabled()) { + log.debug("checkKeyTool:status=" + status); + log.debug(nativeCommand.getOutResult()); + } + /* + * Some implementations of keytool return status 1 for -help + * MacOS/Java 7 returns 2 if it cannot find keytool + */ + return status == 0 || status == 1; // TODO this is rather fragile + } catch (IOException ioe) { + return false; + } catch (InterruptedException e) { + log.error("Command was interrupted\n" + nativeCommand.getOutResult(), e); + return false; + } + } +} diff --git a/src/jorphan/org/apache/jorphan/exec/StreamCopier.java b/src/jorphan/org/apache/jorphan/exec/StreamCopier.java new file mode 100644 index 00000000000..fc2d0bd4d26 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/exec/StreamCopier.java @@ -0,0 +1,72 @@ +/* + * 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.jorphan.exec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Thread that copies a stream in the background; closes both input and output streams. + * @since 2.8 + */ +class StreamCopier extends Thread { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final InputStream is; + private final OutputStream os; + + /** + * @param is {@link InputStream} + * @param os {@link OutputStream} + * @throws IOException + */ + StreamCopier(InputStream is, OutputStream os) throws IOException { + this.is = is; + this.os = os; + } + + /** + * @see java.lang.Thread#run() + */ + @Override + public void run() { + final boolean isSystemOutput = os.equals(System.out) || os.equals(System.err); + try { + IOUtils.copyLarge(is, os); + if (!isSystemOutput){ + os.close(); + } + is.close(); + } catch (IOException e) { + log.warn("Error writing stream", e); + } finally { + IOUtils.closeQuietly(is); + if (!isSystemOutput){ + IOUtils.closeQuietly(os); + } + } + } + +} diff --git a/src/jorphan/org/apache/jorphan/exec/StreamGobbler.java b/src/jorphan/org/apache/jorphan/exec/StreamGobbler.java new file mode 100644 index 00000000000..8e0d70717ee --- /dev/null +++ b/src/jorphan/org/apache/jorphan/exec/StreamGobbler.java @@ -0,0 +1,73 @@ +/* + * 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.jorphan.exec; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Thread that eats Output and Error Stream to avoid Deadlock on Windows Machines + * Inspired from: + * http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html + */ +class StreamGobbler extends Thread { + private final InputStream is; + private final StringBuilder buffer = new StringBuilder(); + /** + * @param is {@link InputStream} + */ + StreamGobbler(InputStream is) { + this.is = is; + } + + /** + * @see java.lang.Thread#run() + */ + @Override + public void run() { + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(is)); // default charset + String line = null; + while ((line = br.readLine()) != null) + { + buffer.append(line); + buffer.append("\r\n"); + } + } catch (IOException e) { + buffer.append(e.getMessage()); + } + finally + { + JOrphanUtils.closeQuietly(br); + } + } + + /** + * @return Output + */ + public String getResult() + { + return buffer.toString(); + } +} diff --git a/src/jorphan/org/apache/jorphan/exec/SystemCommand.java b/src/jorphan/org/apache/jorphan/exec/SystemCommand.java new file mode 100644 index 00000000000..0c92f0e559d --- /dev/null +++ b/src/jorphan/org/apache/jorphan/exec/SystemCommand.java @@ -0,0 +1,256 @@ +/* + * 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.jorphan.exec; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Utility class for invoking native system applications + */ +public class SystemCommand { + public static final int POLL_INTERVAL = 100; + private final File directory; + private final Map env; + private Map executionEnvironment; + private final InputStream stdin; + private final OutputStream stdout; + private final boolean stdoutWasNull; + private final OutputStream stderr; + private final long timeoutMillis; + private final int pollInterval; + + /** + * @param env Environment variables appended to environment (may be null) + * @param directory File working directory (may be null) + */ + public SystemCommand(File directory, Map env) { + this(directory, 0L, POLL_INTERVAL, env, (InputStream) null, (OutputStream) null, (OutputStream) null); + } + + /** + * + * @param env Environment variables appended to environment (may be null) + * @param directory File working directory (may be null) + * @param timeoutMillis timeout in Milliseconds + * @param pollInterval Value used to poll for Process execution end + * @param stdin File name that will contain data to be input to process (may be null) + * @param stdout File name that will contain out stream (may be null) + * @param stderr File name that will contain err stream (may be null) + * @throws IOException if the input file is not found or output cannot be written + */ + public SystemCommand(File directory, long timeoutMillis, int pollInterval, Map env, String stdin, String stdout, String stderr) throws IOException { + this(directory, timeoutMillis, pollInterval, env, checkIn(stdin), checkOut(stdout), checkOut(stderr)); + } + + private static InputStream checkIn(String stdin) throws FileNotFoundException { + String in = JOrphanUtils.nullifyIfEmptyTrimmed(stdin); + if (in == null) { + return null; + } else { + return new FileInputStream(in); + } + } + + private static OutputStream checkOut(String path) throws IOException { + String in = JOrphanUtils.nullifyIfEmptyTrimmed(path); + if (in == null) { + return null; + } else { + return new FileOutputStream(path); + } + } + + /** + * + * @param env Environment variables appended to environment (may be null) + * @param directory File working directory (may be null) + * @param timeoutMillis timeout in Milliseconds + * @param pollInterval Value used to poll for Process execution end + * @param stdin File name that will contain data to be input to process (may be null) + * @param stdout File name that will contain out stream (may be null) + * @param stderr File name that will contain err stream (may be null) + */ + public SystemCommand(File directory, long timeoutMillis, int pollInterval, Map env, InputStream stdin, OutputStream stdout, OutputStream stderr) { + super(); + this.timeoutMillis = timeoutMillis; + this.directory = directory; + this.env = env; + this.pollInterval = pollInterval; + this.stdin = stdin; + this.stdoutWasNull = stdout == null; + if (stdout == null) { + this.stdout = new ByteArrayOutputStream(); // capture the output + } else { + this.stdout = stdout; + } + this.stderr = stderr; + } + + /** + * @param arguments List of strings, not null + * @return return code + * @throws InterruptedException when execution was interrupted + * @throws IOException when I/O error occurs while execution + */ + public int run(List arguments) throws InterruptedException, IOException { + return run(arguments, stdin, stdout, stderr); + } + + // helper method to allow input and output to be changed for chaining + private int run(List arguments, InputStream in, OutputStream out, OutputStream err) throws InterruptedException, IOException { + Process proc = null; + final ProcessBuilder procBuild = new ProcessBuilder(arguments); + if (env != null) { + procBuild.environment().putAll(env); + } + this.executionEnvironment = Collections.unmodifiableMap(procBuild.environment()); + procBuild.directory(directory); + if (err == null) { + procBuild.redirectErrorStream(true); + } + try + { + proc = procBuild.start(); + + final OutputStream procOut = proc.getOutputStream(); + final InputStream procErr = proc.getErrorStream(); + final InputStream procIn = proc.getInputStream(); + + final StreamCopier swerr; + if (err != null){ + swerr = new StreamCopier(procErr, err); + swerr.start(); + } else { + swerr = null; + } + + final StreamCopier swout = new StreamCopier(procIn, out); + swout.start(); + + final StreamCopier swin; + if (in != null) { + swin = new StreamCopier(in, procOut); + swin.start(); + } else { + swin = null; + procOut.close(); // ensure the application does not hang if it requests input + } + int exitVal = waitForEndWithTimeout(proc, timeoutMillis); + + swout.join(); + if (swerr != null) { + swerr.join(); + } + if (swin != null) { + swin.interrupt(); // the copying thread won't generally detect EOF + swin.join(); + } + procErr.close(); + procIn.close(); + procOut.close(); + return exitVal; + } finally { + if(proc != null) { + try { + proc.destroy(); + } catch (Exception ignored) { + // Ignored + } + } + } + } + + /** + * Pipe the output of one command into another + * + * @param arguments1 first command to run + * @param arguments2 second command to run + * @return exit status + * @throws InterruptedException when execution gets interrupted + * @throws IOException when I/O error occurs while execution + */ + public int run(List arguments1, List arguments2) throws InterruptedException, IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); // capture the intermediate output + int exitCode=run(arguments1,stdin,out, stderr); + if (exitCode == 0) { + exitCode = run(arguments2,new ByteArrayInputStream(out.toByteArray()),stdout,stderr); + } + return exitCode; + } + + /** + * Wait for end of proc execution or timeout if timeoutInMillis is greater than 0 + * @param proc Process + * @param timeoutInMillis long timeout in ms + * @return proc exit value + * @throws InterruptedException + */ + private int waitForEndWithTimeout(Process proc, long timeoutInMillis) throws InterruptedException { + if (timeoutInMillis <= 0L) { + return proc.waitFor(); + } else { + long now = System.currentTimeMillis(); + long finish = now + timeoutInMillis; + while(System.currentTimeMillis() < finish) { + try { + return proc.exitValue(); + } catch (IllegalThreadStateException e) { // not yet terminated + Thread.sleep(pollInterval); + } + } + try { + return proc.exitValue(); + } catch (IllegalThreadStateException e) { // not yet terminated + // N.B. proc.destroy() is called by the finally clause in the run() method + throw new InterruptedException( "Process timeout out after " + timeoutInMillis + " milliseconds" ); + } + } + } + + /** + * @return Out/Err stream contents + */ + public String getOutResult() { + if (stdoutWasNull) { // we are capturing output + return stdout.toString(); // Default charset is probably appropriate here. + } else { + return ""; + } + } + + /** + * @return the executionEnvironment + */ + public Map getExecutionEnvironment() { + return executionEnvironment; + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/AbstractTreeTableModel.java b/src/jorphan/org/apache/jorphan/gui/AbstractTreeTableModel.java new file mode 100644 index 00000000000..757f54e0ac1 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/AbstractTreeTableModel.java @@ -0,0 +1,224 @@ +/* + * 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.jorphan.gui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.swing.event.TableModelListener; +import javax.swing.event.EventListenerList; +import javax.swing.table.DefaultTableModel; +import javax.swing.tree.TreeNode; + +import org.apache.jorphan.reflect.Functor; + +public abstract class AbstractTreeTableModel extends DefaultTableModel implements TreeTableModel { + + private static final long serialVersionUID = 240L; + + protected final TreeNode rootNode; + protected final EventListenerList listener = new EventListenerList(); + + protected transient final List objects = new ArrayList(); + + protected transient final List headers = new ArrayList(); + + protected transient final List> classes = new ArrayList>(); + + protected transient final List readFunctors; + + protected transient final List writeFunctors; + + public AbstractTreeTableModel(TreeNode root) { + this.rootNode = root; + readFunctors = new ArrayList(); + writeFunctors = new ArrayList(); + } + + public AbstractTreeTableModel(String[] headers, + Functor[] readFunctors, + Functor[] writeFunctors, + Class[] editorClasses) { + this.rootNode = null; + this.headers.addAll(Arrays.asList(headers)); + this.classes.addAll(Arrays.asList(editorClasses)); + this.readFunctors = new ArrayList(Arrays.asList(readFunctors)); + this.writeFunctors = new ArrayList(Arrays.asList(writeFunctors)); + } + + /** + * The root node for the TreeTable + * @return the root node + */ + public Object getRootNode() { + return this.rootNode; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getValueAt(Object node, int col) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCellEditable(Object node, int col) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void setValueAt(Object val, Object node, int column) { + } + + /** + * The implementation is exactly the same as ObjectTableModel.getColumnCount. + *

+ * {@inheritDoc} + */ + @Override + public int getColumnCount() { + return headers.size(); + } + + /** + * The implementation is exactly the same as ObjectTableModel.getRowCount. + *

+ * {@inheritDoc} + */ + @Override + public int getRowCount() { + if (objects == null) { + return 0; + } + return objects.size(); + } + + /** + * By default the abstract class returns true. It is up to subclasses + * to override the implementation. + *

+ * {@inheritDoc} + */ + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getColumnClass(int arg0) { + return classes.get(arg0); + } + + /** + * Subclasses need to implement the logic for the method and + * return the value at the specific cell. + *

+ * {@inheritDoc} + */ + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + + } + + /** + * {@inheritDoc} + */ + @Override + public String getColumnName(int columnIndex) { + return headers.get(columnIndex); + } + + public int getChildCount(Object parent) { + return 0; + } + + public Object getChild(Object parent, int index) { + return null; + } + + /** + * the implementation checks if the Object is a treenode. If it is, it + * returns {@link TreeNode#isLeaf() isLeaf()}, otherwise it returns + * false. + * + * @param node + * object to be checked for {@link TreeNode#isLeaf() isLeaf()} + * @return true if object is a leaf node, false + * otherwise + */ + public boolean isLeaf(Object node) { + if (node instanceof TreeNode) { + return ((TreeNode)node).isLeaf(); + } else { + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addTableModelListener(TableModelListener l) { + this.listener.add(TableModelListener.class,l); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeTableModelListener(TableModelListener l) { + this.listener.remove(TableModelListener.class,l); + } + + public void nodeStructureChanged(TreeNode node) { + + } + + public void fireTreeNodesChanged(TreeNode source, + Object[] path, + int[] indexes, + Object[] children) { + + } + + public void clearData() { + int size = getRowCount(); + objects.clear(); + super.fireTableRowsDeleted(0, size); + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/ComponentUtil.java b/src/jorphan/org/apache/jorphan/gui/ComponentUtil.java new file mode 100644 index 00000000000..ab901421676 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/ComponentUtil.java @@ -0,0 +1,95 @@ +/* + * 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.jorphan.gui; + +import java.awt.Component; +import java.awt.Dimension; + +/** + * This class is a Util for awt Component and could be used to place them in + * center of an other. + * + * @version $Revision$ + */ +public final class ComponentUtil { + /** + * Use this static method if you want to center and set its position + * compared to the size of the current users screen size. Valid percent is + * between +-(0-100) minus is treated as plus, bigger than 100 is always set + * to 100. + * + * @param component + * the component you want to center and set size on + * @param percentOfScreen + * the percent of the current screensize you want the component + * to be + */ + public static void centerComponentInWindow(Component component, int percentOfScreen) { + if (percentOfScreen < 0) { + centerComponentInWindow(component, -percentOfScreen); + return; + } + if (percentOfScreen > 100) { + centerComponentInWindow(component, 100); + return; + } + double percent = percentOfScreen / 100.d; + Dimension dimension = component.getToolkit().getScreenSize(); + component.setSize((int) (dimension.getWidth() * percent), (int) (dimension.getHeight() * percent)); + centerComponentInWindow(component); + } + + /** + * Use this static method if you want to center a component in Window. + * + * @param component + * the component you want to center in window + */ + public static void centerComponentInWindow(Component component) { + Dimension dimension = component.getToolkit().getScreenSize(); + + component.setLocation((int) ((dimension.getWidth() - component.getWidth()) / 2), + (int) ((dimension.getHeight() - component.getHeight()) / 2)); + component.validate(); + component.repaint(); + } + + /** + * Use this static method if you want to center a component over another + * component. + * + * @param parent + * the component you want to use to place it on + * @param toBeCentered + * the component you want to center + */ + public static void centerComponentInComponent(Component parent, Component toBeCentered) { + toBeCentered.setLocation(parent.getX() + (parent.getWidth() - toBeCentered.getWidth()) / 2, parent.getY() + + (parent.getHeight() - toBeCentered.getHeight()) / 2); + + toBeCentered.validate(); + toBeCentered.repaint(); + } + + /** + * Private constructor to prevent instantiation. + */ + private ComponentUtil() { + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/DefaultTreeTableModel.java b/src/jorphan/org/apache/jorphan/gui/DefaultTreeTableModel.java new file mode 100644 index 00000000000..aae5553bc68 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/DefaultTreeTableModel.java @@ -0,0 +1,52 @@ +/* + * 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.jorphan.gui; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; + +import org.apache.jorphan.reflect.Functor; + +public class DefaultTreeTableModel extends AbstractTreeTableModel { + + private static final long serialVersionUID = 240L; + + public DefaultTreeTableModel() { + this(new DefaultMutableTreeNode()); + } + + /** + * @param root the {@link TreeNode} to use as root + */ + public DefaultTreeTableModel(TreeNode root) { + super(root); + } + + /** + * @param headers the headers to use + * @param readFunctors the read functors to use + * @param writeFunctors the write functors to use + * @param editorClasses the editor classes to use + */ + public DefaultTreeTableModel(String[] headers, Functor[] readFunctors, + Functor[] writeFunctors, Class[] editorClasses) { + super(headers, readFunctors, writeFunctors, editorClasses); + } + +} diff --git a/src/jorphan/org/apache/jorphan/gui/GuiUtils.java b/src/jorphan/org/apache/jorphan/gui/GuiUtils.java new file mode 100644 index 00000000000..70526380a80 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/GuiUtils.java @@ -0,0 +1,149 @@ +/* + * 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.jorphan.gui; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +public final class GuiUtils { + + /** + * Create a scroll panel that sets its preferred size to its minimum size. + * Explicitly for scroll panes that live inside other scroll panes, or + * within containers that stretch components to fill the area they exist in. + * Use this for any component you would put in a scroll pane (such as + * TextAreas, tables, JLists, etc). It is here for convenience and to avoid + * duplicate code. JMeter displays best if you follow this custom. + * + * @param comp + * the component which should be placed inside the scroll pane + * @return a JScrollPane containing the specified component + */ + public static JScrollPane makeScrollPane(Component comp) { + JScrollPane pane = new JScrollPane(comp); + pane.setPreferredSize(pane.getMinimumSize()); + return pane; + } + + /** + * Fix the size of a column according to the header text. + * + * @param column to be resized + * @param table containing the column + */ + public static void fixSize(TableColumn column, JTable table) { + TableCellRenderer rndr; + rndr = column.getHeaderRenderer(); + if (rndr == null){ + rndr = table.getTableHeader().getDefaultRenderer(); + } + Component c = rndr.getTableCellRendererComponent( + table, column.getHeaderValue(), false, false, -1, column.getModelIndex()); + int width = c.getPreferredSize().width+10; + column.setMaxWidth(width); + column.setPreferredWidth(width); + column.setResizable(false); + } + + /** + * Create a GUI component JLabel + JComboBox with a left and right margin (5px) + * @param label the label + * @param comboBox the combo box + * @return the JComponent (margin+JLabel+margin+JComboBox) + */ + public static JComponent createLabelCombo(String label, JComboBox comboBox) { + JPanel labelCombo = new JPanel(); + labelCombo.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + JLabel caption = new JLabel(label); + caption.setBorder(new EmptyBorder(0, 5, 0, 5)); + labelCombo.add(caption); + labelCombo.add(comboBox); + return labelCombo; + } + + /** + * Stop any editing that is currently being done on the table. This will + * save any changes that have already been made. + * + * @param table the table to stop on editing + */ + public static void stopTableEditing(JTable table) { + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.stopCellEditing(); + } + } + + /** + * Get pasted text from clipboard + * + * @return String Pasted text + * @throws UnsupportedFlavorException + * if the clipboard data can not be get as a {@link String} + * @throws IOException + * if the clipboard data is no longer available + */ + public static String getPastedText() throws UnsupportedFlavorException, IOException { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable trans = clipboard.getContents(null); + DataFlavor[] flavourList = trans.getTransferDataFlavors(); + Collection flavours = new ArrayList(flavourList.length); + if (Collections.addAll(flavours, flavourList) && flavours.contains(DataFlavor.stringFlavor)) { + return (String) trans.getTransferData(DataFlavor.stringFlavor); + } else { + return null; + } + } + + /** + * Make menu scrollable + * @param menu {@link JMenu} + */ + public static void makeScrollableMenu(JMenu menu) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + if(menu.getItemCount()>0) { + // We use 80% of height + int maxItems = (int)Math.round( + screenSize.getHeight()*0.8/menu.getMenuComponent(0).getPreferredSize().getHeight()); + MenuScroller.setScrollerFor(menu, maxItems, 200); + } + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/JLabeledChoice.java b/src/jorphan/org/apache/jorphan/gui/JLabeledChoice.java new file mode 100644 index 00000000000..8fdcdbad268 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/JLabeledChoice.java @@ -0,0 +1,307 @@ +/* + * 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.jorphan.gui; + +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +public class JLabeledChoice extends JPanel implements JLabeledField { + private static final long serialVersionUID = 240L; + + private final JLabel mLabel = new JLabel(); + + private final JComboBox choiceList; + + // Maybe move to vector if MT problems occur + private final ArrayList mChangeListeners = new ArrayList(3); + + private JButton delete, add; + + /** + * Default constructor, The label and the Text field are left empty. + */ + public JLabeledChoice() { + super(); + choiceList = new JComboBox(); + init(); + } + + public JLabeledChoice(String pLabel, boolean editable) { + super(); + choiceList = new JComboBox(); + mLabel.setText(pLabel); + choiceList.setEditable(editable); + init(); + } + + /** + * Constructs a non-edittable combo-box with the label displaying the passed text. + * + * @param pLabel - the text to display in the label. + * @param items - the items to display in the Combo box + */ + public JLabeledChoice(String pLabel, String[] items) { + this(pLabel, items, false); + } + + /** + * Constructs a combo-box with the label displaying the passed text. + * + * @param pLabel - the text to display in the label. + * @param items - the items to display in the Combo box + * @param editable - if true, then Add and Delete buttons are created. + * + */ + public JLabeledChoice(String pLabel, String[] items, boolean editable) { + super(); + mLabel.setText(pLabel); + choiceList = new JComboBox(items); + choiceList.setEditable(editable); + init(); + } + + /** + * Get the label {@link JLabel} followed by the combo-box @link {@link JComboBox}. + */ + @Override + public List getComponentList() { + List comps = new LinkedList(); + comps.add(mLabel); + comps.add(choiceList); + return comps; + } + + public void setEditable(boolean editable) { + choiceList.setEditable(editable); + } + + public void addValue(String item) { + choiceList.addItem(item); + } + + public void setValues(String[] items) { + choiceList.removeAllItems(); + for (int i = 0; i < items.length; i++) { + choiceList.addItem(items[i]); + } + } + + /** + * Initialises all of the components on this panel. + */ + private void init() { + /* + * if(choiceList.isEditable()) { choiceList.addActionListener(new + * ComboListener()); } + */ + // Register the handler for focus listening. This handler will + // only notify the registered when the text changes from when + // the focus is gained to when it is lost. + choiceList.addItemListener(new ItemListener() { + /** + * Callback method when the focus to the Text Field component is + * lost. + * + * @param e + * The focus event that occured. + */ + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + notifyChangeListeners(); + } + } + }); + + // Add the sub components + this.add(mLabel); + this.add(choiceList); + if (choiceList.isEditable()) { + add = new JButton("Add"); + add.setMargin(new Insets(1, 1, 1, 1)); + add.addActionListener(new AddListener()); + this.add(add); + delete = new JButton("Del"); + delete.setMargin(new Insets(1, 1, 1, 1)); + delete.addActionListener(new DeleteListener()); + this.add(delete); + } + + } + + /** + * Set the text displayed in the label. + * + * @param pLabel + * The new label text. + */ + @Override + public void setLabel(String pLabel) { + mLabel.setText(pLabel); + } + + /** + * Set the text displayed in the Text Field. + * + * @param pText + * The new text to display in the text field. + */ + @Override + public void setText(String pText) { + choiceList.setSelectedItem(pText); + } + + public void setSelectedIndex(int index){ + choiceList.setSelectedIndex(index); + } + /** + * Returns the text in the Text Field. + * + * @return The text in the Text Field. Never returns null. + */ + @Override + public String getText() { + Object item = choiceList.getSelectedItem(); + if (item == null) { + return ""; + } else { + return (String) item; + } + } + + public int getSelectedIndex(){ + return choiceList.getSelectedIndex(); + } + + public Object[] getSelectedItems() { + return choiceList.getSelectedObjects(); + } + + public String[] getItems() { + String[] items = new String[choiceList.getItemCount()]; + for (int i = 0; i < items.length; i++) { + items[i] = (String) choiceList.getItemAt(i); + } + return items; + } + + /** + * Returns the text of the label. + * + * @return The text of the label. + */ + public String getLabel() { + return mLabel.getText(); + } + + /** + * Registers the text to display in a tool tip. + * The text displays when the cursor lingers over the component. + * @param text the string to display; if the text is null, + * the tool tip is turned off for this component + */ + @Override +public void setToolTipText(String text) { + choiceList.setToolTipText(text); + } + + /** + * Returns the tooltip string that has been set with setToolTipText + * @return the text of the tool tip + */ + @Override +public String getToolTipText() { + if (choiceList == null){ // Necessary to avoid NPE when testing serialisation + return null; + } + return choiceList.getToolTipText(); + } + + /** + * Adds a change listener, that will be notified when the text in the text + * field is changed. The ChangeEvent that will be passed to registered + * listeners will contain this object as the source, allowing the new text + * to be extracted using the {@link #getText() getText} method. + * + * @param pChangeListener + * The listener to add + */ + @Override + public void addChangeListener(ChangeListener pChangeListener) { + mChangeListeners.add(pChangeListener); + } + + /** + * Removes a change listener. + * + * @param pChangeListener + * The change listener to remove. + */ + public void removeChangeListener(ChangeListener pChangeListener) { + mChangeListeners.remove(pChangeListener); + } + + /** + * Notify all registered change listeners that the text in the text field + * has changed. + */ + private void notifyChangeListeners() { + ChangeEvent ce = new ChangeEvent(this); + for (int index = 0; index < mChangeListeners.size(); index++) { + mChangeListeners.get(index).stateChanged(ce); + } + } + + private class AddListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + Object item = choiceList.getSelectedItem(); + int index = choiceList.getSelectedIndex(); + if (!item.equals(choiceList.getItemAt(index))) { + choiceList.addItem(item); + } + choiceList.setSelectedItem(item); + notifyChangeListeners(); + } + } + + private class DeleteListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + if (choiceList.getItemCount() > 1) { + choiceList.removeItemAt(choiceList.getSelectedIndex()); + notifyChangeListeners(); + } + } + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/JLabeledField.java b/src/jorphan/org/apache/jorphan/gui/JLabeledField.java new file mode 100644 index 00000000000..e0382414010 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/JLabeledField.java @@ -0,0 +1,39 @@ +/* + * 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.jorphan.gui; + +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.event.ChangeListener; + +/** + * @version $Revision$ + */ +public interface JLabeledField { + String getText(); + + void setText(String text); + + void setLabel(String pLabel); + + void addChangeListener(ChangeListener pChangeListener); + + List getComponentList(); +} diff --git a/src/jorphan/org/apache/jorphan/gui/JLabeledPasswordField.java b/src/jorphan/org/apache/jorphan/gui/JLabeledPasswordField.java new file mode 100644 index 00000000000..6736f40871e --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/JLabeledPasswordField.java @@ -0,0 +1,49 @@ +/* + * 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.jorphan.gui; + +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +/** + * @version $Revision$ + */ +public class JLabeledPasswordField extends JLabeledTextField { + + private static final long serialVersionUID = 240L; + + public JLabeledPasswordField() { + super(); + } + + /** + * Constructs a new component with the label displaying the passed text. + * + * @param pLabel + * The text to in the label. + */ + public JLabeledPasswordField(String pLabel) { + super(pLabel); + } + + @Override + protected JTextField createTextField(int size) { + return new JPasswordField(size); + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/JLabeledTextArea.java b/src/jorphan/org/apache/jorphan/gui/JLabeledTextArea.java new file mode 100644 index 00000000000..955f1900d02 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/JLabeledTextArea.java @@ -0,0 +1,268 @@ +/* + * 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.jorphan.gui; + +import java.awt.BorderLayout; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; + +/** + * A Helper component that wraps a JTextField with a label into a JPanel (this). + * This component also has an efficient event handling mechanism for handling + * the text changing in the Text Field. The registered change listeners are only + * called when the text has changed. + * + */ +public class JLabeledTextArea extends JPanel implements JLabeledField, FocusListener { + private static final long serialVersionUID = 240L; + + private final JLabel mLabel; + + private final JTextArea mTextArea; + + // Maybe move to vector if MT problems occur + private final ArrayList mChangeListeners = new ArrayList(3); + + // A temporary cache for the focus listener + private String oldValue = ""; + + /** + * Default constructor, The label and the Text field are left empty. + */ + public JLabeledTextArea() { + this("", null); + } + + /** + * Constructs a new component with the label displaying the passed text. + * + * @param label + * The text to display in the label. + */ + public JLabeledTextArea(String label) { + this(label, null); + } + + /** + * Constructs a new component with the label displaying the passed text. + * + * @param pLabel + * The text to display in the label. + * @param docModel the document for the text area + */ + public JLabeledTextArea(String pLabel, Document docModel) { + super(); + mTextArea = new JTextArea(); + mLabel = new JLabel(pLabel); + init(); + if (docModel != null) { + mTextArea.setDocument(docModel); + } + } + + /** + * Get the label {@link JLabel} followed by the text field @link {@link JTextArea}. + */ + @Override + public List getComponentList() { + List comps = new LinkedList(); + comps.add(mLabel); + comps.add(mTextArea); + return comps; + } + + public void setDocumentModel(Document docModel) { + mTextArea.setDocument(docModel); + } + + /** + * Initialises all of the components on this panel. + */ + private void init() { + setLayout(new BorderLayout()); + + mTextArea.setRows(4); + mTextArea.setLineWrap(true); + mTextArea.setWrapStyleWord(true); + // Register the handler for focus listening. This handler will + // only notify the registered when the text changes from when + // the focus is gained to when it is lost. + mTextArea.addFocusListener(this); + + // Add the sub components + this.add(mLabel, BorderLayout.NORTH); + this.add(new JScrollPane(mTextArea), BorderLayout.CENTER); + } + + /** + * Callback method when the focus to the Text Field component is lost. + * + * @param pFocusEvent + * The focus event that occured. + */ + @Override + public void focusLost(FocusEvent pFocusEvent) { + // Compare if the value has changed, since we received focus. + if (!oldValue.equals(mTextArea.getText())) { + notifyChangeListeners(); + } + } + + /** + * Catch what the value was when focus was gained. + */ + @Override + public void focusGained(FocusEvent pFocusEvent) { + oldValue = mTextArea.getText(); + } + + /** + * Set the text displayed in the label. + * + * @param pLabel + * The new label text. + */ + @Override + public void setLabel(String pLabel) { + mLabel.setText(pLabel); + } + + /** + * Set the text displayed in the Text Field. + * + * @param pText + * The new text to display in the text field. + */ + @Override + public void setText(String pText) { + mTextArea.setText(pText); + } + + /** + * Returns the text in the Text Field. + * + * @return The text in the Text Field. + */ + @Override + public String getText() { + return mTextArea.getText(); + } + + /** + * Returns the text of the label. + * + * @return The text of the label. + */ + public String getLabel() { + return mLabel.getText(); + } + + /** {@inheritDoc} */ + @Override + public void setEnabled(boolean enable) { + super.setEnabled(enable); + mTextArea.setEnabled(enable); + } + + /** + * Registers the text to display in a tool tip. + * The text displays when the cursor lingers over the component. + * @param text the string to display; if the text is null, + * the tool tip is turned off for this component + */ + @Override + public void setToolTipText(String text) { + mTextArea.setToolTipText(text); + } + + /** + * Returns the tooltip string that has been set with setToolTipText + * @return the text of the tool tip + */ + @Override + public String getToolTipText() { + return mTextArea.getToolTipText(); + } + + /** + * Adds a change listener, that will be notified when the text in the text + * field is changed. The ChangeEvent that will be passed to registered + * listeners will contain this object as the source, allowing the new text + * to be extracted using the {@link #getText() getText} method. + * + * @param pChangeListener + * The listener to add + */ + @Override + public void addChangeListener(ChangeListener pChangeListener) { + mChangeListeners.add(pChangeListener); + } + + /** + * Removes a change listener. + * + * @param pChangeListener + * The change listener to remove. + */ + public void removeChangeListener(ChangeListener pChangeListener) { + mChangeListeners.remove(pChangeListener); + } + + /** + * Notify all registered change listeners that the text in the text field + * has changed. + */ + private void notifyChangeListeners() { + ChangeEvent ce = new ChangeEvent(this); + for (int index = 0; index < mChangeListeners.size(); index++) { + mChangeListeners.get(index).stateChanged(ce); + } + } + + public String[] getTextLines() { + int numLines = mTextArea.getLineCount(); + String[] lines = new String[numLines]; + for(int i = 0; i < numLines; i++) { + try { + int start = mTextArea.getLineStartOffset(i); + int end = mTextArea.getLineEndOffset(i); // treats last line specially + if (i == numLines-1) { // Last line + end++; // Allow for missing terminator + } + lines[i]=mTextArea.getText(start, end-start-1); + } catch (BadLocationException e) { // should not happen + throw new IllegalStateException("Could not read line "+i,e); + } + } + return lines; + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/JLabeledTextField.java b/src/jorphan/org/apache/jorphan/gui/JLabeledTextField.java new file mode 100644 index 00000000000..e9af6e3af98 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/JLabeledTextField.java @@ -0,0 +1,250 @@ +/* + * 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.jorphan.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +/** + * A Helper component that wraps a JTextField with a label into a JPanel (this). + * This component also has an efficient event handling mechanism for handling + * the text changing in the Text Field. The registered change listeners are only + * called when the text has changed. + * + * @version $Revision$ + */ +public class JLabeledTextField extends JPanel implements JLabeledField, FocusListener { + private static final long serialVersionUID = 240L; + + private JLabel mLabel; + + private JTextField mTextField; + + // Maybe move to vector if MT problems occur + private final ArrayList mChangeListeners = new ArrayList(3); + + // A temporary cache for the focus listener + private String oldValue = ""; + + /** + * Default constructor, The label and the Text field are left empty. + */ + public JLabeledTextField() { + this("", 20); + } + + /** + * Constructs a new component with the label displaying the passed text. + * + * @param pLabel + * The text to in the label. + */ + public JLabeledTextField(String pLabel) { + this(pLabel, 20); + } + + public JLabeledTextField(String pLabel, int size) { + super(); + mTextField = createTextField(size); + mLabel = new JLabel(pLabel); + mLabel.setLabelFor(mTextField); + init(); + } + + public JLabeledTextField(String pLabel, Color bk) { + super(); + mTextField = createTextField(20); + mLabel = new JLabel(pLabel); + mLabel.setBackground(bk); + mLabel.setLabelFor(mTextField); + this.setBackground(bk); + init(); + } + + /** + * Get the label {@link JLabel} followed by the text field @link {@link JTextField}. + */ + @Override + public List getComponentList() { + List comps = new LinkedList(); + comps.add(mLabel); + comps.add(mTextField); + return comps; + } + + /** {@inheritDoc} */ + @Override + public void setEnabled(boolean enable) { + super.setEnabled(enable); + mTextField.setEnabled(enable); + } + + protected JTextField createTextField(int size) { + return new JTextField(size); + } + + /** + * Initialises all of the components on this panel. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + // Register the handler for focus listening. This handler will + // only notify the registered when the text changes from when + // the focus is gained to when it is lost. + mTextField.addFocusListener(this); + + // Add the sub components + add(mLabel, BorderLayout.WEST); + add(mTextField, BorderLayout.CENTER); + } + + /** + * Callback method when the focus to the Text Field component is lost. + * + * @param pFocusEvent + * The focus event that occured. + */ + @Override + public void focusLost(FocusEvent pFocusEvent) { + // Compare if the value has changed, since we received focus. + if (!oldValue.equals(mTextField.getText())) { + notifyChangeListeners(); + } + } + + /** + * Catch what the value was when focus was gained. + */ + @Override + public void focusGained(FocusEvent pFocusEvent) { + oldValue = mTextField.getText(); + } + + /** + * Set the text displayed in the label. + * + * @param pLabel + * The new label text. + */ + @Override + public void setLabel(String pLabel) { + mLabel.setText(pLabel); + } + + /** + * Set the text displayed in the Text Field. + * + * @param pText + * The new text to display in the text field. + */ + @Override + public void setText(String pText) { + mTextField.setText(pText); + } + + /** + * Returns the text in the Text Field. + * + * @return The text in the Text Field. + */ + @Override + public String getText() { + return mTextField.getText(); + } + + /** + * Returns the text of the label. + * + * @return The text of the label. + */ + public String getLabel() { + return mLabel.getText(); + } + + /** + * Registers the text to display in a tool tip. + * The text displays when the cursor lingers over the component. + * @param text the string to display; if the text is null, + * the tool tip is turned off for this component + */ + @Override + public void setToolTipText(String text) { + mLabel.setToolTipText(text); + mTextField.setToolTipText(text); + } + + /** + * Returns the tooltip string that has been set with setToolTipText + * @return the text of the tool tip + */ + @Override + public String getToolTipText() { + if (mTextField == null){ // Necessary to avoid NPE when testing serialisation + return null; + } + return mTextField.getToolTipText(); + } + + /** + * Adds a change listener, that will be notified when the text in the text + * field is changed. The ChangeEvent that will be passed to registered + * listeners will contain this object as the source, allowing the new text + * to be extracted using the {@link #getText() getText} method. + * + * @param pChangeListener + * The listener to add + */ + @Override + public void addChangeListener(ChangeListener pChangeListener) { + mChangeListeners.add(pChangeListener); + } + + /** + * Removes a change listener. + * + * @param pChangeListener + * The change listener to remove. + */ + public void removeChangeListener(ChangeListener pChangeListener) { + mChangeListeners.remove(pChangeListener); + } + + /** + * Notify all registered change listeners that the text in the text field + * has changed. + */ + protected void notifyChangeListeners() { + ChangeEvent ce = new ChangeEvent(this); + for (int index = 0; index < mChangeListeners.size(); index++) { + mChangeListeners.get(index).stateChanged(ce); + } + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/JTreeTable.java b/src/jorphan/org/apache/jorphan/gui/JTreeTable.java new file mode 100644 index 00000000000..8a5bf542ab6 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/JTreeTable.java @@ -0,0 +1,67 @@ +/* + * 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.jorphan.gui; + +import java.util.Vector; + +import javax.swing.JTable; + +public class JTreeTable extends JTable { + + private static final long serialVersionUID = 240L; + + /** + * The default implementation will use DefaultTreeTableModel + */ + public JTreeTable() { + super(new DefaultTreeTableModel()); + } + + /** + * @param numRows number of rows the table holds + * @param numColumns number of columns the table holds + */ + public JTreeTable(int numRows, int numColumns) { + super(numRows, numColumns); + } + + /** + * @param dm the data model to use + */ + public JTreeTable(TreeTableModel dm) { + super(dm); + } + + /** + * @param rowData the data for the table + * @param columnNames the names for the columns + */ + public JTreeTable(Object[][] rowData, Object[] columnNames) { + super(rowData, columnNames); + } + + /** + * @param rowData the data for the table + * @param columnNames the names for the columns + */ + public JTreeTable(Vector rowData, Vector columnNames) { + super(rowData, columnNames); + } + +} diff --git a/src/jorphan/org/apache/jorphan/gui/MenuScroller.java b/src/jorphan/org/apache/jorphan/gui/MenuScroller.java new file mode 100644 index 00000000000..b4eac25c4e1 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/MenuScroller.java @@ -0,0 +1,701 @@ +/* + * 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.jorphan.gui; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.MenuSelectionManager; +import javax.swing.Timer; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + +/** + * A class that provides scrolling capabilities to a long menu dropdown or popup + * menu. A number of items can optionally be frozen at the top and/or bottom of + * the menu. + *

+ * Implementation note: The default number of items to display at a time + * is 15, and the default scrolling interval is 125 milliseconds. + *

+ * + * Class is slightly changed as per comments on the webpage + * @version 1.5.0 04/05/12 + * @author Darryl Burke (http://tips4java.wordpress.com/2009/02/01/menu-scroller/) + */ +public class MenuScroller { + + // private JMenu menu; + private JPopupMenu menu; + private Component[] menuItems; + private MenuScrollItem upItem; + private MenuScrollItem downItem; + private final MenuScrollListener menuListener = new MenuScrollListener(); + private final MouseWheelListener mouseWheelListener = new MouseScrollListener(); + private int scrollCount; + private int interval; + private int topFixedCount; + private int bottomFixedCount; + private int firstIndex = 0; + private int keepVisibleIndex = -1; + + /** + * Registers a menu to be scrolled with the default number of items to + * display at a time and the default scrolling interval. + * + * @param menu + * the menu + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JMenu menu) { + return new MenuScroller(menu); + } + + /** + * Registers a popup menu to be scrolled with the default number of items to + * display at a time and the default scrolling interval. + * + * @param menu + * the popup menu + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JPopupMenu menu) { + return new MenuScroller(menu); + } + + /** + * Registers a menu to be scrolled with the default number of items to + * display at a time and the specified scrolling interval. + * + * @param menu + * the menu + * @param scrollCount + * the number of items to display at a time + * @return the MenuScroller + * @throws IllegalArgumentException + * if scrollCount is 0 or negative + */ + public static MenuScroller setScrollerFor(JMenu menu, int scrollCount) { + return new MenuScroller(menu, scrollCount); + } + + /** + * Registers a popup menu to be scrolled with the default number of items to + * display at a time and the specified scrolling interval. + * + * @param menu + * the popup menu + * @param scrollCount + * the number of items to display at a time + * @return the MenuScroller + * @throws IllegalArgumentException + * if scrollCount is 0 or negative + */ + public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount) { + return new MenuScroller(menu, scrollCount); + } + + /** + * Registers a menu to be scrolled, with the specified number of items to + * display at a time and the specified scrolling interval. + * + * @param menu + * the menu + * @param scrollCount + * the number of items to be displayed at a time + * @param interval + * the scroll interval, in milliseconds + * @return the MenuScroller + * @throws IllegalArgumentException + * if scrollCount or interval is 0 or negative + */ + public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, + int interval) { + return new MenuScroller(menu, scrollCount, interval); + } + + /** + * Registers a popup menu to be scrolled, with the specified number of items + * to display at a time and the specified scrolling interval. + * + * @param menu + * the popup menu + * @param scrollCount + * the number of items to be displayed at a time + * @param interval + * the scroll interval, in milliseconds + * @return the MenuScroller + * @throws IllegalArgumentException + * if scrollCount or interval is 0 or negative + */ + public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, + int interval) { + return new MenuScroller(menu, scrollCount, interval); + } + + /** + * Registers a menu to be scrolled, with the specified number of items to + * display in the scrolling region, the specified scrolling interval, and + * the specified numbers of items fixed at the top and bottom of the menu. + * + * @param menu + * the menu + * @param scrollCount + * the number of items to display in the scrolling portion + * @param interval + * the scroll interval, in milliseconds + * @param topFixedCount + * the number of items to fix at the top. May be 0. + * @param bottomFixedCount + * the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException + * if scrollCount or interval is 0 or negative or if + * topFixedCount or bottomFixedCount is negative + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, + int interval, int topFixedCount, int bottomFixedCount) { + return new MenuScroller(menu, scrollCount, interval, topFixedCount, + bottomFixedCount); + } + + /** + * Registers a popup menu to be scrolled, with the specified number of items + * to display in the scrolling region, the specified scrolling interval, and + * the specified numbers of items fixed at the top and bottom of the popup + * menu. + * + * @param menu + * the popup menu + * @param scrollCount + * the number of items to display in the scrolling portion + * @param interval + * the scroll interval, in milliseconds + * @param topFixedCount + * the number of items to fix at the top. May be 0 + * @param bottomFixedCount + * the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException + * if scrollCount or interval is 0 or negative or if + * topFixedCount or bottomFixedCount is negative + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, + int interval, int topFixedCount, int bottomFixedCount) { + return new MenuScroller(menu, scrollCount, interval, topFixedCount, + bottomFixedCount); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * default number of items to display at a time, and default scrolling + * interval. + * + * @param menu + * the menu + */ + public MenuScroller(JMenu menu) { + this(menu, 15); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * default number of items to display at a time, and default scrolling + * interval. + * + * @param menu + * the popup menu + */ + public MenuScroller(JPopupMenu menu) { + this(menu, 15); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * specified number of items to display at a time, and default scrolling + * interval. + * + * @param menu + * the menu + * @param scrollCount + * the number of items to display at a time + * @throws IllegalArgumentException + * if scrollCount is 0 or negative + */ + public MenuScroller(JMenu menu, int scrollCount) { + this(menu, scrollCount, 150); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * specified number of items to display at a time, and default scrolling + * interval. + * + * @param menu + * the popup menu + * @param scrollCount + * the number of items to display at a time + * @throws IllegalArgumentException + * if scrollCount is 0 or negative + */ + public MenuScroller(JPopupMenu menu, int scrollCount) { + this(menu, scrollCount, 150); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * specified number of items to display at a time, and specified scrolling + * interval. + * + * @param menu + * the menu + * @param scrollCount + * the number of items to display at a time + * @param interval + * the scroll interval, in milliseconds + * @throws IllegalArgumentException + * if scrollCount or interval is 0 or negative + */ + public MenuScroller(JMenu menu, int scrollCount, int interval) { + this(menu, scrollCount, interval, 0, 0); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * specified number of items to display at a time, and specified scrolling + * interval. + * + * @param menu + * the popup menu + * @param scrollCount + * the number of items to display at a time + * @param interval + * the scroll interval, in milliseconds + * @throws IllegalArgumentException + * if scrollCount or interval is 0 or negative + */ + public MenuScroller(JPopupMenu menu, int scrollCount, int interval) { + this(menu, scrollCount, interval, 0, 0); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * specified number of items to display in the scrolling region, the + * specified scrolling interval, and the specified numbers of items fixed at + * the top and bottom of the menu. + * + * @param menu + * the menu + * @param scrollCount + * the number of items to display in the scrolling portion + * @param interval + * the scroll interval, in milliseconds + * @param topFixedCount + * the number of items to fix at the top. May be 0 + * @param bottomFixedCount + * the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException + * if scrollCount or interval is 0 or negative or if + * topFixedCount or bottomFixedCount is negative + */ + public MenuScroller(JMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + this(menu.getPopupMenu(), scrollCount, interval, topFixedCount, + bottomFixedCount); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * specified number of items to display in the scrolling region, the + * specified scrolling interval, and the specified numbers of items fixed at + * the top and bottom of the popup menu. + * + * @param menu + * the popup menu + * @param scrollCount + * the number of items to display in the scrolling portion + * @param interval + * the scroll interval, in milliseconds + * @param topFixedCount + * the number of items to fix at the top. May be 0 + * @param bottomFixedCount + * the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException + * if scrollCount or interval is 0 or negative or if + * topFixedCount or bottomFixedCount is negative + */ + public MenuScroller(JPopupMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + if (scrollCount <= 0 || interval <= 0) { + throw new IllegalArgumentException( + "scrollCount and interval must be greater than 0"); + } + if (topFixedCount < 0 || bottomFixedCount < 0) { + throw new IllegalArgumentException( + "topFixedCount and bottomFixedCount cannot be negative"); + } + + upItem = new MenuScrollItem(MenuIcon.UP, -1); + downItem = new MenuScrollItem(MenuIcon.DOWN, +1); + setScrollCount(scrollCount); + setInterval(interval); + setTopFixedCount(topFixedCount); + setBottomFixedCount(bottomFixedCount); + + this.menu = menu; + menu.addPopupMenuListener(menuListener); + menu.addMouseWheelListener(mouseWheelListener); + } + + /** + * Returns the scroll interval in milliseconds + * + * @return the scroll interval in milliseconds + */ + public int getInterval() { + return interval; + } + + /** + * Sets the scroll interval in milliseconds + * + * @param interval + * the scroll interval in milliseconds + * @throws IllegalArgumentException + * if interval is 0 or negative + */ + public void setInterval(int interval) { + if (interval <= 0) { + throw new IllegalArgumentException( + "interval must be greater than 0"); + } + upItem.setInterval(interval); + downItem.setInterval(interval); + this.interval = interval; + } + + /** + * Returns the number of items in the scrolling portion of the menu. + * + * @return the number of items to display at a time + */ + public int getscrollCount() { + return scrollCount; + } + + /** + * Sets the number of items in the scrolling portion of the menu. + * + * @param scrollCount + * the number of items to display at a time + * @throws IllegalArgumentException + * if scrollCount is 0 or negative + */ + public void setScrollCount(int scrollCount) { + if (scrollCount <= 0) { + throw new IllegalArgumentException( + "scrollCount must be greater than 0"); + } + this.scrollCount = scrollCount; + MenuSelectionManager.defaultManager().clearSelectedPath(); + } + + /** + * Returns the number of items fixed at the top of the menu or popup menu. + * + * @return the number of items + */ + public int getTopFixedCount() { + return topFixedCount; + } + + /** + * Sets the number of items to fix at the top of the menu or popup menu. + * + * @param topFixedCount + * the number of items + */ + public void setTopFixedCount(int topFixedCount) { + if (firstIndex <= topFixedCount) { + firstIndex = topFixedCount; + } else { + firstIndex += (topFixedCount - this.topFixedCount); + } + this.topFixedCount = topFixedCount; + } + + /** + * Returns the number of items fixed at the bottom of the menu or popup + * menu. + * + * @return the number of items + */ + public int getBottomFixedCount() { + return bottomFixedCount; + } + + /** + * Sets the number of items to fix at the bottom of the menu or popup menu. + * + * @param bottomFixedCount + * the number of items + */ + public void setBottomFixedCount(int bottomFixedCount) { + this.bottomFixedCount = bottomFixedCount; + } + + /** + * Scrolls the specified item into view each time the menu is opened. Call + * this method with null to restore the default behavior, which + * is to show the menu as it last appeared. + * + * @param item + * the item to keep visible + * @see #keepVisible(int) + */ + public void keepVisible(JMenuItem item) { + if (item == null) { + keepVisibleIndex = -1; + } else { + int index = menu.getComponentIndex(item); + keepVisibleIndex = index; + } + } + + /** + * Scrolls the item at the specified index into view each time the menu is + * opened. Call this method with -1 to restore the default + * behavior, which is to show the menu as it last appeared. + * + * @param index + * the index of the item to keep visible + * @see #keepVisible(javax.swing.JMenuItem) + */ + public void keepVisible(int index) { + keepVisibleIndex = index; + } + + /** + * Removes this MenuScroller from the associated menu and restores the + * default behavior of the menu. + */ + public void dispose() { + if (menu != null) { + menu.removePopupMenuListener(menuListener); + menu.removeMouseWheelListener(mouseWheelListener); + menu = null; + } + } + + /** + * Ensures that the dispose method of this MenuScroller is + * called when there are no more refrences to it. + * + * @exception Throwable + * if an error occurs. + * @see MenuScroller#dispose() + */ + @Override + public void finalize() throws Throwable { + dispose(); + } + + private void refreshMenu() { + if (menuItems != null && menuItems.length > 0) { + firstIndex = Math.max(topFixedCount, firstIndex); + firstIndex = Math.min(menuItems.length - bottomFixedCount + - scrollCount, firstIndex); + + upItem.setEnabled(firstIndex > topFixedCount); + downItem.setEnabled(firstIndex + scrollCount < menuItems.length + - bottomFixedCount); + + menu.removeAll(); + for (int i = 0; i < topFixedCount; i++) { + menu.add(menuItems[i]); + } + if (topFixedCount > 0) { + menu.addSeparator(); + } + + menu.add(upItem); + for (int i = firstIndex; i < scrollCount + firstIndex; i++) { + menu.add(menuItems[i]); + } + menu.add(downItem); + + if (bottomFixedCount > 0) { + menu.addSeparator(); + } + for (int i = menuItems.length - bottomFixedCount; i < menuItems.length; i++) { + menu.add(menuItems[i]); + } + + int preferredWidth = 0; + for (Component item : menuItems) { + preferredWidth = Math.max(preferredWidth, item.getPreferredSize().width); + } + menu.setPreferredSize(new Dimension(preferredWidth, menu.getPreferredSize().height)); + JComponent parent = (JComponent) upItem.getParent(); + parent.revalidate(); + parent.repaint(); + } + } + + private class MouseScrollListener implements MouseWheelListener { + @Override + public void mouseWheelMoved(MouseWheelEvent mwe){ + firstIndex += mwe.getWheelRotation(); + refreshMenu(); + mwe.consume(); // (Comment 16, Huw) + } + } + + private class MenuScrollListener implements PopupMenuListener { + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + setMenuItems(); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + restoreMenuItems(); + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + restoreMenuItems(); + } + + private void setMenuItems() { + menuItems = menu.getComponents(); + if (keepVisibleIndex >= topFixedCount + && keepVisibleIndex <= menuItems.length - bottomFixedCount + && (keepVisibleIndex > firstIndex + scrollCount || keepVisibleIndex < firstIndex)) { + firstIndex = Math.min(firstIndex, keepVisibleIndex); + firstIndex = Math.max(firstIndex, keepVisibleIndex + - scrollCount + 1); + } + if (menuItems.length > topFixedCount + scrollCount + + bottomFixedCount) { + refreshMenu(); + } + } + + private void restoreMenuItems() { + menu.removeAll(); + for (Component component : menuItems) { + menu.add(component); + } + } + } + + private class MenuScrollTimer extends Timer { + + private static final long serialVersionUID = 1; + + public MenuScrollTimer(final int increment, int interval) { + super(interval, new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + firstIndex += increment; + refreshMenu(); + } + }); + } + } + + private class MenuScrollItem extends JMenuItem implements ChangeListener { + + private static final long serialVersionUID = 1; + + private MenuScrollTimer timer; + + public MenuScrollItem(MenuIcon icon, int increment) { + setIcon(icon); + setDisabledIcon(icon); + timer = new MenuScrollTimer(increment, interval); + addChangeListener(this); + } + + public void setInterval(int interval) { + timer.setDelay(interval); + } + + @Override + public void stateChanged(ChangeEvent e) { + if (isArmed() && !timer.isRunning()) { + timer.start(); + } + if (!isArmed() && timer.isRunning()) { + timer.stop(); + } + } + } + + private static enum MenuIcon implements Icon { + + UP(9, 1, 9), DOWN(1, 9, 1); + final int[] xPoints = { 1, 5, 9 }; + final int[] yPoints; + + MenuIcon(int... yPoints) { + this.yPoints = yPoints; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + Dimension size = c.getSize(); + Graphics g2 = g.create(size.width / 2 - 5, size.height / 2 - 5, 10, + 10); + g2.setColor(Color.GRAY); + g2.drawPolygon(xPoints, yPoints, 3); + if (c.isEnabled()) { + g2.setColor(Color.BLACK); + g2.fillPolygon(xPoints, yPoints, 3); + } + g2.dispose(); + } + + @Override + public int getIconWidth() { + return 0; + } + + @Override + public int getIconHeight() { + return 10; + } + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/NumberRenderer.java b/src/jorphan/org/apache/jorphan/gui/NumberRenderer.java new file mode 100644 index 00000000000..93a8eef042d --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/NumberRenderer.java @@ -0,0 +1,51 @@ +/* + * 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.jorphan.gui; + +import java.text.DecimalFormat; +import java.text.NumberFormat; + +import javax.swing.SwingConstants; +import javax.swing.table.DefaultTableCellRenderer; + +/** + * Renders numbers in a JTable with a specified format + */ +public class NumberRenderer extends DefaultTableCellRenderer { + private static final long serialVersionUID = 240L; + + protected final NumberFormat formatter; + + public NumberRenderer() { + super(); + formatter = NumberFormat.getInstance(); + setHorizontalAlignment(SwingConstants.RIGHT); + } + + public NumberRenderer(String format) { + super(); + formatter = new DecimalFormat(format); + setHorizontalAlignment(SwingConstants.RIGHT); + } + + @Override + public void setValue(Object value) { + setText((value == null) ? "" : formatter.format(value)); + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/ObjectTableModel.java b/src/jorphan/org/apache/jorphan/gui/ObjectTableModel.java new file mode 100644 index 00000000000..12a73ba767c --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/ObjectTableModel.java @@ -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.jorphan.gui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import javax.swing.event.TableModelEvent; +import javax.swing.table.DefaultTableModel; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.log.Logger; + +/** + * The ObjectTableModel is a TableModel whose rows are objects; + * columns are defined as Functors on the object. + */ +public class ObjectTableModel extends DefaultTableModel { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private transient ArrayList objects = new ArrayList(); + + private transient List headers = new ArrayList(); + + private transient ArrayList> classes = new ArrayList>(); + + private transient ArrayList readFunctors = new ArrayList(); + + private transient ArrayList writeFunctors = new ArrayList(); + + private transient Class objectClass = null; // if provided + + private transient boolean cellEditable = true; + + /** + * The ObjectTableModel is a TableModel whose rows are objects; + * columns are defined as Functors on the object. + * + * @param headers - Column names + * @param _objClass - Object class that will be used + * @param readFunctors - used to get the values + * @param writeFunctors - used to set the values + * @param editorClasses - class for each column + */ + public ObjectTableModel(String[] headers, Class _objClass, Functor[] readFunctors, Functor[] writeFunctors, Class[] editorClasses) { + this(headers, readFunctors, writeFunctors, editorClasses); + this.objectClass=_objClass; + } + + /** + * The ObjectTableModel is a TableModel whose rows are objects; + * columns are defined as Functors on the object. + * + * @param headers - Column names + * @param _objClass - Object class that will be used + * @param readFunctors - used to get the values + * @param writeFunctors - used to set the values + * @param editorClasses - class for each column + * @param cellEditable - if cell must editable (false to allow double click on cell) + */ + public ObjectTableModel(String[] headers, Class _objClass, Functor[] readFunctors, + Functor[] writeFunctors, Class[] editorClasses, boolean cellEditable) { + this(headers, readFunctors, writeFunctors, editorClasses); + this.objectClass=_objClass; + this.cellEditable = cellEditable; + } + + /** + * The ObjectTableModel is a TableModel whose rows are objects; + * columns are defined as Functors on the object. + * + * @param headers - Column names + * @param readFunctors - used to get the values + * @param writeFunctors - used to set the values + * @param editorClasses - class for each column + */ + public ObjectTableModel(String[] headers, Functor[] readFunctors, Functor[] writeFunctors, Class[] editorClasses) { + this.headers.addAll(Arrays.asList(headers)); + this.classes.addAll(Arrays.asList(editorClasses)); + this.readFunctors = new ArrayList(Arrays.asList(readFunctors)); + this.writeFunctors = new ArrayList(Arrays.asList(writeFunctors)); + + int numHeaders = headers.length; + + int numClasses = classes.size(); + if (numClasses != numHeaders){ + log.warn("Header count="+numHeaders+" but classes count="+numClasses); + } + + // Functor count = 0 is handled specially + int numWrite = writeFunctors.length; + if (numWrite > 0 && numWrite != numHeaders){ + log.warn("Header count="+numHeaders+" but writeFunctor count="+numWrite); + } + + int numRead = readFunctors.length; + if (numRead > 0 && numRead != numHeaders){ + log.warn("Header count="+numHeaders+" but readFunctor count="+numRead); + } + } + + private Object readResolve() { + objects = new ArrayList(); + headers = new ArrayList(); + classes = new ArrayList>(); + readFunctors = new ArrayList(); + writeFunctors = new ArrayList(); + return this; + } + + public Iterator iterator() { + return objects.iterator(); + } + + public void clearData() { + int size = getRowCount(); + objects.clear(); + super.fireTableRowsDeleted(0, size); + } + + public void addRow(Object value) { + log.debug("Adding row value: " + value); + if (objectClass != null) { + final Class valueClass = value.getClass(); + if (!objectClass.isAssignableFrom(valueClass)){ + throw new IllegalArgumentException("Trying to add class: "+valueClass.getName() + +"; expecting class: "+objectClass.getName()); + } + } + objects.add(value); + super.fireTableRowsInserted(objects.size() - 1, objects.size()); + } + + public void insertRow(Object value, int index) { + objects.add(index, value); + super.fireTableRowsInserted(index, index + 1); + } + + /** {@inheritDoc} */ + @Override + public int getColumnCount() { + return headers.size(); + } + + /** {@inheritDoc} */ + @Override + public String getColumnName(int col) { + return headers.get(col); + } + + /** {@inheritDoc} */ + @Override + public int getRowCount() { + if (objects == null) { + return 0; + } + return objects.size(); + } + + /** {@inheritDoc} */ + @Override + public Object getValueAt(int row, int col) { + log.debug("Getting row value"); + Object value = objects.get(row); + if(headers.size() == 1 && col >= readFunctors.size()) { + return value; + } + Functor getMethod = readFunctors.get(col); + if (getMethod != null && value != null) { + return getMethod.invoke(value); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean isCellEditable(int arg0, int arg1) { + return cellEditable; + } + + /** {@inheritDoc} */ + @Override + public void moveRow(int start, int end, int to) { + List subList = new ArrayList(objects.subList(start, end)); + for (int x = end - 1; x >= start; x--) { + objects.remove(x); + } + objects.addAll(to, subList); + super.fireTableChanged(new TableModelEvent(this)); + } + + /** {@inheritDoc} */ + @Override + public void removeRow(int row) { + objects.remove(row); + super.fireTableRowsDeleted(row, row); + } + + /** {@inheritDoc} */ + @Override + public void setValueAt(Object cellValue, int row, int col) { + if (row < objects.size()) { + Object value = objects.get(row); + if (col < writeFunctors.size()) { + Functor setMethod = writeFunctors.get(col); + if (setMethod != null) { + setMethod.invoke(value, new Object[] { cellValue }); + super.fireTableDataChanged(); + } + } + else if(headers.size() == 1) + { + objects.set(row,cellValue); + } + } + } + + /** {@inheritDoc} */ + @Override + public Class getColumnClass(int arg0) { + return classes.get(arg0); + } + + /** + * Check all registered functors. + *

+ * ** only for use in unit test code ** + *

+ * + * @param _value - an instance of the table model row data item + * (if null, use the class passed to the constructor). + * + * @param caller - class of caller. + * + * @return false if at least one Functor cannot be found. + */ + @SuppressWarnings("deprecation") + public boolean checkFunctors(Object _value, Class caller){ + Object value; + if (_value == null && objectClass != null) { + try { + value = objectClass.newInstance(); + } catch (InstantiationException e) { + log.error("Cannot create instance of class "+objectClass.getName(),e); + return false; + } catch (IllegalAccessException e) { + log.error("Cannot create instance of class "+objectClass.getName(),e); + return false; + } + } else { + value = _value; + } + boolean status = true; + for(int i=0;i rows) { // used by TableEditor + clearData(); + for(Object val : rows) + { + addRow(val); + } + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/RateRenderer.java b/src/jorphan/org/apache/jorphan/gui/RateRenderer.java new file mode 100644 index 00000000000..f0640558bea --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/RateRenderer.java @@ -0,0 +1,65 @@ +/* + * 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.jorphan.gui; + +/** + * Renders a rate in a JTable. + * + * The output is in units appropriate to its dimension: + *

+ * The number is represented in one of: + * - requests/second + * - requests/minute + * - requests/hour. + *

+ * Examples: "34.2/sec" "0.1/sec" "43.0/hour" "15.9/min" + */ +public class RateRenderer extends NumberRenderer{ + + private static final long serialVersionUID = 240L; + + public RateRenderer(String format) { + super(format); + } + + @Override + public void setValue(Object value) { + if (!(value instanceof Double)) { + setText("#N/A"); // TODO: should this just call super()? + return; + } + double rate = ((Double) value).doubleValue(); + if (rate == Double.MAX_VALUE){ + setText("#N/A"); // TODO: should this just call super()? + return; + } + + String unit = "sec"; + + if (rate < 1.0) { + rate *= 60.0; + unit = "min"; + } + if (rate < 1.0) { + rate *= 60.0; + unit = "hour"; + } + setText(formatter.format(rate) + "/" + unit); + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/RendererUtils.java b/src/jorphan/org/apache/jorphan/gui/RendererUtils.java new file mode 100644 index 00000000000..646d93afa7b --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/RendererUtils.java @@ -0,0 +1,41 @@ +/* + * 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.jorphan.gui; + +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumnModel; + +/** + * Utility class for Renderers + */ +public final class RendererUtils { + private RendererUtils(){ + // uninstantiable + } + public static void applyRenderers(final JTable table, final TableCellRenderer [] renderers){ + final TableColumnModel columnModel = table.getColumnModel(); + for(int i = 0; i < renderers.length; i++){ + final TableCellRenderer rend = renderers[i]; + if (rend != null) { + columnModel.getColumn(i).setCellRenderer(rend); + } + } +} +} diff --git a/src/jorphan/org/apache/jorphan/gui/RightAlignRenderer.java b/src/jorphan/org/apache/jorphan/gui/RightAlignRenderer.java new file mode 100644 index 00000000000..e8b17415f57 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/RightAlignRenderer.java @@ -0,0 +1,34 @@ +/* + * 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.jorphan.gui; + +import javax.swing.SwingConstants; +import javax.swing.table.DefaultTableCellRenderer; + +/** + * Renders items in a JTable right-aligned + */ +public class RightAlignRenderer extends DefaultTableCellRenderer { + private static final long serialVersionUID = 240L; + + public RightAlignRenderer() { + super(); + setHorizontalAlignment(SwingConstants.RIGHT); + } +} diff --git a/src/jorphan/org/apache/jorphan/gui/TreeTableModel.java b/src/jorphan/org/apache/jorphan/gui/TreeTableModel.java new file mode 100644 index 00000000000..6c789a2aef5 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/TreeTableModel.java @@ -0,0 +1,66 @@ +/* + * 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.jorphan.gui; + +import javax.swing.table.TableModel; + +/** + * + * This is a basic interface for TreeTableModel that extends TableModel. + * It's pretty minimal and isn't as full featured at other implementations. + */ +public interface TreeTableModel extends TableModel { + + /** + * The method is similar to getValueAt(int,int). Instead of int, the row is + * an object. + * + * @param node + * the node which value is to be fetched + * @param col + * the column of the node + * @return the value at the column + */ + Object getValueAt(Object node, int col); + + /** + * the method is similar to isCellEditable(int,int). Instead of int, the row + * is an object. + * + * @param node + * the node which value is to be fetched + * @param col + * the column of the node + * @return true if cell is editable + */ + boolean isCellEditable(Object node, int col); + + /** + * the method is similar to isCellEditable(int,int). Instead of int, the row + * is an object. + * + * @param val + * the value to be set + * @param node + * the node which value is to be set + * @param column + * the column of the node + */ + void setValueAt(Object val, Object node, int column); +} diff --git a/src/jorphan/org/apache/jorphan/gui/layout/VerticalLayout.java b/src/jorphan/org/apache/jorphan/gui/layout/VerticalLayout.java new file mode 100644 index 00000000000..c8c45b79c98 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/gui/layout/VerticalLayout.java @@ -0,0 +1,244 @@ +/* + * 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.jorphan.gui.layout; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.io.Serializable; + +/** + * A vertical layout manager similar to java.awt.FlowLayout. Like FlowLayout + * components do not expand to fill available space except when the horizontal + * alignment is BOTH in which case components are stretched + * horizontally. Unlike FlowLayout, components will not wrap to form another + * column if there isn't enough space vertically. VerticalLayout can optionally + * anchor components to the top or bottom of the display area or center them + * between the top and bottom. Revision date 04 April 1999 + * + * @version $Revision$ + */ +public class VerticalLayout implements LayoutManager, Serializable { + private static final long serialVersionUID = 240L; + + /** + * The horizontal alignment constant that designates centering. Also used to + * designate center anchoring. + */ + public static final int CENTER = 0; + + /** + * The horizontal alignment constant that designates right justification. + */ + public static final int RIGHT = 1; + + /** + * The horizontal alignment constant that designates left justification. + */ + public static final int LEFT = 2; + + /** + * The horizontal alignment constant that designates stretching the + * component horizontally. + */ + public static final int BOTH = 3; + + /** + * The anchoring constant that designates anchoring to the top of the + * display area. + */ + public static final int TOP = 1; + + /** + * The anchoring constant that designates anchoring to the bottom of the + * display area. + */ + public static final int BOTTOM = 2; + + /** The vertical vgap between components...defaults to 5. */ + private int vgap; + + /** LEFT, RIGHT, CENTER or BOTH...how the components are justified. */ + private int alignment; + + /** + * TOP, BOTTOM or CENTER ...where are the components positioned in an + * overlarge space. + */ + private int anchor; + + // Constructors + /** + * Constructs an instance of VerticalLayout with a vertical vgap of 5 + * pixels, horizontal centering and anchored to the top of the display area. + */ + public VerticalLayout() { + this(5, CENTER, TOP); + } + + /** + * Constructs a VerticalLayout instance with horizontal centering, anchored + * to the top with the specified vgap. + * + * @param vgap + * an int value indicating the vertical seperation of the + * components + */ + public VerticalLayout(int vgap) { + this(vgap, CENTER, TOP); + } + + /** + * Constructs a VerticalLayout instance anchored to the top with the + * specified vgap and horizontal alignment. + * + * @param vgap + * an int value indicating the vertical seperation of the + * components + * @param alignment + * an int value which is one of RIGHT, LEFT, + * CENTER, BOTH + * for the horizontal alignment. + */ + public VerticalLayout(int vgap, int alignment) { + this(vgap, alignment, TOP); + } + + /** + * Constructs a VerticalLayout instance with the specified vgap, horizontal + * alignment and anchoring + * + * @param vgap + * an int value indicating the vertical seperation of the + * components + * @param alignment + * an int value which is one of RIGHT, LEFT, CENTER, + * BOTH + * for the horizontal alignment. + * @param anchor + * an int value which is one of TOP, BOTTOM, + * CENTER + * indicating where the components are to appear if the display + * area exceeds the minimum necessary. + */ + public VerticalLayout(int vgap, int alignment, int anchor) { + this.vgap = vgap; + this.alignment = alignment; + this.anchor = anchor; + } + + /** + * Lays out the container. + */ + @Override + public void layoutContainer(Container parent) { + Insets insets = parent.getInsets(); + // NOTUSED Dimension dim = layoutSize(parent, false); + synchronized (parent.getTreeLock()) { + int n = parent.getComponentCount(); + Dimension pd = parent.getSize(); + int y = 0; + // work out the total size + for (int i = 0; i < n; i++) { + Component c = parent.getComponent(i); + Dimension d = c.getPreferredSize(); + y += d.height + vgap; + } + y -= vgap; // otherwise there's a vgap too many + // Work out the anchor paint + if (anchor == TOP) { + y = insets.top; + } else if (anchor == CENTER) { + y = (pd.height - y) / 2; + } else { + y = pd.height - y - insets.bottom; + } + // do layout + for (int i = 0; i < n; i++) { + Component c = parent.getComponent(i); + Dimension d = c.getPreferredSize(); + int x = insets.left; + int wid = d.width; + if (alignment == CENTER) { + x = (pd.width - d.width) / 2; + } else if (alignment == RIGHT) { + x = pd.width - d.width - insets.right; + } else if (alignment == BOTH) { + wid = pd.width - insets.left - insets.right; + } + c.setBounds(x, y, wid, d.height); + y += d.height + vgap; + } + } + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return layoutSize(parent, true); + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + return layoutSize(parent, false); + } + + /** + * Not used by this class. + */ + @Override + public void addLayoutComponent(String name, Component comp) { + } + + /** + * Not used by this class. + */ + @Override + public void removeLayoutComponent(Component comp) { + } + + @Override + public String toString() { + return getClass().getName() + "[vgap=" + vgap + " align=" + alignment + " anchor=" + anchor + "]"; + } + + private Dimension layoutSize(Container parent, boolean minimum) { + Dimension dim = new Dimension(0, 0); + Dimension d; + synchronized (parent.getTreeLock()) { + int n = parent.getComponentCount(); + for (int i = 0; i < n; i++) { + Component c = parent.getComponent(i); + if (c.isVisible()) { + d = minimum ? c.getMinimumSize() : c.getPreferredSize(); + dim.width = Math.max(dim.width, d.width); + dim.height += d.height; + if (i > 0) { + dim.height += vgap; + } + } + } + } + Insets insets = parent.getInsets(); + dim.width += insets.left + insets.right; + dim.height += insets.top + insets.bottom + vgap + vgap; + return dim; + } +} diff --git a/src/jorphan/org/apache/jorphan/io/TextFile.java b/src/jorphan/org/apache/jorphan/io/TextFile.java new file mode 100644 index 00000000000..4a0b0eb8963 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/io/TextFile.java @@ -0,0 +1,209 @@ +/* + * 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.jorphan.io; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Utility class to handle text files as a single lump of text. + *

+ * Note this is just as memory-inefficient as handling a text file can be. Use + * with restraint. + * + * @version $Revision$ + */ +public class TextFile extends File { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * File encoding. null means use the platform's default. + */ + private String encoding = null; + + /** + * Create a TextFile object to handle the named file with the given + * encoding. + * + * @param filename + * File to be read and written through this object. + * @param encoding + * Encoding to be used when reading and writing this file. + */ + public TextFile(File filename, String encoding) { + super(filename.toString()); + setEncoding(encoding); + } + + /** + * Create a TextFile object to handle the named file with the platform + * default encoding. + * + * @param filename + * File to be read and written through this object. + */ + public TextFile(File filename) { + super(filename.toString()); + } + + /** + * Create a TextFile object to handle the named file with the platform + * default encoding. + * + * @param filename + * Name of the file to be read and written through this object. + */ + public TextFile(String filename) { + super(filename); + } + + /** + * Create a TextFile object to handle the named file with the given + * encoding. + * + * @param filename + * Name of the file to be read and written through this object. + * @param encoding + * Encoding to be used when reading and writing this file. + */ + public TextFile(String filename, String encoding) { + super(filename); + setEncoding(encoding); + } + + /** + * Create the file with the given string as content -- or replace it's + * content with the given string if the file already existed. + * + * @param body + * New content for the file. + */ + public void setText(String body) { + Writer writer = null; + try { + if (encoding == null) { + writer = new FileWriter(this); + } else { + writer = new OutputStreamWriter(new FileOutputStream(this), encoding); + } + writer.write(body); + writer.flush(); + } catch (IOException ioe) { + log.error("", ioe); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } + + /** + * Read the whole file content and return it as a string. + * + * @return the content of the file + */ + public String getText() { + String lineEnd = System.getProperty("line.separator"); //$NON-NLS-1$ + StringBuilder sb = new StringBuilder(); + Reader reader = null; + BufferedReader br = null; + try { + if (encoding == null) { + reader = new FileReader(this); + } else { + reader = new InputStreamReader(new FileInputStream(this), encoding); + } + br = new BufferedReader(reader); + String line = "NOTNULL"; //$NON-NLS-1$ + while (line != null) { + line = br.readLine(); + if (line != null) { + sb.append(line + lineEnd); + } + } + } catch (IOException ioe) { + log.error("", ioe); //$NON-NLS-1$ + } finally { + JOrphanUtils.closeQuietly(br); // closes reader as well + } + + return sb.toString(); + } + + /** + * @return Encoding being used to read and write this file. + */ + public String getEncoding() { + return encoding; + } + + /** + * @param string + * Encoding to be used to read and write this file. + */ + public void setEncoding(String string) { + encoding = string; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + + ((encoding == null) ? 0 : encoding.hashCode()); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof TextFile)) { + return false; + } + TextFile other = (TextFile) obj; + if (encoding == null) { + return other.encoding == null; + } + return encoding.equals(other.encoding); + } +} diff --git a/src/jorphan/org/apache/jorphan/logging/LoggingManager.java b/src/jorphan/org/apache/jorphan/logging/LoggingManager.java new file mode 100644 index 00000000000..ddb589b1e07 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/logging/LoggingManager.java @@ -0,0 +1,368 @@ +/* + * 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.jorphan.logging; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Iterator; +import java.util.Properties; + +import org.apache.avalon.excalibur.logger.LogKitLoggerManager; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; +import org.apache.avalon.framework.context.Context; +import org.apache.avalon.framework.context.ContextException; +import org.apache.avalon.framework.context.DefaultContext; +import org.apache.log.Hierarchy; +import org.apache.log.LogTarget; +import org.apache.log.Logger; +import org.apache.log.Priority; +import org.apache.log.format.PatternFormatter; +import org.apache.log.output.NullOutputLogTarget; +import org.apache.log.output.io.WriterTarget; +import org.xml.sax.SAXException; + +/** + * Manages JMeter logging + */ +public final class LoggingManager { + // N.B time pattern is passed to java.text.SimpleDateFormat + /* + * Predefined format patterns, selected by the property log_format_type (see + * jmeter.properties) The new-line is added later + */ + public static final String DEFAULT_PATTERN = "%{time:yyyy/MM/dd HH:mm:ss} %5.5{priority} - " //$NON_NLS-1$ + + "%{category}: %{message} %{throwable}"; //$NON_NLS-1$ + + private static final String PATTERN_THREAD_PREFIX = "%{time:yyyy/MM/dd HH:mm:ss} %5.5{priority} " //$NON_NLS-1$ + + "%20{thread} %{category}: %{message} %{throwable}"; //$NON_NLS-1$ + + private static final String PATTERN_THREAD_SUFFIX = "%{time:yyyy/MM/dd HH:mm:ss} %5.5{priority} " //$NON_NLS-1$ + + "%{category}[%{thread}]: %{message} %{throwable}"; //$NON_NLS-1$ + + // Needs to be volatile as may be referenced from multiple threads + // TODO see if this can be made final somehow + private static volatile PatternFormatter format = null; + + /** Used to hold the default logging target. */ + //@GuardedBy("this") + private static LogTarget target = new NullOutputLogTarget(); + + // Hack to detect when System.out has been set as the target, to avoid closing it + private static volatile boolean isTargetSystemOut = false;// Is the target System.out? + + private static volatile boolean isWriterSystemOut = false;// Is the Writer System.out? + + public static final String LOG_FILE = "log_file"; //$NON_NLS-1$ + + public static final String LOG_PRIORITY = "log_level"; //$NON_NLS-1$ + + private LoggingManager() { + // non-instantiable - static methods only + } + + /** + * Initialise the logging system from the Jmeter properties. Logkit loggers + * inherit from their parents. + * + * Normally the jmeter properties file defines a single log file, so set + * this as the default from "log_file", default "jmeter.log" The default + * priority is set from "log_level", with a default of INFO + * + * @param properties + * {@link Properties} to be used for initialization + */ + public static void initializeLogging(Properties properties) { + setFormat(properties); + + // Set the top-level defaults + setTarget(makeWriter(properties.getProperty(LOG_FILE, "jmeter.log"), LOG_FILE)); //$NON_NLS-1$ + setPriority(properties.getProperty(LOG_PRIORITY, "INFO")); + + setLoggingLevels(properties); + // now set the individual categories (if any) + + setConfig(properties);// Further configuration + } + + private static void setFormat(Properties properties) { + String pattern = DEFAULT_PATTERN; + String type = properties.getProperty("log_format_type", ""); //$NON_NLS-1$ + if (type.length() == 0) { + pattern = properties.getProperty("log_format", DEFAULT_PATTERN); //$NON_NLS-1$ + } else { + if (type.equalsIgnoreCase("thread_suffix")) { //$NON_NLS-1$ + pattern = PATTERN_THREAD_SUFFIX; + } else if (type.equalsIgnoreCase("thread_prefix")) { //$NON_NLS-1$ + pattern = PATTERN_THREAD_PREFIX; + } else { + pattern = DEFAULT_PATTERN; + } + } + format = new PatternFormatter(pattern + "\n"); //$NON_NLS-1$ + } + + private static void setConfig(Properties p) { + String cfg = p.getProperty("log_config"); //$NON_NLS-1$ + if (cfg == null) { + return; + } + + // Make sure same hierarchy is used + Hierarchy hier = Hierarchy.getDefaultHierarchy(); + LogKitLoggerManager manager = new LogKitLoggerManager(null, hier, null, null); + + DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); + try { + Configuration c = builder.buildFromFile(cfg); + Context ctx = new DefaultContext(); + manager.contextualize(ctx); + manager.configure(c); + } catch (IllegalArgumentException e) { + // This happens if the default log-target id-ref specifies a non-existent target + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } catch (NullPointerException e) { + // This can happen if a log-target id-ref specifies a non-existent target + System.out.println("Error processing logging config " + cfg); + System.out.println("Perhaps a log target is missing?"); + } catch (ConfigurationException e) { + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } catch (SAXException e) { + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } catch (IOException e) { + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } catch (ContextException e) { + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } + } + + /* + * Helper method to ensure that format is initialised if initializeLogging() + * has not yet been called. + */ + private static PatternFormatter getFormat() { + if (format == null) { + format = new PatternFormatter(DEFAULT_PATTERN + "\n"); //$NON_NLS-1$ + } + return format; + } + + /* + * Helper method to handle log target creation. If there is an error + * creating the file, then it uses System.out. + */ + private static Writer makeWriter(String logFile, String propName) { + // If the name contains at least one set of paired single-quotes, reformat using DateFormat + final int length = logFile.split("'",-1).length; + if (length > 1 && length %2 == 1){ + try { + SimpleDateFormat df = new SimpleDateFormat(logFile); + logFile = df.format(new Date()); + } catch (Exception ignored) { + } + } + Writer wt; + isWriterSystemOut = false; + try { + wt = new FileWriter(logFile); + } catch (Exception e) { + System.out.println(propName + "=" + logFile + " " + e.toString()); + System.out.println("[" + propName + "-> System.out]"); + isWriterSystemOut = true; + wt = new PrintWriter(System.out); + } + return wt; + } + + /** + * Handle LOG_PRIORITY.category=priority and LOG_FILE.category=file_name + * properties. If the prefix is detected, then remove it to get the + * category. + * + * @param appProperties + * {@link Properties} that contain the + * {@link LoggingManager#LOG_PRIORITY LOG_PRIORITY} and + * {@link LoggingManager#LOG_FILE LOG_FILE} prefixed entries + */ + public static void setLoggingLevels(Properties appProperties) { + Iterator props = appProperties.keySet().iterator(); + while (props.hasNext()) { + String prop = (String) props.next(); + if (prop.startsWith(LOG_PRIORITY + ".")) //$NON_NLS-1$ + // don't match the empty category + { + String category = prop.substring(LOG_PRIORITY.length() + 1); + setPriority(appProperties.getProperty(prop), category); + } + if (prop.startsWith(LOG_FILE + ".")) { //$NON_NLS-1$ + String category = prop.substring(LOG_FILE.length() + 1); + String file = appProperties.getProperty(prop); + setTarget(new WriterTarget(makeWriter(file, prop), getFormat()), category); + } + } + } + + private static final String PACKAGE_PREFIX = "org.apache."; //$NON_NLS-1$ + + /** + * Removes the standard prefix, i.e. "org.apache.". + * + * @param name from which to remove the prefix + * @return the name with the prefix removed + */ + public static String removePrefix(String name){ + if (name.startsWith(PACKAGE_PREFIX)) { // remove the package prefix + name = name.substring(PACKAGE_PREFIX.length()); + } + return name; + } + /** + * Get the Logger for a class - no argument needed because the calling class + * name is derived automatically from the call stack. + * + * @return Logger + */ + public static Logger getLoggerForClass() { + String className = new Exception().getStackTrace()[1].getClassName(); + return Hierarchy.getDefaultHierarchy().getLoggerFor(removePrefix(className)); + } + + /** + * Get the Logger for a class. + * + * @param category - the full name of the logger category + * + * @return Logger + */ + public static Logger getLoggerFor(String category) { + return Hierarchy.getDefaultHierarchy().getLoggerFor(category); + } + + /** + * Get the Logger for a class. + * + * @param category - the full name of the logger category, this will have the prefix removed. + * + * @return Logger + */ + public static Logger getLoggerForShortName(String category) { + return Hierarchy.getDefaultHierarchy().getLoggerFor(removePrefix(category)); + } + + /** + * Set the logging priority for a category. + * + * @param priority - string containing the priority name, e.g. "INFO", "WARN", "DEBUG", "FATAL_ERROR" + * @param category - string containing the category + */ + public static void setPriority(String priority, String category) { + setPriority(Priority.getPriorityForName(priority), category); + } + + /** + * Set the logging priority for a category. + * + * @param priority - priority, e.g. DEBUG, INFO + * @param fullName - e.g. org.apache.jmeter.etc, will have the prefix removed. + */ + public static void setPriorityFullName(String priority, String fullName) { + setPriority(Priority.getPriorityForName(priority), removePrefix(fullName)); + } + + /** + * Set the logging priority for a category. + * + * @param priority - e.g. Priority.DEBUG + * @param category - string containing the category + */ + public static void setPriority(Priority priority, String category) { + Hierarchy.getDefaultHierarchy().getLoggerFor(category).setPriority(priority); + } + + public static void setPriority(String p) { + setPriority(Priority.getPriorityForName(p)); + } + + /** + * Set the default logging priority. + * + * @param priority e.g. Priority.DEBUG + */ + public static void setPriority(Priority priority) { + Hierarchy.getDefaultHierarchy().setDefaultPriority(priority); + } + + /** + * Set the logging target for a category. + * + * @param target the LogTarget + * @param category the category name + */ + public static void setTarget(LogTarget target, String category) { + Logger logger = Hierarchy.getDefaultHierarchy().getLoggerFor(category); + logger.setLogTargets(new LogTarget[] { target }); + } + + /** + * Sets the default log target from the parameter. The existing target is + * first closed if necessary. + * + * @param targetFile + * (Writer) + */ + private static synchronized void setTarget(Writer targetFile) { + if (target == null) { + target = getTarget(targetFile, getFormat()); + isTargetSystemOut = isWriterSystemOut; + } else { + if (!isTargetSystemOut && target instanceof WriterTarget) { + ((WriterTarget) target).close(); + } + target = getTarget(targetFile, getFormat()); + isTargetSystemOut = isWriterSystemOut; + } + Hierarchy.getDefaultHierarchy().setDefaultLogTarget(target); + } + + private static LogTarget getTarget(Writer targetFile, PatternFormatter fmt) { + return new WriterTarget(targetFile, fmt); + } + + /** + * Add logTargets to root logger + * FIXME What's the clean way to add a LogTarget afterwards ? + * @param logTargets LogTarget array + */ + public static void addLogTargetToRootLogger(LogTarget[] logTargets) { + LogTarget[] newLogTargets = new LogTarget[logTargets.length+1]; + System.arraycopy(logTargets, 0, newLogTargets, 1, logTargets.length); + newLogTargets[0] = target; + Hierarchy.getDefaultHierarchy().getRootLogger().setLogTargets(newLogTargets); + } +} diff --git a/src/jorphan/org/apache/jorphan/math/NumberComparator.java b/src/jorphan/org/apache/jorphan/math/NumberComparator.java new file mode 100644 index 00000000000..adc7390782d --- /dev/null +++ b/src/jorphan/org/apache/jorphan/math/NumberComparator.java @@ -0,0 +1,47 @@ +/* + * 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. + * + */ + +/* + * Created on May 25, 2004 + */ +package org.apache.jorphan.math; + +import java.io.Serializable; +import java.util.Comparator; + +public class NumberComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 1L; + + public NumberComparator() { + super(); + } + + /** {@inheritDoc} */ + @Override + public int compare(Number[] n1, Number[] n2) { + if (n1[0].longValue() < n2[0].longValue()) { + return -1; + } else if (n1[0].longValue() == n2[0].longValue()) { + return 0; + } else { + return 1; + } + } + +} diff --git a/src/jorphan/org/apache/jorphan/math/StatCalculator.java b/src/jorphan/org/apache/jorphan/math/StatCalculator.java new file mode 100644 index 00000000000..398959d062f --- /dev/null +++ b/src/jorphan/org/apache/jorphan/math/StatCalculator.java @@ -0,0 +1,276 @@ +/* + * 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.jorphan.math; + +import java.util.ConcurrentModificationException; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.Map.Entry; + +import org.apache.commons.lang3.mutable.MutableLong; + +/** + * This class serves as a way to calculate the median, max, min etc. of a list of values. + * It is not threadsafe. + * + * @param type parameter for the calculator + * + */ +public abstract class StatCalculator> { + + // key is the type to collect (usually long), value = count of entries + private final Map valuesMap = new TreeMap(); + // We use a TreeMap because we need the entries to be sorted + + // Running values, updated for each sample + private double sum = 0; + + private double sumOfSquares = 0; + + private double mean = 0; + + private double deviation = 0; + + private long count = 0; + + private T min; + + private T max; + + private long bytes = 0; + + private final T ZERO; + + private final T MAX_VALUE; // e.g. Long.MAX_VALUE + + private final T MIN_VALUE; // e.g. Long.MIN_VALUE + + /** + * This constructor is used to set up particular values for the generic class instance. + * + * @param zero - value to return for Median and PercentPoint if there are no values + * @param min - value to return for minimum if there are no values + * @param max - value to return for maximum if there are no values + */ + public StatCalculator(final T zero, final T min, final T max) { + super(); + ZERO = zero; + MAX_VALUE = max; + MIN_VALUE = min; + this.min = MAX_VALUE; + this.max = MIN_VALUE; + } + + public void clear() { + valuesMap.clear(); + sum = 0; + sumOfSquares = 0; + mean = 0; + deviation = 0; + count = 0; + bytes = 0; + max = MIN_VALUE; + min = MAX_VALUE; + } + + + public void addBytes(long newValue) { + bytes += newValue; + } + + public void addAll(StatCalculator calc) { + for(Entry ent : calc.valuesMap.entrySet()) { + addEachValue(ent.getKey(), ent.getValue().longValue()); + } + } + + public T getMedian() { + return getPercentPoint(0.5); + } + + public long getTotalBytes() { + return bytes; + } + + /** + * Get the value which %percent% of the values are less than. This works + * just like median (where median represents the 50% point). A typical + * desire is to see the 90% point - the value that 90% of the data points + * are below, the remaining 10% are above. + * + * @param percent + * number representing the wished percent (between 0 + * and 1.0) + * @return number of values less than the percentage + */ + public T getPercentPoint(float percent) { + return getPercentPoint((double) percent); + } + + /** + * Get the value which %percent% of the values are less than. This works + * just like median (where median represents the 50% point). A typical + * desire is to see the 90% point - the value that 90% of the data points + * are below, the remaining 10% are above. + * + * @param percent + * number representing the wished percent (between 0 + * and 1.0) + * @return the value which %percent% of the values are less than + */ + public T getPercentPoint(double percent) { + if (count <= 0) { + return ZERO; + } + if (percent >= 1.0) { + return getMax(); + } + + // use Math.round () instead of simple (long) to provide correct value rounding + long target = Math.round (count * percent); + try { + for (Entry val : valuesMap.entrySet()) { + target -= val.getValue().longValue(); + if (target <= 0){ + return val.getKey(); + } + } + } catch (ConcurrentModificationException ignored) { + // ignored. May happen occasionally, but no harm done if so. + } + return ZERO; // TODO should this be getMin()? + } + + /** + * Returns the distribution of the values in the list. + * + * @return map containing either Integer or Long keys; entries are a Number array containing the key and the [Integer] count. + * TODO - why is the key value also stored in the entry array? See Bug 53825 + */ + public Map getDistribution() { + Map items = new HashMap(); + + for (Entry entry : valuesMap.entrySet()) { + Number[] dis = new Number[2]; + dis[0] = entry.getKey(); + dis[1] = entry.getValue(); + items.put(entry.getKey(), dis); + } + return items; + } + + public double getMean() { + return mean; + } + + public double getStandardDeviation() { + return deviation; + } + + public T getMin() { + return min; + } + + public T getMax() { + return max; + } + + public long getCount() { + return count; + } + + public double getSum() { + return sum; + } + + protected abstract T divide(T val, int n); + + protected abstract T divide(T val, long n); + + /** + * Update the calculator with the values for a set of samples. + * + * @param val the common value, normally the elapsed time + * @param sampleCount the number of samples with the same value + */ + void addEachValue(T val, long sampleCount) { + count += sampleCount; + double currentVal = val.doubleValue(); + sum += currentVal * sampleCount; + // For n same values in sum of square is equal to n*val^2 + sumOfSquares += currentVal * currentVal * sampleCount; + updateValueCount(val, sampleCount); + calculateDerivedValues(val); + } + + /** + * Update the calculator with the value for an aggregated sample. + * + * @param val the aggregate value, normally the elapsed time + * @param sampleCount the number of samples contributing to the aggregate value + */ + public void addValue(T val, long sampleCount) { + count += sampleCount; + double currentVal = val.doubleValue(); + sum += currentVal; + T actualValue = val; + if (sampleCount > 1){ + // For n values in an aggregate sample the average value = (val/n) + // So need to add n * (val/n) * (val/n) = val * val / n + sumOfSquares += currentVal * currentVal / sampleCount; + actualValue = divide(val, sampleCount); + } else { // no need to divide by 1 + sumOfSquares += currentVal * currentVal; + } + updateValueCount(actualValue, sampleCount); + calculateDerivedValues(actualValue); + } + + private void calculateDerivedValues(T actualValue) { + mean = sum / count; + deviation = Math.sqrt((sumOfSquares / count) - (mean * mean)); + if (actualValue.compareTo(max) > 0){ + max=actualValue; + } + if (actualValue.compareTo(min) < 0){ + min=actualValue; + } + } + + /** + * Add a single value (normally elapsed time) + * + * @param val the value to add, which should correspond with a single sample + * @see #addValue(Number, long) + */ + public void addValue(T val) { + addValue(val, 1L); + } + + private void updateValueCount(T actualValue, long sampleCount) { + MutableLong count = valuesMap.get(actualValue); + if (count != null) { + count.add(sampleCount); + } else { + // insert new value + valuesMap.put(actualValue, new MutableLong(sampleCount)); + } + } +} diff --git a/src/jorphan/org/apache/jorphan/math/StatCalculatorInteger.java b/src/jorphan/org/apache/jorphan/math/StatCalculatorInteger.java new file mode 100644 index 00000000000..acdfcd6550f --- /dev/null +++ b/src/jorphan/org/apache/jorphan/math/StatCalculatorInteger.java @@ -0,0 +1,53 @@ +/* + * 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.jorphan.math; + +/** + * StatCalculator for Integer values + */ +public class StatCalculatorInteger extends StatCalculator { + + public StatCalculatorInteger() { + super(Integer.valueOf(0), Integer.valueOf(Integer.MIN_VALUE), Integer.valueOf(Integer.MAX_VALUE)); + } + + public void addValue(int val){ + super.addValue(Integer.valueOf(val)); + } + + /** + * Update the calculator with the value for an aggregated sample. + * + * @param val the aggregate value + * @param sampleCount the number of samples contributing to the aggregate value + */ + public void addValue(int val, int sampleCount){ + super.addValue(Integer.valueOf(val), sampleCount); + } + + @Override + protected Integer divide(Integer val, int n) { + return Integer.valueOf(val.intValue() / n); + } + + @Override + protected Integer divide(Integer val, long n) { + return Integer.valueOf((int) (val.intValue() / n)); + } +} diff --git a/src/jorphan/org/apache/jorphan/math/StatCalculatorLong.java b/src/jorphan/org/apache/jorphan/math/StatCalculatorLong.java new file mode 100644 index 00000000000..4cfcd7655c0 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/math/StatCalculatorLong.java @@ -0,0 +1,58 @@ +/* + * 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.jorphan.math; + +/** + * StatCalculator for Long values + */ +public class StatCalculatorLong extends StatCalculator { + + public StatCalculatorLong() { + super(Long.valueOf(0L), Long.valueOf(Long.MIN_VALUE), Long.valueOf(Long.MAX_VALUE)); + } + + /** + * Add a single value (normally elapsed time) + * + * @param val the value to add, which should correspond with a single sample + */ + public void addValue(long val){ + super.addValue(Long.valueOf(val)); + } + + /** + * Update the calculator with the value for an aggregated sample. + * + * @param val the aggregate value, normally the elapsed time + * @param sampleCount the number of samples contributing to the aggregate value + */ + public void addValue(long val, int sampleCount){ + super.addValue(Long.valueOf(val), sampleCount); + } + + @Override + protected Long divide(Long val, int n) { + return Long.valueOf(val.longValue() / n); + } + + @Override + protected Long divide(Long val, long n) { + return Long.valueOf(val.longValue() / n); + } +} diff --git a/src/jorphan/org/apache/jorphan/reflect/ClassFinder.java b/src/jorphan/org/apache/jorphan/reflect/ClassFinder.java new file mode 100644 index 00000000000..6e44398eadf --- /dev/null +++ b/src/jorphan/org/apache/jorphan/reflect/ClassFinder.java @@ -0,0 +1,577 @@ +/* + * 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.jorphan.reflect; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * This class finds classes that extend one of a set of parent classes + * + */ +public final class ClassFinder { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String DOT_JAR = ".jar"; // $NON-NLS-1$ + private static final String DOT_CLASS = ".class"; // $NON-NLS-1$ + private static final int DOT_CLASS_LEN = DOT_CLASS.length(); + + // static only + private ClassFinder() { + } + + /** + * Filter updates to TreeSet by only storing classes + * that extend one of the parent classes + * + * + */ + private static class FilterTreeSet extends TreeSet{ + private static final long serialVersionUID = 234L; + + private final Class[] parents; // parent classes to check + private final boolean inner; // are inner classes OK? + + // hack to reduce the need to load every class in non-GUI mode, which only needs functions + // TODO perhaps use BCEL to scan class files instead? + private final String contains; // class name should contain this string + private final String notContains; // class name should not contain this string + + private final transient ClassLoader contextClassLoader + = Thread.currentThread().getContextClassLoader(); // Potentially expensive; do it once + + FilterTreeSet(Class []parents, boolean inner, String contains, String notContains){ + super(); + this.parents=parents; + this.inner=inner; + this.contains=contains; + this.notContains=notContains; + } + + /** + * Override the superclass so we only add classnames that + * meet the criteria. + * + * @param s - classname (must be a String) + * @return true if it is a new entry + * + * @see java.util.TreeSet#add(java.lang.Object) + */ + @Override + public boolean add(String s){ + if (contains(s)) { + return false;// No need to check it again + } + if (contains!=null && s.indexOf(contains) == -1){ + return false; // It does not contain a required string + } + if (notContains!=null && s.indexOf(notContains) != -1){ + return false; // It contains a banned string + } + if ((s.indexOf('$') == -1) || inner) { // $NON-NLS-1$ + if (isChildOf(parents,s, contextClassLoader)) { + return super.add(s); + } + } + return false; + } + } + + private static class AnnoFilterTreeSet extends TreeSet{ + private static final long serialVersionUID = 240L; + + private final boolean inner; // are inner classes OK? + + private final Class[] annotations; // annotation classes to check + private final transient ClassLoader contextClassLoader + = Thread.currentThread().getContextClassLoader(); // Potentially expensive; do it once + AnnoFilterTreeSet(Class []annotations, boolean inner){ + super(); + this.annotations = annotations; + this.inner=inner; + } + /** + * Override the superclass so we only add classnames that + * meet the criteria. + * + * @param s - classname (must be a String) + * @return true if it is a new entry + * + * @see java.util.TreeSet#add(java.lang.Object) + */ + @Override + public boolean add(String s){ + if (contains(s)) { + return false;// No need to check it again + } + if ((s.indexOf('$') == -1) || inner) { // $NON-NLS-1$ + if (hasAnnotationOnMethod(annotations,s, contextClassLoader)) { + return super.add(s); + } + } + return false; + } + } + + /** + * Convenience method for + * {@link #findClassesThatExtend(String[], Class[], boolean)} with the + * option to include inner classes in the search set to false. + * + * @param paths + * pathnames or jarfiles to search for classes + * @param superClasses + * required parent class(es) + * @return List of Strings containing discovered class names. + * @throws IOException + * when scanning the classes fails + */ + public static List findClassesThatExtend(String[] paths, Class[] superClasses) + throws IOException { + return findClassesThatExtend(paths, superClasses, false); + } + + // For each directory in the search path, add all the jars found there + private static String[] addJarsInPath(String[] paths) { + Set fullList = new HashSet(); + for (int i = 0; i < paths.length; i++) { + final String path = paths[i]; + fullList.add(path); // Keep the unexpanded path + // TODO - allow directories to end with .jar by removing this check? + if (!path.endsWith(DOT_JAR)) { + File dir = new File(path); + if (dir.exists() && dir.isDirectory()) { + String[] jars = dir.list(new FilenameFilter() { + @Override + public boolean accept(File f, String name) { + return name.endsWith(DOT_JAR); + } + }); + for (int x = 0; x < jars.length; x++) { + fullList.add(jars[x]); + } + } + } + } + return fullList.toArray(new String[fullList.size()]); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * @param strPathsOrJars - pathnames or jarfiles to search for classes + * @param superClasses - required parent class(es) + * @param innerClasses - should we include inner classes? + * + * @return List containing discovered classes + * @throws IOException when scanning for classes fails + */ + public static List findClassesThatExtend(String[] strPathsOrJars, + final Class[] superClasses, final boolean innerClasses) + throws IOException { + return findClassesThatExtend(strPathsOrJars,superClasses,innerClasses,null,null); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * @param strPathsOrJars - pathnames or jarfiles to search for classes + * @param superClasses - required parent class(es) + * @param innerClasses - should we include inner classes? + * @param contains - classname should contain this string + * @param notContains - classname should not contain this string + * + * @return List containing discovered classes + * @throws IOException when scanning classes fails + */ + public static List findClassesThatExtend(String[] strPathsOrJars, + final Class[] superClasses, final boolean innerClasses, + String contains, String notContains) + throws IOException { + return findClassesThatExtend(strPathsOrJars, superClasses, innerClasses, contains, notContains, false); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * @param strPathsOrJars - pathnames or jarfiles to search for classes + * @param annotations - required annotations + * @param innerClasses - should we include inner classes? + * + * @return List containing discovered classes + * @throws IOException when scanning classes fails + */ + public static List findAnnotatedClasses(String[] strPathsOrJars, + final Class[] annotations, final boolean innerClasses) + throws IOException { + return findClassesThatExtend(strPathsOrJars, annotations, innerClasses, null, null, true); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * Inner classes are not searched. + * + * @param strPathsOrJars - pathnames or jarfiles to search for classes + * @param annotations - required annotations + * + * @return List containing discovered classes + * @throws IOException when scanning classes fails + */ + public static List findAnnotatedClasses(String[] strPathsOrJars, + final Class[] annotations) + throws IOException { + return findClassesThatExtend(strPathsOrJars, annotations, false, null, null, true); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * @param searchPathsOrJars - pathnames or jarfiles to search for classes + * @param classNames - required parent class(es) or annotations + * @param innerClasses - should we include inner classes? + * @param contains - classname should contain this string + * @param notContains - classname should not contain this string + * @param annotations - true if classnames are annotations + * + * @return List containing discovered classes + * @throws IOException when scanning classes fails + */ + public static List findClassesThatExtend(String[] searchPathsOrJars, + final Class[] classNames, final boolean innerClasses, + String contains, String notContains, boolean annotations) + throws IOException { + if (log.isDebugEnabled()) { + log.debug("searchPathsOrJars : " + Arrays.toString(searchPathsOrJars)); + log.debug("superclass : " + Arrays.toString(classNames)); + log.debug("innerClasses : " + innerClasses + " annotations: " + annotations); + log.debug("contains: " + contains + " notContains: " + notContains); + } + + // Find all jars in the search path + String[] strPathsOrJars = addJarsInPath(searchPathsOrJars); + for (int k = 0; k < strPathsOrJars.length; k++) { + strPathsOrJars[k] = fixPathEntry(strPathsOrJars[k]); + } + + // Now eliminate any classpath entries that do not "match" the search + List listPaths = getClasspathMatches(strPathsOrJars); + if (log.isDebugEnabled()) { + for (String path : listPaths) { + log.debug("listPaths : " + path); + } + } + + @SuppressWarnings("unchecked") // Should only be called with classes that extend annotations + final Class[] annoclassNames = (Class[]) classNames; + Set listClasses = + annotations ? + new AnnoFilterTreeSet(annoclassNames, innerClasses) + : + new FilterTreeSet(classNames, innerClasses, contains, notContains); + // first get all the classes + findClassesInPaths(listPaths, listClasses); + if (log.isDebugEnabled()) { + log.debug("listClasses.size()="+listClasses.size()); + for (String clazz : listClasses) { + log.debug("listClasses : " + clazz); + } + } + +// // Now keep only the required classes +// Set subClassList = findAllSubclasses(superClasses, listClasses, innerClasses); +// if (log.isDebugEnabled()) { +// log.debug("subClassList.size()="+subClassList.size()); +// Iterator tIter = subClassList.iterator(); +// while (tIter.hasNext()) { +// log.debug("subClassList : " + tIter.next()); +// } +// } + + return new ArrayList(listClasses);//subClassList); + } + + /* + * Returns the classpath entries that match the search list of jars and paths + */ + private static List getClasspathMatches(String[] strPathsOrJars) { + final String javaClassPath = System.getProperty("java.class.path"); // $NON-NLS-1$ + StringTokenizer stPaths = + new StringTokenizer(javaClassPath, File.pathSeparator); + if (log.isDebugEnabled()) { + log.debug("Classpath = " + javaClassPath); + for (int i = 0; i < strPathsOrJars.length; i++) { + log.debug("strPathsOrJars[" + i + "] : " + strPathsOrJars[i]); + } + } + + // find all jar files or paths that end with strPathOrJar + ArrayList listPaths = new ArrayList(); + String strPath = null; + while (stPaths.hasMoreTokens()) { + strPath = fixPathEntry(stPaths.nextToken()); + if (strPathsOrJars == null) { + log.debug("Adding: " + strPath); + listPaths.add(strPath); + } else { + boolean found = false; + for (int i = 0; i < strPathsOrJars.length; i++) { + if (strPath.endsWith(strPathsOrJars[i])) { + found = true; + log.debug("Adding " + strPath + " found at " + i); + listPaths.add(strPath); + break;// no need to look further + } + } + if (!found) { + log.debug("Did not find: " + strPath); + } + } + } + return listPaths; + } + + /** + * Fix a path: + * - replace "." by current directory + * - trim any trailing spaces + * - replace \ by / + * - replace // by / + * - remove all trailing / + */ + private static String fixPathEntry(String path){ + if (path == null ) { + return null; + } + if (path.equals(".")) { // $NON-NLS-1$ + return System.getProperty("user.dir"); // $NON-NLS-1$ + } + path = path.trim().replace('\\', '/'); // $NON-NLS-1$ // $NON-NLS-2$ + path = JOrphanUtils.substitute(path, "//", "/"); // $NON-NLS-1$// $NON-NLS-2$ + + while (path.endsWith("/")) { // $NON-NLS-1$ + path = path.substring(0, path.length() - 1); + } + return path; + } + + /* + * NOTUSED * Determine if the class implements the interface. + * + * @param theClass + * the class to check + * @param theInterface + * the interface to look for + * @return boolean true if it implements + * + * private static boolean classImplementsInterface( Class theClass, Class + * theInterface) { HashMap mapInterfaces = new HashMap(); String strKey = + * null; // pass in the map by reference since the method is recursive + * getAllInterfaces(theClass, mapInterfaces); Iterator iterInterfaces = + * mapInterfaces.keySet().iterator(); while (iterInterfaces.hasNext()) { + * strKey = (String) iterInterfaces.next(); if (mapInterfaces.get(strKey) == + * theInterface) { return true; } } return false; } + */ + + /* + * Finds all classes that extend the classes in the listSuperClasses + * ArrayList, searching in the listAllClasses ArrayList. + * + * @param superClasses + * the base classes to find subclasses for + * @param listAllClasses + * the collection of classes to search in + * @param innerClasses + * indicate whether to include inner classes in the search + * @return ArrayList of the subclasses + */ +// private static Set findAllSubclasses(Class []superClasses, Set listAllClasses, boolean innerClasses) { +// Set listSubClasses = new TreeSet(); +// for (int i=0; i< superClasses.length; i++) { +// findAllSubclassesOneClass(superClasses[i], listAllClasses, listSubClasses, innerClasses); +// } +// return listSubClasses; +// } + + /* + * Finds all classes that extend the class, searching in the listAllClasses + * ArrayList. + * + * @param theClass + * the parent class + * @param listAllClasses + * the collection of classes to search in + * @param listSubClasses + * the collection of discovered subclasses + * @param innerClasses + * indicates whether inners classes should be included in the + * search + */ +// private static void findAllSubclassesOneClass(Class theClass, Set listAllClasses, Set listSubClasses, +// boolean innerClasses) { +// Iterator iterClasses = listAllClasses.iterator(); +// while (iterClasses.hasNext()) { +// String strClassName = (String) iterClasses.next(); +// // only check classes if they are not inner classes +// // or we intend to check for inner classes +// if ((strClassName.indexOf("$") == -1) || innerClasses) { // $NON-NLS-1$ +// // might throw an exception, assume this is ignorable +// try { +// Class c = Class.forName(strClassName, false, Thread.currentThread().getContextClassLoader()); +// +// if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) { +// if(theClass.isAssignableFrom(c)){ +// listSubClasses.add(strClassName); +// } +// } +// } catch (Throwable ignored) { +// log.debug(ignored.getLocalizedMessage()); +// } +// } +// } +// } + + /** + * + * @param parentClasses list of classes to check for + * @param strClassName name of class to be checked + * @param innerClasses should we allow inner classes? + * @param contextClassLoader the classloader to use + * @return true if the class is a non-abstract, non-interface instance of at least one of the parent classes + */ + private static boolean isChildOf(Class [] parentClasses, String strClassName, + ClassLoader contextClassLoader){ + // might throw an exception, assume this is ignorable + try { + Class c = Class.forName(strClassName, false, contextClassLoader); + + if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) { + for (int i=0; i< parentClasses.length; i++) { + if(parentClasses[i].isAssignableFrom(c)){ + return true; + } + } + } + } catch (UnsupportedClassVersionError ignored) { + log.debug(ignored.getLocalizedMessage()); + } catch (NoClassDefFoundError ignored) { + log.debug(ignored.getLocalizedMessage()); + } catch (ClassNotFoundException ignored) { + log.debug(ignored.getLocalizedMessage()); + } + return false; + } + + private static boolean hasAnnotationOnMethod(Class[] annotations, String classInQuestion, + ClassLoader contextClassLoader ){ + try{ + Class c = Class.forName(classInQuestion, false, contextClassLoader); + for(Method method : c.getMethods()) { + for(Class annotation : annotations) { + if(method.isAnnotationPresent(annotation)) { + return true; + } + } + } + } catch (NoClassDefFoundError ignored) { + log.debug(ignored.getLocalizedMessage()); + } catch (ClassNotFoundException ignored) { + log.debug(ignored.getLocalizedMessage()); + } + return false; + } + + + /* + * Converts a class file from the text stored in a Jar file to a version + * that can be used in Class.forName(). + * + * @param strClassName + * the class name from a Jar file + * @return String the Java-style dotted version of the name + */ + private static String fixClassName(String strClassName) { + strClassName = strClassName.replace('\\', '.'); // $NON-NLS-1$ // $NON-NLS-2$ + strClassName = strClassName.replace('/', '.'); // $NON-NLS-1$ // $NON-NLS-2$ + // remove ".class" + strClassName = strClassName.substring(0, strClassName.length() - DOT_CLASS_LEN); + return strClassName; + } + + private static void findClassesInOnePath(String strPath, Set listClasses) throws IOException { + File file = new File(strPath); + if (file.isDirectory()) { + findClassesInPathsDir(strPath, file, listClasses); + } else if (file.exists()) { + ZipFile zipFile = null; + try { + zipFile = new ZipFile(file); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + String strEntry = entries.nextElement().toString(); + if (strEntry.endsWith(DOT_CLASS)) { + listClasses.add(fixClassName(strEntry)); + } + } + } catch (IOException e) { + log.warn("Can not open the jar " + strPath + " " + e.getLocalizedMessage(),e); + } + finally { + if(zipFile != null) { + try {zipFile.close();} catch (Exception e) {} + } + } + } + } + + private static void findClassesInPaths(List listPaths, Set listClasses) throws IOException { + for (String path : listPaths) { + findClassesInOnePath(path, listClasses); + } + } + + private static void findClassesInPathsDir(String strPathElement, File dir, Set listClasses) throws IOException { + String[] list = dir.list(); + for (int i = 0; i < list.length; i++) { + File file = new File(dir, list[i]); + if (file.isDirectory()) { + // Recursive call + findClassesInPathsDir(strPathElement, file, listClasses); + } else if (list[i].endsWith(DOT_CLASS) && file.exists() && (file.length() != 0)) { + final String path = file.getPath(); + listClasses.add(path.substring(strPathElement.length() + 1, + path.lastIndexOf('.')) // $NON-NLS-1$ + .replace(File.separator.charAt(0), '.')); // $NON-NLS-1$ + } + } + } +} diff --git a/src/jorphan/org/apache/jorphan/reflect/ClassTools.java b/src/jorphan/org/apache/jorphan/reflect/ClassTools.java new file mode 100644 index 00000000000..c764045d5d5 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/reflect/ClassTools.java @@ -0,0 +1,147 @@ +/* + * 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.jorphan.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.commons.lang3.ClassUtils; +import org.apache.jorphan.util.JMeterException; + +/** + * Utility methods for handling dynamic access to classes. + */ +public class ClassTools { + + + /** + * Call no-args constructor for a class. + * + * @param className name of the class to be constructed + * @return an instance of the class + * @throws JMeterException if class cannot be created + */ + public static Object construct(String className) throws JMeterException { + Object instance = null; + try { + instance = ClassUtils.getClass(className).newInstance(); + } catch (ClassNotFoundException e) { + throw new JMeterException(e); + } catch (InstantiationException e) { + throw new JMeterException(e); + } catch (IllegalAccessException e) { + throw new JMeterException(e); + } + return instance; + } + + /** + * Call a class constructor with an integer parameter + * @param className name of the class to be constructed + * @param parameter the value to be used in the constructor + * @return an instance of the class + * @throws JMeterException if class cannot be created + */ + public static Object construct(String className, int parameter) throws JMeterException + { + Object instance = null; + try { + Class clazz = ClassUtils.getClass(className); + Constructor constructor = clazz.getConstructor(Integer.TYPE); + instance = constructor.newInstance(Integer.valueOf(parameter)); + } catch (ClassNotFoundException e) { + throw new JMeterException(e); + } catch (InstantiationException e) { + throw new JMeterException(e); + } catch (IllegalAccessException e) { + throw new JMeterException(e); + } catch (SecurityException e) { + throw new JMeterException(e); + } catch (NoSuchMethodException e) { + throw new JMeterException(e); + } catch (IllegalArgumentException e) { + throw new JMeterException(e); + } catch (InvocationTargetException e) { + throw new JMeterException(e); + } + return instance; + } + + /** + * Call a class constructor with an String parameter + * @param className the name of the class to construct + * @param parameter to be used for the construction of the class instance + * @return an instance of the class + * @throws JMeterException if class cannot be created + */ + public static Object construct(String className, String parameter) + throws JMeterException { + Object instance = null; + try { + Class clazz = Class.forName(className); + Constructor constructor = clazz.getConstructor(String.class); + instance = constructor.newInstance(parameter); + } catch (ClassNotFoundException e) { + throw new JMeterException(e); + } catch (InstantiationException e) { + throw new JMeterException(e); + } catch (IllegalAccessException e) { + throw new JMeterException(e); + } catch (NoSuchMethodException e) { + throw new JMeterException(e); + } catch (IllegalArgumentException e) { + throw new JMeterException(e); + } catch (InvocationTargetException e) { + throw new JMeterException(e); + } + return instance; + } + + /** + * Invoke a public method on a class instance + * + * @param instance + * object on which the method should be called + * @param methodName + * name of the method to be called + * @throws SecurityException + * if a security violation occurred while looking for the method + * @throws IllegalArgumentException + * if the method parameters (none given) do not match the + * signature of the method + * @throws JMeterException + * if something went wrong in the invoked method + */ + public static void invoke(Object instance, String methodName) + throws SecurityException, IllegalArgumentException, JMeterException + { + Method m; + try { + m = ClassUtils.getPublicMethod(instance.getClass(), methodName, new Class [] {}); + m.invoke(instance, (Object [])null); + } catch (NoSuchMethodException e) { + throw new JMeterException(e); + } catch (IllegalAccessException e) { + throw new JMeterException(e); + } catch (InvocationTargetException e) { + throw new JMeterException(e); + } + } +} diff --git a/src/jorphan/org/apache/jorphan/reflect/Functor.java b/src/jorphan/org/apache/jorphan/reflect/Functor.java new file mode 100644 index 00000000000..77eb9e4a29f --- /dev/null +++ b/src/jorphan/org/apache/jorphan/reflect/Functor.java @@ -0,0 +1,513 @@ +/* + * 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.jorphan.reflect; + +import java.lang.reflect.Method; +import java.util.Arrays; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +/** + * Implements function call-backs. + *

+ * Functors may be defined for instance objects or classes. + *

+ * The method is created on first use, which allows the invokee (class or instance) + * to be omitted from the constructor. + *

+ * The class name takes precedence over the instance. + *

+ * If a functor is created with a particular instance, then that is used for all future calls; + * if an object is provided, it is ignored. + * This allows easy override of the table model behaviour. + *

+ * If an argument list is provided in the constructor, then that is ignored in subsequent invoke() calls. + *

+ * Usage: + * + *

+ * f = new Functor("methodName");
+ * o = f.invoke(object); // - OR -
+ * o = f.invoke(object, params);
+ * 
+ * + *
+ * f2 = new Functor(object, "methodName");
+ * o = f2.invoke(); // - OR -
+ * o = f2.invoke(params);
+ * 
+ * + *
+ * f3 = new Functor(class, "methodName");
+ * o = f3.invoke(object); // - will be ignored
+ * o = f3.invoke(); // - OR -
+ * o = f3.invoke(params);
+ * o = f3.invoke(object, params); // - object will be ignored
+ * 
+ */ +public class Functor { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * If non-null, then any object provided to invoke() is ignored. + */ + private final Object invokee; + + /* + * Class to be used to create the Method. + * Will be non-null if either Class or Object was provided during construction. + * + * Can be used instead of invokee, e.g. when using interfaces. + */ + private final Class clazz; + + // Methondname must always be provided. + private final String methodName; + + /* + * If non-null, then any argument list passed to invoke() will be ignored. + */ + private Object[] args; + + /* + * Argument types used to create the method. + * May be provided explicitly, or derived from the constructor argument list. + */ + private final Class[] types; + + /* + * This depends on the class or invokee and either args or types; + * it is set once by doCreateMethod(), which must be the only method to access it. + */ + private Method methodToInvoke; + + Functor(){ + throw new IllegalArgumentException("Must provide at least one argument"); + } + + /** + * Create a functor with the invokee and a method name. + * + * The invokee will be used in all future invoke calls. + * + * @param _invokee object on which to invoke the method + * @param _methodName method name + */ + public Functor(Object _invokee, String _methodName) { + this(null, _invokee, _methodName, null, null); + } + + /** + * Create a functor from class and method name. + * This is useful for methods defined in interfaces. + * + * The actual invokee must be provided in all invoke() calls, + * and must be an instance of the class. + * + * @param _clazz class to be used + * @param _methodName method name + */ + public Functor(Class _clazz, String _methodName) { + this(_clazz, null, _methodName, null, null); + } + + /** + * Create a functor with the invokee, method name, and argument class types. + * + * The invokee will be ignored in any invoke() calls. + * + * @param _invokee object on which to invoke the method + * @param _methodName method name + * @param _types types of arguments to be used + */ + public Functor(Object _invokee, String _methodName, Class[] _types) { + this(null, _invokee, _methodName, null, _types); + } + + /** + * Create a functor with the class, method name, and argument class types. + * + * Subsequent invoke() calls must provide the appropriate ivokee object. + * + * @param _clazz the class in which to find the method + * @param _methodName method name + * @param _types types of arguments to be used + */ + public Functor(Class _clazz, String _methodName, Class[] _types) { + this(_clazz, null, _methodName, null, _types); + } + + /** + * Create a functor with just the method name. + * + * The invokee and any parameters must be provided in all invoke() calls. + * + * @param _methodName method name + */ + public Functor(String _methodName) { + this(null, null, _methodName, null, null); + } + + /** + * Create a functor with the method name and argument class types. + * + * The invokee must be provided in all invoke() calls + * + * @param _methodName method name + * @param _types parameter types + */ + public Functor(String _methodName, Class[] _types) { + this(null, null, _methodName, null, _types); + } + + /** + * Create a functor with an invokee, method name, and argument values. + * + * The invokee will be ignored in any invoke() calls. + * + * @param _invokee object on which to invoke the method + * @param _methodName method name + * @param _args arguments to be passed to the method + */ + public Functor(Object _invokee, String _methodName, Object[] _args) { + this(null, _invokee, _methodName, _args, null); + } + + /** + * Create a functor from method name and arguments. + * + * The class will be determined from the first invoke call. + * All invoke calls must include a target object; + * which must be of the same type as the initial invokee. + * + * @param _methodName method name + * @param _args arguments to be used + */ + public Functor(String _methodName, Object[] _args) { + this(null, null, _methodName, _args, null); + } + + /** + * Create a functor from various different combinations of parameters. + * + * @param _clazz class containing the method + * @param _invokee invokee to use for the method call + * @param _methodName the method name (required) + * @param _args arguments to be used + * @param _types types of arguments to be used + * + * @throws IllegalArgumentException if: + * - methodName is null + * - both class and invokee are specified + * - both arguments and types are specified + */ + private Functor(Class _clazz, Object _invokee, String _methodName, Object[] _args, Class[] _types) { + if (_methodName == null){ + throw new IllegalArgumentException("Methodname must not be null"); + } + if (_clazz != null && _invokee != null){ + throw new IllegalArgumentException("Cannot provide both Class and Object"); + } + if (_args != null && _types != null){ + throw new IllegalArgumentException("Cannot provide both arguments and argument types"); + } + // If class not provided, default to invokee class, else null + this.clazz = _clazz != null ? _clazz : (_invokee != null ? _invokee.getClass() : null); + this.invokee = _invokee; + this.methodName = _methodName; + this.args = _args; + // If types not provided, default to argument types, else null + this.types = _types != null ? _types : (_args != null ? _getTypes(_args) : null); + } + + ////////////////////////////////////////// + + /* + * Low level invocation routine. + * + * Should only be called after any defaults have been applied. + * + */ + private Object doInvoke(Class _class, Object _invokee, Object[] _args) { + Class[] argTypes = getTypes(_args); + try { + Method method = doCreateMethod(_class , argTypes); + if (method == null){ + final String message = "Can't find method " + +_class.getName()+"#"+methodName+typesToString(argTypes); + log.error(message, new Throwable()); + throw new JMeterError(message); + } + return method.invoke(_invokee, _args); + } catch (Exception e) { + final String message = "Trouble functing: " + +_class.getName() + +"."+methodName+"(...) : " + +" invokee: "+_invokee + +" "+e.getMessage(); + log.warn(message, e); + throw new JMeterError(message,e); + } + } + + /** + * Invoke a Functor, which must have been created with either a class name or object. + * + * @return the object if any + */ + public Object invoke() { + if (invokee == null) { + throw new IllegalStateException("Cannot call invoke() - invokee not known"); + } + // If invokee was provided, then clazz has been set up + return doInvoke(clazz, invokee, getArgs()); + } + + /** + * Invoke the method on a given object. + * + * @param p_invokee - provides the object to call; ignored if the class or object were provided to the constructor + * @return the value + */ + public Object invoke(Object p_invokee) { + return invoke(p_invokee, getArgs()); + } + + /** + * Invoke the method with the provided parameters. + * + * The invokee must have been provided in the constructor. + * + * @param p_args parameters for the method + * @return the value + */ + public Object invoke(Object[] p_args) { + if (invokee == null){ + throw new IllegalStateException("Invokee was not provided in constructor"); + } + // If invokee was provided, then clazz has been set up + return doInvoke(clazz, invokee, args != null? args : p_args); + } + + /** + * Invoke the method on the invokee with the provided parameters. + * + * The invokee must agree with the class (if any) provided at construction time. + * + * If the invokee was provided at construction time, then this invokee will be ignored. + * If actual arguments were provided at construction time, then arguments will be ignored. + * @param p_invokee invokee to use, if no class or invokee was provided at construction time + * @param p_args arguments to use + * @return result of invocation + * + */ + public Object invoke(Object p_invokee, Object[] p_args) { + return doInvoke(clazz != null ? clazz : p_invokee.getClass(), // Use constructor class if present + invokee != null ? invokee : p_invokee, // use invokee if provided + args != null? args : p_args);// use arguments if provided + } + + /* + * Low-level (recursive) routine to define the method - if not already defined. + * Synchronized to protect access to methodToInvoke. + */ + private synchronized Method doCreateMethod(Class p_class, Class[] p_types) { + if (log.isDebugEnabled()){ + log.debug("doCreateMethod() using "+this.toString() + +"class=" + + p_class.getName() + + " types: " + Arrays.asList(p_types)); + } + if (methodToInvoke == null) { + try { + methodToInvoke = p_class.getMethod(methodName, p_types); + } catch (Exception e) { + for (int i = 0; i < p_types.length; i++) { + Class primitive = getPrimitive(p_types[i]); + if (primitive != null) { + methodToInvoke = doCreateMethod(p_class, getNewArray(i, primitive, p_types)); + if (methodToInvoke != null) { + return methodToInvoke; + } + } + Class[] interfaces = p_types[i].getInterfaces(); + for (int j = 0; j < interfaces.length; j++) { + methodToInvoke = doCreateMethod(p_class,getNewArray(i, interfaces[j], p_types)); + if (methodToInvoke != null) { + return methodToInvoke; + } + } + Class parent = p_types[i].getSuperclass(); + if (parent != null) { + methodToInvoke = doCreateMethod(p_class,getNewArray(i, parent, p_types)); + if (methodToInvoke != null) { + return methodToInvoke; + } + } + } + } + } + return methodToInvoke; + } + + /** + * Check if a read Functor method is valid. + * + * @param _invokee + * instance on which the method should be tested + * + * @deprecated ** for use by Unit test code only ** + * + * @return true if method exists + */ + @Deprecated + public boolean checkMethod(Object _invokee){ + Method m = null; + try { + m = doCreateMethod(_invokee.getClass(), getTypes(args)); + } catch (Exception e){ + // ignored + } + return null != m; + } + + /** + * Check if a write Functor method is valid. + * + * @param _invokee + * instance on which the method should be tested + * @param c + * type of parameter + * + * @deprecated ** for use by Unit test code only ** + * + * @return true if method exists + */ + @Deprecated + public boolean checkMethod(Object _invokee, Class c){ + Method m = null; + try { + m = doCreateMethod(_invokee.getClass(), new Class[]{c}); + } catch (Exception e){ + // ignored + } + return null != m; + } + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(100); + if (clazz != null){ + sb.append(clazz.getName()); + } + if (invokee != null){ + sb.append("@"); + sb.append(System.identityHashCode(invokee)); + } + sb.append("."); + sb.append(methodName); + typesToString(sb,types); + return sb.toString(); + } + + private void typesToString(StringBuilder sb,Class[] _types) { + sb.append("("); + if (_types != null){ + for(int i=0; i < _types.length; i++){ + if (i>0) { + sb.append(","); + } + sb.append(_types[i].getName()); + } + } + sb.append(")"); + } + + private String typesToString(Class[] argTypes) { + StringBuilder sb = new StringBuilder(); + typesToString(sb,argTypes); + return sb.toString(); + } + + private Class getPrimitive(Class t) { + if (t==null) { + return null; + } + if (t.equals(Integer.class)) { + return int.class; + } else if (t.equals(Long.class)) { + return long.class; + } else if (t.equals(Double.class)) { + return double.class; + } else if (t.equals(Float.class)) { + return float.class; + } else if (t.equals(Byte.class)) { + return byte.class; + } else if (t.equals(Boolean.class)) { + return boolean.class; + } else if (t.equals(Short.class)) { + return short.class; + } else if (t.equals(Character.class)) { + return char.class; + } + return null; + } + + private Class[] getNewArray(int i, Class replacement, Class[] orig) { + Class[] newArray = new Class[orig.length]; + for (int j = 0; j < newArray.length; j++) { + if (j == i) { + newArray[j] = replacement; + } else { + newArray[j] = orig[j]; + } + } + return newArray; + } + + private Class[] getTypes(Object[] _args) { + if (types == null) + { + return _getTypes(_args); + } + return types; + } + + private static Class[] _getTypes(Object[] _args) { + Class[] _types; + if (_args != null) { + _types = new Class[_args.length]; + for (int i = 0; i < _args.length; i++) { + _types[i] = _args[i].getClass(); + } + } else { + _types = new Class[0]; + } + return _types; + } + + private Object[] getArgs() { + if (args == null) { + args = new Object[0]; + } + return args; + } +} diff --git a/src/jorphan/org/apache/jorphan/test/UnitTestManager.java b/src/jorphan/org/apache/jorphan/test/UnitTestManager.java new file mode 100644 index 00000000000..5d7fc911578 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/test/UnitTestManager.java @@ -0,0 +1,41 @@ +/* + * 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.jorphan.test; + +/** + * Implement this interface to work with the AllTests class. This interface + * allows AllTests to pass a configuration file to your application before + * running the junit unit tests. + *

+ * N.B. This interface must be in the main src/ tree (not test/) because it is + * implemented by JMeterUtils + *

+ * see JUnit class: org.apache.jorphan.test.AllTests + */ +public interface UnitTestManager { + /** + * Your implementation will be handed the filename that was provided to + * AllTests as a configuration file. It can hold whatever properties you + * need to configure your system prior to the unit tests running. + * + * @param filename + * path to the configuration file + */ + void initializeProperties(String filename); +} diff --git a/src/jorphan/org/apache/jorphan/util/Converter.java b/src/jorphan/org/apache/jorphan/util/Converter.java new file mode 100644 index 00000000000..98ec5f03b35 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/util/Converter.java @@ -0,0 +1,585 @@ +/* + * 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.jorphan.util; + +import java.io.File; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.StringTokenizer; + +/** + * Converter utilities for TestBeans + */ +public class Converter { + + /** + * Convert the given value object to an object of the given type + * + * @param value + * object to convert + * @param toType + * type to convert object to + * @return converted object or original value if no conversion could be + * applied + */ + public static Object convert(Object value, Class toType) { + if (value == null) { + value = ""; // TODO should we allow null for non-primitive types? + } else if (toType.isAssignableFrom(value.getClass())) { + return value; + } else if (toType.equals(float.class) || toType.equals(Float.class)) { + return Float.valueOf(getFloat(value)); + } else if (toType.equals(double.class) || toType.equals(Double.class)) { + return Double.valueOf(getDouble(value)); + } else if (toType.equals(String.class)) { + return getString(value); + } else if (toType.equals(int.class) || toType.equals(Integer.class)) { + return Integer.valueOf(getInt(value)); + } else if (toType.equals(char.class) || toType.equals(Character.class)) { + return Character.valueOf(getChar(value)); + } else if (toType.equals(long.class) || toType.equals(Long.class)) { + return Long.valueOf(getLong(value)); + } else if (toType.equals(boolean.class) || toType.equals(Boolean.class)) { + return Boolean.valueOf(getBoolean(value)); + } else if (toType.equals(java.util.Date.class)) { + return getDate(value); + } else if (toType.equals(Calendar.class)) { + return getCalendar(value); + } else if (toType.equals(File.class)) { + return getFile(value); + } else if (toType.equals(Class.class)) { + try { + return Class.forName(value.toString()); + } catch (Exception e) { + // don't do anything + } + } + return value; + } + + /** + * Converts the given object to a calendar object. Defaults to the + * defaultValue if the given object can't be converted. + * + * @param date + * object that should be converted to a {@link Calendar} + * @param defaultValue + * default value that will be returned if date can + * not be converted + * @return {@link Calendar} representing the given date or + * defaultValue if conversion failed + */ + public static Calendar getCalendar(Object date, Calendar defaultValue) { + Calendar cal = new GregorianCalendar(); + if (date instanceof java.util.Date) { + cal.setTime((java.util.Date) date); + return cal; + } else if (date != null) { + DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT); + java.util.Date d = null; + try { + d = formatter.parse(date.toString()); + } catch (ParseException e) { + formatter = DateFormat.getDateInstance(DateFormat.MEDIUM); + try { + d = formatter.parse((String) date); + } catch (ParseException e1) { + formatter = DateFormat.getDateInstance(DateFormat.LONG); + try { + d = formatter.parse((String) date); + } catch (ParseException e2) { + formatter = DateFormat.getDateInstance(DateFormat.FULL); + try { + d = formatter.parse((String) date); + } catch (ParseException e3) { + return defaultValue; + } + } + } + } + cal.setTime(d); + } else { + cal = defaultValue; + } + return cal; + } + + /** + * Converts the given object to a calendar object. Defaults to a calendar + * using the current time if the given object can't be converted. + * + * @param o + * object that should be converted to a {@link Calendar} + * @return {@link Calendar} representing the given o or a new + * {@link GregorianCalendar} using the current time if conversion + * failed + */ + public static Calendar getCalendar(Object o) { + return getCalendar(o, new GregorianCalendar()); + } + + /** + * Converts the given object to a {@link Date} object. Defaults to the + * current time if the given object can't be converted. + * + * @param date + * object that should be converted to a {@link Date} + * @return {@link Date} representing the given date or + * the current time if conversion failed + */ + public static Date getDate(Object date) { + return getDate(date, Calendar.getInstance().getTime()); + } + + /** + * Converts the given object to a {@link Date} object. Defaults to the + * defaultValue if the given object can't be converted. + * + * @param date + * object that should be converted to a {@link Date} + * @param defaultValue + * default value that will be returned if date can + * not be converted + * @return {@link Date} representing the given date or + * defaultValue if conversion failed + */ + public static Date getDate(Object date, Date defaultValue) { + Date val = null; + if (date instanceof java.util.Date) { + return (Date) date; + } else if (date != null) { + DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT); + // java.util.Date d = null; + try { + val = formatter.parse(date.toString()); + } catch (ParseException e) { + formatter = DateFormat.getDateInstance(DateFormat.MEDIUM); + try { + val = formatter.parse((String) date); + } catch (ParseException e1) { + formatter = DateFormat.getDateInstance(DateFormat.LONG); + try { + val = formatter.parse((String) date); + } catch (ParseException e2) { + formatter = DateFormat.getDateInstance(DateFormat.FULL); + try { + val = formatter.parse((String) date); + } catch (ParseException e3) { + return defaultValue; + } + } + } + } + } else { + return defaultValue; + } + return val; + } + + /** + * Convert object to float, or defaultValue if conversion + * failed + * + * @param o + * object to convert + * @param defaultValue + * default value to use, when conversion failed + * @return converted float or defaultValue if conversion + * failed + */ + public static float getFloat(Object o, float defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Number) { + return ((Number) o).floatValue(); + } + return Float.parseFloat(o.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * Convert object to float, or 0 if conversion + * failed + * + * @param o + * object to convert + * @return converted float or 0 if conversion + * failed + */ + public static float getFloat(Object o) { + return getFloat(o, 0); + } + + /** + * Convert object to double, or defaultValue if conversion + * failed + * + * @param o + * object to convert + * @param defaultValue + * default value to use, when conversion failed + * @return converted double or defaultValue if conversion + * failed + */ + public static double getDouble(Object o, double defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Number) { + return ((Number) o).doubleValue(); + } + return Double.parseDouble(o.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * Convert object to double, or 0 if conversion + * failed + * + * @param o + * object to convert + * @return converted double or 0 if conversion + * failed + */ + public static double getDouble(Object o) { + return getDouble(o, 0); + } + + /** + * Convert object to boolean, or false if conversion + * failed + * + * @param o + * object to convert + * @return converted boolean or false if conversion + * failed + */ + public static boolean getBoolean(Object o) { + return getBoolean(o, false); + } + + /** + * Convert object to boolean, or defaultValue if conversion + * failed + * + * @param o + * object to convert + * @param defaultValue + * default value to use, when conversion failed + * @return converted boolean or defaultValue if conversion + * failed + */ + public static boolean getBoolean(Object o, boolean defaultValue) { + if (o == null) { + return defaultValue; + } else if (o instanceof Boolean) { + return ((Boolean) o).booleanValue(); + } + return Boolean.parseBoolean(o.toString()); + } + + /** + * Convert object to integer, return defaultValue if object is not + * convertible or is null. + * + * @param o + * object to convert + * @param defaultValue + * default value to be used when no conversion can be done + * @return converted int or default value if conversion failed + */ + public static int getInt(Object o, int defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Number) { + return ((Number) o).intValue(); + } + return Integer.parseInt(o.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * Convert object to char, or ' ' if no conversion can + * be applied + * + * @param o + * object to convert + * @return converted char or ' ' if conversion failed + */ + public static char getChar(Object o) { + return getChar(o, ' '); + } + + /** + * Convert object to char, or defaultValue if no conversion can + * be applied + * + * @param o + * object to convert + * @param defaultValue + * default value to use, when conversion failed + * @return converted char or defaultValue if conversion failed + */ + public static char getChar(Object o, char defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Character) { + return ((Character) o).charValue(); + } else if (o instanceof Byte) { + return (char) ((Byte) o).byteValue(); + } else if (o instanceof Integer) { + return (char) ((Integer) o).intValue(); + } else { + String s = o.toString(); + if (s.length() > 0) { + return o.toString().charAt(0); + } + return defaultValue; + } + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Converts object to an integer, defaults to 0 if object is + * not convertible or is null. + * + * @param o + * object to convert + * @return converted int, or 0 if conversion failed + */ + public static int getInt(Object o) { + return getInt(o, 0); + } + + /** + * Converts object to a long, return defaultValue if object is + * not convertible or is null. + * + * @param o + * object to convert + * @param defaultValue + * default value to use, when conversion failed + * @return converted long or defaultValue when conversion + * failed + */ + public static long getLong(Object o, long defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Number) { + return ((Number) o).longValue(); + } + return Long.parseLong(o.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * Converts object to a long, defaults to 0 if object is not + * convertible or is null + * + * @param o + * object to convert + * @return converted long or 0 if conversion failed + */ + public static long getLong(Object o) { + return getLong(o, 0); + } + + /** + * Format a date using a given pattern + * + * @param date + * date to format + * @param pattern + * pattern to use for formatting + * @return formatted date, or empty string if date was null + * @throws IllegalArgumentException + * when pattern is invalid + */ + public static String formatDate(Date date, String pattern) { + if (date == null) { + return ""; + } + SimpleDateFormat format = new SimpleDateFormat(pattern); + return format.format(date); + } + + /** + * Format a date using a given pattern + * + * @param date + * date to format + * @param pattern + * pattern to use for formatting + * @return formatted date, or empty string if date was null + * @throws IllegalArgumentException + * when pattern is invalid + */ + public static String formatDate(java.sql.Date date, String pattern) { + if (date == null) { + return ""; + } + SimpleDateFormat format = new SimpleDateFormat(pattern); + return format.format(date); + } + + /** + * Format a date using a given pattern + * + * @param date + * date to format + * @param pattern + * pattern to use for formatting + * @return formatted date, or empty string if date was null + * @throws IllegalArgumentException + * when pattern is invalid + */ + public static String formatDate(String date, String pattern) { + return formatDate(getCalendar(date, null), pattern); + } + + /** + * Format a date using a given pattern + * + * @param date + * date to format + * @param pattern + * pattern to use for formatting + * @return formatted date, or empty string if date was null + * @throws IllegalArgumentException + * when pattern is invalid + */ + public static String formatDate(Calendar date, String pattern) { + return formatCalendar(date, pattern); + } + + /** + * Format a calendar using a given pattern + * + * @param date + * calendar to format + * @param pattern + * pattern to use for formatting + * @return formatted date, or empty string if date was null + * @throws IllegalArgumentException + * when pattern is invalid + */ + public static String formatCalendar(Calendar date, String pattern) { + if (date == null) { + return ""; + } + SimpleDateFormat format = new SimpleDateFormat(pattern); + return format.format(date.getTime()); + } + + /** + * Converts object to a String, return defaultValue if object + * is null. + * + * @param o + * object to convert + * @param defaultValue + * default value to use when conversion failed + * @return converted String or defaultValue when conversion + * failed + */ + public static String getString(Object o, String defaultValue) { + if (o == null) { + return defaultValue; + } + return o.toString(); + } + + /** + * Replace newlines "\n" with insertion + * + * @param v + * String in which the newlines should be replaced + * @param insertion + * new string which should be used instead of "\n" + * @return new string with newlines replaced by insertion + */ + public static String insertLineBreaks(String v, String insertion) { + if (v == null) { + return ""; + } + StringBuilder replacement = new StringBuilder(); + StringTokenizer tokens = new StringTokenizer(v, "\n", true); + while (tokens.hasMoreTokens()) { + String token = tokens.nextToken(); + if (token.compareTo("\n") == 0) { + replacement.append(insertion); + } else { + replacement.append(token); + } + } + return replacement.toString(); + } + + /** + * Converts object to a String, defaults to empty string if object is null. + * + * @param o + * object to convert + * @return converted String or empty string when conversion failed + */ + public static String getString(Object o) { + return getString(o, ""); + } + + /** + * Converts an object to a {@link File} + * + * @param o + * object to convert (must be a {@link String} or a {@link File}) + * @return converted file + * @throws IllegalArgumentException + * when object can not be converted + */ + public static File getFile(Object o){ + if (o instanceof File) { + return (File) o; + } + if (o instanceof String) { + return new File((String) o); + } + throw new IllegalArgumentException("Expected String or file, actual "+o.getClass().getName()); + } +} diff --git a/src/jorphan/org/apache/jorphan/util/HeapDumper.java b/src/jorphan/org/apache/jorphan/util/HeapDumper.java new file mode 100644 index 00000000000..01a727b2755 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/util/HeapDumper.java @@ -0,0 +1,204 @@ +/* + * 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.jorphan.util; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.management.InstanceNotFoundException; +import javax.management.MBeanException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.RuntimeMBeanException; + +/** + * Class allowing access to Sun's heapDump method (Java 1.6+). + * Uses Reflection so that the code compiles on Java 1.5. + * The code will only work on Sun Java 1.6+. + */ +public class HeapDumper { + + // SingletonHolder idiom for lazy initialisation + private static class DumperHolder { + private static final HeapDumper DUMPER = new HeapDumper(); + } + + private static HeapDumper getInstance(){ + return DumperHolder.DUMPER; + } + + // This is the name of the HotSpot Diagnostic platform MBean (Sun Java 1.6) + // See: http://download.oracle.com/javase/6/docs/jre/api/management/extension/com/sun/management/HotSpotDiagnosticMXBean.html + private static final String HOTSPOT_BEAN_NAME = + "com.sun.management:type=HotSpotDiagnostic"; + + // These are needed for invoking the method + private final MBeanServer server; + private final ObjectName hotspotDiagnosticBean; + + // If we could not find the method, store the exception here + private final Exception exception; + + // Only invoked by IODH class + private HeapDumper() { + server = ManagementFactory.getPlatformMBeanServer(); // get the platform beans + ObjectName on = null; + Exception ex = null; + try { + on = new ObjectName(HOTSPOT_BEAN_NAME); // should never fail + server.getObjectInstance(on); // See if we can actually find the object + } catch (MalformedObjectNameException e) { // Should never happen + throw new AssertionError("Could not establish the HotSpotDiagnostic Bean Name: "+e); + } catch (InstanceNotFoundException e) { + ex = e; + on = null; // Prevent useless dump attempts + } + exception = ex; + hotspotDiagnosticBean = on; + } + + /** + * Initialise the dumper, and report if there is a problem. + * This is optional, as the dump methods will initialise if necessary. + * + * @throws Exception if there is a problem finding the heapDump MXBean + */ + public static void init() throws Exception { + Exception e =getInstance().exception; + if (e != null) { + throw e; + } + } + + /** + * Dumps the heap to the outputFile file in the same format as the hprof heap dump. + *

+ * Calls the dumpHeap() method of the HotSpotDiagnostic MXBean, if available. + *

+ * See + * + * HotSpotDiagnosticMXBean + * + * @param fileName name of the heap dump file. Must be creatable, i.e. must not exist. + * @param live if true, dump only the live objects + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static void dumpHeap(String fileName, boolean live) throws Exception{ + getInstance().dumpHeap0(fileName, live); + } + + /** + * Dumps live objects from the heap to the outputFile file in the same format as the hprof heap dump. + *

+ * @see #dumpHeap(String, boolean) + * @param fileName name of the heap dump file. Must be creatable, i.e. must not exist. + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static void dumpHeap(String fileName) throws Exception{ + dumpHeap(fileName, true); + } + + /** + * Dumps live objects from the heap to the outputFile file in the same format as the hprof heap dump. + *

+ * Creates the dump using the file name: dump_yyyyMMdd_hhmmss_SSS.hprof + * The dump is created in the current directory. + *

+ * @see #dumpHeap(boolean) + * @return the name of the dump file that was created + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static String dumpHeap() throws Exception{ + return dumpHeap(true); + } + + /** + * Dumps objects from the heap to the outputFile file in the same format as the hprof heap dump. + *

+ * Creates the dump using the file name: dump_yyyyMMdd_hhmmss_SSS.hprof + * The dump is created in the current directory. + *

+ * @see #dumpHeap(String, boolean) + * @param live true id only live objects are to be dumped. + * + * @return the name of the dump file that was created + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static String dumpHeap(boolean live) throws Exception { + return dumpHeap(new File("."), live); + } + + /** + * Dumps objects from the heap to the outputFile file in the same format as the hprof heap dump. + * The dump is created in the specified directory. + *

+ * Creates the dump using the file name: dump_yyyyMMdd_hhmmss_SSS.hprof + *

+ * @see #dumpHeap(String, boolean) + * @param basedir File object for the target base directory. + * @param live true id only live objects are to be dumped. + * + * @return the name of the dump file that was created + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static String dumpHeap(File basedir, boolean live) throws Exception { + SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyyMMdd_hhmmss_SSS"); + String stamp = timestampFormat.format(new Date()); + File temp = new File(basedir,"dump_"+stamp+".hprof"); + final String path = temp.getPath(); + dumpHeap(path, live); + return path; + } + + /** + * Perform the dump using the dumpHeap method. + * + * @param fileName the file to use + * @param live true to dump only live objects + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + private void dumpHeap0(String fileName, boolean live) throws Exception { + try { + if (exception == null) { + server.invoke(hotspotDiagnosticBean, + "dumpHeap", + new Object[]{fileName, Boolean.valueOf(live)}, + new String[]{"java.lang.String", "boolean"}); + } else { + throw exception; + } + } catch (RuntimeMBeanException e) { + Throwable f = e.getCause(); + if (f instanceof Exception){ + throw (Exception )f; + } + throw e; + } catch (MBeanException e) { + Throwable f = e.getCause(); + if (f instanceof Exception){ + throw (Exception )f; + } + throw e; + } + } +} + diff --git a/src/jorphan/org/apache/jorphan/util/JMeterError.java b/src/jorphan/org/apache/jorphan/util/JMeterError.java new file mode 100644 index 00000000000..56ea1c8314a --- /dev/null +++ b/src/jorphan/org/apache/jorphan/util/JMeterError.java @@ -0,0 +1,47 @@ +/* + * 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.jorphan.util; + +/** + * The rationale for this class was originally to support chained Errors in JDK 1.3 + * However, the class is now used in its own right. + * + * @version $Revision$ + */ +public class JMeterError extends Error { + + private static final long serialVersionUID = 240L; + + public JMeterError() { + super(); + } + + public JMeterError(String s) { + super(s); + } + + public JMeterError(Throwable cause) { + super(cause); + } + + public JMeterError(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/jorphan/org/apache/jorphan/util/JMeterException.java b/src/jorphan/org/apache/jorphan/util/JMeterException.java new file mode 100644 index 00000000000..ece1895f354 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/util/JMeterException.java @@ -0,0 +1,46 @@ +/* + * 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.jorphan.util; + +/** + * The rationale for this class was originally to support chained Exceptions in JDK 1.3 + * However, the class is now used in its own right. + * + * @version $Revision$ + */ +public class JMeterException extends Exception { + + private static final long serialVersionUID = 240L; + + public JMeterException() { + super(); + } + + public JMeterException(String s) { + super(s); + } + + public JMeterException(Throwable cause) { + super(cause); + } + + public JMeterException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/jorphan/org/apache/jorphan/util/JMeterStopTestException.java b/src/jorphan/org/apache/jorphan/util/JMeterStopTestException.java new file mode 100644 index 00000000000..f252ffad733 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/util/JMeterStopTestException.java @@ -0,0 +1,37 @@ +/* + * 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.jorphan.util; + +/** + * This Exception is for use by functions etc to signal a Stop Test condition + * where there is no access to the normal stop method + * + * @version $Revision$ + */ +public class JMeterStopTestException extends RuntimeException { + private static final long serialVersionUID = 240L; + + public JMeterStopTestException() { + super(); + } + + public JMeterStopTestException(String s) { + super(s); + } +} diff --git a/src/jorphan/org/apache/jorphan/util/JMeterStopTestNowException.java b/src/jorphan/org/apache/jorphan/util/JMeterStopTestNowException.java new file mode 100644 index 00000000000..35107a59658 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/util/JMeterStopTestNowException.java @@ -0,0 +1,37 @@ +/* + * 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.jorphan.util; + +/** + * This Exception is for use by functions etc to signal a Stop Test Now condition + * where there is no access to the normal stop method + * + * @version $Revision$ + */ +public class JMeterStopTestNowException extends RuntimeException { + private static final long serialVersionUID = 240L; + + public JMeterStopTestNowException() { + super(); + } + + public JMeterStopTestNowException(String s) { + super(s); + } +} diff --git a/src/jorphan/org/apache/jorphan/util/JMeterStopThreadException.java b/src/jorphan/org/apache/jorphan/util/JMeterStopThreadException.java new file mode 100644 index 00000000000..a4ab2aa229d --- /dev/null +++ b/src/jorphan/org/apache/jorphan/util/JMeterStopThreadException.java @@ -0,0 +1,45 @@ +/* + * 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.jorphan.util; + +/** + * This Exception is for use by functions etc to signal a Stop Thread condition + * where there is no access to the normal stop method + * + * @version $Revision$ + */ +public class JMeterStopThreadException extends RuntimeException { + private static final long serialVersionUID = 240L; + + public JMeterStopThreadException() { + super(); + } + + public JMeterStopThreadException(String s) { + super(s); + } + + public JMeterStopThreadException(String message, Throwable cause) { + super(message, cause); + } + + public JMeterStopThreadException(Throwable cause) { + super(cause); + } +} diff --git a/src/jorphan/org/apache/jorphan/util/JOrphanUtils.java b/src/jorphan/org/apache/jorphan/util/JOrphanUtils.java new file mode 100644 index 00000000000..11c5630266e --- /dev/null +++ b/src/jorphan/org/apache/jorphan/util/JOrphanUtils.java @@ -0,0 +1,570 @@ +/* + * 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.jorphan.util; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.commons.lang3.StringUtils; + +/** + * This class contains frequently-used static utility methods. + * + */ + +// @see TestJorphanUtils for unit tests + +public final class JOrphanUtils { + + private static final int DEFAULT_CHUNK_SIZE = 4096; + + /** + * Private constructor to prevent instantiation. + */ + private JOrphanUtils() { + } + + /** + * This is _almost_ equivalent to the String.split method in JDK 1.4. It is + * here to enable us to support earlier JDKs. + * + * Note that unlike JDK1.4 split(), it optionally ignores leading split Characters, + * and the splitChar parameter is not a Regular expression + * + *

+ * This piece of code used to be part of JMeterUtils, but was moved here + * because some JOrphan classes use it too. + * + * @param splittee + * String to be split + * @param splitChar + * Character(s) to split the string on, these are treated as a single unit + * @param truncate + * Should adjacent and leading/trailing splitChars be removed? + * + * @return Array of all the tokens; empty if the input string is null or the splitChar is null + * + * @see #split(String, String, String) + * + */ + public static String[] split(String splittee, String splitChar,boolean truncate) { + if (splittee == null || splitChar == null) { + return new String[0]; + } + final String EMPTY_ELEMENT = ""; + int spot; + final int splitLength = splitChar.length(); + final String adjacentSplit = splitChar + splitChar; + final int adjacentSplitLength = adjacentSplit.length(); + if(truncate) { + while ((spot = splittee.indexOf(adjacentSplit)) != -1) { + splittee = splittee.substring(0, spot + splitLength) + + splittee.substring(spot + adjacentSplitLength, splittee.length()); + } + if(splittee.startsWith(splitChar)) { + splittee = splittee.substring(splitLength); + } + if(splittee.endsWith(splitChar)) { // Remove trailing splitter + splittee = splittee.substring(0,splittee.length()-splitLength); + } + } + List returns = new ArrayList(); + final int length = splittee.length(); // This is the new length + int start = 0; + spot = 0; + while (start < length && (spot = splittee.indexOf(splitChar, start)) > -1) { + if (spot > 0) { + returns.add(splittee.substring(start, spot)); + } + else + { + returns.add(EMPTY_ELEMENT); + } + start = spot + splitLength; + } + if (start < length) { + returns.add(splittee.substring(start)); + } else if (spot == length - splitLength){// Found splitChar at end of line + returns.add(EMPTY_ELEMENT); + } + return returns.toArray(new String[returns.size()]); + } + + public static String[] split(String splittee,String splitChar) + { + return split(splittee,splitChar,true); + } + + /** + * Takes a String and a tokenizer character string, and returns a new array of + * strings of the string split by the tokenizer character(s). + * + * Trailing delimiters are significant (unless the default = null) + * + * @param splittee + * String to be split. + * @param delims + * Delimiter character(s) to split the string on + * @param def + * Default value to place between two split chars that have + * nothing between them. If null, then ignore omitted elements. + * + * @return Array of all the tokens. + * + * @throws NullPointerException if splittee or delims are null + * + * @see #split(String, String, boolean) + * @see #split(String, String) + * + * This is a rewritten version of JMeterUtils.split() + */ + public static String[] split(String splittee, String delims, String def) { + StringTokenizer tokens = new StringTokenizer(splittee,delims,def!=null); + boolean lastWasDelim=false; + List strList=new ArrayList(); + while (tokens.hasMoreTokens()) { + String tok=tokens.nextToken(); + if ( tok.length()==1 // we have a single character; could be a token + && delims.indexOf(tok)!=-1) // it is a token + { + if (lastWasDelim) {// we saw a delimiter last time + strList.add(def);// so add the default + } + lastWasDelim=true; + } else { + lastWasDelim=false; + strList.add(tok); + } + } + if (lastWasDelim) { + strList.add(def); + } + return strList.toArray(new String[strList.size()]); + } + + + private static final String SPACES = " "; + + private static final int SPACES_LEN = SPACES.length(); + + /** + * Right aligns some text in a StringBuilder N.B. modifies the input buffer + * + * @param in + * StringBuilder containing some text + * @param len + * output length desired + * @return input StringBuilder, with leading spaces + */ + public static StringBuilder rightAlign(StringBuilder in, int len) { + int pfx = len - in.length(); + if (pfx <= 0) { + return in; + } + if (pfx > SPACES_LEN) { + pfx = SPACES_LEN; + } + in.insert(0, SPACES.substring(0, pfx)); + return in; + } + + /** + * Left aligns some text in a StringBuilder N.B. modifies the input buffer + * + * @param in + * StringBuilder containing some text + * @param len + * output length desired + * @return input StringBuilder, with trailing spaces + */ + public static StringBuilder leftAlign(StringBuilder in, int len) { + int sfx = len - in.length(); + if (sfx <= 0) { + return in; + } + if (sfx > SPACES_LEN) { + sfx = SPACES_LEN; + } + in.append(SPACES.substring(0, sfx)); + return in; + } + + /** + * Convert a boolean to its upper case string representation. + * Equivalent to Boolean.valueOf(boolean).toString().toUpperCase(). + * + * @param value + * boolean to convert + * @return "TRUE" or "FALSE" + */ + public static String booleanToSTRING(boolean value) { + return value ? "TRUE" : "FALSE"; + } + + /** + * Simple-minded String.replace() for JDK1.3 Should probably be recoded... + * + * @param source + * input string + * @param search + * string to look for (no regular expressions) + * @param replace + * string to replace the search string + * @return the output string + */ + public static String replaceFirst(String source, String search, String replace) { + int start = source.indexOf(search); + int len = search.length(); + if (start == -1) { + return source; + } + if (start == 0) { + return replace + source.substring(len); + } + return source.substring(0, start) + replace + source.substring(start + len); + } + + /** + * Version of String.replaceAll() for JDK1.3 + * See below for another version which replaces strings rather than chars + * + * @param source + * input string + * @param search + * char to look for (no regular expressions) + * @param replace + * string to replace the search string + * @return the output string + */ + public static String replaceAllChars(String source, char search, String replace) { + char[] chars = source.toCharArray(); + StringBuilder sb = new StringBuilder(source.length()+20); + for(char c : chars){ + if (c == search){ + sb.append(replace); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Replace all patterns in a String + * + * @see String#replaceAll(String regex,String replacement) - JDK1.4 only + * + * @param input - string to be transformed + * @param pattern - pattern to replace + * @param sub - replacement + * @return the updated string + */ + public static String substitute(final String input, final String pattern, final String sub) { + StringBuilder ret = new StringBuilder(input.length()); + int start = 0; + int index = -1; + final int length = pattern.length(); + while ((index = input.indexOf(pattern, start)) >= start) { + ret.append(input.substring(start, index)); + ret.append(sub); + start = index + length; + } + ret.append(input.substring(start)); + return ret.toString(); + } + + /** + * Trim a string by the tokens provided. + * + * @param input string to trim + * @param delims list of delimiters + * @return input trimmed at the first delimiter + */ + public static String trim(final String input, final String delims){ + StringTokenizer tokens = new StringTokenizer(input,delims); + return tokens.hasMoreTokens() ? tokens.nextToken() : ""; + } + + /** + * Returns a slice of a byte array. + * + * TODO - add bounds checking? + * + * @param array - + * input array + * @param begin - + * start of slice + * @param end - + * end of slice + * @return slice from the input array + */ + public static byte[] getByteArraySlice(byte[] array, int begin, int end) { + byte[] slice = new byte[(end - begin + 1)]; + System.arraycopy(array, begin, slice, 0, slice.length); + return slice; + } + + // N.B. Commons IO IOUtils has equivalent methods; these were added before IO was included + // TODO - perhaps deprecate these in favour of Commons IO? + /** + * Close a Closeable with no error thrown + * @param cl - Closeable (may be null) + */ + public static void closeQuietly(Closeable cl){ + try { + if (cl != null) { + cl.close(); + } + } catch (IOException ignored) { + // NOOP + } + } + + /** + * close a Socket with no error thrown + * @param sock - Socket (may be null) + */ + public static void closeQuietly(Socket sock){ + try { + if (sock!= null) { + sock.close(); + } + } catch (IOException ignored) { + // NOOP + } + } + + /** + * close a Socket with no error thrown + * @param sock - ServerSocket (may be null) + */ + public static void closeQuietly(ServerSocket sock){ + try { + if (sock!= null) { + sock.close(); + } + } catch (IOException ignored) { + // NOOP + } + } + + /** + * Check if a byte array starts with the given byte array. + * + * @see String#startsWith(String, int) + * + * @param target array to scan + * @param search array to search for + * @param offset starting offset (>=0) + * @return true if the search array matches the target at the current offset + */ + public static boolean startsWith(byte [] target, byte [] search, int offset){ + final int targetLength = target.length; + final int searchLength = search.length; + if (offset < 0 || searchLength > targetLength+offset){ + return false; + } + for(int i=0;i < searchLength; i++){ + if (target[i+offset] != search[i]){ + return false; + } + } + return true; + } + + private static final byte[] XML_PFX = {'<','?','x','m','l'};// " 0 && separator != 0) { + sb.append(separator); + } + int j = ba[i] & 0xff; + if (j < 16) { + sb.append("0"); // $NON-NLS-1$ add zero padding + } + sb.append(Integer.toHexString(j)); + } + return sb.toString(); + } + + /** + * Convert binary byte array to hex string. + * + * @param ba input binary byte array + * @return hex representation of binary input + */ + public static byte[] baToHexBytes(byte ba[]) { + byte[] hb = new byte[ba.length*2]; + for (int i = 0; i < ba.length; i++) { + byte upper = (byte) ((ba[i] & 0xf0) >> 4); + byte lower = (byte) (ba[i] & 0x0f); + hb[2*i]=toHexChar(upper); + hb[2*i+1]=toHexChar(lower); + } + return hb; + } + + private static byte toHexChar(byte in){ + if (in < 10) { + return (byte) (in+'0'); + } + return (byte) ((in-10)+'a'); + } + + /** + * Read as much as possible into buffer. + * + * @param is the stream to read from + * @param buffer output buffer + * @param offset offset into buffer + * @param length number of bytes to read + * + * @return the number of bytes actually read + * @throws IOException if some I/O errors occur + */ + public static int read(InputStream is, byte[] buffer, int offset, int length) throws IOException { + int remaining = length; + while ( remaining > 0 ) { + int location = ( length - remaining ); + int count = is.read( buffer, location, remaining ); + if ( -1 == count ) { // EOF + break; + } + remaining -= count; + } + return length - remaining; + } + + /** + * Display currently running threads on system.out + * This may be expensive to run. + * Mainly designed for use at the end of a non-GUI test to check for threads that might prevent the JVM from exitting. + * + * @param includeDaemons whether to include daemon threads or not. + */ + public static void displayThreads(boolean includeDaemons) { + Map m = Thread.getAllStackTraces(); + String lineSeparator = System.getProperty("line.separator"); + StringBuilder builder = new StringBuilder(); + for(Map.Entry e : m.entrySet()) { + boolean daemon = e.getKey().isDaemon(); + if (includeDaemons || !daemon){ + builder.setLength(0); + StackTraceElement[] ste = e.getValue(); + for (StackTraceElement stackTraceElement : ste) { + int lineNumber = stackTraceElement.getLineNumber(); + builder.append(stackTraceElement.getClassName()+"#"+stackTraceElement.getMethodName()+ + (lineNumber >=0 ? " at line:"+ stackTraceElement.getLineNumber() : "")+lineSeparator); + } + System.out.println(e.getKey().toString()+((daemon ? " (daemon)" : ""))+", stackTrace:"+ builder.toString()); + } + } + } + + /** + * Returns null if input is empty, null or contains spaces + * @param input String + * @return String + */ + public static String nullifyIfEmptyTrimmed(final String input) { + if (input == null) { + return null; + } + String trimmed = input.trim(); + if (trimmed.length() == 0) { + return null; + } + return trimmed; + } + + /** + * Check that value is empty (""), null or whitespace only. + * @param value Value + * @return true if the String is not empty (""), not null and not whitespace only. + */ + public static boolean isBlank(final String value) { + return StringUtils.isBlank(value); + } + + /** + * Write data to an output stream in chunks with a maximum size of 4K. + * This is to avoid OutOfMemory issues if the data buffer is very large + * and the JVM needs to copy the buffer for use by native code. + * + * @param data the buffer to be written + * @param output the output stream to use + * @throws IOException if there is a problem writing the data + */ + // Bugzilla 54990 + public static void write(byte[] data, OutputStream output) throws IOException { + int bytes = data.length; + int offset = 0; + while(bytes > 0) { + int chunk = Math.min(bytes, DEFAULT_CHUNK_SIZE); + output.write(data, offset, chunk); + bytes -= chunk; + offset += chunk; + } + } +} diff --git a/src/jorphan/org/apache/jorphan/util/XMLBuffer.java b/src/jorphan/org/apache/jorphan/util/XMLBuffer.java new file mode 100644 index 00000000000..47d88c72b78 --- /dev/null +++ b/src/jorphan/org/apache/jorphan/util/XMLBuffer.java @@ -0,0 +1,135 @@ +/* + * 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.jorphan.util; + +import org.apache.commons.collections.ArrayStack; + +// @see org.apache.jorphan.util.TestXMLBuffer for unit tests + +/** + * Provides XML string building methods. + * Not synchronised. + * + */ +public class XMLBuffer{ + private final StringBuilder sb = new StringBuilder(); // the string so far + + private final ArrayStack tags = new ArrayStack(); // opened tags + + public XMLBuffer(){ + + } + + private void startTag(String t){ + sb.append("<"); + sb.append(t); + sb.append(">"); + } + + private void endTag(String t){ + sb.append(""); + sb.append("\n"); + } + + private void emptyTag(String t){ + sb.append("<"); + sb.append(t); + sb.append("/>"); + sb.append("\n"); + } + + /** + * Open a tag; save on stack. + * + * @param tagname name of the tag + * @return this + */ + public XMLBuffer openTag(String tagname){ + tags.push(tagname); + startTag(tagname); + return this; + } + + /** + * Close top tag from stack. + * + * @param tagname name of the tag to close + * + * @return this + * + * @throws IllegalArgumentException if the tag names do not match + */ + public XMLBuffer closeTag(String tagname){ + String tag = (String) tags.pop(); + if (!tag.equals(tagname)) { + throw new IllegalArgumentException("Trying to close tag: "+tagname+" ; should be "+tag); + } + endTag(tag); + return this; + } + + /** + * Add a complete tag with content. + * + * @param tagname name of the tag + * @param content content to put in tag, or empty content, if an empty tag should be used + * @return this + */ + public XMLBuffer tag(String tagname, String content){ + if (content.length() == 0) { + emptyTag(tagname); + } else { + startTag(tagname); + sb.append(content); + endTag(tagname); + } + return this; + } + + /** + * Add a complete tag with content. + * + * @param tagname name of the tag + * @param content content to put in tag, or empty content, if an empty tag should be used + * @return this + */ + public XMLBuffer tag(String tagname,StringBuilder content){ + if (content.length() == 0) { + emptyTag(tagname); + } else { + startTag(tagname); + sb.append(content); + endTag(tagname); + } + return this; + } + + /** + * Convert the buffer to a string, closing any open tags + */ + @Override + public String toString(){ + while(!tags.isEmpty()){ + endTag((String)tags.pop()); + } + return sb.toString(); + } +} diff --git a/src/junit/org/apache/jmeter/protocol/java/control/gui/ClassFilter.java b/src/junit/org/apache/jmeter/protocol/java/control/gui/ClassFilter.java new file mode 100644 index 00000000000..1a9e0c36a27 --- /dev/null +++ b/src/junit/org/apache/jmeter/protocol/java/control/gui/ClassFilter.java @@ -0,0 +1,64 @@ +/* + * 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.protocol.java.control.gui; + +import java.util.ArrayList; +import java.util.List; + +class ClassFilter { + + private String[] pkgs = new String[0]; + + ClassFilter() { + super(); + } + + void setPackges(String[] pk) { + this.pkgs = pk; + } + + private boolean include(String text) { + if (pkgs.length == 0) return true; // i.e. no filter + boolean inc = false; + for (int idx=0; idx < pkgs.length; idx++) { + if (text.startsWith(pkgs[idx])){ + inc = true; + break; + } + } + return inc; + } + + Object[] filterArray(List items) { + ArrayList newlist = new ArrayList(); + for (String item : items) { + if (include(item)) { + newlist.add(item); + } + } + if (newlist.size() > 0) { + return newlist.toArray(); + } else { + return new Object[0]; + } + } + + int size(){ + return pkgs.length; + } +} diff --git a/src/junit/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java b/src/junit/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java new file mode 100644 index 00000000000..dd5ff44900d --- /dev/null +++ b/src/junit/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java @@ -0,0 +1,437 @@ +/* + * 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.protocol.java.control.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import junit.framework.TestCase; + +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.java.sampler.JUnitSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * The JUnitTestSamplerGui class provides the user interface + * for the {@link JUnitSampler}. + * + */ +public class JUnitTestSamplerGui extends AbstractSamplerGui +implements ChangeListener, ActionListener, ItemListener +{ + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String TESTMETHOD_PREFIX = "test"; //$NON-NLS-1$ + + // Names of JUnit3 methods + private static final String ONETIMESETUP = "oneTimeSetUp"; //$NON-NLS-1$ + private static final String ONETIMETEARDOWN = "oneTimeTearDown"; //$NON-NLS-1$ + private static final String SUITE = "suite"; //$NON-NLS-1$ + + private static final String[] SPATHS; + + static { + String paths[]; + String ucp = JMeterUtils.getProperty("user.classpath"); + if (ucp!=null){ + String parts[] = ucp.split(File.pathSeparator); + paths = new String[parts.length+1]; + paths[0] = JMeterUtils.getJMeterHome() + "/lib/junit/"; //$NON-NLS-1$ + System.arraycopy(parts, 0, paths, 1, parts.length); + } else { + paths = new String[]{ + JMeterUtils.getJMeterHome() + "/lib/junit/" //$NON-NLS-1$ + }; + } + SPATHS = paths; + } + + private JLabeledTextField constructorLabel = + new JLabeledTextField( + JMeterUtils.getResString("junit_constructor_string")); //$NON-NLS-1$ + + private JLabel methodLabel = + new JLabel( + JMeterUtils.getResString("junit_test_method")); //$NON-NLS-1$ + + private JLabeledTextField successMsg = + new JLabeledTextField( + JMeterUtils.getResString("junit_success_msg")); //$NON-NLS-1$ + + private JLabeledTextField failureMsg = + new JLabeledTextField( + JMeterUtils.getResString("junit_failure_msg")); //$NON-NLS-1$ + + private JLabeledTextField errorMsg = + new JLabeledTextField( + JMeterUtils.getResString("junit_error_msg")); //$NON-NLS-1$ + + private JLabeledTextField successCode = + new JLabeledTextField( + JMeterUtils.getResString("junit_success_code")); //$NON-NLS-1$ + + private JLabeledTextField failureCode = + new JLabeledTextField( + JMeterUtils.getResString("junit_failure_code")); //$NON-NLS-1$ + + private JLabeledTextField errorCode = + new JLabeledTextField( + JMeterUtils.getResString("junit_error_code")); //$NON-NLS-1$ + + private JLabeledTextField filterpkg = + new JLabeledTextField( + JMeterUtils.getResString("junit_pkg_filter")); //$NON-NLS-1$ + + private JCheckBox doSetup = new JCheckBox(JMeterUtils.getResString("junit_do_setup_teardown")); //$NON-NLS-1$ + private JCheckBox appendError = new JCheckBox(JMeterUtils.getResString("junit_append_error")); //$NON-NLS-1$ + private JCheckBox appendExc = new JCheckBox(JMeterUtils.getResString("junit_append_exception")); //$NON-NLS-1$ + private JCheckBox junit4 = new JCheckBox(JMeterUtils.getResString("junit_junit4")); //$NON-NLS-1$ + private JCheckBox createInstancePerSample = new JCheckBox(JMeterUtils.getResString("junit_create_instance_per_sample")); //$NON-NLS-1$ + + /** A combo box allowing the user to choose a test class. */ + private JComboBox classnameCombo; + private JComboBox methodName; + + private final transient ClassLoader contextClassLoader = + Thread.currentThread().getContextClassLoader(); // Potentially expensive; do it once + + /** + * Constructor for JUnitTestSamplerGui + */ + public JUnitTestSamplerGui() + { + super(); + init(); + } + + @Override + public String getLabelResource() + { + return "junit_request"; //$NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout. + */ + private void init() + { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + + add(createClassPanel(), BorderLayout.CENTER); + } + + @SuppressWarnings("unchecked") + private void setupClasslist(){ + classnameCombo.removeAllItems(); + methodName.removeAllItems(); + try + { + List classList; + if (junit4.isSelected()){ + classList = ClassFinder.findAnnotatedClasses(SPATHS, + new Class[] {Test.class}, false); + } else { + classList = ClassFinder.findClassesThatExtend(SPATHS, + new Class[] { TestCase.class }); + } + ClassFilter filter = new ClassFilter(); + filter.setPackges(JOrphanUtils.split(filterpkg.getText(),",")); //$NON-NLS-1$ + // change the classname drop down + Object[] clist = filter.filterArray(classList); + for (int idx=0; idx < clist.length; idx++) { + classnameCombo.addItem(clist[idx]); + } + } + catch (IOException e) + { + log.error("Exception getting interfaces.", e); + } + } + + private JPanel createClassPanel() + { + JLabel label = + new JLabel(JMeterUtils.getResString("protocol_java_classname")); //$NON-NLS-1$ + + classnameCombo = new JComboBox(); + classnameCombo.addActionListener(this); + classnameCombo.setEditable(false); + label.setLabelFor(classnameCombo); + + methodName = new JComboBox(); + methodName.addActionListener(this); + methodLabel.setLabelFor(methodName); + + setupClasslist(); + + VerticalPanel panel = new VerticalPanel(); + panel.add(junit4); + junit4.addItemListener(this); + panel.add(filterpkg); + filterpkg.addChangeListener(this); + + panel.add(label); + panel.add(classnameCombo); + + constructorLabel.setText(""); + panel.add(constructorLabel); + panel.add(methodLabel); + panel.add(methodName); + + panel.add(successMsg); + panel.add(successCode); + panel.add(failureMsg); + panel.add(failureCode); + panel.add(errorMsg); + panel.add(errorCode); + panel.add(doSetup); + panel.add(appendError); + panel.add(appendExc); + panel.add(createInstancePerSample); + return panel; + } + + private void initGui(){ + appendError.setSelected(false); + appendExc.setSelected(false); + createInstancePerSample.setSelected(false); + doSetup.setSelected(false); + junit4.setSelected(false); + filterpkg.setText(""); //$NON-NLS-1$ + constructorLabel.setText(""); //$NON-NLS-1$ + successCode.setText(JMeterUtils.getResString("junit_success_default_code")); //$NON-NLS-1$ + successMsg.setText(JMeterUtils.getResString("junit_success_default_msg")); //$NON-NLS-1$ + failureCode.setText(JMeterUtils.getResString("junit_failure_default_code")); //$NON-NLS-1$ + failureMsg.setText(JMeterUtils.getResString("junit_failure_default_msg")); //$NON-NLS-1$ + errorMsg.setText(JMeterUtils.getResString("junit_error_default_msg")); //$NON-NLS-1$ + errorCode.setText(JMeterUtils.getResString("junit_error_default_code")); //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void clearGui() { + super.clearGui(); + initGui(); + } + + /** {@inheritDoc} */ + @Override + public TestElement createTestElement() + { + JUnitSampler sampler = new JUnitSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** {@inheritDoc} */ + @Override + public void modifyTestElement(TestElement el) + { + JUnitSampler sampler = (JUnitSampler)el; + configureTestElement(sampler); + if (classnameCombo.getSelectedItem() != null && + classnameCombo.getSelectedItem() instanceof String) { + sampler.setClassname((String)classnameCombo.getSelectedItem()); + } else { + sampler.setClassname(null); + } + sampler.setConstructorString(constructorLabel.getText()); + if (methodName.getSelectedItem() != null) { + Object mobj = methodName.getSelectedItem(); + sampler.setMethod((String)mobj); + } else { + sampler.setMethod(null); + } + sampler.setFilterString(filterpkg.getText()); + sampler.setSuccess(successMsg.getText()); + sampler.setSuccessCode(successCode.getText()); + sampler.setFailure(failureMsg.getText()); + sampler.setFailureCode(failureCode.getText()); + sampler.setError(errorMsg.getText()); + sampler.setErrorCode(errorCode.getText()); + sampler.setDoNotSetUpTearDown(doSetup.isSelected()); + sampler.setAppendError(appendError.isSelected()); + sampler.setAppendException(appendExc.isSelected()); + sampler.setCreateOneInstancePerSample(createInstancePerSample.isSelected()); + sampler.setJunit4(junit4.isSelected()); + } + + /** {@inheritDoc} */ + @Override + public void configure(TestElement el) + { + super.configure(el); + JUnitSampler sampler = (JUnitSampler)el; + junit4.setSelected(sampler.getJunit4()); + filterpkg.setText(sampler.getFilterString()); + classnameCombo.setSelectedItem(sampler.getClassname()); + setupMethods(); + methodName.setSelectedItem(sampler.getMethod()); + constructorLabel.setText(sampler.getConstructorString()); + if (sampler.getSuccessCode().length() > 0) { + successCode.setText(sampler.getSuccessCode()); + } else { + successCode.setText(JMeterUtils.getResString("junit_success_default_code")); //$NON-NLS-1$ + } + if (sampler.getSuccess().length() > 0) { + successMsg.setText(sampler.getSuccess()); + } else { + successMsg.setText(JMeterUtils.getResString("junit_success_default_msg")); //$NON-NLS-1$ + } + if (sampler.getFailureCode().length() > 0) { + failureCode.setText(sampler.getFailureCode()); + } else { + failureCode.setText(JMeterUtils.getResString("junit_failure_default_code")); //$NON-NLS-1$ + } + if (sampler.getFailure().length() > 0) { + failureMsg.setText(sampler.getFailure()); + } else { + failureMsg.setText(JMeterUtils.getResString("junit_failure_default_msg")); //$NON-NLS-1$ + } + if (sampler.getError().length() > 0) { + errorMsg.setText(sampler.getError()); + } else { + errorMsg.setText(JMeterUtils.getResString("junit_error_default_msg")); //$NON-NLS-1$ + } + if (sampler.getErrorCode().length() > 0) { + errorCode.setText(sampler.getErrorCode()); + } else { + errorCode.setText(JMeterUtils.getResString("junit_error_default_code")); //$NON-NLS-1$ + } + doSetup.setSelected(sampler.getDoNotSetUpTearDown()); + appendError.setSelected(sampler.getAppendError()); + appendExc.setSelected(sampler.getAppendException()); + createInstancePerSample.setSelected(sampler.getCreateOneInstancePerSample()); + } + + private void setupMethods(){ + String className = + ((String) classnameCombo.getSelectedItem()); + methodName.removeAllItems(); + if (className != null) { + try { + // Don't instantiate class + Class testClass = Class.forName(className, false, contextClassLoader); + String [] names = getMethodNames(testClass); + for (int idx=0; idx < names.length; idx++){ + methodName.addItem(names[idx]); + } + methodName.repaint(); + } catch (ClassNotFoundException e) { + } + } + } + + private String[] getMethodNames(Class clazz) + { + Method[] meths = clazz.getMethods(); + List list = new ArrayList(); + for (int idx=0; idx < meths.length; idx++){ + final Method method = meths[idx]; + final String name = method.getName(); + if (junit4.isSelected()){ + if (method.isAnnotationPresent(Test.class) || + method.isAnnotationPresent(BeforeClass.class) || + method.isAnnotationPresent(AfterClass.class)) { + list.add(name); + } + } else { + if (name.startsWith(TESTMETHOD_PREFIX) || + name.equals(ONETIMESETUP) || + name.equals(ONETIMETEARDOWN) || + name.equals(SUITE)) { + list.add(name); + } + } + } + if (list.size() > 0){ + return list.toArray(new String[list.size()]); + } + return new String[0]; + } + + /** + * Handle action events for this component. This method currently handles + * events for the classname combo box, and sets up the associated method names. + * + * @param evt the ActionEvent to be handled + */ + @Override + public void actionPerformed(ActionEvent evt) + { + if (evt.getSource() == classnameCombo) + { + setupMethods(); + } + } + + /** + * Handle change events: currently handles events for the JUnit4 + * checkbox, and sets up the relevant class names. + */ + @Override + public void itemStateChanged(ItemEvent event) { + if (event.getItem() == junit4){ + setupClasslist(); + } + } + + /** + * the current implementation checks to see if the source + * of the event is the filterpkg field. + */ + @Override + public void stateChanged(ChangeEvent event) { + if ( event.getSource() == filterpkg) { + setupClasslist(); + } + } +} + diff --git a/src/junit/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java b/src/junit/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java new file mode 100644 index 00000000000..e0108cf37c8 --- /dev/null +++ b/src/junit/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java @@ -0,0 +1,726 @@ +/* + * 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.protocol.java.sampler; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Enumeration; + +import junit.framework.AssertionFailedError; +import junit.framework.Protectable; +import junit.framework.TestCase; +import junit.framework.TestFailure; +import junit.framework.TestResult; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.Test.None; + +/** + * + * This is a basic implementation that runs a single test method of + * a JUnit test case. The current implementation will use the string + * constructor first. If the test class does not declare a string + * constructor, the sampler will try empty constructor. + */ +public class JUnitSampler extends AbstractSampler implements ThreadListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; // Remember to change this when the class changes ... + + //++ JMX file attributes - do not change + private static final String CLASSNAME = "junitSampler.classname"; + private static final String CONSTRUCTORSTRING = "junitsampler.constructorstring"; + private static final String METHOD = "junitsampler.method"; + private static final String ERROR = "junitsampler.error"; + private static final String ERRORCODE = "junitsampler.error.code"; + private static final String FAILURE = "junitsampler.failure"; + private static final String FAILURECODE = "junitsampler.failure.code"; + private static final String SUCCESS = "junitsampler.success"; + private static final String SUCCESSCODE = "junitsampler.success.code"; + private static final String FILTER = "junitsampler.pkg.filter"; + private static final String DOSETUP = "junitsampler.exec.setup"; + private static final String APPEND_ERROR = "junitsampler.append.error"; + private static final String APPEND_EXCEPTION = "junitsampler.append.exception"; + private static final String JUNIT4 = "junitsampler.junit4"; + private static final String CREATE_INSTANCE_PER_SAMPLE="junitsampler.createinstancepersample"; + private static final boolean CREATE_INSTANCE_PER_SAMPLE_DEFAULT = false; + //-- JMX file attributes - do not change + + private static final String SETUP = "setUp"; + private static final String TEARDOWN = "tearDown"; + + // the Method objects for setUp (@Before) and tearDown (@After) methods + // Will be null if not provided or not required + private transient Method setUpMethod; + private transient Method tearDownMethod; + + // The TestCase to run + private transient TestCase testCase; + // The test object, i.e. the instance of the class containing the test method + // This is the same as testCase for JUnit3 tests + // but different for JUnit4 tests which use a wrapper + private transient Object testObject; + + // The method name to be invoked + private transient String methodName; + // The name of the class containing the method + private transient String className; + // The wrapper used to invoke the method + private transient Protectable protectable; + + public JUnitSampler(){ + super(); + } + + /** + * Method tries to get the setUp and tearDown method for the class + * @param testObject + */ + private void initMethodObjects(Object testObject){ + setUpMethod = null; + tearDownMethod = null; + if (!getDoNotSetUpTearDown()) { + setUpMethod = getJunit4() ? + getMethodWithAnnotation(testObject, Before.class) + : + getMethod(testObject, SETUP); + tearDownMethod = getJunit4() ? + getMethodWithAnnotation(testObject, After.class) + : + getMethod(testObject, TEARDOWN); + } + } + + /** + * Sets the Classname attribute of the JavaConfig object + * + * @param classname + * the new Classname value + */ + public void setClassname(String classname) + { + setProperty(CLASSNAME, classname); + } + + /** + * Gets the Classname attribute of the JavaConfig object + * + * @return the Classname value + */ + public String getClassname() + { + return getPropertyAsString(CLASSNAME); + } + + /** + * Set the string label used to create an instance of the + * test with the string constructor. + * @param constr the string passed to the constructor + */ + public void setConstructorString(String constr) + { + setProperty(CONSTRUCTORSTRING,constr); + } + + /** + * @return the string passed to the string constructor + */ + public String getConstructorString() + { + return getPropertyAsString(CONSTRUCTORSTRING); + } + + /** + * @return the name of the method to test + */ + public String getMethod(){ + return getPropertyAsString(METHOD); + } + + /** + * Method should add the JUnit testXXX method to the list at + * the end, since the sequence matters. + * @param methodName name of the method to test + */ + public void setMethod(String methodName){ + setProperty(METHOD,methodName); + } + + /** + * @return the success message + */ + public String getSuccess(){ + return getPropertyAsString(SUCCESS); + } + + /** + * set the success message + * @param success message to be used for success + */ + public void setSuccess(String success){ + setProperty(SUCCESS,success); + } + + /** + * @return the success code defined by the user + */ + public String getSuccessCode(){ + return getPropertyAsString(SUCCESSCODE); + } + + /** + * Set the success code. The success code should + * be unique. + * @param code unique success code + */ + public void setSuccessCode(String code){ + setProperty(SUCCESSCODE,code); + } + + /** + * @return the failure message + */ + public String getFailure(){ + return getPropertyAsString(FAILURE); + } + + /** + * set the failure message + * @param fail the failure message + */ + public void setFailure(String fail){ + setProperty(FAILURE,fail); + } + + /** + * @return The failure code that is used by other components + */ + public String getFailureCode(){ + return getPropertyAsString(FAILURECODE); + } + + /** + * Provide some unique code to denote a type of failure + * @param code unique code to denote the type of failure + */ + public void setFailureCode(String code){ + setProperty(FAILURECODE,code); + } + + /** + * @return the descriptive error for the test + */ + public String getError(){ + return getPropertyAsString(ERROR); + } + + /** + * provide a descriptive error for the test method. For + * a description of the difference between failure and + * error, please refer to the + * junit faq + * @param error the description of the error + */ + public void setError(String error){ + setProperty(ERROR,error); + } + + /** + * @return the error code for the test method. It should + * be an unique error code. + */ + public String getErrorCode(){ + return getPropertyAsString(ERRORCODE); + } + + /** + * Provide an unique error code for when the test + * does not pass the assert test. + * @param code unique error code + */ + public void setErrorCode(String code){ + setProperty(ERRORCODE,code); + } + + /** + * @return the comma separated string for the filter + */ + public String getFilterString(){ + return getPropertyAsString(FILTER); + } + + /** + * set the filter string in comma separated format + * @param text comma separated filter + */ + public void setFilterString(String text){ + setProperty(FILTER,text); + } + + /** + * if the sample shouldn't call setup/teardown, the + * method returns true. It's meant for onetimesetup + * and onetimeteardown. + * + * @return flag whether setup/teardown methods should not be called + */ + public boolean getDoNotSetUpTearDown(){ + return getPropertyAsBoolean(DOSETUP); + } + + /** + * set the setup/teardown option + * + * @param setup flag whether the setup/teardown methods should not be called + */ + public void setDoNotSetUpTearDown(boolean setup){ + setProperty(DOSETUP,String.valueOf(setup)); + } + + /** + * If append error is not set, by default it is set to false, + * which means users have to explicitly set the sampler to + * append the assert errors. Because of how junit works, there + * should only be one error + * + * @return flag whether errors should be appended + */ + public boolean getAppendError() { + return getPropertyAsBoolean(APPEND_ERROR,false); + } + + /** + * Set whether to append errors or not. + * + * @param error the setting to apply + */ + public void setAppendError(boolean error) { + setProperty(APPEND_ERROR,String.valueOf(error)); + } + + /** + * If append exception is not set, by default it is set to false. + * Users have to explicitly set it to true to see the exceptions + * in the result tree. + * + * @return flag whether exceptions should be appended to the result tree + */ + public boolean getAppendException() { + return getPropertyAsBoolean(APPEND_EXCEPTION,false); + } + + /** + * Set whether to append exceptions or not. + * + * @param exc the setting to apply. + */ + public void setAppendException(boolean exc) { + setProperty(APPEND_EXCEPTION,String.valueOf(exc)); + } + + /** + * Check if JUnit4 (annotations) are to be used instead of + * the JUnit3 style (TestClass and specific method names) + * + * @return true if JUnit4 (annotations) are to be used. + * Default is false. + */ + public boolean getJunit4() { + return getPropertyAsBoolean(JUNIT4, false); + } + + /** + * Set whether to use JUnit4 style or not. + * @param junit4 true if JUnit4 style is to be used. + */ + public void setJunit4(boolean junit4) { + setProperty(JUNIT4, junit4, false); + } + + /** {@inheritDoc} */ + @Override + public SampleResult sample(Entry entry) { + if(getCreateOneInstancePerSample()) { + initializeTestObject(); + } + SampleResult sresult = new SampleResult(); + sresult.setSampleLabel(getName());// Bug 41522 - don't use rlabel here + sresult.setSamplerData(className + "." + methodName); + sresult.setDataType(SampleResult.TEXT); + // Assume success + sresult.setSuccessful(true); + sresult.setResponseMessage(getSuccess()); + sresult.setResponseCode(getSuccessCode()); + if (this.testCase != null){ + // create a new TestResult + TestResult tr = new TestResult(); + final TestCase theClazz = this.testCase; + try { + if (setUpMethod != null){ + setUpMethod.invoke(this.testObject,new Object[0]); + } + sresult.sampleStart(); + tr.startTest(this.testCase); + // Do not use TestCase.run(TestResult) method, since it will + // call setUp and tearDown. Doing that will result in calling + // the setUp and tearDown method twice and the elapsed time + // will include setup and teardown. + tr.runProtected(theClazz, protectable); + tr.endTest(this.testCase); + sresult.sampleEnd(); + if (tearDownMethod != null){ + tearDownMethod.invoke(testObject,new Object[0]); + } + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof AssertionFailedError){ + tr.addFailure(theClazz, (AssertionFailedError) cause); + } else if (cause instanceof AssertionError) { + // Convert JUnit4 failure to Junit3 style + AssertionFailedError afe = new AssertionFailedError(cause.toString()); + // copy the original stack trace + afe.setStackTrace(cause.getStackTrace()); + tr.addFailure(theClazz, afe); + } else if (cause != null) { + tr.addError(theClazz, cause); + } else { + tr.addError(theClazz, e); + } + } catch (IllegalAccessException e) { + tr.addError(theClazz, e); + } catch (IllegalArgumentException e) { + tr.addError(theClazz, e); + } + if ( !tr.wasSuccessful() ){ + sresult.setSuccessful(false); + StringBuilder buf = new StringBuilder(); + StringBuilder buftrace = new StringBuilder(); + Enumeration en; + if (getAppendError()) { + en = tr.failures(); + if (en.hasMoreElements()){ + sresult.setResponseCode(getFailureCode()); + buf.append( getFailure() ); + buf.append("\n"); + } + while (en.hasMoreElements()){ + TestFailure item = en.nextElement(); + buf.append( "Failure -- "); + buf.append( item.toString() ); + buf.append("\n"); + buftrace.append( "Failure -- "); + buftrace.append( item.toString() ); + buftrace.append("\n"); + buftrace.append( "Trace -- "); + buftrace.append( item.trace() ); + } + en = tr.errors(); + if (en.hasMoreElements()){ + sresult.setResponseCode(getErrorCode()); + buf.append( getError() ); + buf.append("\n"); + } + while (en.hasMoreElements()){ + TestFailure item = en.nextElement(); + buf.append( "Error -- "); + buf.append( item.toString() ); + buf.append("\n"); + buftrace.append( "Error -- "); + buftrace.append( item.toString() ); + buftrace.append("\n"); + buftrace.append( "Trace -- "); + buftrace.append( item.trace() ); + } + } + sresult.setResponseMessage(buf.toString()); + sresult.setResponseData(buftrace.toString(), null); + } + } else { + // we should log a warning, but allow the test to keep running + sresult.setSuccessful(false); + // this should be externalized to the properties + sresult.setResponseMessage("Failed to create an instance of the class:"+getClassname() + +", reasons may be missing both empty constructor and one " + + "String constructor or failure to instantiate constructor," + + " check warning messages in jmeter log file"); + sresult.setResponseCode(getErrorCode()); + } + return sresult; + } + + /** + * If the method is not able to create a new instance of the + * class, it returns null and logs all the exceptions at + * warning level. + */ + private static Object getClassInstance(String className, String label){ + Object testclass = null; + if (className != null){ + Constructor con = null; + Constructor strCon = null; + Class theclazz = null; + Object[] strParams = null; + Object[] params = null; + try + { + theclazz = + Thread.currentThread().getContextClassLoader().loadClass(className.trim()); + } catch (ClassNotFoundException e) { + log.warn("ClassNotFoundException:: " + e.getMessage()); + } + if (theclazz != null) { + // first we see if the class declares a string + // constructor. if it is doesn't we look for + // empty constructor. + try { + strCon = theclazz.getDeclaredConstructor( + new Class[] {String.class}); + // we have to check and make sure the constructor is + // accessible. if we didn't it would throw an exception + // and cause a NPE. + if (label == null || label.length() == 0) { + label = className; + } + if (strCon.getModifiers() == Modifier.PUBLIC) { + strParams = new Object[]{label}; + } else { + strCon = null; + } + } catch (NoSuchMethodException e) { + log.info("Trying to find constructor with one String parameter returned error: " + e.getMessage()); + } + try { + con = theclazz.getDeclaredConstructor(new Class[0]); + if (con != null){ + params = new Object[]{}; + } + } catch (NoSuchMethodException e) { + log.info("Trying to find empty constructor returned error: " + e.getMessage()); + } + try { + // if the string constructor is not null, we use it. + // if the string constructor is null, we use the empty + // constructor to get a new instance + if (strCon != null) { + testclass = strCon.newInstance(strParams); + } else if (con != null){ + testclass = con.newInstance(params); + } else { + log.error("No empty constructor nor string constructor found for class:"+theclazz); + } + } catch (InvocationTargetException e) { + log.error("Error instantiating class:"+theclazz+":"+e.getMessage(), e); + } catch (InstantiationException e) { + log.error("Error instantiating class:"+theclazz+":"+e.getMessage(), e); + } catch (IllegalAccessException e) { + log.error("Error instantiating class:"+theclazz+":"+e.getMessage(), e); + } + } + } + return testclass; + } + + /** + * Get a method. + * @param clazz the classname (may be null) + * @param method the method name (may be null) + * @return the method or null if an error occurred + * (or either parameter is null) + */ + private Method getMethod(Object clazz, String method){ + if (clazz != null && method != null){ + try { + return clazz.getClass().getMethod(method,new Class[0]); + } catch (NoSuchMethodException e) { + log.warn(e.getMessage()); + } + } + return null; + } + + private Method getMethodWithAnnotation(Object clazz, Class annotation) { + if(null != clazz && null != annotation) { + for(Method m : clazz.getClass().getMethods()) { + if(m.isAnnotationPresent(annotation)) { + return m; + } + } + } + return null; + } + + /* + * Wrapper to convert a JUnit4 class into a TestCase + * + * TODO - work out how to convert JUnit4 assertions so they are treated as failures rather than errors + */ + private class AnnotatedTestCase extends TestCase { + private final Method method; + private final Class expectedException; + private final long timeout; + public AnnotatedTestCase(Method method, Class expectedException2, long timeout) { + this.method = method; + this.expectedException = expectedException2; + this.timeout = timeout; + } + + @Override + protected void runTest() throws Throwable { + try { + long start = System.currentTimeMillis(); + method.invoke(testObject, (Object[])null); + if (expectedException != None.class) { + throw new AssertionFailedError( + "No error was generated for a test case which specifies an error."); + } + if (timeout > 0){ + long elapsed = System.currentTimeMillis() - start; + if (elapsed > timeout) { + throw new AssertionFailedError("Test took longer than the specified timeout."); + } + } + } catch (InvocationTargetException e) { + Throwable thrown = e.getCause(); + if (thrown == null) { // probably should not happen + throw e; + } + if (expectedException == None.class){ + // Convert JUnit4 AssertionError failures to JUnit3 style so + // will be treated as failure rather than error. + if (thrown instanceof AssertionError && !(thrown instanceof AssertionFailedError)){ + AssertionFailedError afe = new AssertionFailedError(thrown.toString()); + // copy the original stack trace + afe.setStackTrace(thrown.getStackTrace()); + throw afe; + } + throw thrown; + } + if (!expectedException.isAssignableFrom(thrown.getClass())){ + throw new AssertionFailedError("The wrong exception was thrown from the test case"); + } + } + } + } + + @Override + public void threadFinished() { + } + + /** + * Set up all variables that don't change between samples. + */ + @Override + public void threadStarted() { + testObject = null; + testCase = null; + methodName = getMethod(); + className = getClassname(); + protectable = null; + if(!getCreateOneInstancePerSample()) { + // NO NEED TO INITIALIZE WHEN getCreateOneInstancePerSample + // is true cause it will be done in sample + initializeTestObject(); + } + } + + /** + * Initialize test object + */ + private void initializeTestObject() { + String rlabel = getConstructorString(); + if (rlabel.length()== 0) { + rlabel = JUnitSampler.class.getName(); + } + this.testObject = getClassInstance(className, rlabel); + if (this.testObject != null){ + initMethodObjects(this.testObject); + final Method m = getMethod(this.testObject,methodName); + if (getJunit4()){ + Class expectedException = None.class; + long timeout = 0; + Test annotation = m.getAnnotation(Test.class); + if(null != annotation) { + expectedException = annotation.expected(); + timeout = annotation.timeout(); + } + final AnnotatedTestCase at = new AnnotatedTestCase(m, expectedException, timeout); + testCase = at; + protectable = new Protectable() { + @Override + public void protect() throws Throwable { + at.runTest(); + } + }; + } else { + this.testCase = (TestCase) this.testObject; + final Object theClazz = this.testObject; // Must be final to create instance + protectable = new Protectable() { + @Override + public void protect() throws Throwable { + try { + m.invoke(theClazz,new Object[0]); + } catch (InvocationTargetException e) { + /* + * Calling a method via reflection results in wrapping any + * Exceptions in ITE; unwrap these here so runProtected can + * allocate them correctly. + */ + Throwable t = e.getCause(); + if (t != null) { + throw t; + } + throw e; + } + } + }; + } + if (this.testCase != null){ + this.testCase.setName(methodName); + } + } + } + + /** + * + * @param createOneInstancePerSample + * flag whether a new instance for each call should be created + */ + public void setCreateOneInstancePerSample(boolean createOneInstancePerSample) { + this.setProperty(CREATE_INSTANCE_PER_SAMPLE, createOneInstancePerSample, CREATE_INSTANCE_PER_SAMPLE_DEFAULT); + } + + /** + * + * @return boolean create New Instance For Each Call + */ + public boolean getCreateOneInstancePerSample() { + return getPropertyAsBoolean(CREATE_INSTANCE_PER_SAMPLE, CREATE_INSTANCE_PER_SAMPLE_DEFAULT); + } +} diff --git a/src/junit/test/AfterAnnotatedTest.java b/src/junit/test/AfterAnnotatedTest.java new file mode 100644 index 00000000000..e91ac6b9294 --- /dev/null +++ b/src/junit/test/AfterAnnotatedTest.java @@ -0,0 +1,39 @@ +/* + * 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 test; + +import static org.junit.Assert.fail; +import org.junit.After; +import org.junit.Test; + +/** + * Test to demonstrate how @After failures are handled + */ +public class AfterAnnotatedTest { + + @After + public void afterFail(){ + fail("afterFail()"); + } + + @Test + public void afterTest(){ + // Dummy to ensure there is a test to run + } +} diff --git a/src/junit/test/BeforeAnnotatedTest.java b/src/junit/test/BeforeAnnotatedTest.java new file mode 100644 index 00000000000..2d697f3f944 --- /dev/null +++ b/src/junit/test/BeforeAnnotatedTest.java @@ -0,0 +1,39 @@ +/* + * 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 test; + +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Test; + +/** + * Test to demonstrate how @Before failures are handled + */ +public class BeforeAnnotatedTest { + + @Before + public void beginFail(){ + fail("beginFail()"); + } + + @Test + public void beginTest(){ + // Dummy to ensure there is a test to run + } +} diff --git a/src/junit/test/DummyAnnotatedTest.java b/src/junit/test/DummyAnnotatedTest.java new file mode 100644 index 00000000000..3bc61270b28 --- /dev/null +++ b/src/junit/test/DummyAnnotatedTest.java @@ -0,0 +1,111 @@ +/* + * 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 test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Sample test cases for demonstrating JUnit4 sampler. + * + */ +public class DummyAnnotatedTest +{ + public int two = 1; //very wrong. + + public DummyAnnotatedTest() { + } + + // Generates expected Exception + @Test(expected=RuntimeException.class) + public void expectedExceptionPass() { + throw new RuntimeException(); + } + + // Fails to generate expected Exception + @Test(expected=RuntimeException.class) + public void expectedExceptionFail() { + } + + @Before + public void verifyTwo() { + System.out.println("DummyAnnotatedTest#verifyTwo()"); + two = 2; + } + + @After + public void printDone() { + System.out.println("DummyAnnotatedTest#printDone()"); + } + + @Test + // Succeeds only if Before method - verifyTwo() - is run. + public void add() { + int four = two+2; + if(4!=four) { + throw new RuntimeException("4 did not equal four."); + } + //or if you have assertions enabled + assert 4 == four; + } + + //should always fail + @Test(timeout=1000) + public void timeOutFail() { + try{ + TimeUnit.SECONDS.sleep(2); + }catch (InterruptedException e) { } + } + + //should not fail + @Test(timeout=1000) + public void timeOutPass() { + try{ + TimeUnit.MILLISECONDS.sleep(500); + }catch (InterruptedException e) { } + } + + @Test + public void alwaysFail() { + fail("This always fails"); + } + + @Test + // Generate a test error + public void divideByZero() { + @SuppressWarnings("unused") + int i = 27 / 0; // will generate Divide by zero error + } + + @Test + public void stringCompareFail(){ + assertEquals("this","that"); + } + + @Test + public void objectCompareFail(){ + assertEquals(new Object(),new Object()); + } +} diff --git a/src/junit/test/Junit4AnnotationsTest.java b/src/junit/test/Junit4AnnotationsTest.java new file mode 100644 index 00000000000..4a3c178e7db --- /dev/null +++ b/src/junit/test/Junit4AnnotationsTest.java @@ -0,0 +1,55 @@ +/* + * 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 test; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test to demonstrate all the common method annotations + */ +public class Junit4AnnotationsTest { + + @BeforeClass + public static void beforeClass(){ + System.out.println("beforeClass"); + } + @Before + public void before(){ + System.out.println("before"); + } + + @Test + public void test(){ + System.out.println("test"); + } + + @After + public void after(){ + System.out.println("after"); + } + + @AfterClass + public static void afterClass(){ + System.out.println("afterClass"); + } +} diff --git a/src/junit/test/RerunTest.java b/src/junit/test/RerunTest.java new file mode 100644 index 00000000000..3d96ad5f716 --- /dev/null +++ b/src/junit/test/RerunTest.java @@ -0,0 +1,36 @@ +/* + * 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 test; + +import org.junit.Test; + +import junit.framework.TestCase; + +/** + * Test to demonstrate whether a test instance can be re-run + */ +public class RerunTest extends TestCase { + + private int i = 123; + @Test + public void testRerun(){ + assertEquals(123,i); + i++; + } +} diff --git a/src/junit/test/SetupTestError.java b/src/junit/test/SetupTestError.java new file mode 100644 index 00000000000..f7976df6749 --- /dev/null +++ b/src/junit/test/SetupTestError.java @@ -0,0 +1,41 @@ +/* + * 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 test; + +import org.junit.Before; +import org.junit.Test; + +import junit.framework.TestCase; + +/** + * Test to demonstrate how setUp errors are handled + */ +public class SetupTestError extends TestCase { + + @Override + @Before + public void setUp(){ + throw new Error("setUp()"); + } + + @Test + public void testSetUpError(){ + // Dummy to ensure there is a test to run + } +} diff --git a/src/junit/test/SetupTestFail.java b/src/junit/test/SetupTestFail.java new file mode 100644 index 00000000000..1716bacf290 --- /dev/null +++ b/src/junit/test/SetupTestFail.java @@ -0,0 +1,41 @@ +/* + * 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 test; + +import org.junit.Before; +import org.junit.Test; + +import junit.framework.TestCase; + +/** + * Test to demonstrate how setUp failures are handled + */ +public class SetupTestFail extends TestCase { + + @Override + @Before + public void setUp(){ + fail("setUp()"); + } + + @Test + public void testSetUpFail(){ + // Dummy to ensure there is a test to run + } +} diff --git a/src/junit/test/TearDownTestFail.java b/src/junit/test/TearDownTestFail.java new file mode 100644 index 00000000000..d9c14cfbc01 --- /dev/null +++ b/src/junit/test/TearDownTestFail.java @@ -0,0 +1,36 @@ +/* + * 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 test; + +import junit.framework.TestCase; + +/** + * Test to demonstrate how tearDown failures are handled + */ +public class TearDownTestFail extends TestCase { + + @Override + public void tearDown(){ + fail("tearDown()"); + } + + public void testTearDownFail(){ + // Dummy to ensure there is a test to run + } +} diff --git a/src/junit/woolfel/DummyTestCase.java b/src/junit/woolfel/DummyTestCase.java new file mode 100644 index 00000000000..2523c6bb848 --- /dev/null +++ b/src/junit/woolfel/DummyTestCase.java @@ -0,0 +1,101 @@ +/* + * 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 woolfel; + +import java.util.concurrent.TimeUnit; + +import junit.framework.TestCase; + +public class DummyTestCase extends TestCase { + + public DummyTestCase() { + super(); + System.out.println("public DummyTestCase()"); + } + + protected DummyTestCase(String arg0) { + super(arg0); + System.out.println("protected DummyTestCase("+arg0+")"); + } + + @Override + public void setUp(){ + System.out.println("DummyTestCase#setup(): "+getName()); + } + + @Override + public void tearDown(){ + System.out.println("DummyTestCase#tearDown(): "+getName()); + } + + public void testMethodPass() { + try { + TimeUnit.MILLISECONDS.sleep(100); + assertEquals(10,10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void testMethodPass2() { + try { + TimeUnit.MILLISECONDS.sleep(100); + assertEquals("one","one"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void testMethodFail() { + try { + TimeUnit.MILLISECONDS.sleep(100); + assertEquals(20,10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void testMethodFail2() { + try { + TimeUnit.MILLISECONDS.sleep(100); + assertEquals("one","two"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // Normal test failure + public void testFail() { + fail("Test failure"); + } + + // Generate test error + public void testException() { + @SuppressWarnings("unused") + int i = 27 / 0; // will generate Divide by zero error + } + + public void testStringCompareFail(){ + assertEquals("this","that"); + } + + public void testObjectCompareFail(){ + assertEquals(new Object(),new Object()); + } +} diff --git a/src/junit/woolfel/SubDummyTest.java b/src/junit/woolfel/SubDummyTest.java new file mode 100644 index 00000000000..7fdf3cf07b8 --- /dev/null +++ b/src/junit/woolfel/SubDummyTest.java @@ -0,0 +1,40 @@ +/* + * 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 woolfel; + +public class SubDummyTest extends DummyTestCase { + + public SubDummyTest() { + super(); + System.out.println("public SubDummyTest()"); + } + + public SubDummyTest(String arg0) { + super(arg0); + System.out.println("public SubDummyTest("+arg0+")"); + } + + public void oneTimeSetUp() { + System.out.println("SubDummyTest#oneTimeSetUp(): "+getName()); + } + + public void oneTimeTearDown() { + System.out.println("SubDummyTest#oneTimeTearDown(): "+getName()); + } +} diff --git a/src/junit/woolfel/SubDummyTest2.java b/src/junit/woolfel/SubDummyTest2.java new file mode 100644 index 00000000000..dcbb7b22c73 --- /dev/null +++ b/src/junit/woolfel/SubDummyTest2.java @@ -0,0 +1,41 @@ +/* + * 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 woolfel; + +public class SubDummyTest2 extends DummyTestCase { + + @SuppressWarnings("unused") + private SubDummyTest2() { + super(); + System.out.println("private SubDummyTest2()"); + } + + public SubDummyTest2(String arg0) { + super(arg0); + System.out.println("public SubDummyTest2("+arg0+")"); + } + + public void oneTimeSetUp() { + System.out.println("SubDummyTest2#oneTimeSetUp(): "+getName()); + } + + public void oneTimeTearDown() { + System.out.println("SubDummyTest2#oneTimeTearDown(): "+getName()); + } +} diff --git a/src/make.bat b/src/make.bat deleted file mode 100755 index 217bffd2138..00000000000 --- a/src/make.bat +++ /dev/null @@ -1,54 +0,0 @@ -@echo off - -rem ###################################### -rem # Apache JMeter Makefile for Windows # -rem ###################################### - -rem ########### Set your favorite java compiler and javadoc engine here ############### - -set javac=jikes -O -set javadoc=javadoc -author -version - -rem ########### Do not touch below here ################# - -set cp=xcopy /Q -set rmdir=deltree /y -set noecho=> nul -set jar=jar cf0M - -echo Creating temp directory for classfiles... -%rmdir% temp %noecho% -mkdir temp - -echo Building classes... -%javac% -d .\temp .\org\apache\jmeter\timers\*.java .\org\apache\jmeter\visualisers\*.java .\org\apache\jmeter\*.java %noecho% - -echo Copying image files... -mkdir temp\org\apache\jmeter\images -%cp% org\apache\jmeter\images\*.* temp\org\apache\jmeter\images\*.* %noecho% - -echo Copying property files... -%cp% org\apache\jmeter\*.properties temp\org\apache\jmeter\images\*.properties %noecho% - -echo Creating Apache-JMeter.jar file... -%jar% ..\bin\Apache-JMeter.jar .\temp\. %noecho% - -echo Cleaning javadoc directory... -%rmdir% ..\docs\api %noecho% -mkdir ..\docs\api - -echo Building javadoc files... -%javadoc% -d ..\docs\api org.apache.jmeter org.apache.jmeter.timers org.apache.jmeter.visualisers %noecho% - -echo Removing temp directory... -%rmdir% temp %noecho% - -echo ...done - -rem ######### Cleanup variables ########### -set javac= -set javadoc= -set cp= -set rmdir= -set noecho= -set jar= \ No newline at end of file diff --git a/src/monitor/components/org/apache/jmeter/monitor/util/MemoryBenchmark.java b/src/monitor/components/org/apache/jmeter/monitor/util/MemoryBenchmark.java new file mode 100644 index 00000000000..a83f7cd6815 --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/monitor/util/MemoryBenchmark.java @@ -0,0 +1,130 @@ +/* + * 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.monitor.util; + +import java.util.List; +import java.util.LinkedList; + +import org.apache.jmeter.monitor.model.Connector; +import org.apache.jmeter.monitor.model.Jvm; +import org.apache.jmeter.monitor.model.Memory; +import org.apache.jmeter.monitor.model.ObjectFactory; +import org.apache.jmeter.monitor.model.RequestInfo; +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.monitor.model.ThreadInfo; +import org.apache.jmeter.monitor.model.Worker; +import org.apache.jmeter.monitor.model.Workers; +import org.apache.jmeter.visualizers.MonitorModel; +import org.apache.jmeter.visualizers.MonitorStats; + +/** + * + * @version $Revision$ + */ +public class MemoryBenchmark { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static void main(String[] args) { + if (args.length == 1) { + int objects = 10000; + if (args[0] != null) { + objects = Integer.parseInt(args[0]); + } + List objs = new LinkedList(); + ObjectFactory of = ObjectFactory.getInstance(); + + long bfree = Runtime.getRuntime().freeMemory(); + long btotal = Runtime.getRuntime().totalMemory(); + long bmax = Runtime.getRuntime().maxMemory(); + System.out.println("Before we create objects:"); + System.out.println("------------------------------"); + System.out.println("free: " + bfree); + System.out.println("total: " + btotal); + System.out.println("max: " + bmax); + + for (int idx = 0; idx < objects; idx++) { + Connector cnn = of.createConnector(); + Workers wkrs = of.createWorkers(); + for (int idz = 0; idz < 26; idz++) { + Worker wk0 = of.createWorker(); + wk0.setCurrentQueryString("/manager/status"); + wk0.setCurrentUri("http://localhost/manager/status"); + wk0.setMethod("GET"); + wk0.setProtocol("http"); + wk0.setRemoteAddr("?"); + wk0.setRequestBytesReceived(132); + wk0.setRequestBytesSent(18532); + wk0.setStage("K"); + wk0.setVirtualHost("?"); + wkrs.getWorker().add(wk0); + } + cnn.setWorkers(wkrs); + + RequestInfo rqinfo = of.createRequestInfo(); + rqinfo.setBytesReceived(0); + rqinfo.setBytesSent(434374); + rqinfo.setErrorCount(10); + rqinfo.setMaxTime(850); + rqinfo.setProcessingTime(2634); + rqinfo.setRequestCount(1002); + cnn.setRequestInfo(rqinfo); + + ThreadInfo thinfo = of.createThreadInfo(); + thinfo.setCurrentThreadCount(50); + thinfo.setCurrentThreadsBusy(12); + thinfo.setMaxSpareThreads(50); + thinfo.setMaxThreads(150); + thinfo.setMinSpareThreads(10); + cnn.setThreadInfo(thinfo); + + Jvm vm = of.createJvm(); + Memory mem = of.createMemory(); + mem.setFree(77280); + mem.setTotal(134210000); + mem.setMax(134217728); + vm.setMemory(mem); + + Status st = of.createStatus(); + st.setJvm(vm); + st.getConnector().add(cnn); + + MonitorStats mstats = new MonitorStats(Stats.calculateStatus(st), Stats.calculateLoad(st), 0, Stats + .calculateMemoryLoad(st), Stats.calculateThreadLoad(st), "localhost", "8080", "http", System + .currentTimeMillis()); + MonitorModel monmodel = new MonitorModel(mstats); + objs.add(monmodel); + } + long afree = Runtime.getRuntime().freeMemory(); + long atotal = Runtime.getRuntime().totalMemory(); + long amax = Runtime.getRuntime().maxMemory(); + long delta = ((atotal - afree) - (btotal - bfree)); + System.out.println("After we create objects:"); + System.out.println("------------------------------"); + System.out.println("free: " + afree); + System.out.println("total: " + atotal); + System.out.println("max: " + amax); + System.out.println("------------------------------"); + System.out.println("delta: " + (delta / 1024) + " kilobytes"); + System.out.println("delta: " + (delta / 1024 / 1024) + " megabytes"); + System.out.println("number of objects: " + objects); + System.out.println("potential number of servers: " + (objects / 1000)); + + } else { + System.out.println("Please provide the number of objects"); + } + } +} diff --git a/src/monitor/components/org/apache/jmeter/monitor/util/Stats.java b/src/monitor/components/org/apache/jmeter/monitor/util/Stats.java new file mode 100644 index 00000000000..fed70de10e9 --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/monitor/util/Stats.java @@ -0,0 +1,193 @@ +/* + * 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.monitor.util; + +import org.apache.jmeter.monitor.model.Connector; +import org.apache.jmeter.monitor.model.Status; + +/** + * + * Description: + *

+ * Stats is responsible for calculating the load and health of a given server. + * It uses tomcat's status servlet results. A schema was generated for the XML + * output and JAXB was used to generate classes. + *

+ * The equations are: + *

+ * memory weight = (int)(50 * (free/max))
+ * thread weight = (int)(50 * (current/max)) + *

+ * The load factors are stored in the properties files. Simply change the values + * in the properties to change how load is calculated. The defaults values are + * memory (50) and threads (50). The sum of the factors must equal 100. + */ +public class Stats { + + public static final int DEAD = 0; + + public static final int ACTIVE = 2; + + public static final int WARNING = 1; + + public static final int HEALTHY = 3; + + public static final int DEFAULT_MEMORY_FACTOR = 50; + + public static final int DEFAULT_THREAD_FACTOR = 50; + + public static final double HEALTHY_PER = 0.00; + + public static final double ACTIVE_PER = 0.25; + + public static final double WARNING_PER = 0.67; + + /** + * The method is responsible for taking a status object and calculating an + * int value from 1 to 100. We use a combination of free memory and free + * threads. The current factor is 50/50. + *

+ * + * @param stat status information about the server + * @return calculated load value + */ + public static int calculateLoad(Status stat) { + if (stat != null) { + // equation for calculating the weight + // w = (int)(33 * (used/max)) + long totMem = stat.getJvm().getMemory().getTotal(); + long freeMem = stat.getJvm().getMemory().getFree(); + long usedMem = totMem - freeMem; + double memdiv = (double) usedMem / (double) totMem; + double memWeight = DEFAULT_MEMORY_FACTOR * memdiv; + + // changed the logic for BEA Weblogic in the case a + // user uses Tomcat's status servlet without any + // modifications. Weblogic will return nothing for + // the connector, therefore we need to check the size + // of the list. Peter 12.22.04 + double threadWeight = 0; + if (stat.getConnector().size() > 0) { + Connector cntr = fetchConnector(stat); + int maxThread = cntr.getThreadInfo().getMaxThreads(); + int curThread = cntr.getThreadInfo().getCurrentThreadsBusy(); + double thdiv = (double) curThread / (double) maxThread; + threadWeight = DEFAULT_THREAD_FACTOR * thdiv; + } + return (int) (memWeight + threadWeight); + } else { + return 0; + } + } + + /** + * Method should calculate if the server is: dead, active, warning or + * healthy. We do this by looking at the current busy threads. + *

    + *
  1. free > spare is {@link Stats#HEALTHY healthy}
  2. + *
  3. free < spare is {@link Stats#ACTIVE active}
  4. + *
  5. busy threads > 75% is {@link Stats#WARNING warning}
  6. + *
  7. none of the above is {@link Stats#DEAD dead}
  8. + *
+ * + * @param stat status information about the server + * @return integer representing the status (one of {@link Stats#HEALTHY + * HEALTHY}, {@link Stats#ACTIVE ACTIVE}, {@link Stats#WARNING + * WARNING} or {@link Stats#DEAD DEAD}) + */ + public static int calculateStatus(Status stat) { + if (stat != null && stat.getConnector().size() > 0) { + Connector cntr = fetchConnector(stat); + int max = cntr.getThreadInfo().getMaxThreads(); + int current = cntr.getThreadInfo().getCurrentThreadsBusy(); + // int spare = cntr.getThreadInfo().getMaxSpareThreads(); + double per = (double) current / (double) max; + if (per > WARNING_PER) { + return WARNING; + } else if (per >= ACTIVE_PER && per <= WARNING_PER) { + return ACTIVE; + } else if (per < ACTIVE_PER && per >= HEALTHY_PER) { + return HEALTHY; + } else { + return DEAD; + } + } else { + return DEAD; + } + } + + /** + * Method will calculate the memory load: used / max = load. The load value + * is an integer between 1 and 100. It is the percent memory used. Changed + * this to be more like other system monitors. Peter Lin 2-11-05 + * + * @param stat status information about the jvm + * @return memory load + */ + public static int calculateMemoryLoad(Status stat) { + double load = 0; + if (stat != null) { + double total = stat.getJvm().getMemory().getTotal(); + double free = stat.getJvm().getMemory().getFree(); + double used = total - free; + load = (used / total); + } + return (int) (load * 100); + } + + /** + * Method will calculate the thread load: busy / max = load. The value is an + * integer between 1 and 100. It is the percent busy. + * + * @param stat status information about the server + * @return thread load + */ + public static int calculateThreadLoad(Status stat) { + int load = 0; + if (stat != null && stat.getConnector().size() > 0) { + Connector cntr = fetchConnector(stat); + double max = cntr.getThreadInfo().getMaxThreads(); + double current = cntr.getThreadInfo().getCurrentThreadsBusy(); + load = (int) ((current / max) * 100); + } + return load; + } + + /** + * Method to get connector to use for calculate server status + * + * @param stat + * @return connector + */ + private static Connector fetchConnector(Status stat) { + Connector cntr = null; + String connectorPrefix = stat.getConnectorPrefix(); + if (connectorPrefix != null && connectorPrefix.length() > 0) { + // loop to fetch desired connector + for (int i = 0; i < stat.getConnector().size(); i++) { + cntr = stat.getConnector().get(i); + if (cntr.getName().startsWith(connectorPrefix)) { + return cntr; + } + } + } + // default : get first connector + cntr = stat.getConnector().get(0); + return cntr; + } + +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorAccumModel.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorAccumModel.java new file mode 100644 index 00000000000..d4227260772 --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorAccumModel.java @@ -0,0 +1,248 @@ +/* + * 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.visualizers; + +import java.io.Serializable; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.monitor.model.ObjectFactory; +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.monitor.util.Stats; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; + +public class MonitorAccumModel implements Clearable, Serializable { + + private static final long serialVersionUID = 240L; + + private final Map> serverListMap; + + /** + * we use this to set the current monitorModel so that we can save the stats + * to the resultcolllector. + */ + private MonitorModel current; + + private final List listeners; + + /** + * By default, we set the default to 800 + */ + private int defaultBufferSize = 800; + + // optional connector name prefix + private String connectorPrefix = null; + + /** + * + */ + public MonitorAccumModel() { + serverListMap = new HashMap>(); + listeners = new LinkedList(); + } + + public int getBufferSize() { + return defaultBufferSize; + } + + public void setBufferSize(int buffer) { + defaultBufferSize = buffer; + } + + public void setPrefix(String prefix) { + connectorPrefix = prefix; + } + + /** + * Added this method we that we can save the calculated stats. + * + * @return current sample + */ + public MonitorModel getLastSample() { + return this.current; + } + + /** + * Method will look up the server in the map. The MonitorModel will be added + * to an existing list, or a new one will be created. + * + * @param model the {@link MonitorModel} to be added + */ + public void addSample(MonitorModel model) { + this.current = model; + if (serverListMap.containsKey(model.getURL())) { + List newlist = updateArray(model, serverListMap.get(model.getURL())); + serverListMap.put(model.getURL(), newlist); + } else { + List samples = Collections.synchronizedList(new LinkedList()); + samples.add(model); + serverListMap.put(model.getURL(), samples); + } + } + + /** + * We want to keep only 240 entries for each server, so we handle the object + * array ourselves. + * + * @param model + */ + private List updateArray(MonitorModel model, List list) { + if (list.size() < defaultBufferSize) { + list.add(model); + } else { + list.add(model); + list.remove(0); + } + return list; + } + + /** + * Get all MonitorModels matching the URL. + * + * @param url to be matched against + * @return list + */ + public List getAllSamples(String url) { + if (!serverListMap.containsKey(url)) { + return Collections.synchronizedList(new LinkedList()); + } else { + return serverListMap.get(url); + } + } + + /** + * Get the MonitorModel matching the url. + * + * @param url + * to be matched against + * @return the first {@link MonitorModel} registered for this + * url + */ + public MonitorModel getSample(String url) { + if (serverListMap.containsKey(url)) { + return serverListMap.get(url).get(0); + } else { + return null; + } + } + + /** + * Method will try to parse the response data. If the request was a monitor + * request, but the response was incomplete, bad or the server refused the + * connection, we will set the server's health to "dead". If the request was + * not a monitor sample, the method will ignore it. + * + * @param sample + * {@link SampleResult} with the result of the status request + */ + public void addSample(SampleResult sample) { + URL surl = null; + if (sample instanceof HTTPSampleResult) { + surl = ((HTTPSampleResult) sample).getURL(); + // String rescontent = new String(sample.getResponseData()); + if (sample.isResponseCodeOK() && ((HTTPSampleResult) sample).isMonitor()) { + ObjectFactory of = ObjectFactory.getInstance(); + Status st = of.parseBytes(sample.getResponseData()); + st.setConnectorPrefix(connectorPrefix); + if (surl != null) {// surl can be null if read from a file + MonitorStats stat = new MonitorStats(Stats.calculateStatus(st), Stats.calculateLoad(st), 0, Stats + .calculateMemoryLoad(st), Stats.calculateThreadLoad(st), surl.getHost(), String.valueOf(surl + .getPort()), surl.getProtocol(), System.currentTimeMillis()); + MonitorModel mo = new MonitorModel(stat); + this.addSample(mo); + notifyListeners(mo); + } + // This part of code throws NullPointerException + // Don't think Monitor results can be loaded from files + // see https://bz.apache.org/bugzilla/show_bug.cgi?id=51810 +// else { +// noResponse(surl); +// } + } else if (((HTTPSampleResult) sample).isMonitor()) { + noResponse(surl); + } + } + } + + /** + * If there is no response from the server, we create a new MonitorStats + * object with the current timestamp and health "dead". + * + * @param url + * URL from where the status should have come + */ + public void noResponse(URL url) { + notifyListeners(createNewMonitorModel(url)); + } + + /** + * Method will return a new MonitorModel object with the given URL. This is + * used when the server fails to respond fully, or is dead. + * + * @param url + * URL from where the status should have come + * @return new MonitorModel + */ + public MonitorModel createNewMonitorModel(URL url) { + MonitorStats stat = new MonitorStats(Stats.DEAD, 0, 0, 0, 0, url.getHost(), String.valueOf(url.getPort()), url + .getProtocol(), System.currentTimeMillis()); + return new MonitorModel(stat); + } + + /** + * Clears everything except the listener. Do not clear the listeners. If we + * clear listeners, subsequent "run" will not notify the gui of data + * changes. + */ + @Override + public void clearData() { + for (List modelList : this.serverListMap.values()) { + modelList.clear(); + } + this.serverListMap.clear(); + } + + /** + * notify the listeners with the MonitorModel object. + * + * @param model + * the {@link MonitorModel} that should be sent to the listeners + */ + public void notifyListeners(MonitorModel model) { + for (int idx = 0; idx < listeners.size(); idx++) { + MonitorListener ml = listeners.get(idx); + ml.addSample(model); + } + } + + /** + * Add a listener. When samples are added, the class will notify the + * listener of the change. + * + * @param listener + * the {@link MonitorListener} that should be added + */ + public void addListener(MonitorListener listener) { + listeners.add(listener); + } +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorGraph.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorGraph.java new file mode 100644 index 00000000000..dab57dc9549 --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorGraph.java @@ -0,0 +1,208 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Graphics; +import java.util.Iterator; +import java.util.List; + +import javax.swing.JComponent; + +import org.apache.jmeter.samplers.Clearable; + +/** + * MonitorGraph will draw the performance history of a given server. It displays + * 4 lines: + */ +public class MonitorGraph extends JComponent implements MonitorGuiListener, Clearable { + + private static final long serialVersionUID = 240L; + + private final MonitorAccumModel model; + + private MonitorModel current; + + private boolean drawHealth = true; + + private boolean drawLoad = true; + + private boolean drawMemory = true; + + private boolean drawThread = true; + + // TODO field always true, what for ? + private final boolean drawYgrid = true; + + // TODO field always true, what for ? + private final boolean drawXgrid = true; + + /** + * Needed for Serialization tests. + * @deprecated Only for use in unit testing + */ + @Deprecated + public MonitorGraph() { + // log.warn("Only for use in unit testing"); + model = null; + } + + public MonitorGraph(MonitorAccumModel model) { + this.model = model; + repaint(); + } + + public void setHealth(boolean health) { + this.drawHealth = health; + } + + public void setLoad(boolean load) { + this.drawLoad = load; + } + + public void setMem(boolean mem) { + this.drawMemory = mem; + } + + public void setThread(boolean thread) { + this.drawThread = thread; + } + + /** + * The method will first check to see if the graph is visible. If it is, it + * will repaint the graph. + */ + @Override + public void updateGui(final MonitorModel model) { + if (this.isShowing()) { + this.current = model; + repaint(); + } + } + + /** + * painComponent is responsible for drawing the actual graph. This is + * because of how screen works. Tried to use clipping, but I don't + * understand it well enough to get the desired effect. + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + if (this.current != null) { + synchronized (model) { + List samples = model.getAllSamples(this.current.getURL()); + int size = samples.size(); + synchronized (samples) { + Iterator e; + if (size > getWidth()) { + e = samples.listIterator(size - getWidth()); + } else { + e = samples.iterator(); + } + MonitorModel last = null; + for (int i = 0; e.hasNext(); i++) { + MonitorModel s = e.next(); + if (last == null) { + last = s; + } + drawSample(i, s, g, last); + last = s; + } + } + } + } + } + + /** + * updateGui() will call repaint + */ + @Override + public void updateGui() { + repaint(); + } + + /** + * clear will repaint the graph + */ + @Override + public void clearData() { + paintComponent(getGraphics()); + this.repaint(); + } + + private void drawSample(int x, MonitorModel model, Graphics g, MonitorModel last) { + double width = getWidth(); + double height = getHeight() - 10.0; + int xaxis = (int) (width * (x / width)); + int lastx = (int) (width * ((x - 1) / width)); + + // draw grid only when x is 1. If we didn't + // the grid line would draw over the data + // lines making it look bad. + if (drawYgrid && x == 1) { + int q1 = (int) (height * 0.25); + int q2 = (int) (height * 0.50); + int q3 = (int) (height * 0.75); + g.setColor(Color.lightGray); + g.drawLine(0, q1, getWidth(), q1); + g.drawLine(0, q2, getWidth(), q2); + g.drawLine(0, q3, getWidth(), q3); + } + if (drawXgrid && x == 1) { + int x1 = (int) (width * 0.25); + int x2 = (int) (width * 0.50); + int x3 = (int) (width * 0.75); + g.drawLine(x1, 0, x1, getHeight()); + g.drawLine(x2, 0, x2, getHeight()); + g.drawLine(x3, 0, x3, getHeight()); + g.drawLine(getWidth(), 0, getWidth(), getHeight()); + } + + if (drawHealth) { + int hly = (int) (height - (height * (model.getHealth() / 3.0))); + int lasty = (int) (height - (height * (last.getHealth() / 3.0))); + + g.setColor(Color.green); + g.drawLine(lastx, lasty, xaxis, hly); + } + + if (drawLoad) { + int ldy = (int) (height - (height * (model.getLoad() / 100.0))); + int lastldy = (int) (height - (height * (last.getLoad() / 100.0))); + + g.setColor(Color.blue); + g.drawLine(lastx, lastldy, xaxis, ldy); + } + + if (drawMemory) { + int mmy = (int) (height - (height * (model.getMemload() / 100.0))); + int lastmmy = (int) (height - (height * (last.getMemload() / 100.0))); + + g.setColor(Color.orange); + g.drawLine(lastx, lastmmy, xaxis, mmy); + } + + if (drawThread) { + int thy = (int) (height - (height * (model.getThreadload() / 100.0))); + int lastthy = (int) (height - (height * (last.getThreadload() / 100.0))); + + g.setColor(Color.red); + g.drawLine(lastx, lastthy, xaxis, thy); + } + } + +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorGuiListener.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorGuiListener.java new file mode 100644 index 00000000000..8ce5ed21cfc --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorGuiListener.java @@ -0,0 +1,23 @@ +/* + * 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.visualizers; + +public interface MonitorGuiListener { + void updateGui(MonitorModel event); + + void updateGui(); +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorHealthPanel.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorHealthPanel.java new file mode 100644 index 00000000000..860dcdcb30c --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorHealthPanel.java @@ -0,0 +1,147 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The health panel is responsible for showing the health of the servers. It + * only uses the most current information to show the status. + */ +public class MonitorHealthPanel extends JPanel implements MonitorListener, Clearable { + private static final long serialVersionUID = 240L; + + private final Map serverPanelMap = new HashMap(); + + private JPanel servers = null; + + private final MonitorAccumModel model; + + private JScrollPane serverScrollPane = null; + + // NOTUSED Font plainText = new Font("plain", Font.PLAIN, 9); + // These must not be static, otherwise Language change does not work + private final String INFO_H = JMeterUtils.getResString("monitor_equation_healthy"); //$NON-NLS-1$ + + private final String INFO_A = JMeterUtils.getResString("monitor_equation_active"); //$NON-NLS-1$ + + private final String INFO_W = JMeterUtils.getResString("monitor_equation_warning"); //$NON-NLS-1$ + + private final String INFO_D = JMeterUtils.getResString("monitor_equation_dead"); //$NON-NLS-1$ + + private final String INFO_LOAD = JMeterUtils.getResString("monitor_equation_load"); //$NON-NLS-1$ + + /** + * + * @deprecated Only for use in unit testing + */ + @Deprecated + public MonitorHealthPanel() { + // log.warn("Only for use in unit testing"); + model = null; + } + + /** + * @param model model to use + * + */ + public MonitorHealthPanel(MonitorAccumModel model) { + this.model = model; + this.model.addListener(this); + init(); + } + + /** + * init is responsible for creating the necessary legends and information + * for the health panel. + */ + private void init() {// called from ctor, so must not be overridable + this.setLayout(new BorderLayout()); + ImageIcon legend = JMeterUtils.getImage("monitor-legend.gif"); // I18N: Contains fixed English text ... + JLabel label = new JLabel(legend); + label.setPreferredSize(new Dimension(550, 25)); + this.add(label, BorderLayout.NORTH); + + this.servers = new JPanel(); + this.servers.setLayout(new BoxLayout(servers, BoxLayout.Y_AXIS)); + this.servers.setAlignmentX(Component.LEFT_ALIGNMENT); + + serverScrollPane = new JScrollPane(this.servers); + serverScrollPane.setPreferredSize(new Dimension(300, 300)); + this.add(serverScrollPane, BorderLayout.CENTER); + + // the equations + String eqstring1 = " " + INFO_H + " | " + INFO_A; + String eqstring2 = " " + INFO_W + " | " + INFO_D; + String eqstring3 = " " + INFO_LOAD; + JLabel eqs = new JLabel(); + eqs.setLayout(new BorderLayout()); + eqs.setPreferredSize(new Dimension(500, 60)); + eqs.add(new JLabel(eqstring1), BorderLayout.NORTH); + eqs.add(new JLabel(eqstring2), BorderLayout.CENTER); + eqs.add(new JLabel(eqstring3), BorderLayout.SOUTH); + this.add(eqs, BorderLayout.SOUTH); + } + + /** + * + * @param model information about monitored server + */ + @Override + public void addSample(MonitorModel model) { + if (serverPanelMap.containsKey(model.getURL())) { + ServerPanel pane = null; + if (serverPanelMap.get(model.getURL()) != null) { + pane = serverPanelMap.get((model.getURL())); + } else { + pane = new ServerPanel(model); + serverPanelMap.put(model.getURL(), pane); + } + pane.updateGui(model); + } else { + ServerPanel newpane = new ServerPanel(model); + serverPanelMap.put(model.getURL(), newpane); + this.servers.add(newpane); + newpane.updateGui(model); + } + this.servers.updateUI(); + } + + /** + * clear will clear the hashmap, remove all ServerPanels from the servers + * pane, and update the ui. + */ + @Override + public void clearData() { + this.serverPanelMap.clear(); + this.servers.removeAll(); + this.servers.updateUI(); + } +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorHealthVisualizer.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorHealthVisualizer.java new file mode 100644 index 00000000000..97dd446667f --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorHealthVisualizer.java @@ -0,0 +1,195 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Graphics; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.Image; + +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * For performance reasons, I am using tabs for the visualizers. Since a + * visualizer is heavy weight, I don not want to have two separate result + * collectors rather the same information. Instead, I would rather have the + * visualizer be the container for the data and simply pass the data to child + * JComponents. In the future, we may want to add email alerts as a third tab. + */ +public class MonitorHealthVisualizer extends AbstractVisualizer implements ImageVisualizer, ItemListener, + GraphListener, Clearable { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String CONNECTOR_PREFIX = "connector.prefix"; // $NON-NLS-1$ + private static final String CONNECTOR_PREFIX_DEFAULT = ""; // $NON-NLS-1$ + + private static final String BUFFER = "monitor.buffer.size"; // $NON-NLS-1$ + + private MonitorTabPane tabPane; + + private MonitorHealthPanel healthPane; + + private MonitorPerformancePanel perfPane; + + private MonitorAccumModel model; + + private MonitorGraph graph; + + private JLabeledTextField prefixField; + + /** + * Constructor for the GraphVisualizer object. + */ + public MonitorHealthVisualizer() { + this.isStats = true; + initModel(); + init(); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + prefixField.setText(el.getPropertyAsString(CONNECTOR_PREFIX, CONNECTOR_PREFIX_DEFAULT)); + model.setPrefix(prefixField.getText()); + } + + @Override + public void modifyTestElement(TestElement c) { + super.modifyTestElement(c); + c.setProperty(CONNECTOR_PREFIX,prefixField.getText(),CONNECTOR_PREFIX_DEFAULT); + model.setPrefix(prefixField.getText()); + } + + private void initModel() { + model = new MonitorAccumModel(); + graph = new MonitorGraph(model); + model.setBufferSize(JMeterUtils.getPropDefault(BUFFER, 800)); + } + + @Override + public String getLabelResource() { + return "monitor_health_title"; // $NON-NLS-1$ + } + + /** + * Because of the unique requirements of a monitor We have to handle the + * results differently than normal GUI components. A monitor should be able + * to run for a very long time without eating up all the memory. + */ + @Override + public void add(SampleResult res) { + model.addSample(res); + try { + collector.recordStats(this.model.getLastSample().cloneMonitorStats()); + } catch (Exception e) { + // for now just swallow the exception + log.debug("StatsModel was null", e); + } + } + + @Override + public Image getImage() { + Image result = graph.createImage(this.getWidth(), this.getHeight()); + Graphics image = result.getGraphics(); + graph.paintComponent(image); + return result; + } + + @Override + public void itemStateChanged(ItemEvent e) { + } + + @Override + public synchronized void updateGui() { + this.repaint(); + } + + @Override + public synchronized void updateGui(Sample s) { + this.repaint(); + } + + /** + * Initialize the GUI. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + Border margin = new EmptyBorder(10, 10, 5, 10); + this.setBorder(margin); + + // Add the main panel and the graph + this.add(this.makeTitlePanel(), BorderLayout.NORTH); + this.createTabs(); + prefixField = new JLabeledTextField(JMeterUtils.getResString("monitor_label_prefix")); // $NON-NLS-1$ + add(prefixField, BorderLayout.SOUTH); + } + + private void createTabs() { + tabPane = new MonitorTabPane(); + createHealthPane(tabPane); + createPerformancePane(tabPane); + this.add(tabPane, BorderLayout.CENTER); + } + + /** + * Create the JPanel + * + * @param pane + */ + private void createHealthPane(MonitorTabPane pane) { + healthPane = new MonitorHealthPanel(model); + pane.addTab(JMeterUtils.getResString("monitor_health_tab_title"), healthPane); // $NON-NLS-1$ + } + + /** + * Create the JSplitPane for the performance history + * + * @param pane + */ + private void createPerformancePane(MonitorTabPane pane) { + perfPane = new MonitorPerformancePanel(model, graph); + pane.addTab(JMeterUtils.getResString("monitor_performance_tab_title"), perfPane); // $NON-NLS-1$ + } + + /** + * Clears the MonitorAccumModel. + */ + @Override + public void clearData() { + this.model.clearData(); + this.healthPane.clearData(); + this.perfPane.clearData(); + } + +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorListener.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorListener.java new file mode 100644 index 00000000000..7776834c740 --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorListener.java @@ -0,0 +1,21 @@ +/* + * 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.visualizers; + +public interface MonitorListener { + void addSample(MonitorModel model); +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorModel.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorModel.java new file mode 100644 index 00000000000..c5d6f9dc8c7 --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorModel.java @@ -0,0 +1,128 @@ +/* + * 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.visualizers; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.jmeter.samplers.Clearable; + +public class MonitorModel implements Clearable, Serializable, Cloneable { + + private static final long serialVersionUID = 240L; + + private MonitorStats current = new MonitorStats(0, 0, 0, 0, 0, "", "", "", System.currentTimeMillis()); + + /** + * + */ + public MonitorModel() { + super(); + } + + public MonitorModel(MonitorStats stat) { + this.current = stat; + } + + public int getHealth() { + return this.current.getHealth(); + } + + public int getLoad() { + return this.current.getLoad(); + } + + public int getCpuload() { + return this.current.getCpuLoad(); + } + + public int getMemload() { + return this.current.getMemLoad(); + } + + public int getThreadload() { + return this.current.getThreadLoad(); + } + + public String getHost() { + return this.current.getHost(); + } + + public String getPort() { + return this.current.getPort(); + } + + public String getProtocol() { + return this.current.getProtocol(); + } + + public long getTimestamp() { + return this.current.getTimeStamp(); + } + + public String getURL() { + return this.current.getURL(); + } + + /** + * Method will return a formatted date using SimpleDateFormat. + * + * @return String + */ + public String getTimestampString() { + Date date = new Date(this.current.getTimeStamp()); + SimpleDateFormat ft = new SimpleDateFormat(); + return ft.format(date); + } + + /** + * Method is used by DefaultMutableTreeNode to get the label for the node. + */ + @Override + public String toString() { + return getURL(); + } + + /** + * clear will create a new MonitorStats object. + */ + @Override + public void clearData() { + current = new MonitorStats(0, 0, 0, 0, 0, "", "", "", System.currentTimeMillis()); + } + + /** + * a clone method is provided for convienance. In some cases, it may be + * desirable to clone the object. + */ + @Override + public Object clone() { + return new MonitorModel(cloneMonitorStats()); + } + + /** + * a clone method to clone the stats + * + * @return new instance of the class + */ + public MonitorStats cloneMonitorStats() { + return new MonitorStats(current.getHealth(), current.getLoad(), current.getCpuLoad(), current.getMemLoad(), + current.getThreadLoad(), current.getHost(), current.getPort(), current.getProtocol(), current + .getTimeStamp()); + } +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorPerformancePanel.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorPerformancePanel.java new file mode 100644 index 00000000000..5770b1455ed --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorPerformancePanel.java @@ -0,0 +1,308 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeSelectionModel; + +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +public class MonitorPerformancePanel extends JSplitPane implements TreeSelectionListener, MonitorListener, Clearable { + + private static final long serialVersionUID = 240L; + + private JScrollPane TREEPANE; + + private JPanel GRAPHPANEL; + + private JTree SERVERTREE; + + private DefaultTreeModel TREEMODEL; + + private final MonitorGraph GRAPH; + + private DefaultMutableTreeNode ROOTNODE; + + private final Map SERVERMAP; + + private final MonitorAccumModel MODEL; + + private SampleResult ROOTSAMPLE; + + // Don't make these static, otherwise language change does not work + private final String LEGEND_HEALTH = JMeterUtils.getResString("monitor_legend_health"); //$NON-NLS-1$ + + private final String LEGEND_LOAD = JMeterUtils.getResString("monitor_legend_load"); //$NON-NLS-1$ + + private final String LEGEND_MEM = JMeterUtils.getResString("monitor_legend_memory_per"); //$NON-NLS-1$ + + private final String LEGEND_THREAD = JMeterUtils.getResString("monitor_legend_thread_per"); //$NON-NLS-1$ + + private final ImageIcon LEGEND_HEALTH_ICON = JMeterUtils.getImage("monitor-green-legend.gif"); //$NON-NLS-1$ + + private final ImageIcon LEGEND_LOAD_ICON = JMeterUtils.getImage("monitor-blue-legend.gif"); //$NON-NLS-1$ + + private final ImageIcon LEGEND_MEM_ICON = JMeterUtils.getImage("monitor-orange-legend.gif"); //$NON-NLS-1$ + + private final ImageIcon LEGEND_THREAD_ICON = JMeterUtils.getImage("monitor-red-legend.gif"); //$NON-NLS-1$ + + private final String GRID_LABEL_TOP = JMeterUtils.getResString("monitor_label_left_top"); //$NON-NLS-1$ + + private final String GRID_LABEL_MIDDLE = JMeterUtils.getResString("monitor_label_left_middle"); //$NON-NLS-1$ + + private final String GRID_LABEL_BOTTOM = JMeterUtils.getResString("monitor_label_left_bottom"); //$NON-NLS-1$ + + private final String GRID_LABEL_HEALTHY = JMeterUtils.getResString("monitor_label_right_healthy"); //$NON-NLS-1$ + +// private final String GRID_LABEL_ACTIVE = JMeterUtils.getResString("monitor_label_right_active"); //$NON-NLS-1$ + +// private final String GRID_LABEL_WARNING = JMeterUtils.getResString("monitor_label_right_warning"); //$NON-NLS-1$ + + private final String GRID_LABEL_DEAD = JMeterUtils.getResString("monitor_label_right_dead"); //$NON-NLS-1$ + + private final String PERF_TITLE = JMeterUtils.getResString("monitor_performance_title"); //$NON-NLS-1$ + + private final String SERVER_TITLE = JMeterUtils.getResString("monitor_performance_servers"); //$NON-NLS-1$ + + private Font plaintext = new Font("plain", Font.TRUETYPE_FONT, 10); //$NON-NLS-1$ + + /** + * + * @deprecated Only for use in unit testing + */ + @Deprecated + public MonitorPerformancePanel() { + // log.warn("Only for use in unit testing"); + SERVERMAP = null; + MODEL = null; + GRAPH = null; + } + + /** + * @param model model to use + * @param graph graph to use + * + */ + public MonitorPerformancePanel(MonitorAccumModel model, MonitorGraph graph) { + super(); + this.SERVERMAP = new HashMap(); + this.MODEL = model; + this.MODEL.addListener(this); + this.GRAPH = graph; + init(); + } + + /** + * init() will create all the necessary swing panels, labels and icons for + * the performance panel. + */ + private void init() {// called from ctor, so must not be overridable + ROOTSAMPLE = new SampleResult(); + ROOTSAMPLE.setSampleLabel(SERVER_TITLE); + ROOTSAMPLE.setSuccessful(true); + ROOTNODE = new DefaultMutableTreeNode(ROOTSAMPLE); + TREEMODEL = new DefaultTreeModel(ROOTNODE); + SERVERTREE = new JTree(TREEMODEL); + SERVERTREE.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + SERVERTREE.addTreeSelectionListener(this); + SERVERTREE.setShowsRootHandles(true); + TREEPANE = new JScrollPane(SERVERTREE); + TREEPANE.setPreferredSize(new Dimension(150, 200)); + this.add(TREEPANE, JSplitPane.LEFT); + this.setDividerLocation(0.18); + + JPanel right = new JPanel(); + right.setLayout(new BorderLayout()); + JLabel title = new JLabel(" " + PERF_TITLE); + title.setPreferredSize(new Dimension(200, 40)); + GRAPHPANEL = new JPanel(); + GRAPHPANEL.setLayout(new BorderLayout()); + GRAPHPANEL.setMaximumSize(new Dimension(MODEL.getBufferSize(), MODEL.getBufferSize())); + GRAPHPANEL.setBackground(Color.white); + GRAPHPANEL.add(GRAPH, BorderLayout.CENTER); + right.add(GRAPHPANEL, BorderLayout.CENTER); + + right.add(title, BorderLayout.NORTH); + right.add(createLegend(), BorderLayout.SOUTH); + right.add(createLeftGridLabels(), BorderLayout.WEST); + right.add(createRightGridLabels(), BorderLayout.EAST); + this.add(right, JSplitPane.RIGHT); + } + + /** + * Method will create the legends at the bottom of the performance tab + * explaining the meaning of each line. + * + * @return JPanel + */ + private JPanel createLegend() { + Dimension lsize = new Dimension(130, 18); + + JPanel legend = new JPanel(); + legend.setLayout(new FlowLayout()); + + JLabel load = new JLabel(LEGEND_LOAD); + load.setFont(plaintext); + load.setPreferredSize(lsize); + load.setIcon(LEGEND_LOAD_ICON); + legend.add(load); + + JLabel mem = new JLabel(LEGEND_MEM); + mem.setFont(plaintext); + mem.setPreferredSize(lsize); + mem.setIcon(LEGEND_MEM_ICON); + legend.add(mem); + + JLabel thd = new JLabel(LEGEND_THREAD); + thd.setFont(plaintext); + thd.setPreferredSize(lsize); + thd.setIcon(LEGEND_THREAD_ICON); + legend.add(thd); + + JLabel health = new JLabel(LEGEND_HEALTH); + health.setFont(plaintext); + health.setPreferredSize(lsize); + health.setIcon(LEGEND_HEALTH_ICON); + legend.add(health); + + return legend; + } + + /** + * Method is responsible for creating the left grid labels. + * + * @return JPanel + */ + private JPanel createLeftGridLabels() { + Dimension lsize = new Dimension(33, 20); + JPanel labels = new JPanel(); + labels.setLayout(new BorderLayout()); + + JLabel top = new JLabel(" " + GRID_LABEL_TOP); + top.setFont(plaintext); + top.setPreferredSize(lsize); + labels.add(top, BorderLayout.NORTH); + + JLabel mid = new JLabel(" " + GRID_LABEL_MIDDLE); + mid.setFont(plaintext); + mid.setPreferredSize(lsize); + labels.add(mid, BorderLayout.CENTER); + + JLabel bottom = new JLabel(" " + GRID_LABEL_BOTTOM); + bottom.setFont(plaintext); + bottom.setPreferredSize(lsize); + labels.add(bottom, BorderLayout.SOUTH); + return labels; + } + + /** + * Method is responsible for creating the grid labels on the right for + * "healthy" and "dead" + * + * @return JPanel + */ + private JPanel createRightGridLabels() { + JPanel labels = new JPanel(); + labels.setLayout(new BorderLayout()); + labels.setPreferredSize(new Dimension(40, GRAPHPANEL.getWidth() - 100)); + Dimension lsize = new Dimension(40, 20); + JLabel h = new JLabel(GRID_LABEL_HEALTHY); + h.setFont(plaintext); + h.setPreferredSize(lsize); + labels.add(h, BorderLayout.NORTH); + + JLabel d = new JLabel(GRID_LABEL_DEAD); + d.setFont(plaintext); + d.setPreferredSize(lsize); + labels.add(d, BorderLayout.SOUTH); + return labels; + } + + /** + * MonitorAccumModel will call this method to notify the component data has + * changed. + */ + @Override + public synchronized void addSample(MonitorModel model) { + if (!SERVERMAP.containsKey(model.getURL())) { + DefaultMutableTreeNode newnode = new DefaultMutableTreeNode(model); + newnode.setAllowsChildren(false); + SERVERMAP.put(model.getURL(), newnode); + ROOTNODE.add(newnode); + this.TREEPANE.updateUI(); + } + DefaultMutableTreeNode node = (DefaultMutableTreeNode) SERVERTREE.getLastSelectedPathComponent(); + if (node != null) { + Object usrobj = node.getUserObject(); + if (usrobj instanceof MonitorModel) { + GRAPH.updateGui((MonitorModel) usrobj); + } + } + } + + /** + * When the user selects a different node in the tree, we get the selected + * node. From the node, we get the UserObject used to create the treenode in + * the constructor. + */ + @Override + public void valueChanged(TreeSelectionEvent e) { + // we check to see if the lastSelectedPath is null + // after we clear, it would return null + if (SERVERTREE.getLastSelectedPathComponent() != null) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) SERVERTREE.getLastSelectedPathComponent(); + Object usrobj = node.getUserObject(); + if (usrobj instanceof MonitorModel) { + MonitorModel mo = (MonitorModel) usrobj; + GRAPH.updateGui(mo); + this.updateUI(); + } + TREEPANE.updateUI(); + } + } + + /** + * clear will remove all child nodes from the ROOTNODE, clear the HashMap, + * update the graph and jpanel for the server tree. + */ + @Override + public void clearData() { + this.SERVERMAP.clear(); + ROOTNODE.removeAllChildren(); + SERVERTREE.updateUI(); + GRAPH.clearData(); + } +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorStats.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorStats.java new file mode 100644 index 00000000000..9d5b7d783fd --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorStats.java @@ -0,0 +1,182 @@ +/* + * 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.visualizers; + +import java.io.Serializable; + +import org.apache.jmeter.monitor.util.Stats; +import org.apache.jmeter.testelement.AbstractTestElement; + +/* + * TODO - convert this into an immutable class using plain variables + * The current implementation is quite inefficient, as it stores everything + * in properties. + * + * This will require changes to ResultCollector.recordStats() + * and SaveService.saveTestElement() which are both currently only used by Monitor classes + */ +public class MonitorStats extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + private static final String HEALTH = "stats.health"; + + private static final String LOAD = "stats.load"; + + private static final String CPULOAD = "stats.cpuload"; + + private static final String MEMLOAD = "stats.memload"; + + private static final String THREADLOAD = "stats.threadload"; + + private static final String HOST = "stats.host"; + + private static final String PORT = "stats.port"; + + private static final String PROTOCOL = "stats.protocol"; + + private static final String TIMESTAMP = "stats.timestamp"; + + /** + * + */ + public MonitorStats() { + super(); + } + + /** + * Default constructor + * + * @param health + * Health of the server. Has to be one of {@link Stats#HEALTHY + * HEALTHY}, {@link Stats#ACTIVE ACTIVE}, {@link Stats#WARNING + * WARNING} or {@link Stats#DEAD DEAD} + * @param load + * load of the server as integer from a range in between 1 and + * 100 + * @param cpuload + * cpu load of the server as integer from range between 1 and 100 + * @param memload + * load of the server as integer from a range in between 1 and + * 100 + * @param threadload + * thread load of the server as an integer from a range in + * between 1 and 100 + * @param host + * name of the host from which the status was taken + * @param port + * port from which the status was taken + * @param protocol + * over which the status was taken + * @param time + * time in milliseconds when this status was created + */ + public MonitorStats(int health, int load, int cpuload, int memload, int threadload, String host, String port, + String protocol, long time) { + this.setHealth(health); + this.setLoad(load); + this.setCpuLoad(cpuload); + this.setMemLoad(memload); + this.setThreadLoad(threadload); + this.setHost(host); + this.setPort(port); + this.setProtocol(protocol); + this.setTimeStamp(time); + } + + /** + * For convienance, this method returns the protocol, host and port as a + * URL. + * + * @return protocol://host:port + */ + public String getURL() { + return this.getProtocol() + "://" + this.getHost() + ":" + this.getPort(); + } + + public void setHealth(int health) { + this.setProperty(HEALTH, String.valueOf(health)); + } + + public void setLoad(int load) { + this.setProperty(LOAD, String.valueOf(load)); + } + + public void setCpuLoad(int load) { + this.setProperty(CPULOAD, String.valueOf(load)); + } + + public void setMemLoad(int load) { + this.setProperty(MEMLOAD, String.valueOf(load)); + } + + public void setThreadLoad(int load) { + this.setProperty(THREADLOAD, String.valueOf(load)); + } + + public void setHost(String host) { + this.setProperty(HOST, host); + } + + public void setPort(String port) { + this.setProperty(PORT, port); + } + + public void setProtocol(String protocol) { + this.setProperty(PROTOCOL, protocol); + } + + public void setTimeStamp(long time) { + this.setProperty(TIMESTAMP, String.valueOf(time)); + } + + public int getHealth() { + return this.getPropertyAsInt(HEALTH); + } + + public int getLoad() { + return this.getPropertyAsInt(LOAD); + } + + public int getCpuLoad() { + return this.getPropertyAsInt(CPULOAD); + } + + public int getMemLoad() { + return this.getPropertyAsInt(MEMLOAD); + } + + public int getThreadLoad() { + return this.getPropertyAsInt(THREADLOAD); + } + + public String getHost() { + return this.getPropertyAsString(HOST); + } + + public String getPort() { + return this.getPropertyAsString(PORT); + } + + public String getProtocol() { + return this.getPropertyAsString(PROTOCOL); + } + + public long getTimeStamp() { + return this.getPropertyAsLong(TIMESTAMP); + } +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/MonitorTabPane.java b/src/monitor/components/org/apache/jmeter/visualizers/MonitorTabPane.java new file mode 100644 index 00000000000..d15c300153d --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/MonitorTabPane.java @@ -0,0 +1,32 @@ +/* + * 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.visualizers; + +import javax.swing.JTabbedPane; + +public class MonitorTabPane extends JTabbedPane { + + private static final long serialVersionUID = 240L; + + /** + * + */ + public MonitorTabPane() { + super(TOP); + } + +} diff --git a/src/monitor/components/org/apache/jmeter/visualizers/ServerPanel.java b/src/monitor/components/org/apache/jmeter/visualizers/ServerPanel.java new file mode 100644 index 00000000000..26a08b0d369 --- /dev/null +++ b/src/monitor/components/org/apache/jmeter/visualizers/ServerPanel.java @@ -0,0 +1,207 @@ +/* + * 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.visualizers; + +import java.awt.Dimension; +import java.awt.FlowLayout; + +import javax.swing.ImageIcon; +import javax.swing.JPanel; +import javax.swing.JLabel; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.monitor.util.Stats; + +/** + * The purpose of ServerPanel is to display an unique server and its current + * status. The server label consist of the protocol, host and port. For example, + * a system with multiple Tomcat's running on different ports would be different + * ServerPanel. + */ +public class ServerPanel extends JPanel implements MonitorGuiListener { + + private static final long serialVersionUID = 240L; + + private JLabel serverField; + + private JLabel timestampField; + + /** + * Preference size for the health icon + */ + private final Dimension prefsize = new Dimension(25, 75); + + private JLabel healthIcon; + + private JLabel loadIcon; + + /** + * Health Icons + */ + private static final ImageIcon HEALTHY = JMeterUtils.getImage("monitor-healthy.gif"); + + private static final ImageIcon ACTIVE = JMeterUtils.getImage("monitor-active.gif"); + + private static final ImageIcon WARNING = JMeterUtils.getImage("monitor-warning.gif"); + + private static final ImageIcon DEAD = JMeterUtils.getImage("monitor-dead.gif"); + + /** + * Load Icons + */ + private static final ImageIcon LOAD_0 = JMeterUtils.getImage("monitor-load-0.gif"); + + private static final ImageIcon LOAD_1 = JMeterUtils.getImage("monitor-load-1.gif"); + + private static final ImageIcon LOAD_2 = JMeterUtils.getImage("monitor-load-2.gif"); + + private static final ImageIcon LOAD_3 = JMeterUtils.getImage("monitor-load-3.gif"); + + private static final ImageIcon LOAD_4 = JMeterUtils.getImage("monitor-load-4.gif"); + + private static final ImageIcon LOAD_5 = JMeterUtils.getImage("monitor-load-5.gif"); + + private static final ImageIcon LOAD_6 = JMeterUtils.getImage("monitor-load-6.gif"); + + private static final ImageIcon LOAD_7 = JMeterUtils.getImage("monitor-load-7.gif"); + + private static final ImageIcon LOAD_8 = JMeterUtils.getImage("monitor-load-8.gif"); + + private static final ImageIcon LOAD_9 = JMeterUtils.getImage("monitor-load-9.gif"); + + private static final ImageIcon LOAD_10 = JMeterUtils.getImage("monitor-load-10.gif"); + + // private MonitorModel DATA; + + /** + * Creates a new server panel for a monitored server + * + * @param model + * information about the monitored server + */ + public ServerPanel(MonitorModel model) { + super(); + // DATA = model; + init(model); + } + + /** + * + * @deprecated Only for use in unit testing + */ + @Deprecated + public ServerPanel() { + // log.warn("Only for use in unit testing"); + } + + /** + * Init will create the JLabel widgets for the host, health, load and + * timestamp. + * + * @param model information about the monitored server + */ + private void init(MonitorModel model) { + this.setLayout(new FlowLayout()); + serverField = new JLabel(model.getURL()); + this.add(serverField); + healthIcon = new JLabel(getHealthyImageIcon(model.getHealth())); + healthIcon.setPreferredSize(prefsize); + this.add(healthIcon); + loadIcon = new JLabel(getLoadImageIcon(model.getLoad())); + this.add(loadIcon); + timestampField = new JLabel(model.getTimestampString()); + this.add(timestampField); + } + + /** + * Static method for getting the right ImageIcon for the health. + * + * @param health + * @return image for the status + */ + private static ImageIcon getHealthyImageIcon(int health) { + ImageIcon i = null; + switch (health) { + case Stats.HEALTHY: + i = HEALTHY; + break; + case Stats.ACTIVE: + i = ACTIVE; + break; + case Stats.WARNING: + i = WARNING; + break; + case Stats.DEAD: + i = DEAD; + break; + } + return i; + } + + /** + * Static method looks up the right ImageIcon from the load value. + * + * @param load + * @return image for the load + */ + private static ImageIcon getLoadImageIcon(int load) { + if (load == 0) { + return LOAD_0; + } else if (load > 0 && load <= 10) { + return LOAD_1; + } else if (load > 10 && load <= 20) { + return LOAD_2; + } else if (load > 20 && load <= 30) { + return LOAD_3; + } else if (load > 30 && load <= 40) { + return LOAD_4; + } else if (load > 40 && load <= 50) { + return LOAD_5; + } else if (load > 50 && load <= 60) { + return LOAD_6; + } else if (load > 60 && load <= 70) { + return LOAD_7; + } else if (load > 70 && load <= 80) { + return LOAD_8; + } else if (load > 80 && load <= 90) { + return LOAD_9; + } else { + return LOAD_10; + } + } + + /** + * Method will update the ServerPanel's health, load, and timestamp. For + * efficiency, it uses the static method to lookup the images. + */ + @Override + public void updateGui(MonitorModel stat) { + // this.DATA = null; + // this.DATA = stat; + loadIcon.setIcon(getLoadImageIcon(stat.getLoad())); + healthIcon.setIcon(getHealthyImageIcon(stat.getHealth())); + timestampField.setText(stat.getTimestampString()); + this.updateGui(); + } + + /** + * update the gui + */ + @Override + public void updateGui() { + this.repaint(); + } +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/Connector.java b/src/monitor/model/org/apache/jmeter/monitor/model/Connector.java new file mode 100644 index 00000000000..098a4d41f99 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/Connector.java @@ -0,0 +1,36 @@ +/* + * 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.monitor.model; + +public interface Connector { + ThreadInfo getThreadInfo(); + + void setThreadInfo(ThreadInfo value); + + RequestInfo getRequestInfo(); + + void setRequestInfo(RequestInfo value); + + Workers getWorkers(); + + void setWorkers(Workers value); + + String getName(); + + void setName(String value); + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/ConnectorImpl.java b/src/monitor/model/org/apache/jmeter/monitor/model/ConnectorImpl.java new file mode 100644 index 00000000000..056f47dc348 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/ConnectorImpl.java @@ -0,0 +1,79 @@ +/* + * 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.monitor.model; + +/** + * + * @version $Revision$ + */ +public class ConnectorImpl implements Connector { + private ThreadInfo threadinfo = null; + + private RequestInfo requestinfo = null; + + private Workers workers = null; + + private String name = null; + + /** + * + */ + public ConnectorImpl() { + super(); + } + + @Override + public ThreadInfo getThreadInfo() { + return this.threadinfo; + } + + @Override + public void setThreadInfo(ThreadInfo value) { + this.threadinfo = value; + } + + @Override + public RequestInfo getRequestInfo() { + return this.requestinfo; + } + + @Override + public void setRequestInfo(RequestInfo value) { + this.requestinfo = value; + } + + @Override + public Workers getWorkers() { + return this.workers; + } + + @Override + public void setWorkers(Workers value) { + this.workers = value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public void setName(String value) { + this.name = value; + } + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/Jvm.java b/src/monitor/model/org/apache/jmeter/monitor/model/Jvm.java new file mode 100644 index 00000000000..a4e46207b27 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/Jvm.java @@ -0,0 +1,24 @@ +/* + * 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.monitor.model; + +public interface Jvm { + Memory getMemory(); + + void setMemory(Memory mem); +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/JvmImpl.java b/src/monitor/model/org/apache/jmeter/monitor/model/JvmImpl.java new file mode 100644 index 00000000000..932792f2072 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/JvmImpl.java @@ -0,0 +1,45 @@ +/* + * 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.monitor.model; + +/** + * + * @version $Revision$ + */ +public class JvmImpl implements Jvm { + private Memory memory = null; + + /** + * + */ + public JvmImpl() { + super(); + } + + /** {@inheritDoc} */ + @Override + public Memory getMemory() { + return this.memory; + } + + /** {@inheritDoc} */ + @Override + public void setMemory(Memory mem) { + this.memory = mem; + } + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/Memory.java b/src/monitor/model/org/apache/jmeter/monitor/model/Memory.java new file mode 100644 index 00000000000..8e3f7c83bd0 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/Memory.java @@ -0,0 +1,38 @@ +/* + * 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. + */ +/* + * Created on Mar 12, 2004 + */ +package org.apache.jmeter.monitor.model; + +/** + * @version $Revision$ + */ +public interface Memory { + long getMax(); + + void setMax(long value); + + long getFree(); + + void setFree(long value); + + long getTotal(); + + void setTotal(long value); + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/MemoryImpl.java b/src/monitor/model/org/apache/jmeter/monitor/model/MemoryImpl.java new file mode 100644 index 00000000000..89c594b1775 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/MemoryImpl.java @@ -0,0 +1,67 @@ +/* + * 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.monitor.model; + +/** + * + * @version $Revision$ + */ +public class MemoryImpl implements Memory { + private long max = 0; + + private long free = 0; + + private long total = 0; + + /** + * + */ + public MemoryImpl() { + super(); + } + + @Override + public long getMax() { + return this.max; + } + + @Override + public void setMax(long value) { + this.max = value; + } + + @Override + public long getFree() { + return this.free; + } + + @Override + public void setFree(long value) { + this.free = value; + } + + @Override + public long getTotal() { + return this.total; + } + + @Override + public void setTotal(long value) { + this.total = value; + } + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/ObjectFactory.java b/src/monitor/model/org/apache/jmeter/monitor/model/ObjectFactory.java new file mode 100644 index 00000000000..c88d3a2dfe0 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/ObjectFactory.java @@ -0,0 +1,98 @@ +/* + * 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.monitor.model; + +// For unit tests, @see TestObjectFactory + +import org.apache.jmeter.monitor.parser.Parser; +import org.apache.jmeter.monitor.parser.ParserImpl; +import org.apache.jmeter.samplers.SampleResult; + +/** + * ObjectFactory is a simple factory class which creates new instances of + * objects. It also provides convienant method to parse XML status results. + */ +public class ObjectFactory { + + private static class ObjectFactoryHolder { + static final ObjectFactory FACTORY = new ObjectFactory(); + } + + private final Parser PARSER; + + /** + * + */ + protected ObjectFactory() { + super(); + PARSER = new MonitorParser(this); + } + + public static ObjectFactory getInstance() { + return ObjectFactoryHolder.FACTORY; + } + + public Status parseBytes(byte[] bytes) { + return PARSER.parseBytes(bytes); + } + + public Status parseString(String content) { + return PARSER.parseString(content); + } + + public Status parseSampleResult(SampleResult result) { + return PARSER.parseSampleResult(result); + } + + public Status createStatus() { + return new StatusImpl(); + } + + public Connector createConnector() { + return new ConnectorImpl(); + } + + public Jvm createJvm() { + return new JvmImpl(); + } + + public Memory createMemory() { + return new MemoryImpl(); + } + + public RequestInfo createRequestInfo() { + return new RequestInfoImpl(); + } + + public ThreadInfo createThreadInfo() { + return new ThreadInfoImpl(); + } + + public Worker createWorker() { + return new WorkerImpl(); + } + + public Workers createWorkers() { + return new WorkersImpl(); + } + + protected static class MonitorParser extends ParserImpl { + public MonitorParser(ObjectFactory factory) { + super(factory); + } + } +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/RequestInfo.java b/src/monitor/model/org/apache/jmeter/monitor/model/RequestInfo.java new file mode 100644 index 00000000000..628a2cb131a --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/RequestInfo.java @@ -0,0 +1,48 @@ +/* + * 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.monitor.model; + +/** + * @version $Revision$ + */ +public interface RequestInfo { + long getBytesReceived(); + + void setBytesReceived(long value); + + long getBytesSent(); + + void setBytesSent(long value); + + long getRequestCount(); + + void setRequestCount(long value); + + long getErrorCount(); + + void setErrorCount(long value); + + int getMaxTime(); + + void setMaxTime(int value); + + int getProcessingTime(); + + void setProcessingTime(int value); + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/RequestInfoImpl.java b/src/monitor/model/org/apache/jmeter/monitor/model/RequestInfoImpl.java new file mode 100644 index 00000000000..5153929cc7b --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/RequestInfoImpl.java @@ -0,0 +1,103 @@ +/* + * 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.monitor.model; + +/** + * + * @version $Revision$ + */ +public class RequestInfoImpl implements RequestInfo { + private long bytesReceived = 0; + + private long bytesSent = 0; + + private long requestCount = 0; + + private long errorCount = 0; + + private int maxTime = 0; + + private int processingTime = 0; + + /** + * + */ + public RequestInfoImpl() { + super(); + } + + @Override + public long getBytesReceived() { + return this.bytesReceived; + } + + @Override + public void setBytesReceived(long value) { + this.bytesReceived = value; + } + + @Override + public long getBytesSent() { + return this.bytesSent; + } + + @Override + public void setBytesSent(long value) { + this.bytesSent = value; + } + + @Override + public long getRequestCount() { + return requestCount; + } + + @Override + public void setRequestCount(long value) { + this.requestCount = value; + } + + @Override + public long getErrorCount() { + return this.errorCount; + } + + @Override + public void setErrorCount(long value) { + this.errorCount = value; + } + + @Override + public int getMaxTime() { + return this.maxTime; + } + + @Override + public void setMaxTime(int value) { + this.maxTime = value; + } + + @Override + public int getProcessingTime() { + return this.processingTime; + } + + @Override + public void setProcessingTime(int value) { + this.processingTime = value; + } + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/Status.java b/src/monitor/model/org/apache/jmeter/monitor/model/Status.java new file mode 100644 index 00000000000..17694a086a6 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/Status.java @@ -0,0 +1,33 @@ +/* + * 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.monitor.model; + +import java.util.List; + +public interface Status { + Jvm getJvm(); + + void setJvm(Jvm vm); + + List getConnector(); + + void addConnector(Connector conn); + + void setConnectorPrefix(String prefix); + + String getConnectorPrefix(); +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/StatusImpl.java b/src/monitor/model/org/apache/jmeter/monitor/model/StatusImpl.java new file mode 100644 index 00000000000..47122b25838 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/StatusImpl.java @@ -0,0 +1,73 @@ +/* + * 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.monitor.model; + +import java.util.LinkedList; +import java.util.List; + +/** + * + * @version $Revision$ + */ +public class StatusImpl implements Status { + private Jvm jvm = null; + + private String connectorPrefix = null; + + private final List connectors; + + /** + * + */ + public StatusImpl() { + super(); + connectors = new LinkedList(); + } + + /** {@inheritDoc} */ + @Override + public Jvm getJvm() { + return jvm; + } + + /** {@inheritDoc} */ + @Override + public void setJvm(Jvm vm) { + this.jvm = vm; + } + + /** {@inheritDoc} */ + @Override + public List getConnector() { + return this.connectors; + } + + @Override + public void addConnector(Connector conn) { + this.connectors.add(conn); + } + + @Override + public void setConnectorPrefix(String prefix) { + connectorPrefix = prefix; + } + + @Override + public String getConnectorPrefix(){ + return connectorPrefix; + } +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/ThreadInfo.java b/src/monitor/model/org/apache/jmeter/monitor/model/ThreadInfo.java new file mode 100644 index 00000000000..48eb3e21a75 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/ThreadInfo.java @@ -0,0 +1,44 @@ +/* + * 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.monitor.model; + +/** + * @version $Revision$ + */ +public interface ThreadInfo { + int getMaxSpareThreads(); + + void setMaxSpareThreads(int value); + + int getMinSpareThreads(); + + void setMinSpareThreads(int value); + + int getMaxThreads(); + + void setMaxThreads(int value); + + int getCurrentThreadsBusy(); + + void setCurrentThreadsBusy(int value); + + int getCurrentThreadCount(); + + void setCurrentThreadCount(int value); + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/ThreadInfoImpl.java b/src/monitor/model/org/apache/jmeter/monitor/model/ThreadInfoImpl.java new file mode 100644 index 00000000000..12642240d73 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/ThreadInfoImpl.java @@ -0,0 +1,91 @@ +/* + * 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.monitor.model; + +/** + * + * @version $Revision$ + */ +public class ThreadInfoImpl implements ThreadInfo { + private int maxSpareThreads = 0; + + private int minSpareThreads = 0; + + private int maxThreads = 0; + + private int currentThreadCount = 0; + + private int currentThreadsBusy = 0; + + /** + * + */ + public ThreadInfoImpl() { + super(); + } + + @Override + public int getMaxSpareThreads() { + return this.maxSpareThreads; + } + + @Override + public void setMaxSpareThreads(int value) { + this.maxSpareThreads = value; + } + + @Override + public int getMinSpareThreads() { + return this.minSpareThreads; + } + + @Override + public void setMinSpareThreads(int value) { + this.minSpareThreads = value; + } + + @Override + public int getMaxThreads() { + return this.maxThreads; + } + + @Override + public void setMaxThreads(int value) { + this.maxThreads = value; + } + + @Override + public int getCurrentThreadsBusy() { + return this.currentThreadsBusy; + } + + @Override + public void setCurrentThreadsBusy(int value) { + this.currentThreadsBusy = value; + } + + @Override + public int getCurrentThreadCount() { + return this.currentThreadCount; + } + + @Override + public void setCurrentThreadCount(int value) { + this.currentThreadCount = value; + } + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/Worker.java b/src/monitor/model/org/apache/jmeter/monitor/model/Worker.java new file mode 100644 index 00000000000..fa727b459d8 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/Worker.java @@ -0,0 +1,64 @@ +/* + * 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.monitor.model; + +/** + * @version $Revision$ + */ +public interface Worker { + int getRequestProcessingTime(); + + void setRequestProcessingTime(int value); + + long getRequestBytesSent(); + + void setRequestBytesSent(long value); + + String getCurrentQueryString(); + + void setCurrentQueryString(String value); + + String getRemoteAddr(); + + void setRemoteAddr(String value); + + String getCurrentUri(); + + void setCurrentUri(String value); + + String getStage(); + + void setStage(String value); + + String getVirtualHost(); + + void setVirtualHost(String value); + + String getProtocol(); + + void setProtocol(String value); + + long getRequestBytesReceived(); + + void setRequestBytesReceived(long value); + + String getMethod(); + + void setMethod(String value); + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/WorkerImpl.java b/src/monitor/model/org/apache/jmeter/monitor/model/WorkerImpl.java new file mode 100644 index 00000000000..760fb0995f1 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/WorkerImpl.java @@ -0,0 +1,150 @@ +/* + * 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.monitor.model; + +/** + * + */ +public class WorkerImpl implements Worker { + private int requestProcessingTime = 0; + + private long requestBytesSent = 0; + + private long requestBytesReceived = 0; + + private String currentQueryString = null; + + private String remoteAddr = null; + + private String currentUri = null; + + private String method = null; + + private String protocol = null; + + private String stage = null; + + private String virtualHost = null; + + /** + * + */ + public WorkerImpl() { + super(); + } + + @Override + public int getRequestProcessingTime() { + return this.requestProcessingTime; + } + + @Override + public void setRequestProcessingTime(int value) { + this.requestProcessingTime = value; + } + + @Override + public long getRequestBytesSent() { + return this.requestBytesSent; + } + + @Override + public void setRequestBytesSent(long value) { + this.requestBytesSent = value; + } + + @Override + public String getCurrentQueryString() { + return this.currentQueryString; + } + + @Override + public void setCurrentQueryString(String value) { + this.currentQueryString = value; + } + + @Override + public String getRemoteAddr() { + return this.remoteAddr; + } + + @Override + public void setRemoteAddr(String value) { + this.remoteAddr = value; + } + + @Override + public String getCurrentUri() { + return this.currentUri; + } + + @Override + public void setCurrentUri(String value) { + this.currentUri = value; + } + + @Override + public String getStage() { + return this.stage; + } + + @Override + public void setStage(String value) { + this.stage = value; + } + + @Override + public String getVirtualHost() { + return this.virtualHost; + } + + @Override + public void setVirtualHost(String value) { + this.virtualHost = value; + } + + @Override + public String getProtocol() { + return this.protocol; + } + + @Override + public void setProtocol(String value) { + this.protocol = value; + } + + @Override + public long getRequestBytesReceived() { + return this.requestBytesReceived; + } + + @Override + public void setRequestBytesReceived(long value) { + this.requestBytesReceived = value; + } + + @Override + public String getMethod() { + return this.method; + } + + @Override + public void setMethod(String value) { + this.method = value; + } + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/Workers.java b/src/monitor/model/org/apache/jmeter/monitor/model/Workers.java new file mode 100644 index 00000000000..6b96b0e444f --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/Workers.java @@ -0,0 +1,29 @@ +/* + * 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.monitor.model; + +import java.util.List; + +/** + * @version $Revision$ + */ +public interface Workers { + + List getWorker(); + +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/model/WorkersImpl.java b/src/monitor/model/org/apache/jmeter/monitor/model/WorkersImpl.java new file mode 100644 index 00000000000..eb2be1a5526 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/model/WorkersImpl.java @@ -0,0 +1,45 @@ +/* + * 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.monitor.model; + +import java.util.LinkedList; +import java.util.List; + +/** + * + * @version $Revision$ + */ +public class WorkersImpl implements Workers { + private final List worker; + + /** + * + */ + public WorkersImpl() { + super(); + worker = new LinkedList(); + } + + @Override + public List getWorker() { + return worker; + } + + public void addWorker(Worker value) { + this.worker.add(value); + } +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/parser/Constants.java b/src/monitor/model/org/apache/jmeter/monitor/parser/Constants.java new file mode 100644 index 00000000000..8e84318367a --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/parser/Constants.java @@ -0,0 +1,90 @@ +/* + * 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.monitor.parser; + +/** + * Constants for the custom DocumentHandler. + * + * @version $Revision$ + */ +public class Constants { + public static final String STATUS = "status"; + + public static final String JVM = "jvm"; + + public static final String CONNECTOR = "connector"; + + public static final String MEMORY = "memory"; + + public static final String THREADINFO = "threadInfo"; + + public static final String REQUESTINFO = "requestInfo"; + + public static final String WORKER = "worker"; + + public static final String WORKERS = "workers"; + + public static final String MEMORY_FREE = "free"; + + public static final String MEMORY_TOTAL = "total"; + + public static final String MEMORY_MAX = "max"; + + public static final String ATTRIBUTE_NAME = "name"; + + public static final String MAXTHREADS = "maxThreads"; + + public static final String MINSPARETHREADS = "minSpareThreads"; + + public static final String MAXSPARETHREADS = "maxSpareThreads"; + + public static final String CURRENTTHREADCOUNT = "currentThreadCount"; + + public static final String CURRENTBUSYTHREADS = "currentThreadsBusy"; + + public static final String MAXTIME = "maxTime="; + + public static final String PROCESSINGTIME = "processingTime="; + + public static final String REQUESTCOUNT = "requestCount"; + + public static final String ERRORCOUNT = "errorCount="; + + public static final String BYTESRECEIVED = "bytesReceived"; + + public static final String BYTESSENT = "bytesSent"; + + public static final String STAGE = "stage"; + + public static final String REQUESTPROCESSINGTIME = "requestProcessingTime="; + + public static final String REQUESTBYTESSENT = "requestBytesSent"; + + public static final String REQUESTBYTESRECEIVED = "requestBytesReceived"; + + public static final String REMOTEADDR = "remoteAddr"; + + public static final String VIRTUALHOST = "virtualHost"; + + public static final String METHOD = "method"; + + public static final String CURRENTURI = "currentUri"; + + public static final String CURRENTQUERYSTRING = "currentQueryString"; + + public static final String PROTOCOL = "protocol"; +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/parser/MonitorHandler.java b/src/monitor/model/org/apache/jmeter/monitor/parser/MonitorHandler.java new file mode 100644 index 00000000000..512fe7834f6 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/parser/MonitorHandler.java @@ -0,0 +1,367 @@ +/* + * 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.monitor.parser; + +// import java.util.List; +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.jmeter.monitor.model.ObjectFactory; +import org.apache.jmeter.monitor.model.Connector; +import org.apache.jmeter.monitor.model.Jvm; +import org.apache.jmeter.monitor.model.Memory; +import org.apache.jmeter.monitor.model.RequestInfo; +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.monitor.model.ThreadInfo; +import org.apache.jmeter.monitor.model.Worker; +import org.apache.jmeter.monitor.model.Workers; +import org.apache.jmeter.monitor.model.WorkersImpl; + +public class MonitorHandler extends DefaultHandler { + // private boolean startDoc = false; + // private boolean endDoc = false; + private final ObjectFactory factory; + + private Stack stacktree; + + private Status status; + + private Jvm jvm; + + private Memory memory; + + private Connector connector; + + private ThreadInfo threadinfo; + + private RequestInfo requestinfo; + + private Worker worker; + + private Workers workers; + + // private List workerslist; + + /** + * @param factory {@link ObjectFactory} to use + */ + public MonitorHandler(ObjectFactory factory) { + super(); + this.factory = factory; + } + + @Override + public void startDocument() throws SAXException { + // this.startDoc = true; + // Reset all work variables so reusing the instance starts afresh. + this.stacktree = new Stack(); + this.status = null; + this.jvm = null; + this.memory = null; + this.connector = null; + this.threadinfo = null; + this.requestinfo = null; + this.worker = null; + this.workers = null; + } + + /** {@inheritDoc} */ + @Override + public void endDocument() throws SAXException { + // this.startDoc = false; + // this.endDoc = true; + } + + /** + * Receive notification of the start of an element. + * + *

+ * By default, do nothing. Application writers may override this method in a + * subclass to take specific actions at the start of each element (such as + * allocating a new tree node or writing output to a file). + *

+ * + * @param uri + * The namespace uri, or the empty string, if no namespace is available + * @param localName + * The element type name. + * @param qName + * The qualified name, or the empty string (must not be null) + * @param attributes + * The specified or defaulted attributes. + * @exception org.xml.sax.SAXException + * Any SAX exception, possibly wrapping another exception. + * @see org.xml.sax.ContentHandler#startElement + */ + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals(Constants.STATUS)) { + status = factory.createStatus(); + stacktree.push(status); + } else if (qName.equals(Constants.JVM)) { + jvm = factory.createJvm(); + if (stacktree.peek() instanceof Status) { + status.setJvm(jvm); + stacktree.push(jvm); + } + } else if (qName.equals(Constants.MEMORY)) { + memory = factory.createMemory(); + if (stacktree.peek() instanceof Jvm) { + stacktree.push(memory); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.MEMORY_FREE)) { + memory.setFree(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.MEMORY_TOTAL)) { + memory.setTotal(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.MEMORY_MAX)) { + memory.setMax(parseLong(attributes.getValue(idx))); + } + } + } + jvm.setMemory(memory); + } + } else if (qName.equals(Constants.CONNECTOR)) { + connector = factory.createConnector(); + if (stacktree.peek() instanceof Status || stacktree.peek() instanceof Connector) { + status.addConnector(connector); + stacktree.push(connector); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.ATTRIBUTE_NAME)) { + connector.setName(attributes.getValue(idx)); + } + } + } + } + } else if (qName.equals(Constants.THREADINFO)) { + threadinfo = factory.createThreadInfo(); + if (stacktree.peek() instanceof Connector) { + stacktree.push(threadinfo); + connector.setThreadInfo(threadinfo); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.MAXTHREADS)) { + threadinfo.setMaxThreads(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.MINSPARETHREADS)) { + threadinfo.setMinSpareThreads(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.MAXSPARETHREADS)) { + threadinfo.setMaxSpareThreads(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.CURRENTTHREADCOUNT)) { + threadinfo.setCurrentThreadCount(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.CURRENTBUSYTHREADS)) { + threadinfo.setCurrentThreadsBusy(parseInt(attributes.getValue(idx))); + } + } + } + } + } else if (qName.equals(Constants.REQUESTINFO)) { + requestinfo = factory.createRequestInfo(); + if (stacktree.peek() instanceof Connector) { + stacktree.push(requestinfo); + connector.setRequestInfo(requestinfo); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.MAXTIME)) { + requestinfo.setMaxTime(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.PROCESSINGTIME)) { + requestinfo.setProcessingTime(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.REQUESTCOUNT)) { + requestinfo.setRequestCount(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.ERRORCOUNT)) { + requestinfo.setErrorCount(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.BYTESRECEIVED)) { + requestinfo.setBytesReceived(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.BYTESSENT)) { + requestinfo.setBytesSent(parseLong(attributes.getValue(idx))); + } + } + } + } + } else if (qName.equals(Constants.WORKERS)) { + workers = factory.createWorkers(); + if (stacktree.peek() instanceof Connector) { + connector.setWorkers(workers); + stacktree.push(workers); + } + } else if (qName.equals(Constants.WORKER)) { + worker = factory.createWorker(); + if (stacktree.peek() instanceof Workers || stacktree.peek() instanceof Worker) { + stacktree.push(worker); + ((WorkersImpl) workers).addWorker(worker); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.STAGE)) { + worker.setStage(attributes.getValue(idx)); + } else if (attr.equals(Constants.REQUESTPROCESSINGTIME)) { + worker.setRequestProcessingTime(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.REQUESTBYTESSENT)) { + worker.setRequestBytesSent(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.REQUESTBYTESRECEIVED)) { + worker.setRequestBytesReceived(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.REMOTEADDR)) { + worker.setRemoteAddr(attributes.getValue(idx)); + } else if (attr.equals(Constants.VIRTUALHOST)) { + worker.setVirtualHost(attributes.getValue(idx)); + } else if (attr.equals(Constants.METHOD)) { + worker.setMethod(attributes.getValue(idx)); + } else if (attr.equals(Constants.CURRENTURI)) { + worker.setCurrentUri(attributes.getValue(idx)); + } else if (attr.equals(Constants.CURRENTQUERYSTRING)) { + worker.setCurrentQueryString(attributes.getValue(idx)); + } else if (attr.equals(Constants.PROTOCOL)) { + worker.setProtocol(attributes.getValue(idx)); + } + } + } + } + } + } + + /** + * Receive notification of the end of an element. + * + *

+ * By default, do nothing. Application writers may override this method in a + * subclass to take specific actions at the end of each element (such as + * finalising a tree node or writing output to a file). + *

+ * + * @param uri + * the namespace uri, or the empty string, if no namespace is available + * @param localName + * The element type name. + * @param qName + * The specified or defaulted attributes. + * @exception org.xml.sax.SAXException + * Any SAX exception, possibly wrapping another exception. + * @see org.xml.sax.ContentHandler#endElement + */ + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals(Constants.STATUS)) { + if (stacktree.peek() instanceof Status) { + stacktree.pop(); + } + } else if (qName.equals(Constants.JVM)) { + if (stacktree.peek() instanceof Jvm) { + stacktree.pop(); + } + } else if (qName.equals(Constants.MEMORY)) { + if (stacktree.peek() instanceof Memory) { + stacktree.pop(); + } + } else if (qName.equals(Constants.CONNECTOR)) { + if (stacktree.peek() instanceof Connector || stacktree.peek() instanceof Connector) { + stacktree.pop(); + } + } else if (qName.equals(Constants.THREADINFO)) { + if (stacktree.peek() instanceof ThreadInfo) { + stacktree.pop(); + } + } else if (qName.equals(Constants.REQUESTINFO)) { + if (stacktree.peek() instanceof RequestInfo) { + stacktree.pop(); + } + } else if (qName.equals(Constants.WORKERS)) { + if (stacktree.peek() instanceof Workers) { + stacktree.pop(); + } + } else if (qName.equals(Constants.WORKER)) { + if (stacktree.peek() instanceof Worker || stacktree.peek() instanceof Worker) { + stacktree.pop(); + } + } + } + + /** + * Receive notification of character data inside an element. + * + *

+ * By default, do nothing. Application writers may override this method to + * take specific actions for each chunk of character data (such as adding + * the data to a node or buffer, or printing it to a file). + *

+ * + * @param ch + * The characters. + * @param start + * The start position in the character array. + * @param length + * The number of characters to use from the character array. + * @exception org.xml.sax.SAXException + * Any SAX exception, possibly wrapping another exception. + * @see org.xml.sax.ContentHandler#characters + */ + @Override + public void characters(char ch[], int start, int length) throws SAXException { + } + + /** + * Convienance method for parsing long. If the string was not a number, the + * method returns zero. + * + * @param data string representation of a {@link Long} + * @return the value as a long + */ + public long parseLong(String data) { + long val = 0; + if (data.length() > 0) { + try { + val = Long.parseLong(data); + } catch (NumberFormatException e) { + val = 0; + } + } + return val; + } + + /** + * Convienance method for parsing integers. + * + * @param data string representation of an {@link Integer} + * @return the value as an integer + */ + public int parseInt(String data) { + int val = 0; + if (data.length() > 0) { + try { + val = Integer.parseInt(data); + } catch (NumberFormatException e) { + val = 0; + } + } + return val; + } + + /** + * method returns the status object. + * + * @return the status + */ + public Status getContents() { + return this.status; + } +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/parser/Parser.java b/src/monitor/model/org/apache/jmeter/monitor/parser/Parser.java new file mode 100644 index 00000000000..ca25a9371a1 --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/parser/Parser.java @@ -0,0 +1,28 @@ +/* + * 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.monitor.parser; + +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.samplers.SampleResult; + +public interface Parser { + Status parseBytes(byte[] bytes); + + Status parseString(String content); + + Status parseSampleResult(SampleResult result); +} diff --git a/src/monitor/model/org/apache/jmeter/monitor/parser/ParserImpl.java b/src/monitor/model/org/apache/jmeter/monitor/parser/ParserImpl.java new file mode 100644 index 00000000000..60e2e27b32d --- /dev/null +++ b/src/monitor/model/org/apache/jmeter/monitor/parser/ParserImpl.java @@ -0,0 +1,125 @@ +/* + * 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.monitor.parser; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringReader; + +import org.xml.sax.SAXException; +import org.xml.sax.InputSource; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.apache.jmeter.monitor.model.ObjectFactory; +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public abstract class ParserImpl implements Parser { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final SAXParser PARSER; + + private final MonitorHandler DOCHANDLER; + + private final ObjectFactory FACTORY; + + /** + * @param factory {@link ObjectFactory} to use + */ + public ParserImpl(ObjectFactory factory) { + super(); + this.FACTORY = factory; + SAXParser parser = null; + MonitorHandler handler = null; + try { + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parser = parserFactory.newSAXParser(); + handler = new MonitorHandler(this.FACTORY); + } catch (SAXException e) { + log.error("Failed to create the parser",e); + } catch (ParserConfigurationException e) { + log.error("Failed to create the parser",e); + } + PARSER = parser; + DOCHANDLER = handler; + } + + /** + * parse byte array and return Status object + * + * @param bytes bytes to be parsed + * @return Status + */ + @Override + public Status parseBytes(byte[] bytes) { + try { + InputSource is = new InputSource(); + is.setByteStream(new ByteArrayInputStream(bytes)); + PARSER.parse(is, DOCHANDLER); + return DOCHANDLER.getContents(); + } catch (SAXException e) { + log.error("Failed to parse the bytes",e); + // let bad input fail silently + return DOCHANDLER.getContents(); + } catch (IOException e) { // Should never happen + log.error("Failed to read the bytes",e); + // let bad input fail silently + return DOCHANDLER.getContents(); + } + } + + /** + * @param content text to be parsed + * @return Status + */ + @Override + public Status parseString(String content) { + try { + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(content)); + PARSER.parse(is, DOCHANDLER); + return DOCHANDLER.getContents(); + } catch (SAXException e) { + log.error("Failed to parse the String",e); + // let bad input fail silently + return DOCHANDLER.getContents(); + } catch (IOException e) { // Should never happen + log.error("Failed to read the String",e); + // let bad input fail silently + return DOCHANDLER.getContents(); + } + } + + /** + * @param result + * {@link SampleResult} out of which the + * {@link SampleResult#getResponseData() reponseData} will be + * used for parsing + * @return Status + */ + @Override + public Status parseSampleResult(SampleResult result) { + return parseBytes(result.getResponseData()); + } + +} diff --git a/src/protocol/ftp/org/apache/jmeter/protocol/ftp/config/gui/FtpConfigGui.java b/src/protocol/ftp/org/apache/jmeter/protocol/ftp/config/gui/FtpConfigGui.java new file mode 100644 index 00000000000..e57a633df3b --- /dev/null +++ b/src/protocol/ftp/org/apache/jmeter/protocol/ftp/config/gui/FtpConfigGui.java @@ -0,0 +1,244 @@ +/* + * 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.protocol.ftp.config.gui; + +import java.awt.BorderLayout; + +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ftp.sampler.FTPSampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class FtpConfigGui extends AbstractConfigGui { + + private static final long serialVersionUID = 240L; + + private JTextField server; + + private JTextField port; + + private JTextField remoteFile; + + private JTextField localFile; + + private JTextArea inputData; + + private JCheckBox binaryMode; + + private JCheckBox saveResponseData; + + private boolean displayName = true; + + private JRadioButton getBox; + + private JRadioButton putBox; + + public FtpConfigGui() { + this(true); + } + + public FtpConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + @Override + public String getLabelResource() { + return "ftp_sample_title"; // $NON-NLS-1$ + } + + @Override + public void configure(TestElement element) { + super.configure(element); // TODO - should this be done for embedded usage? + // Note: the element is a ConfigTestElement when used standalone, so we cannot use FTPSampler access methods + server.setText(element.getPropertyAsString(FTPSampler.SERVER)); + port.setText(element.getPropertyAsString(FTPSampler.PORT)); + remoteFile.setText(element.getPropertyAsString(FTPSampler.REMOTE_FILENAME)); + localFile.setText(element.getPropertyAsString(FTPSampler.LOCAL_FILENAME)); + inputData.setText(element.getPropertyAsString(FTPSampler.INPUT_DATA)); + binaryMode.setSelected(element.getPropertyAsBoolean(FTPSampler.BINARY_MODE, false)); + saveResponseData.setSelected(element.getPropertyAsBoolean(FTPSampler.SAVE_RESPONSE, false)); + final boolean uploading = element.getPropertyAsBoolean(FTPSampler.UPLOAD_FILE,false); + if (uploading){ + putBox.setSelected(true); + } else { + getBox.setSelected(true); + } + } + + @Override + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + modifyTestElement(element); + return element; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement element) { + configureTestElement(element); + // Note: the element is a ConfigTestElement, so cannot use FTPSampler access methods + element.setProperty(FTPSampler.SERVER,server.getText()); + element.setProperty(FTPSampler.PORT,port.getText()); + element.setProperty(FTPSampler.REMOTE_FILENAME,remoteFile.getText()); + element.setProperty(FTPSampler.LOCAL_FILENAME,localFile.getText()); + element.setProperty(FTPSampler.INPUT_DATA,inputData.getText()); + element.setProperty(FTPSampler.BINARY_MODE,binaryMode.isSelected()); + element.setProperty(FTPSampler.SAVE_RESPONSE, saveResponseData.isSelected()); + element.setProperty(FTPSampler.UPLOAD_FILE,putBox.isSelected()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + server.setText(""); //$NON-NLS-1$ + port.setText(""); //$NON-NLS-1$ + remoteFile.setText(""); //$NON-NLS-1$ + localFile.setText(""); //$NON-NLS-1$ + inputData.setText(""); //$NON-NLS-1$ + binaryMode.setSelected(false); + saveResponseData.setSelected(false); + getBox.setSelected(true); + putBox.setSelected(false); + } + + private JPanel createServerPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("server")); //$NON-NLS-1$ + + server = new JTextField(10); + label.setLabelFor(server); + + JPanel serverPanel = new JPanel(new BorderLayout(5, 0)); + serverPanel.add(label, BorderLayout.WEST); + serverPanel.add(server, BorderLayout.CENTER); + return serverPanel; + } + + private JPanel getPortPanel() { + port = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(port); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(port, BorderLayout.CENTER); + + return panel; + } + + private JPanel createLocalFilenamePanel() { + JLabel label = new JLabel(JMeterUtils.getResString("ftp_local_file")); //$NON-NLS-1$ + + localFile = new JTextField(10); + label.setLabelFor(localFile); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(localFile, BorderLayout.CENTER); + return filenamePanel; + } + + private JPanel createLocalFileContentsPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("ftp_local_file_contents")); //$NON-NLS-1$ + + inputData = new JTextArea(); + label.setLabelFor(inputData); + + JPanel contentsPanel = new JPanel(new BorderLayout(5, 0)); + contentsPanel.add(label, BorderLayout.WEST); + contentsPanel.add(inputData, BorderLayout.CENTER); + return contentsPanel; + } + + private JPanel createRemoteFilenamePanel() { + JLabel label = new JLabel(JMeterUtils.getResString("ftp_remote_file")); //$NON-NLS-1$ + + remoteFile = new JTextField(10); + label.setLabelFor(remoteFile); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(remoteFile, BorderLayout.CENTER); + return filenamePanel; + } + + private JPanel createOptionsPanel(){ + + ButtonGroup group = new ButtonGroup(); + + getBox = new JRadioButton(JMeterUtils.getResString("ftp_get")); //$NON-NLS-1$ + group.add(getBox); + getBox.setSelected(true); + + putBox = new JRadioButton(JMeterUtils.getResString("ftp_put")); //$NON-NLS-1$ + group.add(putBox); + + binaryMode = new JCheckBox(JMeterUtils.getResString("ftp_binary_mode")); //$NON-NLS-1$ + saveResponseData = new JCheckBox(JMeterUtils.getResString("ftp_save_response_data")); //$NON-NLS-1$ + + + JPanel optionsPanel = new HorizontalPanel(); + optionsPanel.add(getBox); + optionsPanel.add(putBox); + optionsPanel.add(binaryMode); + optionsPanel.add(saveResponseData); + return optionsPanel; + } + private void init() { + setLayout(new BorderLayout(0, 5)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + // MAIN PANEL + VerticalPanel mainPanel = new VerticalPanel(); + JPanel serverPanel = new HorizontalPanel(); + serverPanel.add(createServerPanel(), BorderLayout.CENTER); + serverPanel.add(getPortPanel(), BorderLayout.EAST); + mainPanel.add(serverPanel); + mainPanel.add(createRemoteFilenamePanel()); + mainPanel.add(createLocalFilenamePanel()); + mainPanel.add(createLocalFileContentsPanel()); + mainPanel.add(createOptionsPanel()); + + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/src/protocol/ftp/org/apache/jmeter/protocol/ftp/control/gui/FtpTestSamplerGui.java b/src/protocol/ftp/org/apache/jmeter/protocol/ftp/control/gui/FtpTestSamplerGui.java new file mode 100644 index 00000000000..226ffd60fc6 --- /dev/null +++ b/src/protocol/ftp/org/apache/jmeter/protocol/ftp/control/gui/FtpTestSamplerGui.java @@ -0,0 +1,104 @@ +/* + * 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.protocol.ftp.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; + +import org.apache.jmeter.config.gui.LoginConfigGui; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ftp.config.gui.FtpConfigGui; +import org.apache.jmeter.protocol.ftp.sampler.FTPSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class FtpTestSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + private LoginConfigGui loginPanel; + + private FtpConfigGui ftpDefaultPanel; + + public FtpTestSamplerGui() { + init(); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + loginPanel.configure(element); + ftpDefaultPanel.configure(element); + } + + @Override + public TestElement createTestElement() { + FTPSampler sampler = new FTPSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + ftpDefaultPanel.modifyTestElement(sampler); + loginPanel.modifyTestElement(sampler); + this.configureTestElement(sampler); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + ftpDefaultPanel.clearGui(); + loginPanel.clearGui(); + } + + @Override + public String getLabelResource() { + return "ftp_testing_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + VerticalPanel mainPanel = new VerticalPanel(); + + ftpDefaultPanel = new FtpConfigGui(false); + mainPanel.add(ftpDefaultPanel); + + loginPanel = new LoginConfigGui(false); + loginPanel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("login_config"))); // $NON-NLS-1$ + mainPanel.add(loginPanel); + + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/src/protocol/ftp/org/apache/jmeter/protocol/ftp/sampler/FTPSampler.java b/src/protocol/ftp/org/apache/jmeter/protocol/ftp/sampler/FTPSampler.java new file mode 100644 index 00000000000..b5fd4983f28 --- /dev/null +++ b/src/protocol/ftp/org/apache/jmeter/protocol/ftp/sampler/FTPSampler.java @@ -0,0 +1,323 @@ +/* + * 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.protocol.ftp.sampler; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.apache.commons.io.output.TeeOutputStream; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPReply; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler which understands FTP file requests. + * + */ +public class FTPSampler extends AbstractSampler implements Interruptible { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.LoginConfigGui", + "org.apache.jmeter.protocol.ftp.config.gui.FtpConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + public static final String SERVER = "FTPSampler.server"; // $NON-NLS-1$ + + public static final String PORT = "FTPSampler.port"; // $NON-NLS-1$ + + // N.B. Originally there was only one filename, and only get(RETR) was supported + // To maintain backwards compatibility, the property name needs to remain the same + public static final String REMOTE_FILENAME = "FTPSampler.filename"; // $NON-NLS-1$ + + public static final String LOCAL_FILENAME = "FTPSampler.localfilename"; // $NON-NLS-1$ + + public static final String INPUT_DATA = "FTPSampler.inputdata"; // $NON-NLS-1$ + + // Use binary mode file transfer? + public static final String BINARY_MODE = "FTPSampler.binarymode"; // $NON-NLS-1$ + + // Are we uploading? + public static final String UPLOAD_FILE = "FTPSampler.upload"; // $NON-NLS-1$ + + // Should the file data be saved in the response? + public static final String SAVE_RESPONSE = "FTPSampler.saveresponse"; // $NON-NLS-1$ + + private transient volatile FTPClient savedClient; // used for interrupting the sampler + + public FTPSampler() { + } + + public String getUsername() { + return getPropertyAsString(ConfigTestElement.USERNAME); + } + + public String getPassword() { + return getPropertyAsString(ConfigTestElement.PASSWORD); + } + + public void setServer(String newServer) { + this.setProperty(SERVER, newServer); + } + + public String getServer() { + return getPropertyAsString(SERVER); + } + + public void setPort(String newPort) { + this.setProperty(PORT, newPort, ""); // $NON-NLS-1$ + } + + public String getPort() { + return getPropertyAsString(PORT, ""); // $NON-NLS-1$ + } + + public int getPortAsInt() { + return getPropertyAsInt(PORT, 0); + } + + public String getRemoteFilename() { + return getPropertyAsString(REMOTE_FILENAME); + } + + public String getLocalFilename() { + return getPropertyAsString(LOCAL_FILENAME); + } + + private String getLocalFileContents() { + return getPropertyAsString(INPUT_DATA); + } + + public boolean isBinaryMode(){ + return getPropertyAsBoolean(BINARY_MODE,false); + } + + public boolean isSaveResponse(){ + return getPropertyAsBoolean(SAVE_RESPONSE,false); + } + + public boolean isUpload(){ + return getPropertyAsBoolean(UPLOAD_FILE,false); + } + + + /** + * Returns a formatted string label describing this sampler Example output: + * ftp://ftp.nowhere.com/pub/README.txt + * + * @return a formatted string label describing this sampler + */ + public String getLabel() { + StringBuilder sb = new StringBuilder(); + sb.append("ftp://");// $NON-NLS-1$ + sb.append(getServer()); + String port = getPort(); + if (port.length() > 0){ + sb.append(':'); + sb.append(port); + } + sb.append("/");// $NON-NLS-1$ + sb.append(getRemoteFilename()); + sb.append(isBinaryMode() ? " (Binary) " : " (Ascii) ");// $NON-NLS-1$ $NON-NLS-2$ + sb.append(isUpload() ? " <- " : " -> "); // $NON-NLS-1$ $NON-NLS-2$ + sb.append(getLocalFilename()); + return sb.toString(); + } + + @Override + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + res.setSuccessful(false); // Assume failure + String remote = getRemoteFilename(); + String local = getLocalFilename(); + boolean binaryTransfer = isBinaryMode(); + res.setSampleLabel(getName()); + final String label = getLabel(); + res.setSamplerData(label); + try { + res.setURL(new URL(label)); + } catch (MalformedURLException e1) { + log.warn("Cannot set URL: "+e1.getLocalizedMessage()); + } + InputStream input = null; + OutputStream output = null; + + res.sampleStart(); + FTPClient ftp = new FTPClient(); + try { + savedClient = ftp; + final int port = getPortAsInt(); + if (port > 0){ + ftp.connect(getServer(),port); + } else { + ftp.connect(getServer()); + } + res.latencyEnd(); + int reply = ftp.getReplyCode(); + if (FTPReply.isPositiveCompletion(reply)) + { + if (ftp.login( getUsername(), getPassword())){ + if (binaryTransfer) { + ftp.setFileType(FTP.BINARY_FILE_TYPE); + } + ftp.enterLocalPassiveMode();// should probably come from the setup dialog + boolean ftpOK=false; + if (isUpload()) { + String contents=getLocalFileContents(); + if (contents.length() > 0){ + byte bytes[] = contents.getBytes(); // TODO - charset? + input = new ByteArrayInputStream(bytes); + res.setBytes(bytes.length); + } else { + File infile = new File(local); + res.setBytes((int)infile.length()); + input = new BufferedInputStream(new FileInputStream(infile)); + } + ftpOK = ftp.storeFile(remote, input); + } else { + final boolean saveResponse = isSaveResponse(); + ByteArrayOutputStream baos=null; // No need to close this + OutputStream target=null; // No need to close this + if (saveResponse){ + baos = new ByteArrayOutputStream(); + target=baos; + } + if (local.length()>0){ + output=new FileOutputStream(local); + if (target==null) { + target=output; + } else { + target = new TeeOutputStream(output,baos); + } + } + if (target == null){ + target=new NullOutputStream(); + } + input = ftp.retrieveFileStream(remote); + if (input == null){// Could not access file or other error + res.setResponseCode(Integer.toString(ftp.getReplyCode())); + res.setResponseMessage(ftp.getReplyString()); + } else { + long bytes = IOUtils.copy(input,target); + ftpOK = bytes > 0; + if (saveResponse && baos != null){ + res.setResponseData(baos.toByteArray()); + if (!binaryTransfer) { + res.setDataType(SampleResult.TEXT); + } + } else { + res.setBytes((int) bytes); + } + } + } + + if (ftpOK) { + res.setResponseCodeOK(); + res.setResponseMessageOK(); + res.setSuccessful(true); + } else { + res.setResponseCode(Integer.toString(ftp.getReplyCode())); + res.setResponseMessage(ftp.getReplyString()); + } + } else { + res.setResponseCode(Integer.toString(ftp.getReplyCode())); + res.setResponseMessage(ftp.getReplyString()); + } + } else { + res.setResponseCode("501"); // TODO + res.setResponseMessage("Could not connect"); + //res.setResponseCode(Integer.toString(ftp.getReplyCode())); + res.setResponseMessage(ftp.getReplyString()); + } + } catch (IOException ex) { + res.setResponseCode("000"); // TODO + res.setResponseMessage(ex.toString()); + } finally { + savedClient = null; + if (ftp.isConnected()) { + try { + ftp.logout(); + } catch (IOException ignored) { + } + try { + ftp.disconnect(); + } catch (IOException ignored) { + } + } + IOUtils.closeQuietly(input); + IOUtils.closeQuietly(output); + } + + res.sampleEnd(); + return res; + } + + /** {@inheritDoc} */ + @Override + public boolean interrupt() { + FTPClient client = savedClient; + if (client != null) { + savedClient = null; + try { + client.abort(); + } catch (IOException ignored) { + } + try { + client.disconnect(); + } catch (IOException ignored) { + } + } + return client != null; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/config/MultipartUrlConfig.java b/src/protocol/http/org/apache/jmeter/protocol/http/config/MultipartUrlConfig.java new file mode 100644 index 00000000000..8eb13a7cc02 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/config/MultipartUrlConfig.java @@ -0,0 +1,179 @@ +/* + * 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.protocol.http.config; + +import java.io.Serializable; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPFileArgs; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * Configuration element which handles HTTP Parameters and files to be uploaded + */ +public class MultipartUrlConfig implements Serializable { + + private static final long serialVersionUID = 240L; + + private final String boundary; + + private final Arguments args; + + /** + * HTTPFileArgs list to be uploaded with http request. + */ + private final HTTPFileArgs files; + + /** + * @deprecated only for use by unit tests + */ + @Deprecated + public MultipartUrlConfig(){ + this(null); + } + + // called by HttpRequestHdr + public MultipartUrlConfig(String boundary) { + args = new Arguments(); + files = new HTTPFileArgs(); + this.boundary = boundary; + } + + public String getBoundary() { + return boundary; + } + + public Arguments getArguments() { + return args; + } + + public void addArgument(String name, String value) { + Arguments myArgs = this.getArguments(); + myArgs.addArgument(new HTTPArgument(name, value)); + } + + public void addArgument(String name, String value, String metadata) { + Arguments myArgs = this.getArguments(); + myArgs.addArgument(new HTTPArgument(name, value, metadata)); + } + + public HTTPFileArgs getHTTPFileArgs() { + return files; + } + +// NOT USED +// /** +// * @deprecated values in a multipart/form-data are not urlencoded, +// * so it does not make sense to add a value as a encoded value +// */ +// public void addEncodedArgument(String name, String value) { +// Arguments myArgs = getArguments(); +// HTTPArgument arg = new HTTPArgument(name, value, true); +// if (arg.getName().equals(arg.getEncodedName()) && arg.getValue().equals(arg.getEncodedValue())) { +// arg.setAlwaysEncoded(false); +// } +// myArgs.addArgument(arg); +// } + + /** + * Add a value that is not URL encoded, and make sure it + * appears in the GUI that it will not be encoded when + * the request is sent. + * + * @param name + * @param value + */ + private void addNonEncodedArgument(String name, String value) { + Arguments myArgs = getArguments(); + // The value is not encoded + HTTPArgument arg = new HTTPArgument(name, value, false); + // Let the GUI show that it will not be encoded + arg.setAlwaysEncoded(false); + myArgs.addArgument(arg); + } + + /** + * This method allows a proxy server to send over the raw text from a + * browser's output stream to be parsed and stored correctly into the + * UrlConfig object. + * + * @param queryString text to parse + */ + public void parseArguments(String queryString) { + String[] parts = JOrphanUtils.split(queryString, "--" + getBoundary()); //$NON-NLS-1$ + for (int i = 0; i < parts.length; i++) { + String contentDisposition = getHeaderValue("Content-disposition", parts[i]); //$NON-NLS-1$ + String contentType = getHeaderValue("Content-type", parts[i]); //$NON-NLS-1$ + // Check if it is form data + if (contentDisposition != null && contentDisposition.indexOf("form-data") > -1) { //$NON-NLS-1$ + // Get the form field name + final String namePrefix = "name=\""; //$NON-NLS-1$ + int index = contentDisposition.indexOf(namePrefix) + namePrefix.length(); + String name = contentDisposition.substring(index, contentDisposition.indexOf('\"', index)); //$NON-NLS-1$ + + // Check if it is a file being uploaded + final String filenamePrefix = "filename=\""; //$NON-NLS-1$ + if (contentDisposition.indexOf(filenamePrefix) > -1) { + // Get the filename + index = contentDisposition.indexOf(filenamePrefix) + filenamePrefix.length(); + String path = contentDisposition.substring(index, contentDisposition.indexOf('\"', index)); //$NON-NLS-1$ + if(path != null && path.length() > 0) { + // Set the values retrieved for the file upload + files.addHTTPFileArg(path, name, contentType); + } + } + else { + // Find the first empty line of the multipart, it signals end of headers for multipart + // Agents are supposed to terminate lines in CRLF: + final String CRLF = "\r\n"; + final String CRLFCRLF = "\r\n\r\n"; + // Code also allows for LF only (not sure why - perhaps because the test code uses it?) + final String LF = "\n"; + final String LFLF = "\n\n"; + int indexEmptyCrLfCrLfLinePos = parts[i].indexOf(CRLFCRLF); //$NON-NLS-1$ + int indexEmptyLfLfLinePos = parts[i].indexOf(LFLF); //$NON-NLS-1$ + String value = null; + if(indexEmptyCrLfCrLfLinePos > -1) {// CRLF blank line found + value = parts[i].substring(indexEmptyCrLfCrLfLinePos+CRLFCRLF.length(),parts[i].lastIndexOf(CRLF)); + } else if(indexEmptyLfLfLinePos > -1) { // LF blank line found + value = parts[i].substring(indexEmptyLfLfLinePos+LFLF.length(),parts[i].lastIndexOf(LF)); + } + this.addNonEncodedArgument(name, value); + } + } + } + } + + private String getHeaderValue(String headerName, String multiPart) { + String regularExpression = headerName + "\\s*:\\s*(.*)$"; //$NON-NLS-1$ + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + if(localMatcher.contains(multiPart, pattern)) { + return localMatcher.getMatch().group(1).trim(); + } + else { + return null; + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java new file mode 100644 index 00000000000..ec054e9e8c7 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java @@ -0,0 +1,214 @@ +/* + * 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.protocol.http.config.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; + +public class HttpDefaultsGui extends AbstractConfigGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox imageParser; + + private JCheckBox concurrentDwn; + + private JTextField concurrentPool; + + private UrlConfigGui urlConfig; + + private JLabeledTextField embeddedRE; // regular expression used to match against embedded resource URLs + + public HttpDefaultsGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "url_config_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + ConfigTestElement config = new ConfigTestElement(); + modifyTestElement(config); + return config; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement config) { + ConfigTestElement cfg = (ConfigTestElement ) config; + ConfigTestElement el = (ConfigTestElement) urlConfig.createTestElement(); + cfg.clear(); // need to clear because the + cfg.addConfigElement(el); + super.configureTestElement(config); + if (imageParser.isSelected()) { + config.setProperty(new BooleanProperty(HTTPSamplerBase.IMAGE_PARSER, true)); + enableConcurrentDwn(true); + } else { + config.removeProperty(HTTPSamplerBase.IMAGE_PARSER); + enableConcurrentDwn(false); + + } + if (concurrentDwn.isSelected()) { + config.setProperty(new BooleanProperty(HTTPSamplerBase.CONCURRENT_DWN, true)); + } else { + // The default is false, so we can remove the property to simplify JMX files + // This also allows HTTPDefaults to work for this checkbox + config.removeProperty(HTTPSamplerBase.CONCURRENT_DWN); + } + if(!StringUtils.isEmpty(concurrentPool.getText())) { + config.setProperty(new StringProperty(HTTPSamplerBase.CONCURRENT_POOL, + concurrentPool.getText())); + } else { + config.setProperty(new StringProperty(HTTPSamplerBase.CONCURRENT_POOL, + String.valueOf(HTTPSamplerBase.CONCURRENT_POOL_SIZE))); + } + if (!StringUtils.isEmpty(embeddedRE.getText())) { + config.setProperty(new StringProperty(HTTPSamplerBase.EMBEDDED_URL_RE, + embeddedRE.getText())); + } else { + config.removeProperty(HTTPSamplerBase.EMBEDDED_URL_RE); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + urlConfig.clear(); + imageParser.setSelected(false); + concurrentDwn.setSelected(false); + concurrentPool.setText(String.valueOf(HTTPSamplerBase.CONCURRENT_POOL_SIZE)); + embeddedRE.setText(""); // $NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + urlConfig.configure(el); + imageParser.setSelected(((AbstractTestElement) el).getPropertyAsBoolean(HTTPSamplerBase.IMAGE_PARSER)); + concurrentDwn.setSelected(((AbstractTestElement) el).getPropertyAsBoolean(HTTPSamplerBase.CONCURRENT_DWN)); + concurrentPool.setText(((AbstractTestElement) el).getPropertyAsString(HTTPSamplerBase.CONCURRENT_POOL)); + embeddedRE.setText(((AbstractTestElement) el).getPropertyAsString(HTTPSamplerBase.EMBEDDED_URL_RE, "")); + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + urlConfig = new UrlConfigGui(false, true, false); + add(urlConfig, BorderLayout.CENTER); + + // OPTIONAL TASKS + final JPanel embeddedRsrcPanel = new HorizontalPanel(); + embeddedRsrcPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("web_testing_retrieve_title"))); // $NON-NLS-1$ + + imageParser = new JCheckBox(JMeterUtils.getResString("web_testing_retrieve_images")); // $NON-NLS-1$ + embeddedRsrcPanel.add(imageParser); + imageParser.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(final ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { enableConcurrentDwn(true); } + else { enableConcurrentDwn(false); } + } + }); + // Concurrent resources download + concurrentDwn = new JCheckBox(JMeterUtils.getResString("web_testing_concurrent_download")); // $NON-NLS-1$ + concurrentDwn.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(final ItemEvent e) { + if (imageParser.isSelected() && e.getStateChange() == ItemEvent.SELECTED) { concurrentPool.setEnabled(true); } + else { concurrentPool.setEnabled(false); } + } + }); + concurrentPool = new JTextField(2); // 2 columns size + concurrentPool.setMinimumSize(new Dimension(10,20)); + concurrentPool.setMaximumSize(new Dimension(30,20)); + embeddedRsrcPanel.add(concurrentDwn); + embeddedRsrcPanel.add(concurrentPool); + + // Embedded URL match regex + embeddedRE = new JLabeledTextField(JMeterUtils.getResString("web_testing_embedded_url_pattern"),20); // $NON-NLS-1$ + embeddedRsrcPanel.add(embeddedRE); + + + add(embeddedRsrcPanel, BorderLayout.SOUTH); + } + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + private void enableConcurrentDwn(final boolean enable) { + if (enable) { + concurrentDwn.setEnabled(true); + embeddedRE.setEnabled(true); + if (concurrentDwn.isSelected()) { + concurrentPool.setEnabled(true); + } + } else { + concurrentDwn.setEnabled(false); + concurrentPool.setEnabled(false); + embeddedRE.setEnabled(false); + } + } + + public void itemStateChanged(final ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + enableConcurrentDwn(true); + } else { + enableConcurrentDwn(false); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/config/gui/MultipartUrlConfigGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/config/gui/MultipartUrlConfigGui.java new file mode 100644 index 00000000000..30d6b90e382 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/config/gui/MultipartUrlConfigGui.java @@ -0,0 +1,106 @@ +/* + * 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.protocol.http.config.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JPanel; + +import org.apache.jmeter.protocol.http.gui.HTTPFileArgsPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class MultipartUrlConfigGui extends UrlConfigGui { + + private static final long serialVersionUID = 240L; + + /** + * Files panel that holds file informations to be uploaded by + * http request. + */ + private HTTPFileArgsPanel filesPanel; + + // used by HttpTestSampleGui + public MultipartUrlConfigGui() { + super(); + init(); + } + + // not currently used + public MultipartUrlConfigGui(boolean showSamplerFields) { + super(showSamplerFields); + init(); + } + + public MultipartUrlConfigGui(boolean showSamplerFields, boolean showImplementation) { + super(showSamplerFields, showImplementation, true); + init(); + } + + @Override + public void modifyTestElement(TestElement sampler) { + super.modifyTestElement(sampler); + filesPanel.modifyTestElement(sampler); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + filesPanel.configure(el); + } + + private void init() {// called from ctor, so must not be overridable + this.setLayout(new BorderLayout()); + + // WEB REQUEST PANEL + JPanel webRequestPanel = new JPanel(); + webRequestPanel.setLayout(new BorderLayout()); + webRequestPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_request"))); // $NON-NLS-1$ + + JPanel northPanel = new JPanel(); + northPanel.setLayout(new BoxLayout(northPanel, BoxLayout.Y_AXIS)); + northPanel.add(getProtocolAndMethodPanel()); + northPanel.add(getPathPanel()); + + webRequestPanel.add(northPanel, BorderLayout.NORTH); + webRequestPanel.add(getParameterPanel(), BorderLayout.CENTER); + webRequestPanel.add(getHTTPFileArgsPanel(), BorderLayout.SOUTH); + + this.add(getWebServerTimeoutPanel(), BorderLayout.NORTH); + this.add(webRequestPanel, BorderLayout.CENTER); + this.add(getProxyServerPanel(), BorderLayout.SOUTH); + } + + private JPanel getHTTPFileArgsPanel() { + filesPanel = new HTTPFileArgsPanel(JMeterUtils.getResString("send_file")); // $NON-NLS-1$ + return filesPanel; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + super.clear(); + filesPanel.clear(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java new file mode 100644 index 00000000000..f9063d88b4c --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java @@ -0,0 +1,783 @@ +/* + * 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.protocol.http.config.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Font; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; + +/** + * Basic URL / HTTP Request configuration: + *
    + *
  • host and port
  • + *
  • connect and response timeouts
  • + *
  • path, method, encoding, parameters
  • + *
  • redirects and keepalive
  • + *
+ */ +public class UrlConfigGui extends JPanel implements ChangeListener { + + private static final long serialVersionUID = 240L; + + private static final int TAB_PARAMETERS = 0; + + private static final int TAB_RAW_BODY = 1; + + private static final Font FONT_SMALL = new Font("SansSerif", Font.PLAIN, 12); + + private HTTPArgumentsPanel argsPanel; + + private JTextField domain; + + private JTextField port; + + private JTextField proxyHost; + + private JTextField proxyPort; + + private JTextField proxyUser; + + private JPasswordField proxyPass; + + private JTextField connectTimeOut; + + private JTextField responseTimeOut; + + private JTextField protocol; + + private JTextField contentEncoding; + + private JTextField path; + + private JCheckBox followRedirects; + + private JCheckBox autoRedirects; + + private JCheckBox useKeepAlive; + + private JCheckBox useMultipartForPost; + + private JCheckBox useBrowserCompatibleMultipartMode; + + private JLabeledChoice method; + + private JLabeledChoice httpImplementation; + + private final boolean notConfigOnly; + // set this false to suppress some items for use in HTTP Request defaults + + private final boolean showImplementation; // Set false for AJP + + // Body data + private JSyntaxTextArea postBodyContent; + + // Tabbed pane that contains parameters and raw body + private ValidationTabbedPane postContentTabbedPane; + + private boolean showRawBodyPane; + + /** + * Constructor which is setup to show HTTP implementation, raw body pane and + * sampler fields. + */ + public UrlConfigGui() { + this(true); + } + + /** + * Constructor which is setup to show HTTP implementation and raw body pane. + * + * @param showSamplerFields + * flag whether sampler fields should be shown. + */ + public UrlConfigGui(boolean showSamplerFields) { + this(showSamplerFields, true, true); + } + + /** + * @param showSamplerFields + * flag whether sampler fields should be shown + * @param showImplementation + * Show HTTP Implementation + * @param showRawBodyPane + * flag whether the raw body pane should be shown + */ + public UrlConfigGui(boolean showSamplerFields, boolean showImplementation, boolean showRawBodyPane) { + notConfigOnly=showSamplerFields; + this.showImplementation = showImplementation; + this.showRawBodyPane = showRawBodyPane; + init(); + } + + public void clear() { + domain.setText(""); // $NON-NLS-1$ + if (notConfigOnly){ + followRedirects.setSelected(true); + autoRedirects.setSelected(false); + method.setText(HTTPSamplerBase.DEFAULT_METHOD); + useKeepAlive.setSelected(true); + useMultipartForPost.setSelected(false); + useBrowserCompatibleMultipartMode.setSelected(HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + } + if (showImplementation) { + httpImplementation.setText(""); // $NON-NLS-1$ + } + path.setText(""); // $NON-NLS-1$ + port.setText(""); // $NON-NLS-1$ + proxyHost.setText(""); // $NON-NLS-1$ + proxyPort.setText(""); // $NON-NLS-1$ + proxyUser.setText(""); // $NON-NLS-1$ + proxyPass.setText(""); // $NON-NLS-1$ + connectTimeOut.setText(""); // $NON-NLS-1$ + responseTimeOut.setText(""); // $NON-NLS-1$ + protocol.setText(""); // $NON-NLS-1$ + contentEncoding.setText(""); // $NON-NLS-1$ + argsPanel.clear(); + if(showRawBodyPane) { + postBodyContent.setInitialText("");// $NON-NLS-1$ + } + postContentTabbedPane.setSelectedIndex(TAB_PARAMETERS, false); + } + + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + + element.setName(this.getName()); + element.setProperty(TestElement.GUI_CLASS, this.getClass().getName()); + element.setProperty(TestElement.TEST_CLASS, element.getClass().getName()); + modifyTestElement(element); + return element; + } + + /** + * Save the GUI values in the sampler. + * + * @param element {@link TestElement} to modify + */ + public void modifyTestElement(TestElement element) { + boolean useRaw = postContentTabbedPane.getSelectedIndex()==TAB_RAW_BODY; + Arguments args; + if(useRaw) { + args = new Arguments(); + String text = postBodyContent.getText(); + /* + * Textfield uses \n (LF) to delimit lines; we need to send CRLF. + * Rather than change the way that arguments are processed by the + * samplers for raw data, it is easier to fix the data. + * On retrival, CRLF is converted back to LF for storage in the text field. + * See + */ + HTTPArgument arg = new HTTPArgument("", text.replaceAll("\n","\r\n"), false); + arg.setAlwaysEncoded(false); + args.addArgument(arg); + } else { + args = (Arguments) argsPanel.createTestElement(); + HTTPArgument.convertArgumentsToHTTP(args); + } + element.setProperty(HTTPSamplerBase.POST_BODY_RAW, useRaw, HTTPSamplerBase.POST_BODY_RAW_DEFAULT); + element.setProperty(new TestElementProperty(HTTPSamplerBase.ARGUMENTS, args)); + element.setProperty(HTTPSamplerBase.DOMAIN, domain.getText()); + element.setProperty(HTTPSamplerBase.PORT, port.getText()); + element.setProperty(HTTPSamplerBase.PROXYHOST, proxyHost.getText(),""); + element.setProperty(HTTPSamplerBase.PROXYPORT, proxyPort.getText(),""); + element.setProperty(HTTPSamplerBase.PROXYUSER, proxyUser.getText(),""); + element.setProperty(HTTPSamplerBase.PROXYPASS, String.valueOf(proxyPass.getPassword()),""); + element.setProperty(HTTPSamplerBase.CONNECT_TIMEOUT, connectTimeOut.getText()); + element.setProperty(HTTPSamplerBase.RESPONSE_TIMEOUT, responseTimeOut.getText()); + element.setProperty(HTTPSamplerBase.PROTOCOL, protocol.getText()); + element.setProperty(HTTPSamplerBase.CONTENT_ENCODING, contentEncoding.getText()); + element.setProperty(HTTPSamplerBase.PATH, path.getText()); + if (notConfigOnly){ + element.setProperty(HTTPSamplerBase.METHOD, method.getText()); + element.setProperty(new BooleanProperty(HTTPSamplerBase.FOLLOW_REDIRECTS, followRedirects.isSelected())); + element.setProperty(new BooleanProperty(HTTPSamplerBase.AUTO_REDIRECTS, autoRedirects.isSelected())); + element.setProperty(new BooleanProperty(HTTPSamplerBase.USE_KEEPALIVE, useKeepAlive.isSelected())); + element.setProperty(new BooleanProperty(HTTPSamplerBase.DO_MULTIPART_POST, useMultipartForPost.isSelected())); + element.setProperty(HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART, useBrowserCompatibleMultipartMode.isSelected(),HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + } + if (showImplementation) { + element.setProperty(HTTPSamplerBase.IMPLEMENTATION, httpImplementation.getText(),""); + } + } + + // FIXME FACTOR WITH HTTPHC4Impl, HTTPHC3Impl + // Just append all the parameter values, and use that as the post body + /** + * Compute body data from arguments + * @param arguments {@link Arguments} + * @return {@link String} + */ + private static final String computePostBody(Arguments arguments) { + return computePostBody(arguments, false); + } + + /** + * Compute body data from arguments + * @param arguments {@link Arguments} + * @param crlfToLF whether to convert CRLF to LF + * @return {@link String} + */ + private static final String computePostBody(Arguments arguments, boolean crlfToLF) { + StringBuilder postBody = new StringBuilder(); + PropertyIterator args = arguments.iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String value = arg.getValue(); + if (crlfToLF) { + value=value.replaceAll("\r\n", "\n"); // See modifyTestElement + } + postBody.append(value); + } + return postBody.toString(); + } + + /** + * Set the text, etc. in the UI. + * + * @param el + * contains the data to be displayed + */ + public void configure(TestElement el) { + setName(el.getName()); + Arguments arguments = (Arguments) el.getProperty(HTTPSamplerBase.ARGUMENTS).getObjectValue(); + + boolean useRaw = el.getPropertyAsBoolean(HTTPSamplerBase.POST_BODY_RAW, HTTPSamplerBase.POST_BODY_RAW_DEFAULT); + if(useRaw) { + String postBody = computePostBody(arguments, true); // Convert CRLF to CR, see modifyTestElement + postBodyContent.setInitialText(postBody); + postBodyContent.setCaretPosition(0); + postContentTabbedPane.setSelectedIndex(TAB_RAW_BODY, false); + } else { + argsPanel.configure(arguments); + postContentTabbedPane.setSelectedIndex(TAB_PARAMETERS, false); + } + + domain.setText(el.getPropertyAsString(HTTPSamplerBase.DOMAIN)); + + String portString = el.getPropertyAsString(HTTPSamplerBase.PORT); + + // Only display the port number if it is meaningfully specified + if (portString.equals(HTTPSamplerBase.UNSPECIFIED_PORT_AS_STRING)) { + port.setText(""); // $NON-NLS-1$ + } else { + port.setText(portString); + } + proxyHost.setText(el.getPropertyAsString(HTTPSamplerBase.PROXYHOST)); + proxyPort.setText(el.getPropertyAsString(HTTPSamplerBase.PROXYPORT)); + proxyUser.setText(el.getPropertyAsString(HTTPSamplerBase.PROXYUSER)); + proxyPass.setText(el.getPropertyAsString(HTTPSamplerBase.PROXYPASS)); + connectTimeOut.setText(el.getPropertyAsString(HTTPSamplerBase.CONNECT_TIMEOUT)); + responseTimeOut.setText(el.getPropertyAsString(HTTPSamplerBase.RESPONSE_TIMEOUT)); + protocol.setText(el.getPropertyAsString(HTTPSamplerBase.PROTOCOL)); + contentEncoding.setText(el.getPropertyAsString(HTTPSamplerBase.CONTENT_ENCODING)); + path.setText(el.getPropertyAsString(HTTPSamplerBase.PATH)); + if (notConfigOnly){ + method.setText(el.getPropertyAsString(HTTPSamplerBase.METHOD)); + followRedirects.setSelected(el.getPropertyAsBoolean(HTTPSamplerBase.FOLLOW_REDIRECTS)); + autoRedirects.setSelected(el.getPropertyAsBoolean(HTTPSamplerBase.AUTO_REDIRECTS)); + useKeepAlive.setSelected(el.getPropertyAsBoolean(HTTPSamplerBase.USE_KEEPALIVE)); + useMultipartForPost.setSelected(el.getPropertyAsBoolean(HTTPSamplerBase.DO_MULTIPART_POST)); + useBrowserCompatibleMultipartMode.setSelected(el.getPropertyAsBoolean( + HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART, HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT)); + } + if (showImplementation) { + httpImplementation.setText(el.getPropertyAsString(HTTPSamplerBase.IMPLEMENTATION)); + } + } + + private void init() {// called from ctor, so must not be overridable + this.setLayout(new BorderLayout()); + + // WEB REQUEST PANEL + JPanel webRequestPanel = new JPanel(); + webRequestPanel.setLayout(new BorderLayout()); + webRequestPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_request"))); // $NON-NLS-1$ + + JPanel northPanel = new JPanel(); + northPanel.setLayout(new BoxLayout(northPanel, BoxLayout.Y_AXIS)); + northPanel.add(getProtocolAndMethodPanel()); + northPanel.add(getPathPanel()); + + webRequestPanel.add(northPanel, BorderLayout.NORTH); + webRequestPanel.add(getParameterPanel(), BorderLayout.CENTER); + + this.add(getWebServerTimeoutPanel(), BorderLayout.NORTH); + this.add(webRequestPanel, BorderLayout.CENTER); + this.add(getProxyServerPanel(), BorderLayout.SOUTH); + } + + /** + * Create a panel containing the webserver (domain+port) and timeouts (connect+request). + * + * @return the panel + */ + protected final JPanel getWebServerTimeoutPanel() { + // WEB SERVER PANEL + JPanel webServerPanel = new HorizontalPanel(); + webServerPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_server"))); // $NON-NLS-1$ + final JPanel domainPanel = getDomainPanel(); + final JPanel portPanel = getPortPanel(); + webServerPanel.add(domainPanel, BorderLayout.CENTER); + webServerPanel.add(portPanel, BorderLayout.EAST); + + JPanel timeOut = new HorizontalPanel(); + timeOut.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_server_timeout_title"))); // $NON-NLS-1$ + final JPanel connPanel = getConnectTimeOutPanel(); + final JPanel reqPanel = getResponseTimeOutPanel(); + timeOut.add(connPanel); + timeOut.add(reqPanel); + + JPanel webServerTimeoutPanel = new VerticalPanel(); + webServerTimeoutPanel.add(webServerPanel, BorderLayout.CENTER); + webServerTimeoutPanel.add(timeOut, BorderLayout.EAST); + + JPanel bigPanel = new VerticalPanel(); + bigPanel.add(webServerTimeoutPanel); + return bigPanel; + } + + /** + * Create a panel containing the proxy server details + * + * @return the panel + */ + protected final JPanel getProxyServerPanel(){ + JPanel proxyServer = new HorizontalPanel(); + proxyServer.add(getProxyHostPanel(), BorderLayout.CENTER); + proxyServer.add(getProxyPortPanel(), BorderLayout.EAST); + + JPanel proxyLogin = new HorizontalPanel(); + proxyLogin.add(getProxyUserPanel()); + proxyLogin.add(getProxyPassPanel()); + + JPanel proxyServerPanel = new HorizontalPanel(); + proxyServerPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_proxy_server_title"))); // $NON-NLS-1$ + proxyServerPanel.add(proxyServer); + proxyServerPanel.add(proxyLogin); + + return proxyServerPanel; + } + + private JPanel getPortPanel() { + port = new JTextField(10); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(port); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(port, BorderLayout.CENTER); + + return panel; + } + + private JPanel getProxyPortPanel() { + proxyPort = new JTextField(10); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(proxyPort); + label.setFont(FONT_SMALL); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyPort, BorderLayout.CENTER); + + return panel; + } + + private JPanel getConnectTimeOutPanel() { + connectTimeOut = new JTextField(10); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_timeout_connect")); // $NON-NLS-1$ + label.setLabelFor(connectTimeOut); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(connectTimeOut, BorderLayout.CENTER); + + return panel; + } + + private JPanel getResponseTimeOutPanel() { + responseTimeOut = new JTextField(10); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_timeout_response")); // $NON-NLS-1$ + label.setLabelFor(responseTimeOut); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(responseTimeOut, BorderLayout.CENTER); + + return panel; + } + + private JPanel getDomainPanel() { + domain = new JTextField(20); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + label.setLabelFor(domain); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(domain, BorderLayout.CENTER); + return panel; + } + + private JPanel getProxyHostPanel() { + proxyHost = new JTextField(10); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + label.setLabelFor(proxyHost); + label.setFont(FONT_SMALL); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyHost, BorderLayout.CENTER); + return panel; + } + + private JPanel getProxyUserPanel() { + proxyUser = new JTextField(5); + + JLabel label = new JLabel(JMeterUtils.getResString("username")); // $NON-NLS-1$ + label.setLabelFor(proxyUser); + label.setFont(FONT_SMALL); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyUser, BorderLayout.CENTER); + return panel; + } + + private JPanel getProxyPassPanel() { + proxyPass = new JPasswordField(5); + + JLabel label = new JLabel(JMeterUtils.getResString("password")); // $NON-NLS-1$ + label.setLabelFor(proxyPass); + label.setFont(FONT_SMALL); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyPass, BorderLayout.CENTER); + return panel; + } + + /** + * This method defines the Panel for the HTTP path, 'Follow Redirects' + * 'Use KeepAlive', and 'Use multipart for HTTP POST' elements. + * + * @return JPanel The Panel for the path, 'Follow Redirects' and 'Use + * KeepAlive' elements. + */ + protected Component getPathPanel() { + path = new JTextField(15); + + JLabel label = new JLabel(JMeterUtils.getResString("path")); //$NON-NLS-1$ + label.setLabelFor(path); + + if (notConfigOnly){ + followRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects")); // $NON-NLS-1$ + followRedirects.setFont(null); + followRedirects.setSelected(true); + followRedirects.addChangeListener(this); + + autoRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects_auto")); //$NON-NLS-1$ + autoRedirects.setFont(null); + autoRedirects.addChangeListener(this); + autoRedirects.setSelected(false);// Default changed in 2.3 and again in 2.4 + + useKeepAlive = new JCheckBox(JMeterUtils.getResString("use_keepalive")); // $NON-NLS-1$ + useKeepAlive.setFont(null); + useKeepAlive.setSelected(true); + + useMultipartForPost = new JCheckBox(JMeterUtils.getResString("use_multipart_for_http_post")); // $NON-NLS-1$ + useMultipartForPost.setFont(null); + useMultipartForPost.setSelected(false); + + useBrowserCompatibleMultipartMode = new JCheckBox(JMeterUtils.getResString("use_multipart_mode_browser")); // $NON-NLS-1$ + useBrowserCompatibleMultipartMode.setFont(null); + useBrowserCompatibleMultipartMode.setSelected(HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + + } + + JPanel pathPanel = new HorizontalPanel(); + pathPanel.add(label); + pathPanel.add(path); + + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.add(pathPanel); + if (notConfigOnly){ + JPanel optionPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + optionPanel.setFont(FONT_SMALL); // all sub-components with setFont(null) inherit this font + optionPanel.add(autoRedirects); + optionPanel.add(followRedirects); + optionPanel.add(useKeepAlive); + optionPanel.add(useMultipartForPost); + optionPanel.add(useBrowserCompatibleMultipartMode); + optionPanel.setMinimumSize(optionPanel.getPreferredSize()); + panel.add(optionPanel); + } + + return panel; + } + + protected JPanel getProtocolAndMethodPanel() { + + // Implementation + + if (showImplementation) { + httpImplementation = new JLabeledChoice(JMeterUtils.getResString("http_implementation"), // $NON-NLS-1$ + HTTPSamplerFactory.getImplementations()); + httpImplementation.addValue(""); + } + // PROTOCOL + protocol = new JTextField(4); + JLabel protocolLabel = new JLabel(JMeterUtils.getResString("protocol")); // $NON-NLS-1$ + protocolLabel.setLabelFor(protocol); + + // CONTENT_ENCODING + contentEncoding = new JTextField(10); + JLabel contentEncodingLabel = new JLabel(JMeterUtils.getResString("content_encoding")); // $NON-NLS-1$ + contentEncodingLabel.setLabelFor(contentEncoding); + + if (notConfigOnly){ + method = new JLabeledChoice(JMeterUtils.getResString("method"), // $NON-NLS-1$ + HTTPSamplerBase.getValidMethodsAsArray()); + } + + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + + if (showImplementation) { + panel.add(httpImplementation); + } + panel.add(protocolLabel); + panel.add(protocol); + panel.add(Box.createHorizontalStrut(5)); + + if (notConfigOnly){ + panel.add(method); + } + panel.setMinimumSize(panel.getPreferredSize()); + panel.add(Box.createHorizontalStrut(5)); + + panel.add(contentEncodingLabel); + panel.add(contentEncoding); + panel.setMinimumSize(panel.getPreferredSize()); + return panel; + } + + protected JTabbedPane getParameterPanel() { + postContentTabbedPane = new ValidationTabbedPane(); + argsPanel = new HTTPArgumentsPanel(); + postContentTabbedPane.add(JMeterUtils.getResString("post_as_parameters"), argsPanel);// $NON-NLS-1$ + if(showRawBodyPane) { + postBodyContent = new JSyntaxTextArea(30, 50);// $NON-NLS-1$ + postContentTabbedPane.add(JMeterUtils.getResString("post_body"), new JTextScrollPane(postBodyContent));// $NON-NLS-1$ + } + return postContentTabbedPane; + } + + /** + * + */ + class ValidationTabbedPane extends JTabbedPane{ + + /** + * + */ + private static final long serialVersionUID = 7014311238367882880L; + + /* (non-Javadoc) + * @see javax.swing.JTabbedPane#setSelectedIndex(int) + */ + @Override + public void setSelectedIndex(int index) { + setSelectedIndex(index, true); + } + + /** + * Apply some check rules if check is true + * + * @param index + * index to select + * @param check + * flag whether to perform checks before setting the selected + * index + */ + public void setSelectedIndex(int index, boolean check) { + int oldSelectedIndex = getSelectedIndex(); + if(!check || oldSelectedIndex==-1) { + super.setSelectedIndex(index); + } + else if(index != this.getSelectedIndex()) + { + if(noData(getSelectedIndex())) { + // If there is no data, then switching between Parameters and Raw should be + // allowed with no further user interaction. + argsPanel.clear(); + postBodyContent.setInitialText(""); + super.setSelectedIndex(index); + } + else { + if(oldSelectedIndex == TAB_RAW_BODY) { + // If RAW data and Parameters match we allow switching + if(postBodyContent.getText().equals(computePostBody((Arguments)argsPanel.createTestElement()).trim())) { + super.setSelectedIndex(index); + } + else { + // If there is data in the Raw panel, then the user should be + // prevented from switching (that would be easy to track). + JOptionPane.showConfirmDialog(this, + JMeterUtils.getResString("web_cannot_switch_tab"), // $NON-NLS-1$ + JMeterUtils.getResString("warning"), // $NON-NLS-1$ + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE); + return; + } + } + else { + // If the Parameter data can be converted (i.e. no names), we + // warn the user that the Parameter data will be lost. + if(canConvertParameters()) { + Object[] options = { + JMeterUtils.getResString("confirm"), // $NON-NLS-1$ + JMeterUtils.getResString("cancel")}; // $NON-NLS-1$ + int n = JOptionPane.showOptionDialog(this, + JMeterUtils.getResString("web_parameters_lost_message"), // $NON-NLS-1$ + JMeterUtils.getResString("warning"), // $NON-NLS-1$ + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[1]); + if(n == JOptionPane.YES_OPTION) { + convertParametersToRaw(); + super.setSelectedIndex(index); + } + else{ + return; + } + } + else { + // If the Parameter data cannot be converted to Raw, then the user should be + // prevented from doing so raise an error dialog + JOptionPane.showConfirmDialog(this, + JMeterUtils.getResString("web_cannot_convert_parameters_to_raw"), // $NON-NLS-1$ + JMeterUtils.getResString("warning"), // $NON-NLS-1$ + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE); + return; + } + } + } + } + } + } + // autoRedirects and followRedirects cannot both be selected + @Override + public void stateChanged(ChangeEvent e) { + if (e.getSource() == autoRedirects){ + if (autoRedirects.isSelected()) { + followRedirects.setSelected(false); + } + } + if (e.getSource() == followRedirects){ + if (followRedirects.isSelected()) { + autoRedirects.setSelected(false); + } + } + } + + + /** + * Convert Parameters to Raw Body + */ + void convertParametersToRaw() { + postBodyContent.setInitialText(computePostBody((Arguments)argsPanel.createTestElement())); + postBodyContent.setCaretPosition(0); + } + + /** + * + * @return true if no argument has a name + */ + boolean canConvertParameters() { + Arguments arguments = (Arguments)argsPanel.createTestElement(); + for (int i = 0; i < arguments.getArgumentCount(); i++) { + if(!StringUtils.isEmpty(arguments.getArgument(i).getName())) { + return false; + } + } + return true; + } + + /** + * Checks if no data is available in the selected tab + * + * @param oldSelectedIndex + * the tab to check for data + * @return true if neither Parameters tab nor Raw Body tab contain data + */ + boolean noData(int oldSelectedIndex) { + if(oldSelectedIndex == TAB_RAW_BODY) { + return StringUtils.isEmpty(postBodyContent.getText().trim()); + } + else { + Arguments element = (Arguments)argsPanel.createTestElement(); + return StringUtils.isEmpty(computePostBody(element)); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/AuthManager.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/AuthManager.java new file mode 100644 index 00000000000..f074e519ad1 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/AuthManager.java @@ -0,0 +1,538 @@ +/* + * 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.protocol.http.control; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.Principal; +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +import javax.security.auth.Subject; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.NTCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.params.AuthPolicy; +import org.apache.http.impl.auth.SPNegoSchemeFactory; +import org.apache.http.impl.client.AbstractHttpClient; +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.testelement.TestIterationListener; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +// For Unit tests, @see TestAuthManager + +/** + * This class provides a way to provide Authorization in jmeter requests. The + * format of the authorization file is: URL user pass where URL is an HTTP URL, + * user a username to use and pass the appropriate password. + * + */ +public class AuthManager extends ConfigTestElement implements TestStateListener, TestIterationListener, Serializable { + private static final long serialVersionUID = 234L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String CLEAR = "AuthManager.clearEachIteration";// $NON-NLS-1$ + + private static final String AUTH_LIST = "AuthManager.auth_list"; //$NON-NLS-1$ + + private static final String[] COLUMN_RESOURCE_NAMES = { + "auth_base_url", //$NON-NLS-1$ + "username", //$NON-NLS-1$ + "password", //$NON-NLS-1$ + "domain", //$NON-NLS-1$ + "realm", //$NON-NLS-1$ + "mechanism", //$NON-NLS-1$ + }; + + // Column numbers - must agree with order above + public static final int COL_URL = 0; + public static final int COL_USERNAME = 1; + public static final int COL_PASSWORD = 2; + public static final int COL_DOMAIN = 3; + public static final int COL_REALM = 4; + public static final int COL_MECHANISM = 5; + + private static final int COLUMN_COUNT = COLUMN_RESOURCE_NAMES.length; + + private static final Credentials USE_JAAS_CREDENTIALS = new NullCredentials(); + + private static final boolean DEFAULT_CLEAR_VALUE = false; + + /** Decides whether port should be omitted from SPN for kerberos spnego authentication */ + private static final boolean STRIP_PORT = JMeterUtils.getPropDefault("kerberos.spnego.strip_port", true); + + public enum Mechanism { + BASIC_DIGEST, KERBEROS; + } + + private static final class NullCredentials implements Credentials { + @Override + public String getPassword() { + return null; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + } + + private KerberosManager kerberosManager = new KerberosManager(); + + /** + * Default Constructor. + */ + public AuthManager() { + setProperty(new CollectionProperty(AUTH_LIST, new ArrayList())); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + super.clear(); + kerberosManager.clearSubjects(); + setProperty(new CollectionProperty(AUTH_LIST, new ArrayList())); + } + + /** + * Update an authentication record. + * + * @param index + * index at which position the record should be set + * @param url + * url for which the authentication record should be used + * @param user + * name of the user + * @param pass + * password of the user + * @param domain + * domain of the user + * @param realm + * realm of the site + * @param mechanism + * authentication {@link Mechanism} to use + */ + public void set(int index, String url, String user, String pass, String domain, String realm, Mechanism mechanism) { + Authorization auth = new Authorization(url, user, pass, domain, realm, mechanism); + if (index >= 0) { + getAuthObjects().set(index, new TestElementProperty(auth.getName(), auth)); + } else { + getAuthObjects().addItem(auth); + } + } + + public CollectionProperty getAuthObjects() { + return (CollectionProperty) getProperty(AUTH_LIST); + } + + public int getColumnCount() { + return COLUMN_COUNT; + } + + public String getColumnName(int column) { + return COLUMN_RESOURCE_NAMES[column]; + } + + public Class getColumnClass(int column) { + return COLUMN_RESOURCE_NAMES[column].getClass(); + } + + public Authorization getAuthObjectAt(int row) { + return (Authorization) getAuthObjects().get(row).getObjectValue(); + } + + public boolean isEditable() { + return true; + } + + /** + * Return the record at index i + * + * @param i + * index of the record to get + * @return authorization record at index i + */ + public Authorization get(int i) { + return (Authorization) getAuthObjects().get(i).getObjectValue(); + } + + public String getAuthHeaderForURL(URL url) { + Authorization auth = getAuthForURL(url); + if (auth == null) { + return null; + } + return auth.toBasicHeader(); + } + + public Authorization getAuthForURL(URL url) { + if (!isSupportedProtocol(url)) { + return null; + } + + // TODO: replace all this url2 mess with a proper method + // "areEquivalent(url1, url2)" that + // would also ignore case in protocol and host names, etc. -- use that + // method in the CookieManager too. + + URL url2 = null; + + try { + if (url.getPort() == -1) { + // Obtain another URL with an explicit port: + int port = url.getProtocol().equalsIgnoreCase("http") ? HTTPConstants.DEFAULT_HTTP_PORT : HTTPConstants.DEFAULT_HTTPS_PORT; + // only http and https are supported + url2 = new URL(url.getProtocol(), url.getHost(), port, url.getPath()); + } else if ((url.getPort() == HTTPConstants.DEFAULT_HTTP_PORT && url.getProtocol().equalsIgnoreCase("http")) + || (url.getPort() == HTTPConstants.DEFAULT_HTTPS_PORT && url.getProtocol().equalsIgnoreCase("https"))) { + url2 = new URL(url.getProtocol(), url.getHost(), url.getPath()); + } + } catch (MalformedURLException e) { + log.error("Internal error!", e); // this should never happen + // anyway, we'll continue with url2 set to null. + } + + String s1 = url.toString(); + String s2 = null; + if (url2 != null) { + s2 = url2.toString(); + } + + log.debug("Target URL strings to match against: "+s1+" and "+s2); + // TODO should really return most specific (i.e. longest) match. + for (PropertyIterator iter = getAuthObjects().iterator(); iter.hasNext();) { + Authorization auth = (Authorization) iter.next().getObjectValue(); + + String uRL = auth.getURL(); + log.debug("Checking match against auth'n entry: "+uRL); + if (s1.startsWith(uRL) || s2 != null && s2.startsWith(uRL)) { + log.debug("Matched"); + return auth; + } + log.debug("Did not match"); + } + return null; + } + + /** + * Tests whether an authorization record is available for a given URL + * + * @param url + * {@link URL} for which an authorization record should be + * available + * @return true if an authorization is setup for url, + * false otherwise + */ + public boolean hasAuthForURL(URL url) { + return getAuthForURL(url) != null; + } + + /** + * Get a {@link Subject} for a given URL, if available + * + * @param url + * {@link URL} for which the subject was asked + * @return Subject if Auth Scheme uses Subject and an authorization is setup + * for url, null otherwise + */ + public Subject getSubjectForUrl(URL url) { + Authorization authorization = getAuthForURL(url); + if (authorization != null && Mechanism.KERBEROS.equals(authorization.getMechanism())) { + return kerberosManager.getSubjectForUser( + authorization.getUser(), authorization.getPass()); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public void addConfigElement(ConfigElement config) { + } + + /** + * Add newAuthorization if it does not already exist + * @param newAuthorization authorization to be added + */ + public void addAuth(Authorization newAuthorization) { + boolean alreadyExists=false; + PropertyIterator iter = getAuthObjects().iterator(); + //iterate over authentication objects in manager + while (iter.hasNext()) { + Authorization authorization = (Authorization) iter.next().getObjectValue(); + if (authorization == null) { + continue; + } + if (match(authorization,newAuthorization)) { + if (log.isDebugEnabled()) { + log.debug("Found the same Authorization object:" + newAuthorization.toString()); + } + //set true, if found the same one + alreadyExists=true; + break; + } + } + if(!alreadyExists){ + // if there was no such auth object, add. + getAuthObjects().addItem(newAuthorization); + } + } + + public void addAuth() { + getAuthObjects().addItem(new Authorization()); + } + + /** {@inheritDoc} */ + @Override + public boolean expectsModification() { + return false; + } + + /** + * Save the authentication data to a file. + * + * @param authFile + * path of the file to save the authentication data to + * @throws IOException + * when writing to the file fails + */ + public void save(String authFile) throws IOException { + File file = new File(authFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir"),authFile); + } + PrintWriter writer = null; + try { + writer = new PrintWriter(new FileWriter(file)); + writer.println("# JMeter generated Authorization file"); + for (int i = 0; i < getAuthObjects().size(); i++) { + Authorization auth = (Authorization) getAuthObjects().get(i).getObjectValue(); + writer.println(auth.toString()); + } + writer.flush(); + writer.close(); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } + + /** + * Add authentication data from a file. + * + * @param authFile + * path to the file to read the authentication data from + * @throws IOException + * when reading the data fails + */ + public void addFile(String authFile) throws IOException { + File file = new File(authFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir") + File.separator + authFile); + } + if (!file.canRead()) { + throw new IOException("The file you specified cannot be read."); + } + + BufferedReader reader = null; + boolean ok = true; + try { + reader = new BufferedReader(new FileReader(file)); + String line; + while ((line = reader.readLine()) != null) { + try { + if (line.startsWith("#") || JOrphanUtils.isBlank(line)) { //$NON-NLS-1$ + continue; + } + StringTokenizer st = new StringTokenizer(line, "\t"); //$NON-NLS-1$ + String url = st.nextToken(); + String user = st.nextToken(); + String pass = st.nextToken(); + String domain = ""; + String realm = ""; + if (st.hasMoreTokens()){// Allow for old format file without the extra columnns + domain = st.nextToken(); + realm = st.nextToken(); + } + Mechanism mechanism = Mechanism.BASIC_DIGEST; + if (st.hasMoreTokens()){// Allow for old format file without mechanism support + mechanism = Mechanism.valueOf(st.nextToken()); + } + Authorization auth = new Authorization(url, user, pass, domain, realm, mechanism); + getAuthObjects().addItem(auth); + } catch (NoSuchElementException e) { + log.error("Error parsing auth line: '" + line + "'"); + ok = false; + } + } + } finally { + JOrphanUtils.closeQuietly(reader); + } + if (!ok){ + JMeterUtils.reportErrorToUser("One or more errors found when reading the Auth file - see the log file"); + } + } + + /** + * Remove an authentication record. + * + * @param index + * index of the authentication record to remove + */ + public void remove(int index) { + getAuthObjects().remove(index); + } + + /** + * + * @return true if kerberos auth must be cleared on each mail loop iteration + */ + public boolean getClearEachIteration() { + return getPropertyAsBoolean(CLEAR, DEFAULT_CLEAR_VALUE); + } + + public void setClearEachIteration(boolean clear) { + setProperty(CLEAR, clear, DEFAULT_CLEAR_VALUE); + } + + /** + * Return the number of records. + * + * @return the number of records + */ + public int getAuthCount() { + return getAuthObjects().size(); + } + + // Needs to be package protected for Unit test + static boolean isSupportedProtocol(URL url) { + String protocol = url.getProtocol().toLowerCase(java.util.Locale.ENGLISH); + return protocol.equals(HTTPConstants.PROTOCOL_HTTP) || protocol.equals(HTTPConstants.PROTOCOL_HTTPS); + } + + /** + * Configure credentials and auth scheme on client if an authorization is + * available for url + * @param client {@link HttpClient} + * @param url URL to test + * @param credentialsProvider {@link CredentialsProvider} + * @param localHost host running JMeter + */ + public void setupCredentials(HttpClient client, URL url, + CredentialsProvider credentialsProvider, String localHost) { + Authorization auth = getAuthForURL(url); + if (auth != null) { + String username = auth.getUser(); + String realm = auth.getRealm(); + String domain = auth.getDomain(); + if (log.isDebugEnabled()){ + log.debug(username + " > D="+domain+" R="+realm + " M="+auth.getMechanism()); + } + if (Mechanism.KERBEROS.equals(auth.getMechanism())) { + ((AbstractHttpClient) client).getAuthSchemes().register(AuthPolicy.SPNEGO, new SPNegoSchemeFactory(isStripPort(url))); + credentialsProvider.setCredentials(new AuthScope(null, -1, null), USE_JAAS_CREDENTIALS); + } else { + credentialsProvider.setCredentials( + new AuthScope(url.getHost(), url.getPort(), realm.length()==0 ? null : realm), + new NTCredentials(username, auth.getPass(), localHost, domain)); + } + } + } + + /** + * IE and Firefox will always strip port from the url before constructing + * the SPN. Chrome has an option (--enable-auth-negotiate-port) + * to include the port if it differs from 80 or + * 443. That behavior can be changed by setting the jmeter + * property kerberos.spnego.strip_port. + * + * @param url to be checked + * @return true when port should omitted in SPN + */ + private boolean isStripPort(URL url) { + if (STRIP_PORT) { + return true; + } + return (url.getPort() == HTTPConstants.DEFAULT_HTTP_PORT || + url.getPort() == HTTPConstants.DEFAULT_HTTPS_PORT); + } + + /** + * Check if two authorization objects are equal ignoring username/password + * @param a {@link Authorization} + * @param b {@link Authorization} + * @return true if a and b match + */ + private boolean match(Authorization a, Authorization b){ + return + a.getURL().equals(b.getURL())&& + a.getDomain().equals(b.getDomain())&& + a.getRealm().equals(b.getRealm())&& + a.getMechanism().equals(b.getMechanism()); + } + + /** {@inheritDoc} */ + @Override + public void testStarted() { + kerberosManager.clearSubjects(); + } + + /** {@inheritDoc} */ + @Override + public void testEnded() { + } + + /** {@inheritDoc} */ + @Override + public void testStarted(String host) { + testStarted(); + } + + /** {@inheritDoc} */ + @Override + public void testEnded(String host) { + } + + /** {@inheritDoc} */ + @Override + public void testIterationStart(LoopIterationEvent event) { + if (getClearEachIteration()) { + kerberosManager.clearSubjects(); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/Authorization.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/Authorization.java new file mode 100644 index 00000000000..00233dc82c0 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/Authorization.java @@ -0,0 +1,136 @@ +/* + * 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.protocol.http.control; + +import java.io.Serializable; + +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.protocol.http.control.AuthManager.Mechanism; +import org.apache.jmeter.protocol.http.util.Base64Encoder; +import org.apache.jmeter.testelement.AbstractTestElement; + +/** + * This class is an Authorization encapsulator. + * + */ +public class Authorization extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 241L; + + private static final String URL = "Authorization.url"; // $NON-NLS-1$ + + private static final String USERNAME = "Authorization.username"; // $NON-NLS-1$ + + private static final String PASSWORD = "Authorization.password"; // $NON-NLS-1$ + + private static final String DOMAIN = "Authorization.domain"; // $NON-NLS-1$ + + private static final String REALM = "Authorization.realm"; // $NON-NLS-1$ + + private static final String MECHANISM = "Authorization.mechanism"; // $NON-NLS-1$ + + private static final String TAB = "\t"; // $NON-NLS-1$ + + /** + * create the authorization + * @param url url for which the authorization should be considered + * @param user name of the user + * @param pass password for the user + * @param domain authorization domain (used for NTML-authentication) + * @param realm authorization realm + * @param mechanism authorization mechanism, that should be used + */ + Authorization(String url, String user, String pass, String domain, String realm, Mechanism mechanism) { + setURL(url); + setUser(user); + setPass(pass); + setDomain(domain); + setRealm(realm); + setMechanism(mechanism); + } + + public boolean expectsModification() { + return false; + } + + public Authorization() { + this("","","","","", Mechanism.BASIC_DIGEST); + } + + public void addConfigElement(ConfigElement config) { + } + + public String getURL() { + return getPropertyAsString(URL); + } + + public void setURL(String url) { + setProperty(URL, url); + } + + public String getUser() { + return getPropertyAsString(USERNAME); + } + + public void setUser(String user) { + setProperty(USERNAME, user); + } + + public String getPass() { + return getPropertyAsString(PASSWORD); + } + + public void setPass(String pass) { + setProperty(PASSWORD, pass); + } + + public String getDomain() { + return getPropertyAsString(DOMAIN); + } + + public void setDomain(String domain) { + setProperty(DOMAIN, domain); + } + + public String getRealm() { + return getPropertyAsString(REALM); + } + + public void setRealm(String realm) { + setProperty(REALM, realm); + } + + public Mechanism getMechanism() { + return Mechanism.valueOf(getPropertyAsString(MECHANISM, Mechanism.BASIC_DIGEST.name())); + } + + public void setMechanism(Mechanism mechanism) { + setProperty(MECHANISM, mechanism.name(), Mechanism.BASIC_DIGEST.name()); + } + + // Used for saving entries to a file + @Override + public String toString() { + return getURL() + TAB + getUser() + TAB + getPass() + TAB + getDomain() + TAB + getRealm() + TAB + getMechanism(); + } + + public String toBasicHeader(){ + return "Basic " + Base64Encoder.encode(getUser() + ":" + getPass()); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/CacheManager.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/CacheManager.java new file mode 100644 index 00000000000..2cea3265ad4 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/CacheManager.java @@ -0,0 +1,473 @@ +/* + * 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. + * + */ + +// For unit tests @see TestCookieManager + +package org.apache.jmeter.protocol.http.control; + +import java.io.Serializable; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Map; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.URIException; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.impl.cookie.DateParseException; +import org.apache.http.impl.cookie.DateUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.testelement.TestIterationListener; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Handles HTTP Caching + */ +public class CacheManager extends ConfigTestElement implements TestStateListener, TestIterationListener, Serializable { + + private static final Date EXPIRED_DATE = new Date(0L); + + private static final long serialVersionUID = 234L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String[] CACHEABLE_METHODS = JMeterUtils.getPropDefault("cacheable_methods", "GET").split("[ ,]"); + + static { + log.info("Will only cache the following methods: "+Arrays.toString(CACHEABLE_METHODS)); + } + + //+ JMX attributes, do not change values + public static final String CLEAR = "clearEachIteration"; // $NON-NLS-1$ + public static final String USE_EXPIRES = "useExpires"; // $NON-NLS-1$ + public static final String MAX_SIZE = "maxSize"; // $NON-NLS-1$ + //- + + private transient InheritableThreadLocal> threadCache; + + private transient boolean useExpires; // Cached value + + private static final int DEFAULT_MAX_SIZE = 5000; + + private static final long ONE_YEAR_MS = 365*24*60*60*1000L; + + public CacheManager() { + setProperty(new BooleanProperty(CLEAR, false)); + setProperty(new BooleanProperty(USE_EXPIRES, false)); + clearCache(); + useExpires = false; + } + + /* + * Holder for storing cache details. + * Perhaps add original response later? + */ + // package-protected to allow access by unit-test cases + static class CacheEntry{ + private final String lastModified; + private final String etag; + private final Date expires; + public CacheEntry(String lastModified, Date expires, String etag){ + this.lastModified = lastModified; + this.etag = etag; + this.expires = expires; + } + public String getLastModified() { + return lastModified; + } + public String getEtag() { + return etag; + } + @Override + public String toString(){ + return lastModified+" "+etag; + } + public Date getExpires() { + return expires; + } + } + + /** + * Save the Last-Modified, Etag, and Expires headers if the result is cacheable. + * Version for Java implementation. + * @param conn connection + * @param res result + */ + public void saveDetails(URLConnection conn, HTTPSampleResult res){ + if (isCacheable(res)){ + String lastModified = conn.getHeaderField(HTTPConstants.LAST_MODIFIED); + String expires = conn.getHeaderField(HTTPConstants.EXPIRES); + String etag = conn.getHeaderField(HTTPConstants.ETAG); + String url = conn.getURL().toString(); + String cacheControl = conn.getHeaderField(HTTPConstants.CACHE_CONTROL); + String date = conn.getHeaderField(HTTPConstants.DATE); + setCache(lastModified, cacheControl, expires, etag, url, date); + } + } + + /** + * Save the Last-Modified, Etag, and Expires headers if the result is + * cacheable. Version for Commons HttpClient implementation. + * + * @param method + * {@link HttpMethod} to get header information from + * @param res + * result to decide if result is cacheable + * @throws URIException + * if extraction of the the uri from method fails + */ + public void saveDetails(HttpMethod method, HTTPSampleResult res) throws URIException{ + if (isCacheable(res)){ + String lastModified = getHeader(method ,HTTPConstants.LAST_MODIFIED); + String expires = getHeader(method ,HTTPConstants.EXPIRES); + String etag = getHeader(method ,HTTPConstants.ETAG); + String url = method.getURI().toString(); + String cacheControl = getHeader(method, HTTPConstants.CACHE_CONTROL); + String date = getHeader(method, HTTPConstants.DATE); + setCache(lastModified, cacheControl, expires, etag, url, date); + } + } + + /** + * Save the Last-Modified, Etag, and Expires headers if the result is + * cacheable. Version for Apache HttpClient implementation. + * + * @param method + * {@link HttpResponse} to extract header information from + * @param res + * result to decide if result is cacheable + */ + public void saveDetails(HttpResponse method, HTTPSampleResult res) { + if (isCacheable(res)){ + String lastModified = getHeader(method ,HTTPConstants.LAST_MODIFIED); + String expires = getHeader(method ,HTTPConstants.EXPIRES); + String etag = getHeader(method ,HTTPConstants.ETAG); + String cacheControl = getHeader(method, HTTPConstants.CACHE_CONTROL); + String date = getHeader(method, HTTPConstants.DATE); + setCache(lastModified, cacheControl, expires, etag, res.getUrlAsString(), date); // TODO correct URL? + } + } + + // helper method to save the cache entry + private void setCache(String lastModified, String cacheControl, String expires, String etag, String url, String date) { + if (log.isDebugEnabled()){ + log.debug("setCache(" + + lastModified + "," + + cacheControl + "," + + expires + "," + + etag + "," + + url + "," + + date + + ")"); + } + Date expiresDate = null; // i.e. not using Expires + if (useExpires) {// Check that we are processing Expires/CacheControl + final String MAX_AGE = "max-age="; + + if(cacheControl != null && cacheControl.contains("no-store")) { + // We must not store an CacheEntry, otherwise a + // conditional request may be made + return; + } + if (expires != null) { + try { + expiresDate = DateUtils.parseDate(expires); + } catch (org.apache.http.impl.cookie.DateParseException e) { + if (log.isDebugEnabled()){ + log.debug("Unable to parse Expires: '"+expires+"' "+e); + } + expiresDate = CacheManager.EXPIRED_DATE; // invalid dates must be treated as expired + } + } + // if no-cache is present, ensure that expiresDate remains null, which forces revalidation + if(cacheControl != null && !cacheControl.contains("no-cache")) { + // the max-age directive overrides the Expires header, + if(cacheControl.contains(MAX_AGE)) { + long maxAgeInSecs = Long.parseLong( + cacheControl.substring(cacheControl.indexOf(MAX_AGE)+MAX_AGE.length()) + .split("[, ]")[0] // Bug 51932 - allow for optional trailing attributes + ); + expiresDate=new Date(System.currentTimeMillis()+maxAgeInSecs*1000); + + } else if(expires==null) { // No max-age && No expires + if(!StringUtils.isEmpty(lastModified) && !StringUtils.isEmpty(date)) { + try { + Date responseDate = DateUtils.parseDate( date ); + Date lastModifiedAsDate = DateUtils.parseDate( lastModified ); + // see https://developer.mozilla.org/en/HTTP_Caching_FAQ + // see http://www.ietf.org/rfc/rfc2616.txt#13.2.4 + expiresDate=new Date(System.currentTimeMillis() + +Math.round((responseDate.getTime()-lastModifiedAsDate.getTime())*0.1)); + } catch(DateParseException e) { + // date or lastModified may be null or in bad format + if(log.isWarnEnabled()) { + log.warn("Failed computing expiration date with following info:" + +lastModified + "," + + cacheControl + "," + + expires + "," + + etag + "," + + url + "," + + date); + } + // TODO Can't see anything in SPEC + expiresDate = new Date(System.currentTimeMillis()+ONE_YEAR_MS); + } + } else { + // TODO Can't see anything in SPEC + expiresDate = new Date(System.currentTimeMillis()+ONE_YEAR_MS); + } + } + // else expiresDate computed in (expires!=null) condition is used + } + } + getCache().put(url, new CacheEntry(lastModified, expiresDate, etag)); + } + + // Helper method to deal with missing headers - Commons HttpClient + private String getHeader(HttpMethod method, String name){ + org.apache.commons.httpclient.Header hdr = method.getResponseHeader(name); + return hdr != null ? hdr.getValue() : null; + } + + // Apache HttpClient + private String getHeader(HttpResponse method, String name) { + org.apache.http.Header hdr = method.getLastHeader(name); + return hdr != null ? hdr.getValue() : null; + } + + /* + * Is the sample result OK to cache? + * i.e is it in the 2xx range, and is it a cacheable method? + */ + private boolean isCacheable(HTTPSampleResult res){ + final String responseCode = res.getResponseCode(); + return isCacheableMethod(res) + && "200".compareTo(responseCode) <= 0 // $NON-NLS-1$ + && "299".compareTo(responseCode) >= 0; // $NON-NLS-1$ + } + + private boolean isCacheableMethod(HTTPSampleResult res) { + final String resMethod = res.getHTTPMethod(); + for(String method : CACHEABLE_METHODS) { + if (method.equalsIgnoreCase(resMethod)) { + return true; + } + } + return false; + } + + /** + * Check the cache, and if there is a match, set the headers: + *
    + *
  • If-Modified-Since
  • + *
  • If-None-Match
  • + *
+ * Commons HttpClient version + * @param url URL to look up in cache + * @param method where to set the headers + */ + public void setHeaders(URL url, HttpMethod method) { + CacheEntry entry = getCache().get(url.toString()); + if (log.isDebugEnabled()){ + log.debug(method.getName()+"(OACH) "+url.toString()+" "+entry); + } + if (entry != null){ + final String lastModified = entry.getLastModified(); + if (lastModified != null){ + method.setRequestHeader(HTTPConstants.IF_MODIFIED_SINCE, lastModified); + } + final String etag = entry.getEtag(); + if (etag != null){ + method.setRequestHeader(HTTPConstants.IF_NONE_MATCH, etag); + } + } + } + + /** + * Check the cache, and if there is a match, set the headers: + *
    + *
  • If-Modified-Since
  • + *
  • If-None-Match
  • + *
+ * Apache HttpClient version. + * @param url {@link URL} to look up in cache + * @param request where to set the headers + */ + public void setHeaders(URL url, HttpRequestBase request) { + CacheEntry entry = getCache().get(url.toString()); + if (log.isDebugEnabled()){ + log.debug(request.getMethod()+"(OAH) "+url.toString()+" "+entry); + } + if (entry != null){ + final String lastModified = entry.getLastModified(); + if (lastModified != null){ + request.setHeader(HTTPConstants.IF_MODIFIED_SINCE, lastModified); + } + final String etag = entry.getEtag(); + if (etag != null){ + request.setHeader(HTTPConstants.IF_NONE_MATCH, etag); + } + } + } + + /** + * Check the cache, and if there is a match, set the headers: + *
    + *
  • If-Modified-Since
  • + *
  • If-None-Match
  • + *
+ * @param url {@link URL} to look up in cache + * @param conn where to set the headers + */ + public void setHeaders(HttpURLConnection conn, URL url) { + CacheEntry entry = getCache().get(url.toString()); + if (log.isDebugEnabled()){ + log.debug(conn.getRequestMethod()+"(Java) "+url.toString()+" "+entry); + } + if (entry != null){ + final String lastModified = entry.getLastModified(); + if (lastModified != null){ + conn.addRequestProperty(HTTPConstants.IF_MODIFIED_SINCE, lastModified); + } + final String etag = entry.getEtag(); + if (etag != null){ + conn.addRequestProperty(HTTPConstants.IF_NONE_MATCH, etag); + } + } + } + + /** + * Check the cache, if the entry has an expires header and the entry has not expired, return true
+ * @param url {@link URL} to look up in cache + * @return true if entry has an expires header and the entry has not expired, else false + */ + public boolean inCache(URL url) { + CacheEntry entry = getCache().get(url.toString()); + if (log.isDebugEnabled()){ + log.debug("inCache "+url.toString()+" "+entry); + } + if (entry != null){ + final Date expiresDate = entry.getExpires(); + if (expiresDate != null) { + if (expiresDate.after(new Date())) { + if (log.isDebugEnabled()){ + log.debug("Expires= " + expiresDate + " (Valid)"); + } + return true; + } else { + if (log.isDebugEnabled()){ + log.debug("Expires= " + expiresDate + " (Expired)"); + } + } + } + } + return false; + } + + private Map getCache(){ + return threadCache.get(); + } + + public boolean getClearEachIteration() { + return getPropertyAsBoolean(CLEAR); + } + + public void setClearEachIteration(boolean clear) { + setProperty(new BooleanProperty(CLEAR, clear)); + } + + public boolean getUseExpires() { + return getPropertyAsBoolean(USE_EXPIRES); + } + + public void setUseExpires(boolean expires) { + setProperty(new BooleanProperty(USE_EXPIRES, expires)); + } + + /** + * @return int cache max size + */ + public int getMaxSize() { + return getPropertyAsInt(MAX_SIZE, DEFAULT_MAX_SIZE); + } + + /** + * @param size int cache max size + */ + public void setMaxSize(int size) { + setProperty(MAX_SIZE, size, DEFAULT_MAX_SIZE); + } + + + @Override + public void clear(){ + super.clear(); + clearCache(); + } + + private void clearCache() { + log.debug("Clear cache"); + threadCache = new InheritableThreadLocal>(){ + @Override + protected Map initialValue(){ + // Bug 51942 - this map may be used from multiple threads + @SuppressWarnings("unchecked") // LRUMap is not generic currently + Map map = new LRUMap(getMaxSize()); + return Collections.synchronizedMap(map); + } + }; + } + + @Override + public void testStarted() { + } + + @Override + public void testEnded() { + } + + @Override + public void testStarted(String host) { + } + + @Override + public void testEnded(String host) { + } + + @Override + public void testIterationStart(LoopIterationEvent event) { + if (getClearEachIteration()) { + clearCache(); + } + useExpires=getUseExpires(); // cache the value + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/Cookie.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/Cookie.java new file mode 100644 index 00000000000..02043c5457d --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/Cookie.java @@ -0,0 +1,271 @@ +/* + * 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.protocol.http.control; + +import java.io.Serializable; + +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * This class is a Cookie encapsulator. + * + */ +public class Cookie extends AbstractTestElement implements Serializable { + private static final long serialVersionUID = 240L; + + private static final String TAB = "\t"; + + private static final String VALUE = "Cookie.value"; //$NON-NLS-1$ + + private static final String DOMAIN = "Cookie.domain"; //$NON-NLS-1$ + + private static final String EXPIRES = "Cookie.expires"; //$NON-NLS-1$ + + private static final String SECURE = "Cookie.secure"; //$NON-NLS-1$ + + private static final String PATH = "Cookie.path"; //$NON-NLS-1$ + + private static final String PATH_SPECIFIED = "Cookie.path_specified"; //$NON-NLS-1$ + + private static final String DOMAIN_SPECIFIED = "Cookie.domain_specified"; //$NON-NLS-1$ + + private static final String VERSION = "Cookie.version"; //$NON-NLS-1$ + + private static final int DEFAULT_VERSION = 1; + + /** + * create the coookie + */ + public Cookie() { + this("","","","",false,0,false,false); + } + + /** + * create the coookie + * @param name name of the cookie + * @param value value of the cookie + * @param domain domain for which the cookie is valid + * @param path path for which the cookie is valid + * @param secure flag whether cookie is to be handled as 'secure' + * @param expires - this is in seconds + * + */ + public Cookie(String name, String value, String domain, String path, boolean secure, long expires) { + this(name,value,domain,path,secure,expires,true,true); + } + + /** + * create the coookie + * @param name name of the cookie + * @param value value of the cookie + * @param domain domain for which the cookie is valid + * @param path path for which the cookie is valid + * @param secure flag whether cookie is to be handled as 'secure' + * @param expires - this is in seconds + * @param hasPath - was the path explicitly specified? + * @param hasDomain - was the domain explicitly specified? + * + */ + public Cookie(String name, String value, String domain, String path, + boolean secure, long expires, boolean hasPath, boolean hasDomain) { + this(name, value, domain, path, secure, expires, hasPath, hasDomain, DEFAULT_VERSION); + } + + /** + * Create a JMeter Cookie. + * + * @param name name of the cookie + * @param value value of the cookie + * @param domain domain for which the cookie is valid + * @param path path for which the cookie is valid + * @param secure flag whether cookie is to be handled as 'secure' + * @param expires - this is in seconds + * @param hasPath - was the path explicitly specified? + * @param hasDomain - was the domain explicitly specified? + * @param version - cookie spec. version + */ + public Cookie(String name, String value, String domain, String path, + boolean secure, long expires, boolean hasPath, boolean hasDomain, int version) { + this.setName(name); + this.setValue(value); + this.setDomain(domain); + this.setPath(path); + this.setSecure(secure); + this.setExpires(expires); + this.setPathSpecified(hasPath); + this.setDomainSpecified(hasDomain); + this.setVersion(version); + } + + public void addConfigElement(ConfigElement config) { + } + + /** + * get the value for this object. + * + * @return the value of this cookie + */ + public String getValue() { + return getPropertyAsString(VALUE); + } + + /** + * set the value for this object. + * + * @param value the value of this cookie + */ + public void setValue(String value) { + this.setProperty(VALUE, value); + } + + /** + * get the domain for this object. + * + * @return the domain for which this cookie is valid + */ + public String getDomain() { + return getPropertyAsString(DOMAIN); + } + + /** + * set the domain for this object. + * + * @param domain the domain for which this cookie is valid + */ + public void setDomain(String domain) { + setProperty(DOMAIN, domain); + } + + /** + * get the expiry time for the cookie + * + * @return Expiry time in seconds since the Java epoch + */ + public long getExpires() { + return getPropertyAsLong(EXPIRES); + } + + /** + * get the expiry time for the cookie + * + * @return Expiry time in milli-seconds since the Java epoch, + * i.e. same as System.currentTimeMillis() + */ + public long getExpiresMillis() { + return getPropertyAsLong(EXPIRES)*1000; + } + + /** + * set the expiry time for the cookie + * @param expires - expiry time in seconds since the Java epoch + */ + public void setExpires(long expires) { + setProperty(new LongProperty(EXPIRES, expires)); + } + + /** + * get the secure for this object. + * + * @return flag whether this cookie should be treated as a 'secure' cookie + */ + public boolean getSecure() { + return getPropertyAsBoolean(SECURE); + } + + /** + * set the secure for this object. + * + * @param secure flag whether this cookie should be treated as a 'secure' cookie + */ + public void setSecure(boolean secure) { + setProperty(new BooleanProperty(SECURE, secure)); + } + + /** + * get the path for this object. + * + * @return the path for which this cookie is valid + */ + public String getPath() { + return getPropertyAsString(PATH); + } + + /** + * set the path for this object. + * + * @param path the path for which this cookie is valid + */ + public void setPath(String path) { + setProperty(PATH, path); + } + + public void setPathSpecified(boolean b) { + setProperty(PATH_SPECIFIED, b); + } + + public boolean isPathSpecified(){ + return getPropertyAsBoolean(PATH_SPECIFIED); + } + + public void setDomainSpecified(boolean b) { + setProperty(DOMAIN_SPECIFIED, b); + } + + public boolean isDomainSpecified(){ + return getPropertyAsBoolean(DOMAIN_SPECIFIED); + } + + /** + * creates a string representation of this cookie + */ + @Override + public String toString() { + StringBuilder sb=new StringBuilder(80); + sb.append(getDomain()); + // flag - if all machines within a given domain can access the variable. + //(from http://www.cookiecentral.com/faq/ 3.5) + sb.append(TAB).append("TRUE"); + sb.append(TAB).append(getPath()); + sb.append(TAB).append(JOrphanUtils.booleanToSTRING(getSecure())); + sb.append(TAB).append(getExpires()); + sb.append(TAB).append(getName()); + sb.append(TAB).append(getValue()); + return sb.toString(); + } + + /** + * @return the version + */ + public int getVersion() { + return getPropertyAsInt(VERSION, DEFAULT_VERSION); + } + + /** + * @param version the version to set + */ + public void setVersion(int version) { + setProperty(VERSION, version, DEFAULT_VERSION); + } + + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/CookieHandler.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/CookieHandler.java new file mode 100644 index 00000000000..fbf7dc46bfc --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/CookieHandler.java @@ -0,0 +1,52 @@ +/* + * 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.protocol.http.control; + +import java.net.URL; + +import org.apache.jmeter.testelement.property.CollectionProperty; + +/** + * Interface to be implemented by CookieHandler + */ +public interface CookieHandler { + + /** + * Add cookie to CookieManager from cookieHeader and URL + * @param cookieManager CookieManager on which cookies are added + * @param checkCookies boolean to indicate if cookies must be validated against spec + * @param cookieHeader String cookie Header + * @param url URL + */ + void addCookieFromHeader(CookieManager cookieManager, boolean checkCookies, + String cookieHeader, URL url); + + /** + * Find cookies applicable to the given URL and build the Cookie header from + * them. + * @param cookiesCP {@link CollectionProperty} of {@link Cookie} + * @param url + * URL of the request to which the returned header will be added. + * @param allowVariableCookie flag whether to allow jmeter variables in cookie values + * @return the value string for the cookie header (goes after "Cookie: "). + */ + String getCookieHeaderForURL(CollectionProperty cookiesCP, URL url, + boolean allowVariableCookie); + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/CookieManager.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/CookieManager.java new file mode 100644 index 00000000000..d9d66862501 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/CookieManager.java @@ -0,0 +1,439 @@ +/* + * 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. + * + */ + +// For unit tests @see TestCookieManager + +package org.apache.jmeter.protocol.http.control; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; + +import org.apache.http.client.params.CookiePolicy; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.testelement.TestIterationListener; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassTools; +import org.apache.jorphan.util.JMeterException; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * This class provides an interface to the netscape cookies file to pass cookies + * along with a request. + * + * Now uses Commons HttpClient parsing and matching code (since 2.1.2) + * + */ +public class CookieManager extends ConfigTestElement implements TestStateListener, TestIterationListener, Serializable { + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //++ JMX tag values + private static final String CLEAR = "CookieManager.clearEachIteration";// $NON-NLS-1$ + + private static final String COOKIES = "CookieManager.cookies";// $NON-NLS-1$ + + private static final String POLICY = "CookieManager.policy"; //$NON-NLS-1$ + + private static final String IMPLEMENTATION = "CookieManager.implementation"; //$NON-NLS-1$ + //-- JMX tag values + + private static final String TAB = "\t"; //$NON-NLS-1$ + + // See bug 33796 + private static final boolean DELETE_NULL_COOKIES = + JMeterUtils.getPropDefault("CookieManager.delete_null_cookies", true);// $NON-NLS-1$ + + // See bug 28715 + // Package protected for tests + static final boolean ALLOW_VARIABLE_COOKIES + = JMeterUtils.getPropDefault("CookieManager.allow_variable_cookies", true);// $NON-NLS-1$ + + private static final String COOKIE_NAME_PREFIX = + JMeterUtils.getPropDefault("CookieManager.name.prefix", "COOKIE_").trim();// $NON-NLS-1$ $NON-NLS-2$ + + private static final boolean SAVE_COOKIES = + JMeterUtils.getPropDefault("CookieManager.save.cookies", false);// $NON-NLS-1$ + + private static final boolean CHECK_COOKIES = + JMeterUtils.getPropDefault("CookieManager.check.cookies", true);// $NON-NLS-1$ + + static { + log.info("Settings:" + + " Delete null: " + DELETE_NULL_COOKIES + + " Check: " + CHECK_COOKIES + + " Allow variable: " + ALLOW_VARIABLE_COOKIES + + " Save: " + SAVE_COOKIES + + " Prefix: " + COOKIE_NAME_PREFIX + ); + } + private transient CookieHandler cookieHandler; + + private transient CollectionProperty initialCookies; + + public static final String DEFAULT_POLICY = CookiePolicy.BROWSER_COMPATIBILITY; + + public static final String DEFAULT_IMPLEMENTATION = HC3CookieHandler.class.getName(); + + public CookieManager() { + clearCookies(); // Ensure that there is always a collection available + } + + // ensure that the initial cookies are copied to the per-thread instances + /** {@inheritDoc} */ + @Override + public Object clone(){ + CookieManager clone = (CookieManager) super.clone(); + clone.initialCookies = initialCookies; + clone.cookieHandler = cookieHandler; + return clone; + } + + public String getPolicy() { + return getPropertyAsString(POLICY, DEFAULT_POLICY); + } + + public void setCookiePolicy(String policy){ + setProperty(POLICY, policy, DEFAULT_POLICY); + } + + public CollectionProperty getCookies() { + return (CollectionProperty) getProperty(COOKIES); + } + + public int getCookieCount() {// Used by GUI + return getCookies().size(); + } + + public boolean getClearEachIteration() { + return getPropertyAsBoolean(CLEAR); + } + + public void setClearEachIteration(boolean clear) { + setProperty(new BooleanProperty(CLEAR, clear)); + } + + public String getImplementation() { + return getPropertyAsString(IMPLEMENTATION, DEFAULT_IMPLEMENTATION); + } + + public void setImplementation(String implementation){ + setProperty(IMPLEMENTATION, implementation, DEFAULT_IMPLEMENTATION); + } + + /** + * Save the static cookie data to a file. + *

+ * Cookies are only taken from the GUI - runtime cookies are not included. + * + * @param authFile + * name of the file to store the cookies into. If the name is + * relative, the system property user.dir will be + * prepended + * @throws IOException + * when writing to that file fails + */ + public void save(String authFile) throws IOException { + File file = new File(authFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir") // $NON-NLS-1$ + + File.separator + authFile); + } + PrintWriter writer = new PrintWriter(new FileWriter(file)); // TODO Charset ? + writer.println("# JMeter generated Cookie file");// $NON-NLS-1$ + PropertyIterator cookies = getCookies().iterator(); + long now = System.currentTimeMillis(); + while (cookies.hasNext()) { + Cookie cook = (Cookie) cookies.next().getObjectValue(); + final long expiresMillis = cook.getExpiresMillis(); + if (expiresMillis == 0 || expiresMillis > now) { // only save unexpired cookies + writer.println(cookieToString(cook)); + } + } + writer.flush(); + writer.close(); + } + + /** + * Add cookie data from a file. + * + * @param cookieFile + * name of the file to read the cookies from. If the name is + * relative, the system property user.dir will be + * prepended + * @throws IOException + * if reading the file fails + */ + public void addFile(String cookieFile) throws IOException { + File file = new File(cookieFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir") // $NON-NLS-1$ + + File.separator + cookieFile); + } + BufferedReader reader = null; + if (file.canRead()) { + reader = new BufferedReader(new FileReader(file)); // TODO Charset ? + } else { + throw new IOException("The file you specified cannot be read."); + } + + // N.B. this must agree with the save() and cookieToString() methods + String line; + try { + final CollectionProperty cookies = getCookies(); + while ((line = reader.readLine()) != null) { + try { + if (line.startsWith("#") || JOrphanUtils.isBlank(line)) {//$NON-NLS-1$ + continue; + } + String[] st = JOrphanUtils.split(line, TAB, false); + + final int _domain = 0; + //final int _ignored = 1; + final int _path = 2; + final int _secure = 3; + final int _expires = 4; + final int _name = 5; + final int _value = 6; + final int _fields = 7; + if (st.length!=_fields) { + throw new IOException("Expected "+_fields+" fields, found "+st.length+" in "+line); + } + + if (st[_path].length()==0) { + st[_path] = "/"; //$NON-NLS-1$ + } + boolean secure = Boolean.parseBoolean(st[_secure]); + long expires = Long.parseLong(st[_expires]); + if (expires==Long.MAX_VALUE) { + expires=0; + } + //long max was used to represent a non-expiring cookie, but that caused problems + Cookie cookie = new Cookie(st[_name], st[_value], st[_domain], st[_path], secure, expires); + cookies.addItem(cookie); + } catch (NumberFormatException e) { + throw new IOException("Error parsing cookie line\n\t'" + line + "'\n\t" + e); + } + } + } finally { + reader.close(); + } + } + + private String cookieToString(Cookie c){ + StringBuilder sb=new StringBuilder(80); + sb.append(c.getDomain()); + //flag - if all machines within a given domain can access the variable. + //(from http://www.cookiecentral.com/faq/ 3.5) + sb.append(TAB).append("TRUE"); + sb.append(TAB).append(c.getPath()); + sb.append(TAB).append(JOrphanUtils.booleanToSTRING(c.getSecure())); + sb.append(TAB).append(c.getExpires()); + sb.append(TAB).append(c.getName()); + sb.append(TAB).append(c.getValue()); + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void recoverRunningVersion() { + // do nothing, the cookie manager has to accept changes. + } + + /** {@inheritDoc} */ + @Override + public void setRunningVersion(boolean running) { + // do nothing, the cookie manager has to accept changes. + } + + /** + * Add a cookie. + * + * @param c cookie to be added + */ + public void add(Cookie c) { + String cv = c.getValue(); + String cn = c.getName(); + removeMatchingCookies(c); // Can't have two matching cookies + + if (DELETE_NULL_COOKIES && (null == cv || cv.length()==0)) { + if (log.isDebugEnabled()) { + log.debug("Dropping cookie with null value " + c.toString()); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Add cookie to store " + c.toString()); + } + getCookies().addItem(c); + if (SAVE_COOKIES) { + JMeterContext context = getThreadContext(); + if (context.isSamplingStarted()) { + context.getVariables().put(COOKIE_NAME_PREFIX+cn, cv); + } + } + } + } + + /** {@inheritDoc} */ + @Override + public void clear(){ + super.clear(); + clearCookies(); // ensure data is set up OK initially + } + + /* + * Remove all the cookies. + */ + private void clearCookies() { + log.debug("Clear all cookies from store"); + setProperty(new CollectionProperty(COOKIES, new ArrayList())); + } + + /** + * Remove a cookie. + * + * @param index index of the cookie to remove + */ + public void remove(int index) {// TODO not used by GUI + getCookies().remove(index); + } + + /** + * Return the cookie at index i. + * + * @param i index of the cookie to get + * @return cookie at index i + */ + public Cookie get(int i) {// Only used by GUI + return (Cookie) getCookies().get(i).getObjectValue(); + } + + /** + * Find cookies applicable to the given URL and build the Cookie header from + * them. + * + * @param url + * URL of the request to which the returned header will be added. + * @return the value string for the cookie header (goes after "Cookie: "). + */ + public String getCookieHeaderForURL(URL url) { + return cookieHandler.getCookieHeaderForURL(getCookies(), url, ALLOW_VARIABLE_COOKIES); + } + + + public void addCookieFromHeader(String cookieHeader, URL url){ + cookieHandler.addCookieFromHeader(this, CHECK_COOKIES, cookieHeader, url); + } + /** + * Check if cookies match, i.e. name, path and domain are equal. + *
+ * TODO - should we compare secure too? + * @param a + * @param b + * @return true if cookies match + */ + private boolean match(Cookie a, Cookie b){ + return + a.getName().equals(b.getName()) + && + a.getPath().equals(b.getPath()) + && + a.getDomain().equals(b.getDomain()); + } + + void removeMatchingCookies(Cookie newCookie){ + // Scan for any matching cookies + PropertyIterator iter = getCookies().iterator(); + while (iter.hasNext()) { + Cookie cookie = (Cookie) iter.next().getObjectValue(); + if (cookie == null) {// TODO is this possible? + continue; + } + if (match(cookie,newCookie)) { + if (log.isDebugEnabled()) { + log.debug("New Cookie = " + newCookie.toString() + + " removing matching Cookie " + cookie.toString()); + } + iter.remove(); + } + } + } + + /** {@inheritDoc} */ + @Override + public void testStarted() { + initialCookies = getCookies(); + try { + cookieHandler = (CookieHandler) ClassTools.construct(getImplementation(), getPolicy()); + } catch (JMeterException e) { + log.error("Unable to load or invoke class: " + getImplementation(), e); + } + if (log.isDebugEnabled()){ + log.debug("Policy: "+getPolicy()+" Clear: "+getClearEachIteration()); + } + } + + /** {@inheritDoc} */ + @Override + public void testEnded() { + } + + /** {@inheritDoc} */ + @Override + public void testStarted(String host) { + testStarted(); + } + + /** {@inheritDoc} */ + @Override + public void testEnded(String host) { + } + + /** {@inheritDoc} */ + @Override + public void testIterationStart(LoopIterationEvent event) { + if (getClearEachIteration()) { + log.debug("Initialise cookies from pre-defined list"); + // No need to call clear + setProperty(initialCookies.clone()); + } + } + + /** + * Package protected for tests + * @return the cookieHandler + */ + CookieHandler getCookieHandler() { + return cookieHandler; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/DNSCacheManager.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/DNSCacheManager.java new file mode 100644 index 00000000000..3cd31e9a37b --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/DNSCacheManager.java @@ -0,0 +1,241 @@ +/* + * 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.protocol.http.control; + +import java.io.Serializable; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.http.conn.DnsResolver; +import org.apache.http.impl.conn.SystemDefaultDnsResolver; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.testelement.TestIterationListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.Cache; +import org.xbill.DNS.ExtendedResolver; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.Resolver; +import org.xbill.DNS.TextParseException; +import org.xbill.DNS.Type; + +/** + * This config element provides ability to have flexible control over DNS + * caching function. Depending on option from @see + * {@link org.apache.jmeter.protocol.http.gui.DNSCachePanel}, either system or + * custom resolver can be used. Custom resolver uses dnsjava library, and gives + * ability to bypass both OS and JVM cache. It allows to use paradigm + * "1 virtual user - 1 DNS cache" in performance tests. + * + * @since 2.12 + */ + +public class DNSCacheManager extends ConfigTestElement implements TestIterationListener, Serializable, DnsResolver { + private static final long serialVersionUID = 2120L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private transient SystemDefaultDnsResolver systemDefaultDnsResolver = null; + + private Map cache = null; + + private transient Resolver resolver = null; + + //++ JMX tag values + public static final String CLEAR_CACHE_EACH_ITER = "DNSCacheManager.clearEachIteration"; // $NON-NLS-1$ + + public static final String SERVERS = "DNSCacheManager.servers"; // $NON-NLS-1$ + + public static final String IS_CUSTOM_RESOLVER = "DNSCacheManager.isCustomResolver"; // $NON-NLS-1$ + //-- JMX tag values + + public static final boolean DEFAULT_CLEAR_CACHE_EACH_ITER = false; + + public static final String DEFAULT_SERVERS = ""; // $NON-NLS-1$ + + public static final boolean DEFAULT_IS_CUSTOM_RESOLVER = false; + + private final transient Cache lookupCache; + + // ensure that the initial DNSServers are copied to the per-thread instances + + public DNSCacheManager() { + setProperty(new CollectionProperty(SERVERS, new ArrayList())); + //disabling cache + lookupCache = new Cache(); + lookupCache.setMaxCache(0); + lookupCache.setMaxEntries(0); + } + + /** + * {@inheritDoc} + */ + @Override + public Object clone() { + DNSCacheManager clone = (DNSCacheManager) super.clone(); + clone.systemDefaultDnsResolver = new SystemDefaultDnsResolver(); + clone.cache = new LinkedHashMap(); + CollectionProperty dnsServers = getServers(); + try { + String[] serverNames = new String[dnsServers.size()]; + PropertyIterator dnsServIt = dnsServers.iterator(); + int index=0; + while (dnsServIt.hasNext()) { + serverNames[index] = dnsServIt.next().getStringValue(); + index++; + } + clone.resolver = new ExtendedResolver(serverNames); + log.debug("Using DNS Resolvers: " + + Arrays.asList(((ExtendedResolver) clone.resolver) + .getResolvers())); + // resolvers will be chosen via round-robin + ((ExtendedResolver) clone.resolver).setLoadBalance(true); + } catch (UnknownHostException uhe) { + log.warn("Failed to create Extended resolver: " + uhe.getMessage()); + } + return clone; + } + + /** + * + * Resolves address using system or custom DNS resolver + */ + @Override + public InetAddress[] resolve(String host) throws UnknownHostException { + if (cache.containsKey(host)) { + if (log.isDebugEnabled()) { + log.debug("Cache hit thr#" + JMeterContextService.getContext().getThreadNum() + ": " + host + "=>" + + Arrays.toString(cache.get(host))); + } + return cache.get(host); + } else { + InetAddress[] addresses = requestLookup(host); + if (log.isDebugEnabled()) { + log.debug("Cache miss thr#" + JMeterContextService.getContext().getThreadNum() + ": " + host + "=>" + + Arrays.toString(addresses)); + } + cache.put(host, addresses); + return addresses; + } + } + + /** + * Sends DNS request via system or custom DNS resolver + */ + private InetAddress[] requestLookup(String host) throws UnknownHostException { + InetAddress[] addresses = null; + if (isCustomResolver() && ((ExtendedResolver) resolver).getResolvers().length > 0) { + try { + Lookup lookup = new Lookup(host, Type.A); + lookup.setCache(lookupCache); + lookup.setResolver(resolver); + Record[] records = lookup.run(); + if (records == null || records.length == 0) { + throw new UnknownHostException("Failed to resolve host name: " + host); + } + addresses = new InetAddress[records.length]; + for (int i = 0; i < records.length; i++) { + addresses[i] = ((ARecord) records[i]).getAddress(); + } + } catch (TextParseException tpe) { + log.debug("Failed to create Lookup object: " + tpe); + } + } else { + addresses = systemDefaultDnsResolver.resolve(host); + if (log.isDebugEnabled()) { + log.debug("Cache miss: " + host + " Thread #" + JMeterContextService.getContext().getThreadNum() + + ", resolved with system resolver into " + Arrays.toString(addresses)); + } + } + return addresses; + } + + /** + * {@inheritDoc} Clean DNS cache if appropriate check-box was selected + */ + @Override + public void testIterationStart(LoopIterationEvent event) { + if (isClearEachIteration()) { + this.cache.clear(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + super.clear(); + clearServers(); // ensure data is set up OK initially + } + + /** + * Remove all the servers. + */ + private void clearServers() { + log.debug("Clear all servers from store"); + setProperty(new CollectionProperty(SERVERS, new ArrayList())); + } + + public void addServer(String dnsServer) { + getServers().addItem(dnsServer); + } + + public CollectionProperty getServers() { + return (CollectionProperty) getProperty(SERVERS); + } + + /** + * Clean DNS cache each iteration + * + * @return boolean + */ + public boolean isClearEachIteration() { + return this.getPropertyAsBoolean(CLEAR_CACHE_EACH_ITER, DEFAULT_CLEAR_CACHE_EACH_ITER); + } + + /** + * Clean DNS cache each iteration + * + * @param clear + * flag whether DNS cache should be cleared on each iteration + */ + public void setClearEachIteration(boolean clear) { + setProperty(new BooleanProperty(CLEAR_CACHE_EACH_ITER, clear)); + } + + public boolean isCustomResolver() { + return this.getPropertyAsBoolean(IS_CUSTOM_RESOLVER, DEFAULT_IS_CUSTOM_RESOLVER); + } + + public void setCustomResolver(boolean isCustomResolver) { + this.setProperty(IS_CUSTOM_RESOLVER, isCustomResolver); + } + +} \ No newline at end of file diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/HC3CookieHandler.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/HC3CookieHandler.java new file mode 100644 index 00000000000..edb0806f7d9 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/HC3CookieHandler.java @@ -0,0 +1,200 @@ +/* + * 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.protocol.http.control; + +import java.net.URL; +import java.util.Date; + +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.cookie.CookieSpec; +import org.apache.commons.httpclient.cookie.MalformedCookieException; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HTTPClient 3.1 implementation + */ +public class HC3CookieHandler implements CookieHandler { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final transient CookieSpec cookieSpec; + + /** + * @param policy + * cookie policy to which to conform (see + * {@link CookiePolicy#getCookieSpec(String)} + */ + public HC3CookieHandler(String policy) { + super(); + this.cookieSpec = CookiePolicy.getCookieSpec(policy); + } + + /** + * Create an HttpClient cookie from a JMeter cookie + */ + private org.apache.commons.httpclient.Cookie makeCookie(Cookie jmc){ + long exp = jmc.getExpiresMillis(); + org.apache.commons.httpclient.Cookie ret= + new org.apache.commons.httpclient.Cookie( + jmc.getDomain(), + jmc.getName(), + jmc.getValue(), + jmc.getPath(), + exp > 0 ? new Date(exp) : null, // use null for no expiry + jmc.getSecure() + ); + ret.setPathAttributeSpecified(jmc.isPathSpecified()); + ret.setDomainAttributeSpecified(jmc.isDomainSpecified()); + ret.setVersion(jmc.getVersion()); + return ret; + } + /** + * Get array of valid HttpClient cookies for the URL + * + * @param cookiesCP cookies to consider + * @param url the target URL + * @param allowVariableCookie flag whether to allow jmeter variables in cookie values + * @return array of HttpClient cookies + * + */ + org.apache.commons.httpclient.Cookie[] getCookiesForUrl( + CollectionProperty cookiesCP, + URL url, + boolean allowVariableCookie){ + org.apache.commons.httpclient.Cookie cookies[]= + new org.apache.commons.httpclient.Cookie[cookiesCP.size()]; + int i=0; + for (PropertyIterator iter = cookiesCP.iterator(); iter.hasNext();) { + Cookie jmcookie = (Cookie) iter.next().getObjectValue(); + // Set to running version, to allow function evaluation for the cookie values (bug 28715) + if (allowVariableCookie) { + jmcookie.setRunningVersion(true); + } + cookies[i++] = makeCookie(jmcookie); + if (allowVariableCookie) { + jmcookie.setRunningVersion(false); + } + } + String host = url.getHost(); + String protocol = url.getProtocol(); + int port= HTTPSamplerBase.getDefaultPort(protocol,url.getPort()); + String path = url.getPath(); + boolean secure = HTTPSamplerBase.isSecure(protocol); + return cookieSpec.match(host, port, path, secure, cookies); + } + + /** + * Find cookies applicable to the given URL and build the Cookie header from + * them. + * + * @param url + * URL of the request to which the returned header will be added. + * @return the value string for the cookie header (goes after "Cookie: "). + */ + @Override + public String getCookieHeaderForURL( + CollectionProperty cookiesCP, + URL url, + boolean allowVariableCookie) { + org.apache.commons.httpclient.Cookie[] c = + getCookiesForUrl(cookiesCP, url, allowVariableCookie); + int count = c.length; + boolean debugEnabled = log.isDebugEnabled(); + if (debugEnabled){ + log.debug("Found "+count+" cookies for "+url.toExternalForm()); + } + if (count <=0){ + return null; + } + String hdr=cookieSpec.formatCookieHeader(c).getValue(); + if (debugEnabled){ + log.debug("Cookie: "+hdr); + } + return hdr; + } + + /** + * {@inheritDoc} + */ + @Override + public void addCookieFromHeader(CookieManager cookieManager, + boolean checkCookies,String cookieHeader, URL url){ + boolean debugEnabled = log.isDebugEnabled(); + if (debugEnabled) { + log.debug("Received Cookie: " + cookieHeader + " From: " + url.toExternalForm()); + } + String protocol = url.getProtocol(); + String host = url.getHost(); + int port= HTTPSamplerBase.getDefaultPort(protocol,url.getPort()); + String path = url.getPath(); + boolean isSecure=HTTPSamplerBase.isSecure(protocol); + org.apache.commons.httpclient.Cookie[] cookies= null; + try { + cookies = cookieSpec.parse(host, port, path, isSecure, cookieHeader); + } catch (MalformedCookieException e) { + log.warn(cookieHeader+e.getLocalizedMessage()); + } catch (IllegalArgumentException e) { + log.warn(cookieHeader+e.getLocalizedMessage()); + } + if (cookies == null) { + return; + } + for(org.apache.commons.httpclient.Cookie cookie : cookies){ + try { + if (checkCookies) { + cookieSpec.validate(host, port, path, isSecure, cookie); + } + Date expiryDate = cookie.getExpiryDate(); + long exp = 0; + if (expiryDate!= null) { + exp=expiryDate.getTime(); + } + Cookie newCookie = new Cookie( + cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getSecure(), + exp / 1000, + cookie.isPathAttributeSpecified(), + cookie.isDomainAttributeSpecified() + ); + + // Store session cookies as well as unexpired ones + if (exp == 0 || exp >= System.currentTimeMillis()) { + newCookie.setVersion(cookie.getVersion()); + cookieManager.add(newCookie); // Has its own debug log; removes matching cookies + } else { + cookieManager.removeMatchingCookies(newCookie); + if (debugEnabled){ + log.debug("Dropping expired Cookie: "+newCookie.toString()); + } + } + } catch (MalformedCookieException e) { // This means the cookie was wrong for the URL + log.warn("Not storing invalid cookie: <"+cookieHeader+"> for URL "+url+" ("+e.getLocalizedMessage()+")"); + } catch (IllegalArgumentException e) { + log.warn(cookieHeader+e.getLocalizedMessage()); + } + } + + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/HC4CookieHandler.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/HC4CookieHandler.java new file mode 100644 index 00000000000..9e9db8e162c --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/HC4CookieHandler.java @@ -0,0 +1,215 @@ +/* + * 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.protocol.http.control; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.apache.http.Header; +import org.apache.http.client.params.CookiePolicy; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecRegistry; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.impl.cookie.BasicClientCookie; +import org.apache.http.impl.cookie.BestMatchSpecFactory; +import org.apache.http.impl.cookie.BrowserCompatSpecFactory; +import org.apache.http.impl.cookie.IgnoreSpecFactory; +import org.apache.http.impl.cookie.NetscapeDraftSpecFactory; +import org.apache.http.impl.cookie.RFC2109SpecFactory; +import org.apache.http.impl.cookie.RFC2965SpecFactory; +import org.apache.http.message.BasicHeader; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class HC4CookieHandler implements CookieHandler { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final transient CookieSpec cookieSpec; + + private static CookieSpecRegistry registry = new CookieSpecRegistry(); + + static { + registry.register(CookiePolicy.BEST_MATCH, new BestMatchSpecFactory()); + registry.register(CookiePolicy.BROWSER_COMPATIBILITY, new BrowserCompatSpecFactory()); + registry.register(CookiePolicy.RFC_2109, new RFC2109SpecFactory()); + registry.register(CookiePolicy.RFC_2965, new RFC2965SpecFactory()); + registry.register(CookiePolicy.IGNORE_COOKIES, new IgnoreSpecFactory()); + registry.register(CookiePolicy.NETSCAPE, new NetscapeDraftSpecFactory()); + } + + public HC4CookieHandler(String policy) { + super(); + if (policy.equals(org.apache.commons.httpclient.cookie.CookiePolicy.DEFAULT)) { // tweak diff HC3 vs HC4 + policy = CookiePolicy.BEST_MATCH; + } + this.cookieSpec = registry.getCookieSpec(policy); + } + + @Override + public void addCookieFromHeader(CookieManager cookieManager, + boolean checkCookies, String cookieHeader, URL url) { + boolean debugEnabled = log.isDebugEnabled(); + if (debugEnabled) { + log.debug("Received Cookie: " + cookieHeader + " From: " + url.toExternalForm()); + } + String protocol = url.getProtocol(); + String host = url.getHost(); + int port= HTTPSamplerBase.getDefaultPort(protocol,url.getPort()); + String path = url.getPath(); + boolean isSecure=HTTPSamplerBase.isSecure(protocol); + + List cookies = null; + + CookieOrigin cookieOrigin = new CookieOrigin(host, port, path, isSecure); + BasicHeader basicHeader = new BasicHeader(HTTPConstants.HEADER_SET_COOKIE, cookieHeader); + + try { + cookies = cookieSpec.parse(basicHeader, cookieOrigin); + } catch (MalformedCookieException e) { + log.error("Unable to add the cookie", e); + } + if (cookies == null) { + return; + } + for (org.apache.http.cookie.Cookie cookie : cookies) { + try { + if (checkCookies) { + cookieSpec.validate(cookie, cookieOrigin); + } + Date expiryDate = cookie.getExpiryDate(); + long exp = 0; + if (expiryDate!= null) { + exp=expiryDate.getTime(); + } + Cookie newCookie = new Cookie( + cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.isSecure(), + exp / 1000 + ); + + // Store session cookies as well as unexpired ones + if (exp == 0 || exp >= System.currentTimeMillis()) { + newCookie.setVersion(cookie.getVersion()); + cookieManager.add(newCookie); // Has its own debug log; removes matching cookies + } else { + cookieManager.removeMatchingCookies(newCookie); + if (debugEnabled){ + log.info("Dropping expired Cookie: "+newCookie.toString()); + } + } + } catch (MalformedCookieException e) { // This means the cookie was wrong for the URL + log.warn("Not storing invalid cookie: <"+cookieHeader+"> for URL "+url+" ("+e.getLocalizedMessage()+")"); + } catch (IllegalArgumentException e) { + log.warn(cookieHeader+e.getLocalizedMessage()); + } + } + } + + @Override + public String getCookieHeaderForURL(CollectionProperty cookiesCP, URL url, + boolean allowVariableCookie) { + List c = + getCookiesForUrl(cookiesCP, url, allowVariableCookie); + + boolean debugEnabled = log.isDebugEnabled(); + if (debugEnabled){ + log.debug("Found "+c.size()+" cookies for "+url.toExternalForm()); + } + if (c.size() <= 0) { + return null; + } + List
lstHdr = cookieSpec.formatCookies(c); + + StringBuilder sbHdr = new StringBuilder(); + for (Header header : lstHdr) { + sbHdr.append(header.getValue()); + } + + return sbHdr.toString(); + } + + /** + * Get array of valid HttpClient cookies for the URL + * + * @param cookiesCP property with all available cookies + * @param url the target URL + * @param allowVariableCookie flag whether cookies may contain jmeter variables + * @return array of HttpClient cookies + * + */ + List getCookiesForUrl( + CollectionProperty cookiesCP, URL url, boolean allowVariableCookie) { + List cookies = new ArrayList(); + + for (PropertyIterator iter = cookiesCP.iterator(); iter.hasNext();) { + Cookie jmcookie = (Cookie) iter.next().getObjectValue(); + // Set to running version, to allow function evaluation for the cookie values (bug 28715) + if (allowVariableCookie) { + jmcookie.setRunningVersion(true); + } + cookies.add(makeCookie(jmcookie)); + if (allowVariableCookie) { + jmcookie.setRunningVersion(false); + } + } + String host = url.getHost(); + String protocol = url.getProtocol(); + int port = HTTPSamplerBase.getDefaultPort(protocol, url.getPort()); + String path = url.getPath(); + boolean secure = HTTPSamplerBase.isSecure(protocol); + + CookieOrigin cookieOrigin = new CookieOrigin(host, port, path, secure); + + List cookiesValid = new ArrayList(); + for (org.apache.http.cookie.Cookie cookie : cookies) { + if (cookieSpec.match(cookie, cookieOrigin)) { + cookiesValid.add(cookie); + } + } + + return cookiesValid; + } + + /** + * Create an HttpClient cookie from a JMeter cookie + */ + private org.apache.http.cookie.Cookie makeCookie(Cookie jmc) { + long exp = jmc.getExpiresMillis(); + BasicClientCookie ret = new BasicClientCookie(jmc.getName(), + jmc.getValue()); + + ret.setDomain(jmc.getDomain()); + ret.setPath(jmc.getPath()); + ret.setExpiryDate(exp > 0 ? new Date(exp) : null); // use null for no expiry + ret.setSecure(jmc.getSecure()); + ret.setVersion(jmc.getVersion()); + return ret; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/Header.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/Header.java new file mode 100644 index 00000000000..746484de4fc --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/Header.java @@ -0,0 +1,111 @@ +/* + * 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.protocol.http.control; + +import java.io.Serializable; + +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.testelement.AbstractTestElement; + +/** + * This class is an HTTP Header encapsulator. + * + */ +public class Header extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + private static final String HNAME = "Header.name"; //$NON-NLS-1$ + // See TestElementPropertyConverter + + private static final String VALUE = "Header.value"; //$NON-NLS-1$ + + /** + * Create the header. Uses an empty name and value as default + */ + public Header() { + this("", ""); //$NON-NLS-1$ $NON-NLS-2$ + } + + /** + * Create the header. + * + * @param name + * name of the header + * @param value + * name of the header + */ + public Header(String name, String value) { + this.setName(name); + this.setValue(value); + } + + public void addConfigElement(ConfigElement config) { + } + + public boolean expectsModification() { + return false; + } + + /** + * Get the name for this object. + * + * @return the name of this header + */ + @Override + public String getName() { + return getPropertyAsString(HNAME); + } + + /** + * Set the name for this object. + * + * @param name the name of this header + */ + @Override + public void setName(String name) { + this.setProperty(HNAME, name); + } + + /** + * Get the value for this object. + * + * @return the value of this header + */ + public String getValue() { + return getPropertyAsString(VALUE); + } + + /** + * Set the value for this object. + * + * @param value the value of this header + */ + public void setValue(String value) { + this.setProperty(VALUE, value); + } + + /** + * Creates a string representation of this header. + */ + @Override + public String toString() { + return getName() + "\t" + getValue(); //$NON-NLS-1$ + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/HeaderManager.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/HeaderManager.java new file mode 100644 index 00000000000..153ee83ef0b --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/HeaderManager.java @@ -0,0 +1,312 @@ +/* + * 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.protocol.http.control; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * This class provides an interface to headers file to pass HTTP headers along + * with a request. + * + * @version $Revision$ + */ +public class HeaderManager extends ConfigTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + public static final String HEADERS = "HeaderManager.headers";// $NON-NLS-1$ + + private static final String[] COLUMN_RESOURCE_NAMES = { + "name", // $NON-NLS-1$ + "value" // $NON-NLS-1$ + }; + + private static final int COLUMN_COUNT = COLUMN_RESOURCE_NAMES.length; + + + /** + * Apache SOAP driver does not provide an easy way to get and set the cookie + * or HTTP header. Therefore it is necessary to store the SOAPHTTPConnection + * object and reuse it. + */ + private Object SOAPHeader = null; + + public HeaderManager() { + setProperty(new CollectionProperty(HEADERS, new ArrayList())); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(HEADERS, new ArrayList())); + } + + /** + * Get the collection of JMeterProperty entries representing the headers. + * + * @return the header collection property + */ + public CollectionProperty getHeaders() { + return (CollectionProperty) getProperty(HEADERS); + } + + public int getColumnCount() { + return COLUMN_COUNT; + } + + public String getColumnName(int column) { + return COLUMN_RESOURCE_NAMES[column]; + } + + public Class getColumnClass(int column) { + return COLUMN_RESOURCE_NAMES[column].getClass(); + } + + public Header getHeader(int row) { + return (Header) getHeaders().get(row).getObjectValue(); + } + + /** + * Save the header data to a file. + * + * @param headFile + * name of the file to store headers into. If name is relative + * the system property user.dir will be prepended + * @throws IOException + * if writing the headers fails + */ + public void save(String headFile) throws IOException { + File file = new File(headFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir")// $NON-NLS-1$ + + File.separator + headFile); + } + PrintWriter writer = new PrintWriter(new FileWriter(file)); // TODO Charset ? + writer.println("# JMeter generated Header file");// $NON-NLS-1$ + final CollectionProperty hdrs = getHeaders(); + for (int i = 0; i < hdrs.size(); i++) { + final JMeterProperty hdr = hdrs.get(i); + Header head = (Header) hdr.getObjectValue(); + writer.println(head.toString()); + } + writer.flush(); + writer.close(); + } + + /** + * Add header data from a file. + * + * @param headerFile + * name of the file to read headers from. If name is relative the + * system property user.dir will be prepended + * @throws IOException + * if reading headers fails + */ + public void addFile(String headerFile) throws IOException { + File file = new File(headerFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir")// $NON-NLS-1$ + + File.separator + headerFile); + } + if (!file.canRead()) { + throw new IOException("The file you specified cannot be read."); + } + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(file)); // TODO Charset ? + String line; + while ((line = reader.readLine()) != null) { + try { + if (line.startsWith("#") || JOrphanUtils.isBlank(line)) {// $NON-NLS-1$ + continue; + } + String[] st = JOrphanUtils.split(line, "\t", " ");// $NON-NLS-1$ $NON-NLS-2$ + int name = 0; + int value = 1; + Header header = new Header(st[name], st[value]); + getHeaders().addItem(header); + } catch (Exception e) { + throw new IOException("Error parsing header line\n\t'" + line + "'\n\t" + e); + } + } + } finally { + IOUtils.closeQuietly(reader); + } + } + + /** + * Add a header. + * + * @param h {@link Header} to add + */ + public void add(Header h) { + getHeaders().addItem(h); + } + + /** + * Add an empty header. + */ + public void add() { + getHeaders().addItem(new Header()); + } + + /** + * Remove a header. + * + * @param index index from the header to remove + */ + public void remove(int index) { + getHeaders().remove(index); + } + + /** + * Return the number of headers. + * + * @return number of headers + */ + public int size() { + return getHeaders().size(); + } + + /** + * Return the header at index i. + * + * @param i + * index of the header to get + * @return {@link Header} at index i + */ + public Header get(int i) { + return (Header) getHeaders().get(i).getObjectValue(); + } + + /** + * Remove from Headers the header named name + * @param name header name + */ + public void removeHeaderNamed(String name) { + List removeIndices = new ArrayList(); + for (int i = getHeaders().size() - 1; i >= 0; i--) { + Header header = (Header) getHeaders().get(i).getObjectValue(); + if (header == null) { + continue; + } + if (header.getName().equalsIgnoreCase(name)) { + removeIndices.add(Integer.valueOf(i)); + } + } + for (Integer indice : removeIndices) { + getHeaders().remove(indice.intValue()); + } + } + + /** + * Added support for SOAP related header stuff. 1-29-04 Peter Lin + * + * @return the SOAP header Object + */ + public Object getSOAPHeader() { + return this.SOAPHeader; + } + + /** + * Set the SOAPHeader with the SOAPHTTPConnection object. We may or may not + * want to rename this to setHeaderObject(Object). Conceivably, other + * samplers may need this kind of functionality. 1-29-04 Peter Lin + * + * @param header soap header + */ + public void setSOAPHeader(Object header) { + this.SOAPHeader = header; + } + + /** + * Merge the attributes with a another HeaderManager's attributes. + * + * @param element + * The object to be merged with + * @param preferLocalValues + * When both objects have a value for the same attribute, this + * flag determines which value is preferred. + * @return merged HeaderManager + * @throws IllegalArgumentException + * if element is not an instance of + * {@link HeaderManager} + */ + public HeaderManager merge(TestElement element, boolean preferLocalValues) { + if (!(element instanceof HeaderManager)) { + throw new IllegalArgumentException("Cannot merge type:" + this.getClass().getName() + " with type:" + element.getClass().getName()); + } + + // start off with a merged object as a copy of the local object + HeaderManager merged = (HeaderManager)this.clone(); + + HeaderManager other = (HeaderManager)element; + // iterate thru each of the other headers + for (int i = 0; i < other.getHeaders().size(); i++) { + Header otherHeader = other.get(i); + boolean found = false; + // find the same property in the local headers + for (int j = 0; j < merged.getHeaders().size(); j++) { + Header mergedHeader = merged.get(j); + if (mergedHeader.getName().equalsIgnoreCase(otherHeader.getName())) { + // we have a match + found = true; + if (!preferLocalValues) { + // prefer values from the other object + if ( (otherHeader.getValue() == null) || (otherHeader.getValue().length() == 0) ) { + // the other object has an empty value, so remove this value from the merged object + merged.remove(j); + } else { + // use the other object's value + mergedHeader.setValue(otherHeader.getValue()); + } + } + // break out of the inner loop + break; + } + } + if (!found) { + // the other object has a new value to be added to the merged + merged.add(otherHeader); + } + } + + // finally, merge the names so it's clear they've been merged + merged.setName(merged.getName() + ":" + other.getName()); + + return merged; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/HttpMirrorControl.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/HttpMirrorControl.java new file mode 100644 index 00000000000..931cb55a7cc --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/HttpMirrorControl.java @@ -0,0 +1,160 @@ +/* + * 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.protocol.http.control; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; + +//For unit tests, @see TestHttpMirrorControl + +/** + * Test element that implements the Workbench HTTP Mirror function + */ +public class HttpMirrorControl extends AbstractTestElement { + + private static final long serialVersionUID = 233L; + + private transient HttpMirrorServer server; + + // Used by HttpMirrorServer + static final int DEFAULT_PORT = 8081; + + // and as a string + public static final String DEFAULT_PORT_S = + Integer.toString(DEFAULT_PORT);// Used by GUI + + public static final String PORT = "HttpMirrorControlGui.port"; // $NON-NLS-1$ + + public static final String MAX_POOL_SIZE = "HttpMirrorControlGui.maxPoolSize"; // $NON-NLS-1$ + + public static final String MAX_QUEUE_SIZE = "HttpMirrorControlGui.maxQueueSize"; // $NON-NLS-1$ + + public static final int DEFAULT_MAX_POOL_SIZE = 0; + + public static final int DEFAULT_MAX_QUEUE_SIZE = 25; + + + public HttpMirrorControl() { + initPort(DEFAULT_PORT); + } + + public HttpMirrorControl(int port) { + initPort(port); + } + + private void initPort(int port){ + setProperty(new IntegerProperty(PORT, port)); + } + + public void setPort(int port) { + initPort(port); + } + + public void setPort(String port) { + setProperty(PORT, port); + } + + public int getPort() { + return getPropertyAsInt(PORT); + } + + public String getPortString() { + return getPropertyAsString(PORT); + } + + /** + * @return Max Thread Pool size + */ + public String getMaxPoolSizeAsString() { + return getPropertyAsString(MAX_POOL_SIZE); + } + + /** + * @return Max Thread Pool size + */ + private int getMaxPoolSize() { + return getPropertyAsInt(MAX_POOL_SIZE, DEFAULT_MAX_POOL_SIZE); + } + + /** + * @param maxPoolSize Max Thread Pool size + */ + public void setMaxPoolSize(String maxPoolSize) { + setProperty(MAX_POOL_SIZE, maxPoolSize); + } + + /** + * @return Max Queue size + */ + public String getMaxQueueSizeAsString() { + return getPropertyAsString(MAX_QUEUE_SIZE); + } + + /** + * @return Max Queue size + */ + private int getMaxQueueSize() { + return getPropertyAsInt(MAX_QUEUE_SIZE, DEFAULT_MAX_QUEUE_SIZE); + } + + /** + * @param maxQueueSize Max Queue size + */ + public void setMaxQueueSize(String maxQueueSize) { + setProperty(MAX_QUEUE_SIZE, maxQueueSize); + } + + public int getDefaultPort() { + return DEFAULT_PORT; + } + + public void startHttpMirror() { + server = new HttpMirrorServer(getPort(), getMaxPoolSize(), getMaxQueueSize()); + server.start(); + GuiPackage instance = GuiPackage.getInstance(); + if (instance != null) { + instance.register(server); + } + } + + public void stopHttpMirror() { + if (server != null) { + server.stopServer(); + GuiPackage instance = GuiPackage.getInstance(); + if (instance != null) { + instance.unregister(server); + } + try { + server.join(1000); // wait for server to stop + } catch (InterruptedException e) { + } + server = null; + } + } + + @Override + public boolean canRemove() { + return null == server; + } + + public boolean isServerAlive(){ + return server != null && server.isAlive(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/HttpMirrorServer.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/HttpMirrorServer.java new file mode 100644 index 00000000000..710c7903a26 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/HttpMirrorServer.java @@ -0,0 +1,172 @@ +/* + * 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.protocol.http.control; + +import java.io.InterruptedIOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.jmeter.gui.Stoppable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Server daemon thread. + * Creates main socket and listens on it. + * For each client request, creates a thread to handle the request. + * + */ +public class HttpMirrorServer extends Thread implements Stoppable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The time (in milliseconds) to wait when accepting a client connection. + * The accept will be retried until the Daemon is told to stop. So this + * interval is the longest time that the Daemon will have to wait after + * being told to stop. + */ + private static final int ACCEPT_TIMEOUT = 1000; + + private static final long KEEP_ALIVE_TIME = 10; + + /** The port to listen on. */ + private final int daemonPort; + + /** True if the Daemon is currently running. */ + private volatile boolean running; + + // Saves the error if one occurs + private volatile Exception except; + + /** + * Max Executor Pool size + */ + private int maxThreadPoolSize; + + /** + * Max Queue size + */ + private int maxQueueSize; + + /** + * Create a new Daemon with the specified port and target. + * + * @param port + * the port to listen on. + */ + public HttpMirrorServer(int port) { + this(port, HttpMirrorControl.DEFAULT_MAX_POOL_SIZE, HttpMirrorControl.DEFAULT_MAX_QUEUE_SIZE); + } + + /** + * Create a new Daemon with the specified port and target. + * + * @param port + * the port to listen on. + * @param maxThreadPoolSize Max Thread pool size + * @param maxQueueSize Max Queue size + */ + public HttpMirrorServer(int port, int maxThreadPoolSize, int maxQueueSize) { + super("HttpMirrorServer"); + this.daemonPort = port; + this.maxThreadPoolSize = maxThreadPoolSize; + this.maxQueueSize = maxQueueSize; + } + + /** + * Listen on the daemon port and handle incoming requests. This method will + * not exit until {@link #stopServer()} is called or an error occurs. + */ + @Override + public void run() { + except = null; + running = true; + ServerSocket mainSocket = null; + ThreadPoolExecutor threadPoolExecutor = null; + if(maxThreadPoolSize>0) { + final ArrayBlockingQueue queue = new ArrayBlockingQueue( + maxQueueSize); + threadPoolExecutor = new ThreadPoolExecutor( + maxThreadPoolSize/2, + maxThreadPoolSize, KEEP_ALIVE_TIME, TimeUnit.SECONDS, queue); + threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); + } + try { + log.info("Creating HttpMirror ... on port " + daemonPort); + mainSocket = new ServerSocket(daemonPort); + mainSocket.setSoTimeout(ACCEPT_TIMEOUT); + log.info("HttpMirror up and running!"); + while (running) { + try { + // Listen on main socket + Socket clientSocket = mainSocket.accept(); + if (running) { + // Pass request to new thread + if(threadPoolExecutor != null) { + threadPoolExecutor.execute(new HttpMirrorThread(clientSocket)); + } else { + Thread thd = new Thread(new HttpMirrorThread(clientSocket)); + log.debug("Starting new Mirror thread"); + thd.start(); + } + } else { + log.warn("Server not running"); + JOrphanUtils.closeQuietly(clientSocket); + } + } catch (InterruptedIOException e) { + // Timeout occurred. Ignore, and keep looping until we're + // told to stop running. + } + } + log.info("HttpMirror Server stopped"); + } catch (Exception e) { + except = e; + log.warn("HttpMirror Server stopped", e); + } finally { + if(threadPoolExecutor != null) { + threadPoolExecutor.shutdownNow(); + } + JOrphanUtils.closeQuietly(mainSocket); + } + } + + @Override + public void stopServer() { + running = false; + } + + public Exception getException(){ + return except; + } + + public static void main(String args[]){ + int port = HttpMirrorControl.DEFAULT_PORT; + if (args.length > 0){ + port = Integer.parseInt(args[0]); + } + LoggingManager.setPriority("INFO"); // default level + LoggingManager.setLoggingLevels(System.getProperties() ); // allow override by system properties + HttpMirrorServer serv = new HttpMirrorServer(port); + serv.start(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/HttpMirrorThread.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/HttpMirrorThread.java new file mode 100644 index 00000000000..ed558a9ff9c --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/HttpMirrorThread.java @@ -0,0 +1,318 @@ +/* + * 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.protocol.http.control; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * Thread to handle one client request. Gets the request from the client and + * sends the response back to the client. + * The server responds to some header settings: + * X-ResponseStatus - the response code/message; default "200 OK" + * X-SetHeaders - pipe-separated list of headers to return + * X-ResponseLength - truncates the response to the stated length + * X-SetCookie - set a cookie + * X-Sleep - sleep before returning + * + * It also responds to some query strings: + * status=nnn Message (overrides X-ResponseStatus) + * redirect=location - sends a temporary redirect + * v - verbose, i.e. print some details to stdout + */ +public class HttpMirrorThread implements Runnable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ISO_8859_1 = "ISO-8859-1"; //$NON-NLS-1$ + private static final byte[] CRLF = { 0x0d, 0x0a }; + + private static final String REDIRECT = "redirect"; //$NON-NLS-1$ + + private static final String STATUS = "status"; //$NON-NLS-1$ + + private static final String VERBOSE = "v"; // $NON-NLS-1$ + + /** Socket to client. */ + private final Socket clientSocket; + + public HttpMirrorThread(Socket _clientSocket) { + this.clientSocket=_clientSocket; + } + + /** + * Main processing method for the HttpMirror object + */ + @Override + public void run() { + log.debug("Starting thread"); + BufferedInputStream in = null; + BufferedOutputStream out = null; + + try { + in = new BufferedInputStream(clientSocket.getInputStream()); + + // Read the header part, we will be looking for a content-length + // header, so we know how much we should read. + // We assume headers are in ISO_8859_1 + // If we do not find such a header, we will just have to read until + // we have to block to read more, until we support chunked transfer + int contentLength = -1; + boolean isChunked = false; + byte[] buffer = new byte[1024]; + StringBuilder headers = new StringBuilder(); + int length = 0; + int positionOfBody = 0; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while(positionOfBody <= 0 && ((length = in.read(buffer)) != -1)) { + log.debug("Write body"); + baos.write(buffer, 0, length); // echo back + headers.append(new String(buffer, 0, length, ISO_8859_1)); + // Check if we have read all the headers + positionOfBody = getPositionOfBody(headers.toString()); + } + + baos.close(); + final String headerString = headers.toString(); + final String firstLine = headerString.substring(0, headerString.indexOf('\r')); + final String[] requestParts = firstLine.split("\\s+"); + final String requestMethod = requestParts[0]; + final String requestPath = requestParts[1]; + final HashMap parameters = new HashMap(); + if (HTTPConstants.GET.equals(requestMethod)) { + int querypos = requestPath.indexOf('?'); + if (querypos >= 0) { + String query; + try { + URI uri = new URI(requestPath); // Use URI because it will decode the query + query = uri.getQuery(); + } catch (URISyntaxException e) { + log.warn(e.getMessage()); + query=requestPath.substring(querypos+1); + } + if (query != null) { + String params[] = query.split("&"); + for(String param : params) { + String parts[] = param.split("=",2); + if (parts.length==2) { + parameters.put(parts[0], parts[1]); + } else { // allow for parameter name only + parameters.put(parts[0], ""); + } + } + } + } + } + + final boolean verbose = parameters.containsKey(VERBOSE); + + if (verbose) { + System.out.println(firstLine); + } + + // Look for special Response Length header + String responseStatusValue = getRequestHeaderValue(headerString, "X-ResponseStatus"); //$NON-NLS-1$ + if(responseStatusValue == null) { + responseStatusValue = "200 OK"; + } + // Do this before the status check so can override the status, e.g. with a different redirect type + if (parameters.containsKey(REDIRECT)) { + responseStatusValue = "302 Temporary Redirect"; + } + if (parameters.containsKey(STATUS)) { + responseStatusValue = parameters.get(STATUS); + } + + log.debug("Write headers"); + out = new BufferedOutputStream(clientSocket.getOutputStream()); + // The headers are written using ISO_8859_1 encoding + out.write(("HTTP/1.0 "+responseStatusValue).getBytes(ISO_8859_1)); //$NON-NLS-1$ + out.write(CRLF); + out.write("Content-Type: text/plain".getBytes(ISO_8859_1)); //$NON-NLS-1$ + out.write(CRLF); + + if (parameters.containsKey(REDIRECT)) { + StringBuilder sb = new StringBuilder(); + sb.append(HTTPConstants.HEADER_LOCATION); + sb.append(": "); //$NON-NLS-1$ + sb.append(parameters.get(REDIRECT)); + final String redirectLocation = sb.toString(); + if (verbose) { + System.out.println(redirectLocation); + } + out.write(redirectLocation.getBytes(ISO_8859_1)); + out.write(CRLF); + } + + // Look for special Header request + String headersValue = getRequestHeaderValue(headerString, "X-SetHeaders"); //$NON-NLS-1$ + if (headersValue != null) { + String[] headersToSet = headersValue.split("\\|"); + for (String string : headersToSet) { + out.write(string.getBytes(ISO_8859_1)); + out.write(CRLF); + } + } + + // Look for special Response Length header + String responseLengthValue = getRequestHeaderValue(headerString, "X-ResponseLength"); //$NON-NLS-1$ + int responseLength=-1; + if(responseLengthValue != null) { + responseLength = Integer.parseInt(responseLengthValue); + } + + // Look for special Cookie request + String cookieHeaderValue = getRequestHeaderValue(headerString, "X-SetCookie"); //$NON-NLS-1$ + if (cookieHeaderValue != null) { + out.write("Set-Cookie: ".getBytes(ISO_8859_1)); + out.write(cookieHeaderValue.getBytes(ISO_8859_1)); + out.write(CRLF); + } + out.write(CRLF); + out.flush(); + + if(responseLength>=0) { + out.write(baos.toByteArray(), 0, Math.min(baos.toByteArray().length, responseLength)); + } else { + out.write(baos.toByteArray()); + } + // Check if we have found a content-length header + String contentLengthHeaderValue = getRequestHeaderValue(headerString, HTTPConstants.HEADER_CONTENT_LENGTH); + if(contentLengthHeaderValue != null) { + contentLength = Integer.parseInt(contentLengthHeaderValue); + } + // Look for special Sleep request + String sleepHeaderValue = getRequestHeaderValue(headerString, "X-Sleep"); //$NON-NLS-1$ + if(sleepHeaderValue != null) { + TimeUnit.MILLISECONDS.sleep(Integer.parseInt(sleepHeaderValue)); + } + String transferEncodingHeaderValue = getRequestHeaderValue(headerString, HTTPConstants.TRANSFER_ENCODING); + if(transferEncodingHeaderValue != null) { + isChunked = transferEncodingHeaderValue.equalsIgnoreCase("chunked"); //$NON-NLS-1$ + // We only support chunked transfer encoding + if(!isChunked) { + log.error("Transfer-Encoding header set, the value is not supported : " + transferEncodingHeaderValue); + } + } + + // If we know the content length, we can allow the reading of + // the request to block until more data arrives. + // If it is chunked transfer, we cannot allow the reading to + // block, because we do not know when to stop reading, because + // the chunked transfer is not properly supported yet + length = 0; + if(contentLength > 0) { + // Check how much of the body we have already read as part of reading + // the headers + // We subtract two bytes for the crlf divider between header and body + int totalReadBytes = headerString.length() - positionOfBody - 2; + + // We know when to stop reading, so we can allow the read method to block + log.debug("Reading, "+totalReadBytes+" < " +contentLength); + while((totalReadBytes < contentLength) && ((length = in.read(buffer)) != -1)) { + log.debug("Read bytes: "+length); + out.write(buffer, 0, length); + + totalReadBytes += length; + log.debug("totalReadBytes: "+totalReadBytes); + } + } + else if (isChunked) { + // It is chunked transfer encoding, which we do not really support yet. + // So we just read without blocking, because we do not know when to + // stop reading, so we cannot block + // TODO propery implement support for chunked transfer, i.e. to + // know when we have read the whole request, and therefore allow + // the reading to block + log.debug("Chunked"); + while(in.available() > 0 && ((length = in.read(buffer)) != -1)) { + out.write(buffer, 0, length); + } + } + else { + // The reqest has no body, or it has a transfer encoding we do not support. + // In either case, we read any data available + log.debug("Other"); + while(in.available() > 0 && ((length = in.read(buffer)) != -1)) { + log.debug("Read bytes: "+length); + out.write(buffer, 0, length); + } + } + log.debug("Flush"); + out.flush(); + } catch (IOException e) { + log.error("", e); + } catch (InterruptedException e) { + log.error("", e); + } finally { + JOrphanUtils.closeQuietly(out); + JOrphanUtils.closeQuietly(in); + JOrphanUtils.closeQuietly(clientSocket); + } + log.debug("End of Thread"); + } + + private static String getRequestHeaderValue(String requestHeaders, String headerName) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + // We use multi-line mask so can prefix the line with ^ + String expression = "^" + headerName + ":\\s+([^\\r\\n]+)"; // $NON-NLS-1$ $NON-NLS-2$ + Pattern pattern = JMeterUtils.getPattern(expression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + if(localMatcher.contains(requestHeaders, pattern)) { + // The value is in the first group, group 0 is the whole match +// System.out.println("Found:'"+localMatcher.getMatch().group(1)+"'"); +// System.out.println("in: '"+localMatcher.getMatch().group(0)+"'"); + return localMatcher.getMatch().group(1); + } + else { + return null; + } + } + + private static int getPositionOfBody(String stringToCheck) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + // The headers and body are divided by a blank line (the \r is to allow for the CR before LF) + String regularExpression = "^\\r$"; // $NON-NLS-1$ + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + + PatternMatcherInput input = new PatternMatcherInput(stringToCheck); + if(localMatcher.contains(input, pattern)) { + MatchResult match = localMatcher.getMatch(); + return match.beginOffset(0); + } + // No divider was found + return -1; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/KerberosManager.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/KerberosManager.java new file mode 100644 index 00000000000..a68de8aecb0 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/KerberosManager.java @@ -0,0 +1,147 @@ +/* + * 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.protocol.http.control; + +import java.io.IOException; +import java.io.Serializable; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Takes in charge Kerberos auth mechanism + * @since 2.10 + */ +public class KerberosManager implements Serializable { + + private static final long serialVersionUID = 2L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String JAAS_APPLICATION = JMeterUtils.getPropDefault("kerberos_jaas_application", "JMeter"); //$NON-NLS-1$ $NON-NLS-2$ + private final ConcurrentMap> subjects + = new ConcurrentHashMap>(); + + public KerberosManager() { + } + + void clearSubjects() { + subjects.clear(); + } + + public Subject getSubjectForUser(final String username, + final String password) { + Callable callable = new Callable() { + + @Override + public Subject call() throws Exception { + LoginContext loginCtx; + try { + loginCtx = new LoginContext(JAAS_APPLICATION, + new LoginCallbackHandler(username, password)); + loginCtx.login(); + return loginCtx.getSubject(); + } catch (LoginException e) { + log.warn("Could not log in user " + username, e); + } + return null; + } + }; + + FutureTask task = new FutureTask(callable); + if(log.isDebugEnabled()) { + log.debug("Subject cached:"+subjects.keySet() +" before:"+username); + } + Future subjectFuture = subjects.putIfAbsent(username, task); + if (subjectFuture == null) { + subjectFuture = task; + task.run(); + } + try { + return subjectFuture.get(); + } catch (InterruptedException e1) { + log.warn("Interrupted while getting subject for " + username, e1); + } catch (ExecutionException e1) { + log.warn("Execution of getting subject for " + username + " failed", e1); + } + return null; + } + + // Needs to be package-protected to avoid problem with serialisation tests + static class LoginCallbackHandler implements CallbackHandler { + private final String password; + private final String username; + + public LoginCallbackHandler(final String username, final String password) { + super(); + this.username = username; + this.password = password; + } + + @Override + public void handle(Callback[] callbacks) throws IOException, + UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback && username != null) { + NameCallback nc = (NameCallback) callback; + nc.setName(username); + } else if (callback instanceof PasswordCallback) { + PasswordCallback pc = (PasswordCallback) callback; + pc.setPassword(password.toCharArray()); + } else { + throw new UnsupportedCallbackException( callback, + "Unrecognized Callback"); //$NON-NLS-1$ + } + } + } + } + + public String getKrb5Conf() { + return System.getProperty("java.security.krb5.conf"); //$NON-NLS-1$ + } + + public boolean getKrb5Debug() { + return Boolean.getBoolean("java.security.krb5.debug"); //$NON-NLS-1$ + } + + public String getJaasConf() { + return System.getProperty("java.security.auth.login.config"); //$NON-NLS-1$ + } + + @Override + public String toString() { + return "KerberosManager[jaas: " + getJaasConf() + ", krb5: " + getKrb5Conf() + ", debug: " + getKrb5Debug() +"]"; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/RecordingController.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/RecordingController.java new file mode 100644 index 00000000000..adeae5db90e --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/RecordingController.java @@ -0,0 +1,26 @@ +/* + * 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.protocol.http.control; + +import org.apache.jmeter.control.GenericController; + +public class RecordingController extends GenericController { + private static final long serialVersionUID = 240L; + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/AjpSamplerGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/AjpSamplerGui.java new file mode 100644 index 00000000000..3fb653cafcb --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/AjpSamplerGui.java @@ -0,0 +1,51 @@ +/* + * 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.protocol.http.control.gui; + +import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui; +import org.apache.jmeter.protocol.http.sampler.AjpSampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class AjpSamplerGui extends HttpTestSampleGui { + + private static final long serialVersionUID = 240L; + + public AjpSamplerGui() { + super(true); + } + + @Override + public TestElement createTestElement() { + AjpSampler sampler = new AjpSampler(); + modifyTestElement(sampler); + return sampler; + } + + // Use this instead of getLabelResource() otherwise getDocAnchor() below does not work + @Override + public String getStaticLabel() { + return JMeterUtils.getResString("ajp_sampler_title"); // $NON-NLS-1$ + } + + @Override + public String getDocAnchor() {// reuse documentation + return super.getStaticLabel().replace(' ', '_'); //$NON-NLS-1$ //$NON-NLS-2$ + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/HttpMirrorControlGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/HttpMirrorControlGui.java new file mode 100644 index 00000000000..e984605f55b --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/HttpMirrorControlGui.java @@ -0,0 +1,212 @@ +/* + * 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.protocol.http.control.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.gui.LogicControllerGui; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.protocol.http.control.HttpMirrorControl; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class HttpMirrorControlGui extends LogicControllerGui + implements JMeterGUIComponent, ActionListener, UnsharedComponent { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private JTextField portField; + + private JTextField maxPoolSizeField; + + private JTextField maxQueueSizeField; + + private JButton stop, start; + + private static final String ACTION_STOP = "stop"; // $NON-NLS-1$ + + private static final String ACTION_START = "start"; // $NON-NLS-1$ + + private HttpMirrorControl mirrorController; + + + public HttpMirrorControlGui() { + super(); + log.debug("Creating HttpMirrorControlGui"); + init(); + } + + @Override + public TestElement createTestElement() { + mirrorController = new HttpMirrorControl(); + log.debug("creating/configuring model = " + mirrorController); + modifyTestElement(mirrorController); + return mirrorController; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement el) { + configureTestElement(el); + if (el instanceof HttpMirrorControl) { + mirrorController = (HttpMirrorControl) el; + mirrorController.setPort(portField.getText()); + mirrorController.setMaxPoolSize(maxPoolSizeField.getText()); + mirrorController.setMaxQueueSize(maxQueueSizeField.getText()); + } + } + + @Override + public String getLabelResource() { + return "httpmirror_title"; // $NON-NLS-1$ + } + + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.NON_TEST_ELEMENTS }); + } + + @Override + public void configure(TestElement element) { + log.debug("Configuring gui with " + element); + super.configure(element); + mirrorController = (HttpMirrorControl) element; + portField.setText(mirrorController.getPortString()); + maxPoolSizeField.setText(mirrorController.getMaxPoolSizeAsString()); + maxQueueSizeField.setText(mirrorController.getMaxQueueSizeAsString()); + repaint(); + } + + + @Override + public void actionPerformed(ActionEvent action) { + String command = action.getActionCommand(); + + if (command.equals(ACTION_STOP)) { + mirrorController.stopHttpMirror(); + stop.setEnabled(false); + start.setEnabled(true); + } else if (command.equals(ACTION_START)) { + modifyTestElement(mirrorController); + mirrorController.startHttpMirror(); + start.setEnabled(false); + stop.setEnabled(true); + } + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + Box myBox = Box.createVerticalBox(); + myBox.add(createPortPanel()); + mainPanel.add(myBox, BorderLayout.NORTH); + + mainPanel.add(createControls(), BorderLayout.CENTER); + + add(mainPanel, BorderLayout.CENTER); + } + + private JPanel createControls() { + start = new JButton(JMeterUtils.getResString("start")); // $NON-NLS-1$ + start.addActionListener(this); + start.setActionCommand(ACTION_START); + start.setEnabled(true); + + stop = new JButton(JMeterUtils.getResString("stop")); // $NON-NLS-1$ + stop.addActionListener(this); + stop.setActionCommand(ACTION_STOP); + stop.setEnabled(false); + + JPanel panel = new JPanel(); + panel.add(start); + panel.add(stop); + return panel; + } + + private JPanel createPortPanel() { + portField = new JTextField(HttpMirrorControl.DEFAULT_PORT_S, 8); + portField.setName(HttpMirrorControl.PORT); + + JLabel label = new JLabel(JMeterUtils.getResString("port")); // $NON-NLS-1$ + label.setLabelFor(portField); + + maxPoolSizeField = new JTextField(Integer.toString(HttpMirrorControl.DEFAULT_MAX_POOL_SIZE), 8); + maxPoolSizeField.setName(HttpMirrorControl.MAX_POOL_SIZE); + + JLabel mpsLabel = new JLabel(JMeterUtils.getResString("httpmirror_max_pool_size")); // $NON-NLS-1$ + mpsLabel.setLabelFor(maxPoolSizeField); + + maxQueueSizeField = new JTextField(Integer.toString(HttpMirrorControl.DEFAULT_MAX_QUEUE_SIZE), 8); + maxQueueSizeField.setName(HttpMirrorControl.MAX_QUEUE_SIZE); + + JLabel mqsLabel = new JLabel(JMeterUtils.getResString("httpmirror_max_queue_size")); // $NON-NLS-1$ + mqsLabel.setLabelFor(maxQueueSizeField); + + HorizontalPanel panel = new HorizontalPanel(); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("httpmirror_settings"))); // $NON-NLS-1$ + + panel.add(label); + panel.add(portField); + + panel.add(mpsLabel); + panel.add(maxPoolSizeField); + + panel.add(mqsLabel); + panel.add(maxQueueSizeField); + + panel.add(Box.createHorizontalStrut(10)); + + return panel; + } + + @Override + public void clearGui(){ + super.clearGui(); + portField.setText(HttpMirrorControl.DEFAULT_PORT_S); + maxPoolSizeField.setText(Integer.toString(HttpMirrorControl.DEFAULT_MAX_POOL_SIZE)); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java new file mode 100644 index 00000000000..e23d4f31f5b --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java @@ -0,0 +1,296 @@ +/* + * 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.protocol.http.control.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.http.config.gui.MultipartUrlConfigGui; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +//For unit tests, @see TestHttpTestSampleGui + +/** + * HTTP Sampler GUI + * + */ +public class HttpTestSampleGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + private static final Font FONT_VERY_SMALL = new Font("SansSerif", Font.PLAIN, 9); + + private static final Font FONT_SMALL = new Font("SansSerif", Font.PLAIN, 12); + + private MultipartUrlConfigGui urlConfigGui; + + private JCheckBox getImages; + + private JCheckBox concurrentDwn; + + private JTextField concurrentPool; + + private JCheckBox isMon; + + private JCheckBox useMD5; + + private JLabel labelEmbeddedRE = new JLabel(JMeterUtils.getResString("web_testing_embedded_url_pattern")); // $NON-NLS-1$ + + private JTextField embeddedRE; // regular expression used to match against embedded resource URLs + + private JTextField sourceIpAddr; // does not apply to Java implementation + + private JComboBox sourceIpType = new JComboBox(HTTPSamplerBase.getSourceTypeList()); + + private final boolean isAJP; + + public HttpTestSampleGui() { + isAJP = false; + init(); + } + + // For use by AJP + protected HttpTestSampleGui(boolean ajp) { + isAJP = ajp; + init(); + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement element) { + super.configure(element); + final HTTPSamplerBase samplerBase = (HTTPSamplerBase) element; + urlConfigGui.configure(element); + getImages.setSelected(samplerBase.isImageParser()); + concurrentDwn.setSelected(samplerBase.isConcurrentDwn()); + concurrentPool.setText(samplerBase.getConcurrentPool()); + isMon.setSelected(samplerBase.isMonitor()); + useMD5.setSelected(samplerBase.useMD5()); + embeddedRE.setText(samplerBase.getEmbeddedUrlRE()); + if (!isAJP) { + sourceIpAddr.setText(samplerBase.getIpSource()); + sourceIpType.setSelectedIndex(samplerBase.getIpSourceType()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public TestElement createTestElement() { + HTTPSamplerBase sampler = new HTTPSamplerProxy(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + *

+ * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + urlConfigGui.modifyTestElement(sampler); + final HTTPSamplerBase samplerBase = (HTTPSamplerBase) sampler; + samplerBase.setImageParser(getImages.isSelected()); + enableConcurrentDwn(getImages.isSelected()); + samplerBase.setConcurrentDwn(concurrentDwn.isSelected()); + samplerBase.setConcurrentPool(concurrentPool.getText()); + samplerBase.setMonitor(isMon.isSelected()); + samplerBase.setMD5(useMD5.isSelected()); + samplerBase.setEmbeddedUrlRE(embeddedRE.getText()); + if (!isAJP) { + samplerBase.setIpSource(sourceIpAddr.getText()); + samplerBase.setIpSourceType(sourceIpType.getSelectedIndex()); + } + this.configureTestElement(sampler); + } + + /** + * {@inheritDoc} + */ + @Override + public String getLabelResource() { + return "web_testing_title"; // $NON-NLS-1$ + } + + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + // URL CONFIG + urlConfigGui = new MultipartUrlConfigGui(true, !isAJP); + add(urlConfigGui, BorderLayout.CENTER); + + // Bottom (embedded resources, source address and optional tasks) + JPanel bottomPane = new VerticalPanel(); + bottomPane.add(createEmbeddedRsrcPanel()); + JPanel optionAndSourcePane = new HorizontalPanel(); + optionAndSourcePane.add(createSourceAddrPanel()); + optionAndSourcePane.add(createOptionalTasksPanel()); + bottomPane.add(optionAndSourcePane); + add(bottomPane, BorderLayout.SOUTH); + } + + protected JPanel createEmbeddedRsrcPanel() { + final JPanel embeddedRsrcPanel = new VerticalPanel(); + embeddedRsrcPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("web_testing_retrieve_title"))); // $NON-NLS-1$ + + final JPanel checkBoxPanel = new HorizontalPanel(); + // RETRIEVE IMAGES + getImages = new JCheckBox(JMeterUtils.getResString("web_testing_retrieve_images")); // $NON-NLS-1$ + getImages.setFont(FONT_SMALL); + // add a listener to activate or not concurrent dwn. + getImages.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(final ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { enableConcurrentDwn(true); } + else { enableConcurrentDwn(false); } + } + }); + // Download concurrent resources + concurrentDwn = new JCheckBox(JMeterUtils.getResString("web_testing_concurrent_download")); // $NON-NLS-1$ + concurrentDwn.setFont(FONT_SMALL); + concurrentDwn.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(final ItemEvent e) { + if (getImages.isSelected() && e.getStateChange() == ItemEvent.SELECTED) { concurrentPool.setEnabled(true); } + else { concurrentPool.setEnabled(false); } + } + }); + concurrentPool = new JTextField(2); // 2 column size + concurrentPool.setFont(FONT_SMALL); + concurrentPool.setMaximumSize(new Dimension(30,20)); + + checkBoxPanel.add(getImages); + checkBoxPanel.add(concurrentDwn); + checkBoxPanel.add(concurrentPool); + embeddedRsrcPanel.add(checkBoxPanel); + + // Embedded URL match regex + labelEmbeddedRE.setFont(FONT_SMALL); + checkBoxPanel.add(labelEmbeddedRE); + embeddedRE = new JTextField(10); + checkBoxPanel.add(embeddedRE); + embeddedRsrcPanel.add(checkBoxPanel); + + return embeddedRsrcPanel; + } + + protected JPanel createOptionalTasksPanel() { + // OPTIONAL TASKS + final JPanel checkBoxPanel = new HorizontalPanel(); + checkBoxPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("optional_tasks"))); // $NON-NLS-1$ + + // Is monitor + isMon = new JCheckBox(JMeterUtils.getResString("monitor_is_title")); // $NON-NLS-1$ + isMon.setFont(FONT_SMALL); + // Use MD5 + useMD5 = new JCheckBox(JMeterUtils.getResString("response_save_as_md5")); // $NON-NLS-1$ + useMD5.setFont(FONT_SMALL); + + checkBoxPanel.add(isMon); + checkBoxPanel.add(useMD5); + + return checkBoxPanel; + } + + protected JPanel createSourceAddrPanel() { + final JPanel sourceAddrPanel = new HorizontalPanel(); + sourceAddrPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("web_testing_source_ip"))); // $NON-NLS-1$ + + if (!isAJP) { + // Add a new field source ip address (for HC implementations only) + sourceIpType.setSelectedIndex(HTTPSamplerBase.SourceType.HOSTNAME.ordinal()); //default: IP/Hostname + sourceIpType.setFont(FONT_VERY_SMALL); + sourceAddrPanel.add(sourceIpType); + + sourceIpAddr = new JTextField(); + sourceAddrPanel.add(sourceIpAddr); + } + return sourceAddrPanel; + } + + /** + * {@inheritDoc} + */ + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + getImages.setSelected(false); + concurrentDwn.setSelected(false); + concurrentPool.setText(String.valueOf(HTTPSamplerBase.CONCURRENT_POOL_SIZE)); + enableConcurrentDwn(false); + isMon.setSelected(false); + useMD5.setSelected(false); + urlConfigGui.clear(); + embeddedRE.setText(""); // $NON-NLS-1$ + if (!isAJP) { + sourceIpAddr.setText(""); // $NON-NLS-1$ + sourceIpType.setSelectedIndex(HTTPSamplerBase.SourceType.HOSTNAME.ordinal()); //default: IP/Hostname + } + } + + private void enableConcurrentDwn(boolean enable) { + if (enable) { + concurrentDwn.setEnabled(true); + labelEmbeddedRE.setEnabled(true); + embeddedRE.setEnabled(true); + if (concurrentDwn.isSelected()) { + concurrentPool.setEnabled(true); + } + } else { + concurrentDwn.setEnabled(false); + concurrentPool.setEnabled(false); + labelEmbeddedRE.setEnabled(false); + embeddedRE.setEnabled(false); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/RecordController.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/RecordController.java new file mode 100644 index 00000000000..24cd4c1e697 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/RecordController.java @@ -0,0 +1,39 @@ +/* + * 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.protocol.http.control.gui; + +import org.apache.jmeter.control.gui.LogicControllerGui; +import org.apache.jmeter.protocol.http.control.RecordingController; +import org.apache.jmeter.testelement.TestElement; + +public class RecordController extends LogicControllerGui { + private static final long serialVersionUID = 240L; + + @Override + public String getLabelResource() { + return "record_controller_title"; // $NON-NLS-1$ + } + + @Override + public TestElement createTestElement() { + RecordingController con = new RecordingController(); + this.configureTestElement(con); + return con; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/SoapSamplerGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/SoapSamplerGui.java new file mode 100644 index 00000000000..c9606f84683 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/SoapSamplerGui.java @@ -0,0 +1,177 @@ +/* + * 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.protocol.http.control.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBox; +import javax.swing.JPanel; + +import org.apache.jmeter.protocol.http.sampler.SoapSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jorphan.gui.JLabeledTextArea; +import org.apache.jorphan.gui.JLabeledTextField; + +public class SoapSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + private JLabeledTextField urlField; + private JLabeledTextField soapAction; + private JCheckBox sendSoapAction; + private JCheckBox useKeepAlive; + private JLabeledTextArea soapXml; + + private FilePanel soapXmlFile = new FilePanel(); + + public SoapSamplerGui() { + init(); + } + + @Override + public String getLabelResource() { + return "soap_sampler_title"; //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public TestElement createTestElement() { + SoapSampler sampler = new SoapSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement s) { + this.configureTestElement(s); + if (s instanceof SoapSampler) { + SoapSampler sampler = (SoapSampler) s; + sampler.setURLData(urlField.getText()); + sampler.setXmlData(soapXml.getText()); + sampler.setXmlFile(soapXmlFile.getFilename()); + sampler.setSOAPAction(soapAction.getText()); + sampler.setSendSOAPAction(sendSoapAction.isSelected()); + sampler.setUseKeepAlive(useKeepAlive.isSelected()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + urlField.setText(""); //$NON-NLS-1$ + soapAction.setText(""); //$NON-NLS-1$ + soapXml.setText(""); //$NON-NLS-1$ + sendSoapAction.setSelected(true); + soapXmlFile.setFilename(""); //$NON-NLS-1$ + useKeepAlive.setSelected(false); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + urlField = new JLabeledTextField(JMeterUtils.getResString("url"), 10); //$NON-NLS-1$ + soapXml = new JLabeledTextArea(JMeterUtils.getResString("soap_data_title")); //$NON-NLS-1$ + soapAction = new JLabeledTextField("", 10); //$NON-NLS-1$ + sendSoapAction = new JCheckBox(JMeterUtils.getResString("soap_send_action"), true); //$NON-NLS-1$ + useKeepAlive = new JCheckBox(JMeterUtils.getResString("use_keepalive")); // $NON-NLS-1$ + + JPanel mainPanel = new JPanel(new BorderLayout()); + JPanel soapActionPanel = new JPanel(); + soapActionPanel.setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridwidth = 2; + c.gridx = 0; + c.gridy = 0; + c.weightx = 1; + soapActionPanel.add(urlField, c); + c.fill = GridBagConstraints.NONE; + c.gridwidth = 1; + c.gridy = 1; + c.weightx = 0; + soapActionPanel.add(sendSoapAction, c); + c.gridx = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + soapActionPanel.add(soapAction, c); + + c.fill = GridBagConstraints.HORIZONTAL; + c.gridwidth = 2; + c.gridy = 2; + c.gridx = 0; + soapActionPanel.add(useKeepAlive, c); + + mainPanel.add(soapActionPanel, BorderLayout.NORTH); + mainPanel.add(soapXml, BorderLayout.CENTER); + mainPanel.add(soapXmlFile, BorderLayout.SOUTH); + + sendSoapAction.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + soapAction.setEnabled(sendSoapAction.isSelected()); + } + }); + + add(mainPanel, BorderLayout.CENTER); + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement el) { + super.configure(el); + SoapSampler sampler = (SoapSampler) el; + urlField.setText(sampler.getURLData()); + sendSoapAction.setSelected(sampler.getSendSOAPAction()); + soapAction.setText(sampler.getSOAPAction()); + soapXml.setText(sampler.getXmlData()); + soapXmlFile.setFilename(sampler.getXmlFile()); + useKeepAlive.setSelected(sampler.getUseKeepAlive()); + } + + /** + * {@inheritDoc} + */ + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/WebServiceSamplerGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/WebServiceSamplerGui.java new file mode 100644 index 00000000000..df6474c07c3 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/control/gui/WebServiceSamplerGui.java @@ -0,0 +1,533 @@ +/* + * 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.protocol.http.control.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.WebServiceSampler; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.WSDLHelper; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * This is the GUI for the webservice samplers. It extends AbstractSamplerGui + * and is modeled after the SOAP sampler GUI. I've added instructional notes to + * the GUI for instructional purposes. XML parsing is pretty heavy weight, + * therefore the notes address those situations.
+ * Created on: Jun 26, 2003 + * + */ +public class WebServiceSamplerGui extends AbstractSamplerGui implements java.awt.event.ActionListener { + + private static final long serialVersionUID = 240L; + + private final JLabeledTextField domain = new JLabeledTextField(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + + private final JLabeledTextField protocol = new JLabeledTextField(JMeterUtils.getResString("protocol"), 4); // $NON-NLS-1$ + + private final JLabeledTextField port = new JLabeledTextField(JMeterUtils.getResString("web_server_port"), 4); // $NON-NLS-1$ + + private final JLabeledTextField path = new JLabeledTextField(JMeterUtils.getResString("path")); // $NON-NLS-1$ + + private final JLabeledTextField soapAction = new JLabeledTextField(JMeterUtils.getResString("webservice_soap_action")); // $NON-NLS-1$ + + /** + * checkbox for Session maintenance. + */ + private JCheckBox maintainSession = new JCheckBox(JMeterUtils.getResString("webservice_maintain_session"), true); // $NON-NLS-1$ + + + private JTextArea soapXml; + + private final JLabeledTextField wsdlField = new JLabeledTextField(JMeterUtils.getResString("wsdl_url")); // $NON-NLS-1$ + + private final JButton wsdlButton = new JButton(JMeterUtils.getResString("load_wsdl")); // $NON-NLS-1$ + + private final JButton selectButton = new JButton(JMeterUtils.getResString("configure_wsdl")); // $NON-NLS-1$ + + private JLabeledChoice wsdlMethods = null; + + private transient WSDLHelper HELPER = null; + + private final FilePanel soapXmlFile = new FilePanel(JMeterUtils.getResString("get_xml_from_file"), ".xml"); // $NON-NLS-1$ + + private final JLabeledTextField randomXmlFile = new JLabeledTextField(JMeterUtils.getResString("get_xml_from_random")); // $NON-NLS-1$ + + private final JLabeledTextField connectTimeout = new JLabeledTextField(JMeterUtils.getResString("webservice_timeout"), 4); // $NON-NLS-1$ + + /** + * checkbox for memory cache. + */ + private JCheckBox memCache = new JCheckBox(JMeterUtils.getResString("memory_cache"), true); // $NON-NLS-1$ + + /** + * checkbox for reading the response + */ + private JCheckBox readResponse = new JCheckBox(JMeterUtils.getResString("read_soap_response")); // $NON-NLS-1$ + + /** + * checkbox for use proxy + */ + private JCheckBox useProxy = new JCheckBox(JMeterUtils.getResString("webservice_use_proxy")); // $NON-NLS-1$ + + /** + * text field for the proxy host + */ + private JTextField proxyHost; + + /** + * text field for the proxy port + */ + private JTextField proxyPort; + + /** + * Text note about read response and its usage. + */ + private String readToolTip = JMeterUtils.getResString("read_response_note") // $NON-NLS-1$ + + " " // $NON-NLS-1$ + + JMeterUtils.getResString("read_response_note2") // $NON-NLS-1$ + + " " // $NON-NLS-1$ + + JMeterUtils.getResString("read_response_note3"); // $NON-NLS-1$ + + /** + * Text note for proxy + */ + private String proxyToolTip = JMeterUtils.getResString("webservice_proxy_note") // $NON-NLS-1$ + + " " // $NON-NLS-1$ + + JMeterUtils.getResString("webservice_proxy_note2") // $NON-NLS-1$ + + " " // $NON-NLS-1$ + + JMeterUtils.getResString("webservice_proxy_note3"); // $NON-NLS-1$ + public WebServiceSamplerGui() { + init(); + } + + @Override + public String getLabelResource() { + return "webservice_sampler_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + WebServiceSampler sampler = new WebServiceSampler(); + this.configureTestElement(sampler); + this.modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement s) { + WebServiceSampler sampler = (WebServiceSampler) s; + this.configureTestElement(sampler); + sampler.setDomain(domain.getText()); + sampler.setProperty(HTTPSamplerBase.PORT,port.getText()); + sampler.setProtocol(protocol.getText()); + sampler.setPath(path.getText()); + sampler.setWsdlURL(wsdlField.getText()); + sampler.setMethod(HTTPConstants.POST); + sampler.setSoapAction(soapAction.getText()); + sampler.setMaintainSession(maintainSession.isSelected()); + sampler.setXmlData(soapXml.getText()); + sampler.setXmlFile(soapXmlFile.getFilename()); + sampler.setXmlPathLoc(randomXmlFile.getText()); + sampler.setTimeout(connectTimeout.getText()); + sampler.setMemoryCache(memCache.isSelected()); + sampler.setReadResponse(readResponse.isSelected()); + sampler.setUseProxy(useProxy.isSelected()); + sampler.setProxyHost(proxyHost.getText()); + sampler.setProxyPort(proxyPort.getText()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + wsdlMethods.setValues(new String[0]); + domain.setText(""); //$NON-NLS-1$ + protocol.setText(""); //$NON-NLS-1$ + port.setText(""); //$NON-NLS-1$ + path.setText(""); //$NON-NLS-1$ + soapAction.setText(""); //$NON-NLS-1$ + maintainSession.setSelected(WebServiceSampler.MAINTAIN_SESSION_DEFAULT); + soapXml.setText(""); //$NON-NLS-1$ + wsdlField.setText(""); //$NON-NLS-1$ + randomXmlFile.setText(""); //$NON-NLS-1$ + connectTimeout.setText(""); //$NON-NLS-1$ + proxyHost.setText(""); //$NON-NLS-1$ + proxyPort.setText(""); //$NON-NLS-1$ + memCache.setSelected(true); + readResponse.setSelected(false); + useProxy.setSelected(false); + soapXmlFile.setFilename(""); //$NON-NLS-1$ + } + + /** + * init() adds soapAction to the mainPanel. The class reuses logic from + * SOAPSampler, since it is common. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + + mainPanel.add(createTopPanel(), BorderLayout.NORTH); + mainPanel.add(createMessagePanel(), BorderLayout.CENTER); + mainPanel.add(createBottomPanel(), BorderLayout.SOUTH); + this.add(mainPanel); + } + + private final JPanel createTopPanel() { + JPanel topPanel = new JPanel(); + topPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + + JPanel wsdlHelper = new JPanel(); + wsdlHelper.setLayout(new BoxLayout(wsdlHelper, BoxLayout.Y_AXIS)); + wsdlHelper.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("webservice_configuration_wizard"))); // $NON-NLS-1$ + + // Button for browsing webservice wsdl + JPanel wsdlEntry = new JPanel(); + wsdlEntry.setLayout(new BoxLayout(wsdlEntry, BoxLayout.X_AXIS)); + Border margin = new EmptyBorder(0, 5, 0, 5); + wsdlEntry.setBorder(margin); + wsdlHelper.add(wsdlEntry); + wsdlEntry.add(wsdlField); + wsdlEntry.add(wsdlButton); + wsdlButton.addActionListener(this); + + // Web Methods + JPanel listPanel = new JPanel(); + listPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + JLabel selectLabel = new JLabel(JMeterUtils.getResString("webservice_methods")); // $NON-NLS-1$ + wsdlMethods = new JLabeledChoice(); + wsdlHelper.add(listPanel); + listPanel.add(selectLabel); + listPanel.add(wsdlMethods); + listPanel.add(selectButton); + selectButton.addActionListener(this); + + topPanel.add(wsdlHelper); + + JPanel urlPane = new JPanel(); + urlPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + urlPane.add(protocol); + urlPane.add(Box.createRigidArea(new Dimension(5,0))); + urlPane.add(domain); + urlPane.add(Box.createRigidArea(new Dimension(5,0))); + urlPane.add(port); + urlPane.add(Box.createRigidArea(new Dimension(5,0))); + urlPane.add(connectTimeout); + topPanel.add(urlPane); + + topPanel.add(createParametersPanel()); + + return topPanel; + } + + private final JPanel createParametersPanel() { + JPanel paramsPanel = new JPanel(); + paramsPanel.setLayout(new BoxLayout(paramsPanel, BoxLayout.X_AXIS)); + paramsPanel.add(path); + paramsPanel.add(Box.createHorizontalGlue()); + paramsPanel.add(soapAction); + paramsPanel.add(Box.createHorizontalGlue()); + paramsPanel.add(maintainSession); + return paramsPanel; + } + + private final JPanel createMessagePanel() { + JPanel msgPanel = new JPanel(); + msgPanel.setLayout(new BorderLayout(5, 0)); + msgPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("webservice_message_soap"))); // $NON-NLS-1$ + + JPanel soapXmlPane = new JPanel(); + soapXmlPane.setLayout(new BorderLayout(5, 0)); + soapXmlPane.setBorder(BorderFactory.createTitledBorder( + JMeterUtils.getResString("soap_data_title"))); // $NON-NLS-1$ + soapXmlPane.setPreferredSize(new Dimension(4, 4)); // Permit dynamic resize of TextArea + soapXml = new JTextArea(); + soapXml.setLineWrap(true); + soapXml.setWrapStyleWord(true); + soapXml.setTabSize(4); // improve xml display + soapXmlPane.add(new JScrollPane(soapXml), BorderLayout.CENTER); + msgPanel.add(soapXmlPane, BorderLayout.CENTER); + + JPanel southPane = new JPanel(); + southPane.setLayout(new BoxLayout(southPane, BoxLayout.Y_AXIS)); + southPane.add(soapXmlFile); + JPanel randomXmlPane = new JPanel(); + randomXmlPane.setLayout(new BorderLayout(5, 0)); + randomXmlPane.setBorder(BorderFactory.createTitledBorder( + JMeterUtils.getResString("webservice_get_xml_from_random_title"))); // $NON-NLS-1$ + randomXmlPane.add(randomXmlFile, BorderLayout.CENTER); + southPane.add(randomXmlPane); + msgPanel.add(southPane, BorderLayout.SOUTH); + return msgPanel; + } + + private final JPanel createBottomPanel() { + JPanel optionPane = new JPanel(); + optionPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("option"))); // $NON-NLS-1$ + optionPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + JPanel ckboxPane = new HorizontalPanel(); + ckboxPane.add(memCache, BorderLayout.WEST); + ckboxPane.add(readResponse, BorderLayout.CENTER); + readResponse.setToolTipText(readToolTip); + optionPane.add(ckboxPane); + + // add the proxy elements + optionPane.add(getProxyServerPanel()); + return optionPane; + + } + /** + * Create a panel containing the proxy server details + * + * @return the panel + */ + private final JPanel getProxyServerPanel(){ + JPanel proxyServer = new JPanel(); + proxyServer.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + proxyServer.add(useProxy); + useProxy.addActionListener(this); + useProxy.setToolTipText(proxyToolTip); + proxyServer.add(Box.createRigidArea(new Dimension(5,0))); + proxyServer.add(getProxyHostPanel()); + proxyServer.add(Box.createRigidArea(new Dimension(5,0))); + proxyServer.add(getProxyPortPanel()); + return proxyServer; + } + + private JPanel getProxyHostPanel() { + proxyHost = new JTextField(12); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + label.setLabelFor(proxyHost); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyHost, BorderLayout.CENTER); + return panel; + } + + private JPanel getProxyPortPanel() { + proxyPort = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(proxyPort); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyPort, BorderLayout.CENTER); + + return panel; + } + + /** + * the implementation loads the URL and the soap action for the request. + */ + @Override + public void configure(TestElement el) { + super.configure(el); + WebServiceSampler sampler = (WebServiceSampler) el; + wsdlField.setText(sampler.getWsdlURL()); + final String wsdlText = wsdlField.getText(); + if (wsdlText != null && wsdlText.length() > 0) { + fillWsdlMethods(wsdlField.getText(), true, sampler.getSoapAction()); + } + protocol.setText(sampler.getProtocol()); + domain.setText(sampler.getDomain()); + port.setText(sampler.getPropertyAsString(HTTPSamplerBase.PORT)); + path.setText(sampler.getPath()); + soapAction.setText(sampler.getSoapAction()); + maintainSession.setSelected(sampler.getMaintainSession()); + soapXml.setText(sampler.getXmlData()); + soapXml.setCaretPosition(0); // go to 1st line + soapXmlFile.setFilename(sampler.getXmlFile()); + randomXmlFile.setText(sampler.getXmlPathLoc()); + connectTimeout.setText(sampler.getTimeout()); + memCache.setSelected(sampler.getMemoryCache()); + readResponse.setSelected(sampler.getReadResponse()); + useProxy.setSelected(sampler.getUseProxy()); + if (sampler.getProxyHost().length() == 0) { + proxyHost.setEnabled(false); + } else { + proxyHost.setText(sampler.getProxyHost()); + } + if (sampler.getProxyPort() == 0) { + proxyPort.setEnabled(false); + } else { + proxyPort.setText(String.valueOf(sampler.getProxyPort())); + } + } + + /** + * configure the sampler from the WSDL. If the WSDL did not include service + * node, it will use the original URL minus the querystring. That may not be + * correct, so we should probably add a note. For Microsoft webservices it + * will work, since that's how IIS works. + */ + public void configureFromWSDL() { + if (HELPER != null) { + if(HELPER.getBinding() != null) { + this.protocol.setText(HELPER.getProtocol()); + this.domain.setText(HELPER.getBindingHost()); + if (HELPER.getBindingPort() > 0) { + this.port.setText(String.valueOf(HELPER.getBindingPort())); + } else { + this.port.setText("80"); // $NON-NLS-1$ + } + this.path.setText(HELPER.getBindingPath()); + } + this.soapAction.setText(HELPER.getSoapAction(this.wsdlMethods.getText())); + } + } + + /** + * The method uses WSDLHelper to get the information from the WSDL. Since + * the logic for getting the description is isolated to this method, we can + * easily replace it with a different WSDL driver later on. + * + * @param url + * URL to the WSDL + * @param silent + * flag whether errors parsing the WSDL should be shown to the + * user. If true errors will be silently ignored + * @return array of web methods + */ + public String[] browseWSDL(String url, boolean silent) { + try { + // We get the AuthManager and pass it to the WSDLHelper + // once the sampler is updated to Axis, all of this stuff + // should not be necessary. Now I just need to find the + // time and motivation to do it. + WebServiceSampler sampler = (WebServiceSampler) this.createTestElement(); + AuthManager manager = sampler.getAuthManager(); + HELPER = new WSDLHelper(url, manager); + HELPER.parse(); + return HELPER.getWebMethods(); + } catch (Exception exception) { + if (!silent) { + JOptionPane.showConfirmDialog(this, + JMeterUtils.getResString("wsdl_helper_error") // $NON-NLS-1$ + +"\n"+exception, // $NON-NLS-1$ + JMeterUtils.getResString("warning"), // $NON-NLS-1$ + JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE); + } + return ArrayUtils.EMPTY_STRING_ARRAY; + } + } + + /** + * method from ActionListener + * + * @param event + * that occurred + */ + @Override + public void actionPerformed(ActionEvent event) { + final Object eventSource = event.getSource(); + if (eventSource == selectButton) { + this.configureFromWSDL(); + } else if (eventSource == useProxy) { + // if use proxy is checked, we enable + // the text fields for the host and port + boolean use = useProxy.isSelected(); + if (use) { + proxyHost.setEnabled(true); + proxyPort.setEnabled(true); + } else { + proxyHost.setEnabled(false); + proxyPort.setEnabled(false); + } + } else if (eventSource == wsdlButton){ + final String wsdlText = wsdlField.getText(); + if (wsdlText != null && wsdlText.length() > 0) { + fillWsdlMethods(wsdlText, false, null); + } else { + JOptionPane.showConfirmDialog(this, + JMeterUtils.getResString("wsdl_url_error"), // $NON-NLS-1$ + JMeterUtils.getResString("warning"), // $NON-NLS-1$ + JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE); + } + } + } + + /** + * @param wsdlText + * @param silent + * @param soapAction + */ + private void fillWsdlMethods(final String wsdlText, boolean silent, String soapAction) { + String[] wsdlData = browseWSDL(wsdlText, silent); + if (wsdlData != null) { + wsdlMethods.setValues(wsdlData); + if (HELPER != null && soapAction != null) { + String selected = HELPER.getSoapActionName(soapAction); + if (selected != null) { + wsdlMethods.setText(selected); + } + } + wsdlMethods.repaint(); + } + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/gui/AuthPanel.java b/src/protocol/http/org/apache/jmeter/protocol/http/gui/AuthPanel.java new file mode 100644 index 00000000000..48b1889cda3 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/gui/AuthPanel.java @@ -0,0 +1,459 @@ +/* + * 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.protocol.http.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.BorderFactory; +import javax.swing.DefaultCellEditor; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.AuthManager.Mechanism; +import org.apache.jmeter.protocol.http.control.Authorization; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Handles input for determining if authentication services are required for a + * Sampler. It also understands how to get AuthManagers for the files that the + * user selects. + */ +public class AuthPanel extends AbstractConfigGui implements ActionListener { + private static final long serialVersionUID = -378312656300713636L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ADD_COMMAND = "Add"; //$NON-NLS-1$ + + private static final String DELETE_COMMAND = "Delete"; //$NON-NLS-1$ + + private static final String LOAD_COMMAND = "Load"; //$NON-NLS-1$ + + private static final String SAVE_COMMAND = "Save"; //$NON-NLS-1$ + + private InnerTableModel tableModel; + + private JCheckBox clearEachIteration; + + /** + * A table to show the authentication information. + */ + private JTable authTable; + + private JButton addButton; + + private JButton deleteButton; + + private JButton loadButton; + + private JButton saveButton; + + /** + * Default Constructor. + */ + public AuthPanel() { + tableModel = new InnerTableModel(); + init(); + } + + @Override + public TestElement createTestElement() { + AuthManager authMan = tableModel.manager; + configureTestElement(authMan); + authMan.setClearEachIteration(clearEachIteration.isSelected()); + return (TestElement) authMan.clone(); + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement el) { + GuiUtils.stopTableEditing(authTable); + AuthManager authManager = (AuthManager) el; + authManager.clear(); + authManager.addTestElement((TestElement) tableModel.manager.clone()); + authManager.setClearEachIteration(clearEachIteration.isSelected()); + configureTestElement(el); + } + + /** + * Implements JMeterGUIComponent.clear + */ + @Override + public void clearGui() { + super.clearGui(); + + tableModel.clearData(); + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + clearEachIteration.setSelected(false); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + tableModel.manager.clear(); + tableModel.manager.addTestElement((AuthManager) el.clone()); + clearEachIteration.setSelected(((AuthManager) el).getClearEachIteration()); + if (tableModel.getRowCount() != 0) { + deleteButton.setEnabled(true); + saveButton.setEnabled(true); + } + } + + @Override + public String getLabelResource() { + return "auth_manager_title"; //$NON-NLS-1$ + } + + /** + * Shows the main authentication panel for this object. + */ + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + JPanel northPanel = new JPanel(); + northPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + northPanel.add(makeTitlePanel()); + + JPanel optionsPane = new JPanel(); + optionsPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("auth_manager_options"))); // $NON-NLS-1$ + optionsPane.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + clearEachIteration = + new JCheckBox(JMeterUtils.getResString("auth_manager_clear_per_iter"), false); //$NON-NLS-1$ + optionsPane.add(clearEachIteration); + northPanel.add(optionsPane); + add(northPanel, BorderLayout.NORTH); + + + add(createAuthTablePanel(), BorderLayout.CENTER); + } + + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + + if (action.equals(DELETE_COMMAND)) { + if (tableModel.getRowCount() > 0) { + // If a table cell is being edited, we must cancel the editing + // before deleting the row. + if (authTable.isEditing()) { + TableCellEditor cellEditor = authTable.getCellEditor(authTable.getEditingRow(), authTable + .getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = authTable.getSelectedRow(); + + if (rowSelected != -1) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable the DELETE and SAVE buttons if no rows remaining + // after delete. + if (tableModel.getRowCount() == 0) { + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight + // (select) the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + authTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } else if (action.equals(ADD_COMMAND)) { + // If a table cell is being edited, we should accept the current + // value and stop the editing before adding a new row. + GuiUtils.stopTableEditing(authTable); + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable the DELETE and SAVE buttons if they are currently + // disabled. + if (!deleteButton.isEnabled()) { + deleteButton.setEnabled(true); + } + if (!saveButton.isEnabled()) { + saveButton.setEnabled(true); + } + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + authTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } else if (action.equals(LOAD_COMMAND)) { + try { + final String [] _txt={".txt"}; //$NON-NLS-1$ + final JFileChooser dialog = FileDialoger.promptToOpenFile(_txt); + if (dialog != null) { + tableModel.manager.addFile(dialog.getSelectedFile().getAbsolutePath()); + tableModel.fireTableDataChanged(); + + if (tableModel.getRowCount() > 0) { + deleteButton.setEnabled(true); + saveButton.setEnabled(true); + } + } + } catch (IOException ex) { + log.error("", ex); + } + } else if (action.equals(SAVE_COMMAND)) { + try { + final JFileChooser chooser = FileDialoger.promptToSaveFile("auth.txt"); //$NON-NLS-1$ + if (chooser != null) { + tableModel.manager.save(chooser.getSelectedFile().getAbsolutePath()); + } + } catch (IOException ex) { + JMeterUtils.reportErrorToUser(ex.getMessage(), "Error saving auth data"); + } + } + } + + public JPanel createAuthTablePanel() { + // create the JTable that holds auth per row + authTable = new JTable(tableModel); + authTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + authTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + authTable.setPreferredScrollableViewportSize(new Dimension(100, 70)); + + TableColumn passwordColumn = authTable.getColumnModel().getColumn(AuthManager.COL_PASSWORD); + passwordColumn.setCellRenderer(new PasswordCellRenderer()); + + TableColumn mechanismColumn = authTable.getColumnModel().getColumn(AuthManager.COL_MECHANISM); + mechanismColumn.setCellEditor(new MechanismCellEditor()); + + JPanel panel = new JPanel(new BorderLayout(0, 5)); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("auths_stored"))); //$NON-NLS-1$ + panel.add(new JScrollPane(authTable)); + panel.add(createButtonPanel(), BorderLayout.SOUTH); + return panel; + } + + private JButton createButton(String resName, char mnemonic, String command, boolean enabled) { + JButton button = new JButton(JMeterUtils.getResString(resName)); + button.setMnemonic(mnemonic); + button.setActionCommand(command); + button.setEnabled(enabled); + button.addActionListener(this); + return button; + } + + private JPanel createButtonPanel() { + boolean tableEmpty = (tableModel.getRowCount() == 0); + + addButton = createButton("add", 'A', ADD_COMMAND, true); //$NON-NLS-1$ + deleteButton = createButton("delete", 'D', DELETE_COMMAND, !tableEmpty); //$NON-NLS-1$ + loadButton = createButton("load", 'L', LOAD_COMMAND, true); //$NON-NLS-1$ + saveButton = createButton("save", 'S', SAVE_COMMAND, !tableEmpty); //$NON-NLS-1$ + + // Button Panel + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addButton); + buttonPanel.add(deleteButton); + buttonPanel.add(loadButton); + buttonPanel.add(saveButton); + return buttonPanel; + } + + private static class InnerTableModel extends AbstractTableModel { + private static final long serialVersionUID = 4638155137475747946L; + final AuthManager manager; + + public InnerTableModel() { + manager = new AuthManager(); + } + + public void clearData() { + manager.clear(); + fireTableDataChanged(); + } + + public void removeRow(int row) { + manager.remove(row); + } + + public void addNewRow() { + manager.addAuth(); + } + + @Override + public boolean isCellEditable(int row, int column) { + // all table cells are editable + return true; + } + + @Override + public Class getColumnClass(int column) { + return getValueAt(0, column).getClass(); + } + + /** + * Required by table model interface. + */ + @Override + public int getRowCount() { + return manager.getAuthObjects().size(); + } + + /** + * Required by table model interface. + */ + @Override + public int getColumnCount() { + return manager.getColumnCount(); + } + + /** + * Required by table model interface. + */ + @Override + public String getColumnName(int column) { + return manager.getColumnName(column); + } + + /** + * Required by table model interface. + */ + @Override + public Object getValueAt(int row, int column) { + Authorization auth = manager.getAuthObjectAt(row); + + switch (column){ + case AuthManager.COL_URL: + return auth.getURL(); + case AuthManager.COL_USERNAME: + return auth.getUser(); + case AuthManager.COL_PASSWORD: + return auth.getPass(); + case AuthManager.COL_DOMAIN: + return auth.getDomain(); + case AuthManager.COL_REALM: + return auth.getRealm(); + case AuthManager.COL_MECHANISM: + return auth.getMechanism(); + default: + return null; + } + } + + @Override + public void setValueAt(Object value, int row, int column) { + Authorization auth = manager.getAuthObjectAt(row); + log.debug("Setting auth value: " + value); + switch (column){ + case AuthManager.COL_URL: + auth.setURL((String) value); + break; + case AuthManager.COL_USERNAME: + auth.setUser((String) value); + break; + case AuthManager.COL_PASSWORD: + auth.setPass((String) value); + break; + case AuthManager.COL_DOMAIN: + auth.setDomain((String) value); + break; + case AuthManager.COL_REALM: + auth.setRealm((String) value); + break; + case AuthManager.COL_MECHANISM: + auth.setMechanism((Mechanism) value); + break; + default: + break; + } + } + } + + private static class MechanismCellEditor extends DefaultCellEditor { + + private static final long serialVersionUID = 6085773573067229265L; + + public MechanismCellEditor() { + super(new JComboBox(Mechanism.values())); + } + } + + private static class PasswordCellRenderer extends JPasswordField implements TableCellRenderer { + private static final long serialVersionUID = 5169856333827579927L; + private Border myBorder; + + public PasswordCellRenderer() { + super(); + myBorder = new EmptyBorder(1, 2, 1, 2); + setOpaque(true); + setBorder(myBorder); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int column) { + setText((String) value); + + setBackground(isSelected && !hasFocus ? table.getSelectionBackground() : table.getBackground()); + setForeground(isSelected && !hasFocus ? table.getSelectionForeground() : table.getForeground()); + + setFont(table.getFont()); + + return this; + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/gui/CacheManagerGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/gui/CacheManagerGui.java new file mode 100644 index 00000000000..777597dda9a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/gui/CacheManagerGui.java @@ -0,0 +1,139 @@ +/* + * 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.protocol.http.gui; + +import java.awt.BorderLayout; + +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * The GUI for the HTTP Cache Manager + * + */ +public class CacheManagerGui extends AbstractConfigGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox clearEachIteration; + + private JCheckBox useExpires; + + private JTextField maxCacheSize; + + /** + * Create a new LoginConfigGui as a standalone component. + */ + public CacheManagerGui() { + init(); + } + + @Override + public String getLabelResource() { + return "cache_manager_title"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + final CacheManager cacheManager = (CacheManager)element; + clearEachIteration.setSelected(cacheManager.getClearEachIteration()); + useExpires.setSelected(cacheManager.getUseExpires()); + maxCacheSize.setText(Integer.toString(cacheManager.getMaxSize())); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + CacheManager element = new CacheManager(); + modifyTestElement(element); + return element; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement element) { + configureTestElement(element); + final CacheManager cacheManager = (CacheManager)element; + cacheManager.setClearEachIteration(clearEachIteration.isSelected()); + cacheManager.setUseExpires(useExpires.isSelected()); + try { + cacheManager.setMaxSize(Integer.parseInt(maxCacheSize.getText())); + } catch (NumberFormatException e) { + // NOOP + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + clearEachIteration.setSelected(false); + useExpires.setSelected(false); + maxCacheSize.setText(""); //$NON-NLS-1$ + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + clearEachIteration = new JCheckBox(JMeterUtils.getResString("clear_cache_per_iter"), false); // $NON-NLS-1$ + useExpires = new JCheckBox(JMeterUtils.getResString("use_expires"), false); // $NON-NLS-1$ + + JPanel northPanel = new JPanel(); + northPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + northPanel.add(makeTitlePanel()); + northPanel.add(clearEachIteration); + northPanel.add(useExpires); + + JLabel label = new JLabel(JMeterUtils.getResString("cache_manager_size")); //$NON-NLS-1$ + + maxCacheSize = new JTextField(20); + maxCacheSize.setName(CacheManager.MAX_SIZE); + label.setLabelFor(maxCacheSize); + JPanel maxCacheSizePanel = new JPanel(new BorderLayout(5, 0)); + maxCacheSizePanel.add(label, BorderLayout.WEST); + maxCacheSizePanel.add(maxCacheSize, BorderLayout.CENTER); + northPanel.add(maxCacheSizePanel); + add(northPanel, BorderLayout.NORTH); + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/gui/CookiePanel.java b/src/protocol/http/org/apache/jmeter/protocol/http/gui/CookiePanel.java new file mode 100644 index 00000000000..b88e285d17d --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/gui/CookiePanel.java @@ -0,0 +1,427 @@ +/* + * 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.protocol.http.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.protocol.http.control.Cookie; +import org.apache.jmeter.protocol.http.control.CookieHandler; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.HC3CookieHandler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.layout.VerticalLayout; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This is the GUI for Cookie Manager + * + * Allows the user to specify if she needs cookie services, and give parameters + * for this service. + * + */ +public class CookiePanel extends AbstractConfigGui implements ActionListener { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //++ Action command names + private static final String ADD_COMMAND = "Add"; //$NON-NLS-1$ + + private static final String DELETE_COMMAND = "Delete"; //$NON-NLS-1$ + + private static final String LOAD_COMMAND = "Load"; //$NON-NLS-1$ + + private static final String SAVE_COMMAND = "Save"; //$NON-NLS-1$ + + private static final String HANDLER_COMMAND = "Handler"; // $NON-NLS-1$ + //-- + + private JTable cookieTable; + + private PowerTableModel tableModel; + + private JCheckBox clearEachIteration; + + private JComboBox selectHandlerPanel; + + private HashMap handlerMap = new HashMap(); + + private static final String[] COLUMN_RESOURCE_NAMES = { + ("name"), //$NON-NLS-1$ + ("value"), //$NON-NLS-1$ + ("domain"), //$NON-NLS-1$ + ("path"), //$NON-NLS-1$ + ("secure"), //$NON-NLS-1$ + // removed expiration because it's just an annoyance for static cookies + }; + + private static final Class[] columnClasses = { + String.class, + String.class, + String.class, + String.class, + Boolean.class, }; + + private JButton addButton; + + private JButton deleteButton; + + private JButton loadButton; + + private JButton saveButton; + + /** + * List of cookie policies. + * + * These are used both for the display, and for setting the policy + */ + private final String[] policies = new String[] { + "default", + "compatibility", + "rfc2109", + "rfc2965", + "ignorecookies", + "netscape" + }; + + private JLabeledChoice policy; + + /** + * Default constructor. + */ + public CookiePanel() { + init(); + } + + @Override + public String getLabelResource() { + return "cookie_manager_title"; //$NON-NLS-1$ + } + + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + + if (action.equals(DELETE_COMMAND)) { + if (tableModel.getRowCount() > 0) { + // If a table cell is being edited, we must cancel the editing + // before deleting the row. + if (cookieTable.isEditing()) { + TableCellEditor cellEditor = cookieTable.getCellEditor(cookieTable.getEditingRow(), + cookieTable.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = cookieTable.getSelectedRow(); + + if (rowSelected != -1) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable the DELETE and SAVE buttons if no rows remaining + // after delete. + if (tableModel.getRowCount() == 0) { + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight + // (select) the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + cookieTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } else if (action.equals(ADD_COMMAND)) { + // If a table cell is being edited, we should accept the current + // value and stop the editing before adding a new row. + GuiUtils.stopTableEditing(cookieTable); + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable the DELETE and SAVE buttons if they are currently + // disabled. + if (!deleteButton.isEnabled()) { + deleteButton.setEnabled(true); + } + if (!saveButton.isEnabled()) { + saveButton.setEnabled(true); + } + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + cookieTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } else if (action.equals(LOAD_COMMAND)) { + try { + final String [] _txt={".txt"}; //$NON-NLS-1$ + final JFileChooser chooser = FileDialoger.promptToOpenFile(_txt); + if (chooser != null) { + CookieManager manager = new CookieManager(); + manager.addFile(chooser.getSelectedFile().getAbsolutePath()); + for (int i = 0; i < manager.getCookieCount() ; i++){ + addCookieToTable(manager.get(i)); + } + tableModel.fireTableDataChanged(); + + if (tableModel.getRowCount() > 0) { + deleteButton.setEnabled(true); + saveButton.setEnabled(true); + } + } + } catch (IOException ex) { + log.error("", ex); + } + } else if (action.equals(SAVE_COMMAND)) { + try { + final JFileChooser chooser = FileDialoger.promptToSaveFile("cookies.txt"); //$NON-NLS-1$ + if (chooser != null) { + ((CookieManager) createTestElement()).save(chooser.getSelectedFile().getAbsolutePath()); + } + } catch (IOException ex) { + JMeterUtils.reportErrorToUser(ex.getMessage(), "Error saving cookies"); + } + } + } + + private void addCookieToTable(Cookie cookie) { + tableModel.addRow(new Object[] { cookie.getName(), cookie.getValue(), cookie.getDomain(), cookie.getPath(), + Boolean.valueOf(cookie.getSecure()) }); + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement cm) { + GuiUtils.stopTableEditing(cookieTable); + cm.clear(); + configureTestElement(cm); + if (cm instanceof CookieManager) { + CookieManager cookieManager = (CookieManager) cm; + for (int i = 0; i < tableModel.getRowCount(); i++) { + Cookie cookie = createCookie(tableModel.getRowData(i)); + cookieManager.add(cookie); + } + cookieManager.setClearEachIteration(clearEachIteration.isSelected()); + cookieManager.setCookiePolicy(policy.getText()); + cookieManager.setImplementation(handlerMap.get(selectHandlerPanel.getSelectedItem())); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + tableModel.clearData(); + clearEachIteration.setSelected(false); + policy.setText(CookieManager.DEFAULT_POLICY); + selectHandlerPanel.setSelectedItem(CookieManager.DEFAULT_IMPLEMENTATION + .substring(CookieManager.DEFAULT_IMPLEMENTATION.lastIndexOf('.') + 1)); + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + private Cookie createCookie(Object[] rowData) { + Cookie cookie = new Cookie( + (String) rowData[0], + (String) rowData[1], + (String) rowData[2], + (String) rowData[3], + ((Boolean) rowData[4]).booleanValue(), + 0); // Non-expiring + return cookie; + } + + private void populateTable(CookieManager manager) { + tableModel.clearData(); + PropertyIterator iter = manager.getCookies().iterator(); + while (iter.hasNext()) { + addCookieToTable((Cookie) iter.next().getObjectValue()); + } + } + + @Override + public TestElement createTestElement() { + CookieManager cookieManager = new CookieManager(); + modifyTestElement(cookieManager); + return cookieManager; + } + + @Override + public void configure(TestElement el) { + super.configure(el); + + CookieManager cookieManager = (CookieManager) el; + populateTable(cookieManager); + clearEachIteration.setSelected((cookieManager).getClearEachIteration()); + policy.setText(cookieManager.getPolicy()); + String fullImpl = cookieManager.getImplementation(); + selectHandlerPanel.setSelectedItem(fullImpl.substring(fullImpl.lastIndexOf('.') + 1)); + } + + /** + * Shows the main cookie configuration panel. + */ + private void init() { + tableModel = new PowerTableModel(COLUMN_RESOURCE_NAMES, columnClasses); + clearEachIteration = + new JCheckBox(JMeterUtils.getResString("clear_cookies_per_iter"), false); //$NON-NLS-1$ + policy = new JLabeledChoice( + JMeterUtils.getResString("cookie_manager_policy"), //$NON-NLS-1$ + policies); + policy.setText(CookieManager.DEFAULT_POLICY); + setLayout(new BorderLayout()); + setBorder(makeBorder()); + JPanel northPanel = new JPanel(); + northPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + northPanel.add(makeTitlePanel()); + JPanel optionsPane = new JPanel(); + optionsPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("cookie_options"))); // $NON-NLS-1$ + optionsPane.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + optionsPane.add(clearEachIteration); + JPanel policyTypePane = new JPanel(); + policyTypePane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + policyTypePane.add(policy); + policyTypePane.add(GuiUtils.createLabelCombo( + JMeterUtils.getResString("cookie_implementation_choose"), createComboHandler())); // $NON-NLS-1$ + optionsPane.add(policyTypePane); + northPanel.add(optionsPane); + add(northPanel, BorderLayout.NORTH); + add(createCookieTablePanel(), BorderLayout.CENTER); + } + + public JPanel createCookieTablePanel() { + // create the JTable that holds one cookie per row + cookieTable = new JTable(tableModel); + cookieTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + cookieTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + cookieTable.setPreferredScrollableViewportSize(new Dimension(100, 70)); + + JPanel buttonPanel = createButtonPanel(); + + JPanel panel = new JPanel(new BorderLayout(0, 5)); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("cookies_stored"))); //$NON-NLS-1$ + + panel.add(new JScrollPane(cookieTable), BorderLayout.CENTER); + panel.add(buttonPanel, BorderLayout.SOUTH); + return panel; + } + + private JButton createButton(String resName, char mnemonic, String command, boolean enabled) { + JButton button = new JButton(JMeterUtils.getResString(resName)); + button.setMnemonic(mnemonic); + button.setActionCommand(command); + button.setEnabled(enabled); + button.addActionListener(this); + return button; + } + + private JPanel createButtonPanel() { + boolean tableEmpty = (tableModel.getRowCount() == 0); + + addButton = createButton("add", 'A', ADD_COMMAND, true); //$NON-NLS-1$ + deleteButton = createButton("delete", 'D', DELETE_COMMAND, !tableEmpty); //$NON-NLS-1$ + loadButton = createButton("load", 'L', LOAD_COMMAND, true); //$NON-NLS-1$ + saveButton = createButton("save", 'S', SAVE_COMMAND, !tableEmpty); //$NON-NLS-1$ + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addButton); + buttonPanel.add(deleteButton); + buttonPanel.add(loadButton); + buttonPanel.add(saveButton); + return buttonPanel; + } + + /** + * Create the drop-down list to changer render + * @return List of all render (implement ResultsRender) + */ + private JComboBox createComboHandler() { + ComboBoxModel nodesModel = new DefaultComboBoxModel(); + // drop-down list for renderer + selectHandlerPanel = new JComboBox(nodesModel); + selectHandlerPanel.setActionCommand(HANDLER_COMMAND); + selectHandlerPanel.addActionListener(this); + + // if no results render in jmeter.properties, load Standard (default) + List classesToAdd = Collections.emptyList(); + try { + classesToAdd = JMeterUtils.findClassesThatExtend(CookieHandler.class); + } catch (IOException e1) { + // ignored + } + String tmpName = null; + for (String clazz : classesToAdd) { + String shortClazz = clazz.substring(clazz.lastIndexOf('.') + 1); + if (HC3CookieHandler.class.getName().equals(clazz)) { + tmpName = shortClazz; + } + selectHandlerPanel.addItem(shortClazz); + handlerMap.put(shortClazz, clazz); + } + nodesModel.setSelectedItem(tmpName); // preset to default impl + return selectHandlerPanel; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/gui/DNSCachePanel.java b/src/protocol/http/org/apache/jmeter/protocol/http/gui/DNSCachePanel.java new file mode 100644 index 00000000000..ec2433cae82 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/gui/DNSCachePanel.java @@ -0,0 +1,329 @@ +/* + * 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.protocol.http.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.protocol.http.control.DNSCacheManager; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * This gui part of @see + * {@link org.apache.jmeter.protocol.http.control.DNSCacheManager}. Using + * radiobuttons, user can switch between using system DNS resolver and custom + * resolver. Custom resolver functionality is provided by dnsjava library. + * "DNS servers" may contain one or more IP/Name of dns server for resolving + * name DNS servers are chosen via round-robin. If table is empty - system + * resolver is used. + * + * @since 2.12 + */ +public class DNSCachePanel extends AbstractConfigGui implements ActionListener { + + private static final long serialVersionUID = 2120L; + + public static final String OPTIONS = JMeterUtils.getResString("option"); + + private static final String ADD_COMMAND = JMeterUtils.getResString("add"); // $NON-NLS-1$ + + private static final String DELETE_COMMAND = JMeterUtils.getResString("delete"); // $NON-NLS-1$ + + private static final String SYS_RES_COMMAND = JMeterUtils.getResString("use_system_dns_resolver"); // $NON-NLS-1$ + + private static final String CUST_RES_COMMAND = JMeterUtils.getResString("use_custom_dns_resolver"); // $NON-NLS-1$ + + private JTable dnsServersTable; + + private JPanel dnsServersPanel; + + private JPanel dnsServButPanel; + + private PowerTableModel dnsServersTableModel; + + private JRadioButton sysResButton; + + private JRadioButton custResButton; + + private JButton deleteButton; + + private JButton addButton; + + private ButtonGroup providerDNSradioGroup = new ButtonGroup(); + + private static final String[] COLUMN_RESOURCE_NAMES = { + (JMeterUtils.getResString("dns_hostname_or_ip")), //$NON-NLS-1$ + }; + private static final Class[] columnClasses = { + String.class }; + + private JCheckBox clearEachIteration; + + /** + * Default constructor. + */ + public DNSCachePanel() { + init(); + } + + @Override + public String getLabelResource() { + return "dns_cache_manager_title"; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(org.apache.jmeter.testelement.TestElement) + */ + @Override + public void modifyTestElement(TestElement dnsRes) { + GuiUtils.stopTableEditing(dnsServersTable); + dnsRes.clear(); + configureTestElement(dnsRes); + if (dnsRes instanceof DNSCacheManager) { + DNSCacheManager dnsCacheManager = (DNSCacheManager) dnsRes; + for (int i = 0; i < dnsServersTableModel.getRowCount(); i++) { + String server = (String) dnsServersTableModel.getRowData(i)[0]; + dnsCacheManager.addServer(server); + } + dnsCacheManager.setClearEachIteration(clearEachIteration.isSelected()); + if (providerDNSradioGroup.isSelected(custResButton.getModel())) { + dnsCacheManager.setCustomResolver(true); + } else { + dnsCacheManager.setCustomResolver(false); + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + clearEachIteration.setSelected(DNSCacheManager.DEFAULT_CLEAR_CACHE_EACH_ITER); + providerDNSradioGroup.setSelected(sysResButton.getModel(), true); + dnsServersTableModel.clearData(); + deleteButton.setEnabled(false); + + } + + private void populateTable(DNSCacheManager resolver) { + dnsServersTableModel.clearData(); + PropertyIterator iter = resolver.getServers().iterator(); + while (iter.hasNext()) { + addServerToTable((String) iter.next().getObjectValue()); + } + } + + @Override + public TestElement createTestElement() { + DNSCacheManager dnsCacheManager = new DNSCacheManager(); + modifyTestElement(dnsCacheManager); + return dnsCacheManager; + } + + @Override + public void configure(TestElement el) { + super.configure(el); + + DNSCacheManager dnsCacheManager = (DNSCacheManager) el; + populateTable(dnsCacheManager); + clearEachIteration.setSelected(dnsCacheManager.isClearEachIteration()); + if (dnsCacheManager.isCustomResolver()) { + providerDNSradioGroup.setSelected(custResButton.getModel(), true); + deleteButton.setEnabled(dnsServersTable.getColumnCount() > 0); + addButton.setEnabled(true); + } else { + providerDNSradioGroup.setSelected(sysResButton.getModel(), true); + } + } + + private void init() { + dnsServersTableModel = new PowerTableModel(COLUMN_RESOURCE_NAMES, columnClasses); + + clearEachIteration = new JCheckBox(JMeterUtils.getResString("clear_cache_each_iteration"), true); //$NON-NLS-1$ + setLayout(new BorderLayout()); + setBorder(makeBorder()); + JPanel northPanel = new JPanel(); + northPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + northPanel.add(makeTitlePanel()); + JPanel optionsPane = new JPanel(); + optionsPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), OPTIONS)); // $NON-NLS-1$ + optionsPane.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + optionsPane.add(clearEachIteration, BorderLayout.WEST); + optionsPane.add(createChooseResPanel(), BorderLayout.SOUTH); + northPanel.add(optionsPane); + add(northPanel, BorderLayout.NORTH); + + dnsServersPanel = createDnsServersTablePanel(); + add(dnsServersPanel, BorderLayout.CENTER); + + } + + public JPanel createDnsServersTablePanel() { + // create the JTable that holds header per row + dnsServersTable = new JTable(dnsServersTableModel); + dnsServersTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + dnsServersTable.setPreferredScrollableViewportSize(new Dimension(400, 100)); + + JPanel panel = new JPanel(new BorderLayout(0, 5)); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("dns_servers"))); // $NON-NLS-1$ + JScrollPane dnsServScrollPane = new JScrollPane(dnsServersTable); + panel.add(dnsServScrollPane, BorderLayout.CENTER); + dnsServButPanel = createButtonPanel(); + panel.add(dnsServButPanel, BorderLayout.SOUTH); + return panel; + } + + private JPanel createChooseResPanel() { + JPanel chooseResPanel = new JPanel(new BorderLayout(0, 5)); + sysResButton = new JRadioButton(); + sysResButton.setSelected(true); + sysResButton.setText(SYS_RES_COMMAND); + sysResButton.setToolTipText(SYS_RES_COMMAND); + sysResButton.setEnabled(true); + sysResButton.addActionListener(this); + + custResButton = new JRadioButton(); + custResButton.setSelected(false); + custResButton.setText(CUST_RES_COMMAND); + custResButton.setToolTipText(CUST_RES_COMMAND); + custResButton.setEnabled(true); + custResButton.addActionListener(this); + + providerDNSradioGroup.add(sysResButton); + providerDNSradioGroup.add(custResButton); + + chooseResPanel.add(sysResButton, BorderLayout.WEST); + chooseResPanel.add(custResButton, BorderLayout.CENTER); + return chooseResPanel; + } + + private JPanel createButtonPanel() { + boolean tableEmpty = (dnsServersTableModel.getRowCount() == 0); + + addButton = createButton("add", 'A', ADD_COMMAND, custResButton.isSelected()); // $NON-NLS-1$ + deleteButton = createButton("delete", 'D', DELETE_COMMAND, !tableEmpty); // $NON-NLS-1$ + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addButton, BorderLayout.WEST); + buttonPanel.add(deleteButton, BorderLayout.LINE_END); + return buttonPanel; + } + + private JButton createButton(String resName, char mnemonic, String command, boolean enabled) { + JButton button = new JButton(JMeterUtils.getResString(resName)); + button.setMnemonic(mnemonic); + button.setActionCommand(command); + button.setEnabled(enabled); + button.addActionListener(this); + return button; + } + + private void addServerToTable(String dnsServer) { + dnsServersTableModel.addRow(new Object[] { + dnsServer }); + } + + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + dnsServersTable.setEnabled(custResButton.isSelected()); + Color greyColor = new Color(240, 240, 240); + Color blueColor = new Color(184, 207, 229); + dnsServersTable.setBackground(sysResButton.isSelected() ? greyColor : Color.WHITE); + dnsServersTable.setSelectionBackground(sysResButton.isSelected() ? greyColor : blueColor); + addButton.setEnabled(custResButton.isSelected()); + deleteButton.setEnabled(custResButton.isSelected()); + if (custResButton.isSelected() && (dnsServersTableModel.getRowCount() > 0)) { + deleteButton.setEnabled(true); + addButton.setEnabled(true); + } + + if (action.equals(DELETE_COMMAND)) { + if (dnsServersTableModel.getRowCount() > 0) { + // If a table cell is being edited, we must cancel the editing + // before deleting the row. + if (dnsServersTable.isEditing()) { + TableCellEditor cellEditor = dnsServersTable.getCellEditor(dnsServersTable.getEditingRow(), + dnsServersTable.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = dnsServersTable.getSelectedRow(); + + if (rowSelected != -1) { + dnsServersTableModel.removeRow(rowSelected); + dnsServersTableModel.fireTableDataChanged(); + + if (dnsServersTableModel.getRowCount() == 0) { + deleteButton.setEnabled(false); + } + + else { + int rowToSelect = rowSelected; + + if (rowSelected >= dnsServersTableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + dnsServersTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } else if (action.equals(ADD_COMMAND)) { + // If a table cell is being edited, we should accept the current + // value and stop the editing before adding a new row. + GuiUtils.stopTableEditing(dnsServersTable); + + dnsServersTableModel.addNewRow(); + dnsServersTableModel.fireTableDataChanged(); + + if (!deleteButton.isEnabled()) { + deleteButton.setEnabled(true); + } + + // Highlight (select) the appropriate row. + int rowToSelect = dnsServersTableModel.getRowCount() - 1; + dnsServersTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java b/src/protocol/http/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java new file mode 100644 index 00000000000..2ba3a07ba9a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java @@ -0,0 +1,138 @@ +/* + * 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.protocol.http.gui; + +import java.util.Iterator; + +import javax.swing.JTable; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +/** + * A GUI panel allowing the user to enter HTTP Parameters. + * These have names and values, as well as check-boxes to determine whether or not to + * include the "=" sign in the output and whether or not to encode the output. + */ +public class HTTPArgumentsPanel extends ArgumentsPanel { + + private static final long serialVersionUID = 240L; + + private static final String ENCODE_OR_NOT = "encode?"; //$NON-NLS-1$ + + private static final String INCLUDE_EQUALS = "include_equals"; //$NON-NLS-1$ + + @Override + protected void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { + ArgumentsPanel.COLUMN_RESOURCE_NAMES_0, ArgumentsPanel.COLUMN_RESOURCE_NAMES_1, ENCODE_OR_NOT, INCLUDE_EQUALS }, + HTTPArgument.class, + new Functor[] { + new Functor("getName"), //$NON-NLS-1$ + new Functor("getValue"), //$NON-NLS-1$ + new Functor("isAlwaysEncoded"), //$NON-NLS-1$ + new Functor("isUseEquals") }, //$NON-NLS-1$ + new Functor[] { + new Functor("setName"), //$NON-NLS-1$ + new Functor("setValue"), //$NON-NLS-1$ + new Functor("setAlwaysEncoded"), //$NON-NLS-1$ + new Functor("setUseEquals") }, //$NON-NLS-1$ + new Class[] {String.class, String.class, Boolean.class, Boolean.class }); + } + + public static boolean testFunctors(){ + HTTPArgumentsPanel instance = new HTTPArgumentsPanel(); + instance.initializeTableModel(); + return instance.tableModel.checkFunctors(null,instance.getClass()); + } + + @Override + protected void sizeColumns(JTable table) { + GuiUtils.fixSize(table.getColumn(INCLUDE_EQUALS), table); + GuiUtils.fixSize(table.getColumn(ENCODE_OR_NOT), table); + } + + @Override + protected HTTPArgument makeNewArgument() { + HTTPArgument arg = new HTTPArgument("", ""); + arg.setAlwaysEncoded(false); + arg.setUseEquals(true); + return arg; + } + + public HTTPArgumentsPanel() { + super(JMeterUtils.getResString("paramtable")); //$NON-NLS-1$ + } + + @Override + public TestElement createTestElement() { + Arguments args = getUnclonedParameters(); + this.configureTestElement(args); + return (TestElement) args.clone(); + } + + /** + * Convert the argument panel contents to an {@link Arguments} collection. + * + * @return a collection of {@link HTTPArgument} entries + */ + public Arguments getParameters() { + Arguments args = getUnclonedParameters(); + return (Arguments) args.clone(); + } + + private Arguments getUnclonedParameters() { + stopTableEditing(); + @SuppressWarnings("unchecked") // only contains Argument (or HTTPArgument) + Iterator modelData = (Iterator) tableModel.iterator(); + Arguments args = new Arguments(); + while (modelData.hasNext()) { + HTTPArgument arg = modelData.next(); + args.addArgument(arg); + } + return args; + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof Arguments) { + tableModel.clearData(); + HTTPArgument.convertArgumentsToHTTP((Arguments) el); + PropertyIterator iter = ((Arguments) el).getArguments().iterator(); + while (iter.hasNext()) { + HTTPArgument arg = (HTTPArgument) iter.next().getObjectValue(); + tableModel.addRow(arg); + } + } + checkDeleteStatus(); + } + + protected boolean isMetaDataNormal(HTTPArgument arg) { + return arg.getMetaData() == null || arg.getMetaData().equals("=") + || (arg.getValue() != null && arg.getValue().length() > 0); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/gui/HTTPFileArgsPanel.java b/src/protocol/http/org/apache/jmeter/protocol/http/gui/HTTPFileArgsPanel.java new file mode 100644 index 00000000000..812e552f7f4 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/gui/HTTPFileArgsPanel.java @@ -0,0 +1,404 @@ +/* + * 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.protocol.http.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.Iterator; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +/* + * Note: this class is currently only suitable for use with HTTSamplerBase. + * If it is required for other classes, then the appropriate configure() and modifyTestElement() + * method code needs to be written. + */ +/** + * A GUI panel allowing the user to enter file information for http upload. + * Used by MultipartUrlConfigGui for use in HTTP Samplers. + */ +public class HTTPFileArgsPanel extends JPanel implements ActionListener { + + private static final long serialVersionUID = 240L; + + /** The title label for this component. */ + private JLabel tableLabel; + + /** The table containing the list of files. */ + private transient JTable table; + + /** The model for the files table. */ + private transient ObjectTableModel tableModel; // only contains HTTPFileArg elements + + /** A button for adding new files to the table. */ + private JButton add; + + /** A button for browsing file system to set path of selected row in table. */ + private JButton browse; + + /** A button for removing files from the table. */ + private JButton delete; + + /** Command for adding a row to the table. */ + private static final String ADD = "add"; // $NON-NLS-1$ + + /** Command for browsing filesystem to set path of selected row in table. */ + private static final String BROWSE = "browse"; // $NON-NLS-1$ + + /** Command for removing a row from the table. */ + private static final String DELETE = "delete"; // $NON-NLS-1$ + + private static final String FILEPATH = "send_file_filename_label"; // $NON-NLS-1$ + + /** The parameter name column title of file table. */ + private static final String PARAMNAME = "send_file_param_name_label"; //$NON-NLS-1$ + + /** The mime type column title of file table. */ + private static final String MIMETYPE = "send_file_mime_label"; //$NON-NLS-1$ + + public HTTPFileArgsPanel() { + this(""); // required for unit tests + } + + /** + * Create a new HTTPFileArgsPanel as an embedded component, using the + * specified title. + * + * @param label + * the title for the component. + */ + public HTTPFileArgsPanel(String label) { + tableLabel = new JLabel(label); + init(); + } + + /** + * Initialize the table model used for the http files table. + */ + private void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { + FILEPATH, PARAMNAME, MIMETYPE}, + HTTPFileArg.class, + new Functor[] { + new Functor("getPath"), //$NON-NLS-1$ + new Functor("getParamName"), //$NON-NLS-1$ + new Functor("getMimeType")}, //$NON-NLS-1$ + new Functor[] { + new Functor("setPath"), //$NON-NLS-1$ + new Functor("setParamName"), //$NON-NLS-1$ + new Functor("setMimeType")}, //$NON-NLS-1$ + new Class[] {String.class, String.class, String.class}); + } + + public static boolean testFunctors(){ + HTTPFileArgsPanel instance = new HTTPFileArgsPanel(""); //$NON-NLS-1$ + instance.initializeTableModel(); + return instance.tableModel.checkFunctors(null,instance.getClass()); + } + + /** + * Resize the table columns to appropriate widths. + * + * @param table + * the table to resize columns for + */ + private void sizeColumns(JTable table) { + GuiUtils.fixSize(table.getColumn(PARAMNAME), table); + GuiUtils.fixSize(table.getColumn(MIMETYPE), table); + } + + /** + * Save the GUI data in the HTTPSamplerBase element. + * + * @param testElement {@link TestElement} to modify + */ + public void modifyTestElement(TestElement testElement) { + GuiUtils.stopTableEditing(table); + if (testElement instanceof HTTPSamplerBase) { + HTTPSamplerBase base = (HTTPSamplerBase) testElement; + int rows = tableModel.getRowCount(); + @SuppressWarnings("unchecked") // we only put HTTPFileArgs in it + Iterator modelData = (Iterator) tableModel.iterator(); + HTTPFileArg[] files = new HTTPFileArg[rows]; + int row=0; + while (modelData.hasNext()) { + HTTPFileArg file = modelData.next(); + files[row++]=file; + } + base.setHTTPFiles(files); + } + } + + /** + * A newly created component can be initialized with the contents of a + * HTTPSamplerBase object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param testElement the HTTPSamplerBase to be used to configure the GUI + */ + public void configure(TestElement testElement) { + if (testElement instanceof HTTPSamplerBase) { + HTTPSamplerBase base = (HTTPSamplerBase) testElement; + tableModel.clearData(); + for(HTTPFileArg file : base.getHTTPFiles()){ + tableModel.addRow(file); + } + checkDeleteAndBrowseStatus(); + } + } + + + /** + * Enable or disable the delete button depending on whether or not there is + * a row to be deleted. + */ + private void checkDeleteAndBrowseStatus() { + // Disable DELETE and BROWSE buttons if there are no rows in + // the table to delete. + if (tableModel.getRowCount() == 0) { + browse.setEnabled(false); + delete.setEnabled(false); + } else { + browse.setEnabled(true); + delete.setEnabled(true); + } + } + + /** + * Clear all rows from the table. + */ + public void clear() { + GuiUtils.stopTableEditing(table); + tableModel.clearData(); + } + + /** + * Invoked when an action occurs. This implementation supports the add and + * delete buttons. + * + * @param e + * the event that has occurred + */ + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(ADD)) { + addFile(""); //$NON-NLS-1$ + } + runCommandOnSelectedFile(action); + } + + /** + * runs specified command on currently selected file. + * + * @param command specifies which process will be done on selected + * file. it's coming from action command currently catched by + * action listener. + * + * @see runCommandOnRow + */ + private void runCommandOnSelectedFile(String command) { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + int rowSelected = table.getSelectedRow(); + if (rowSelected >= 0) { + runCommandOnRow(command, rowSelected); + tableModel.fireTableDataChanged(); + // Disable DELETE and BROWSE if there are no rows in the table to delete. + checkDeleteAndBrowseStatus(); + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + if (tableModel.getRowCount() != 0) { + int rowToSelect = rowSelected; + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + + /** + * runs specified command on currently selected table row. + * + * @param command specifies which process will be done on selected + * file. it's coming from action command currently catched by + * action listener. + * + * @param rowSelected index of selected row. + */ + private void runCommandOnRow(String command, int rowSelected) { + if (DELETE.equals(command)) { + tableModel.removeRow(rowSelected); + } else if (BROWSE.equals(command)) { + String path = browseAndGetFilePath(); + tableModel.setValueAt(path, rowSelected, 0); + } + } + + /** + * Add a new file row to the table. + */ + private void addFile(String path) { + // If a table cell is being edited, we should accept the current value + // and stop the editing before adding a new row. + GuiUtils.stopTableEditing(table); + + tableModel.addRow(new HTTPFileArg(path)); + + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + browse.setEnabled(true); + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + + /** + * opens a dialog box to choose a file and returns selected file's + * path. + * + * @return a new File object + */ + private String browseAndGetFilePath() { + String path = ""; //$NON-NLS-1$ + JFileChooser chooser = FileDialoger.promptToOpenFile(); + if (chooser != null) { + File file = chooser.getSelectedFile(); + if (file != null) { + path = file.getPath(); + } + } + return path; + } + + /** + * Stop any editing that is currently being done on the table. This will + * save any changes that have already been made. + */ + protected void stopTableEditing() { + GuiUtils.stopTableEditing(table); + } + + /** + * Create the main GUI panel which contains the file table. + * + * @return the main GUI panel + */ + private Component makeMainPanel() { + initializeTableModel(); + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + return makeScrollPane(table); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + private Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + labelPanel.add(tableLabel); + return labelPanel; + } + + /** + * Create a panel containing the add and delete buttons. + * + * @return a GUI panel containing the buttons + */ + private JPanel makeButtonPanel() { + add = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + add.setActionCommand(ADD); + add.setEnabled(true); + + browse = new JButton(JMeterUtils.getResString("browse")); // $NON-NLS-1$ + browse.setActionCommand(BROWSE); + + delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + delete.setActionCommand(DELETE); + + checkDeleteAndBrowseStatus(); + + JPanel buttonPanel = new JPanel(); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + add.addActionListener(this); + browse.addActionListener(this); + delete.addActionListener(this); + buttonPanel.add(add); + buttonPanel.add(browse); + buttonPanel.add(delete); + return buttonPanel; + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + JPanel p = this; + + p.setLayout(new BorderLayout()); + + p.add(makeLabelPanel(), BorderLayout.NORTH); + p.add(makeMainPanel(), BorderLayout.CENTER); + // Force a minimum table height of 70 pixels + p.add(Box.createVerticalStrut(70), BorderLayout.WEST); + p.add(makeButtonPanel(), BorderLayout.SOUTH); + + table.revalidate(); + sizeColumns(table); + } + + private JScrollPane makeScrollPane(Component comp) { + JScrollPane pane = new JScrollPane(comp); + pane.setPreferredSize(pane.getMinimumSize()); + return pane; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/gui/HeaderPanel.java b/src/protocol/http/org/apache/jmeter/protocol/http/gui/HeaderPanel.java new file mode 100644 index 00000000000..4980441a060 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/gui/HeaderPanel.java @@ -0,0 +1,400 @@ +/* + * 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.protocol.http.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Allows the user to specify if she needs HTTP header services, and give + * parameters for this service. + * + */ +public class HeaderPanel extends AbstractConfigGui implements ActionListener +{ + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ADD_COMMAND = "Add"; // $NON-NLS-1$ + + private static final String DELETE_COMMAND = "Delete"; // $NON-NLS-1$ + + private static final String LOAD_COMMAND = "Load"; // $NON-NLS-1$ + + private static final String SAVE_COMMAND = "Save"; // $NON-NLS-1$ + + /** Command for adding rows from the clipboard */ + private static final String ADD_FROM_CLIPBOARD = "addFromClipboard"; // $NON-NLS-1$ + + private final InnerTableModel tableModel; + + private final HeaderManager headerManager; + + private JTable headerTable; + + private JButton deleteButton; + + private JButton saveButton; + + public HeaderPanel() { + headerManager = new HeaderManager(); + tableModel = new InnerTableModel(headerManager); + init(); + } + + @Override + public TestElement createTestElement() { + configureTestElement(headerManager); + return (TestElement) headerManager.clone(); + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement el) { + GuiUtils.stopTableEditing(headerTable); + el.clear(); + el.addTestElement(headerManager); + configureTestElement(el); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + tableModel.clearData(); + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + @Override + public void configure(TestElement el) { + headerManager.clear(); + super.configure(el); + headerManager.addTestElement(el); + boolean hasRows = tableModel.getRowCount() > 0; + deleteButton.setEnabled(hasRows); + saveButton.setEnabled(hasRows); + + } + + @Override + public String getLabelResource() { + return "header_manager_title"; // $NON-NLS-1$ + } + + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + add(createHeaderTablePanel(), BorderLayout.CENTER); + } + + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + + if (action.equals(DELETE_COMMAND)) { + if (tableModel.getRowCount() > 0) { + // If a table cell is being edited, we must cancel the editing + // before deleting the row. + if (headerTable.isEditing()) { + TableCellEditor cellEditor = headerTable.getCellEditor(headerTable.getEditingRow(), + headerTable.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = headerTable.getSelectedRow(); + + if (rowSelected != -1) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable the DELETE and SAVE buttons if no rows remaining + // after delete + if (tableModel.getRowCount() == 0) { + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight + // (select) the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + headerTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } else if (action.equals(ADD_COMMAND)) { + // If a table cell is being edited, we should accept the current + // value and stop the editing before adding a new row. + GuiUtils.stopTableEditing(headerTable); + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable the DELETE and SAVE buttons if they are currently + // disabled. + if (!deleteButton.isEnabled()) { + deleteButton.setEnabled(true); + } + if (!saveButton.isEnabled()) { + saveButton.setEnabled(true); + } + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + headerTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } else if (action.equals(LOAD_COMMAND)) { + try { + final JFileChooser chooser = FileDialoger.promptToOpenFile(); + if (chooser != null) { + headerManager.addFile(chooser.getSelectedFile().getAbsolutePath()); + tableModel.fireTableDataChanged(); + + if (tableModel.getRowCount() > 0) { + deleteButton.setEnabled(true); + saveButton.setEnabled(true); + } + } + } catch (IOException ex) { + log.error("Could not load headers", ex); + } + } else if (action.equals(SAVE_COMMAND)) { + try { + final JFileChooser chooser = FileDialoger.promptToSaveFile(null); + if (chooser != null) { + headerManager.save(chooser.getSelectedFile().getAbsolutePath()); + } + } catch (IOException ex) { + JMeterUtils.reportErrorToUser(ex.getMessage(), "Error saving headers"); + } + } else if (action.equals(ADD_FROM_CLIPBOARD)) { + addFromClipboard(); + } + } + + /** + * Add values from the clipboard. + * The clipboard is first split into lines, and the lines are then split on ':' + * to produce the header name and value. + * Lines without a ':' are ignored. + */ + protected void addFromClipboard() { + GuiUtils.stopTableEditing(this.headerTable); + int rowCount = headerTable.getRowCount(); + try { + String clipboardContent = GuiUtils.getPastedText(); + if(clipboardContent == null) { + return; + } + String[] clipboardLines = clipboardContent.split("\n"); // $NON-NLS-1$ + for (String clipboardLine : clipboardLines) { + int index = clipboardLine.indexOf(":"); // $NON-NLS-1$ + if (index > 0) { + Header header = new Header(clipboardLine.substring(0, index), clipboardLine.substring(index+1)); + headerManager.add(header); + } + } + tableModel.fireTableDataChanged(); + if (headerTable.getRowCount() > rowCount) { + deleteButton.setEnabled(true); + saveButton.setEnabled(true); + + // Highlight (select) the appropriate rows. + int rowToSelect = tableModel.getRowCount() - 1; + headerTable.setRowSelectionInterval(rowCount, rowToSelect); + } + } catch (IOException ioe) { + JOptionPane.showMessageDialog(this, + "Could not add read headers from clipboard:\n" + ioe.getLocalizedMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } catch (UnsupportedFlavorException ufe) { + JOptionPane.showMessageDialog(this, + "Could not add retrieved " + DataFlavor.stringFlavor.getHumanPresentableName() + + " from clipboard" + ufe.getLocalizedMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + + public JPanel createHeaderTablePanel() { + // create the JTable that holds header per row + headerTable = new JTable(tableModel); + headerTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + headerTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + headerTable.setPreferredScrollableViewportSize(new Dimension(100, 70)); + + JPanel panel = new JPanel(new BorderLayout(0, 5)); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("headers_stored"))); // $NON-NLS-1$ + panel.add(new JScrollPane(headerTable), BorderLayout.CENTER); + panel.add(createButtonPanel(), BorderLayout.SOUTH); + return panel; + } + + private JButton createButton(String resName, char mnemonic, String command, boolean enabled) { + JButton button = new JButton(JMeterUtils.getResString(resName)); + button.setMnemonic(mnemonic); + button.setActionCommand(command); + button.setEnabled(enabled); + button.addActionListener(this); + return button; + } + + private JPanel createButtonPanel() { + boolean tableEmpty = (tableModel.getRowCount() == 0); + + JButton addButton = createButton("add", 'A', ADD_COMMAND, true); // $NON-NLS-1$ + deleteButton = createButton("delete", 'D', DELETE_COMMAND, !tableEmpty); // $NON-NLS-1$ + JButton loadButton = createButton("load", 'L', LOAD_COMMAND, true); // $NON-NLS-1$ + saveButton = createButton("save", 'S', SAVE_COMMAND, !tableEmpty); // $NON-NLS-1$ + JButton addFromClipboard = createButton("add_from_clipboard", 'C', ADD_FROM_CLIPBOARD, true); // $NON-NLS-1$ + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addButton); + buttonPanel.add(addFromClipboard); + buttonPanel.add(deleteButton); + buttonPanel.add(loadButton); + buttonPanel.add(saveButton); + return buttonPanel; + } + + private static class InnerTableModel extends AbstractTableModel { + private static final long serialVersionUID = 240L; + + private HeaderManager manager; + + public InnerTableModel(HeaderManager man) { + manager = man; + } + + public void clearData() { + manager.clear(); + fireTableDataChanged(); + } + + public void removeRow(int row) { + manager.remove(row); + } + + public void addNewRow() { + manager.add(); + } + + @Override + public boolean isCellEditable(int row, int column) { + // all table cells are editable + return true; + } + + @Override + public Class getColumnClass(int column) { + return getValueAt(0, column).getClass(); + } + + @Override + public int getRowCount() { + return manager.getHeaders().size(); + } + + /** + * Required by table model interface. + */ + @Override + public int getColumnCount() { + return manager.getColumnCount(); + } + + /** + * Required by table model interface. + */ + @Override + public String getColumnName(int column) { + return manager.getColumnName(column); + } + + /** + * Required by table model interface. + */ + @Override + public Object getValueAt(int row, int column) { + Header head = manager.getHeader(row); + if (column == 0) { + return head.getName(); + } else if (column == 1) { + return head.getValue(); + } + return null; + } + + /** + * Required by table model interface. + */ + @Override + public void setValueAt(Object value, int row, int column) { + Header header = manager.getHeader(row); + + if (column == 0) { + header.setName((String) value); + } else if (column == 1) { + header.setValue((String) value); + } + } + + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/AnchorModifier.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/AnchorModifier.java new file mode 100644 index 00000000000..8cabbfcc312 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/AnchorModifier.java @@ -0,0 +1,237 @@ +/* + * 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.protocol.http.modifier; + +import java.io.Serializable; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.protocol.http.parser.HtmlParsingUtils; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +// For Unit tests, @see TestAnchorModifier + +public class AnchorModifier extends AbstractTestElement implements PreProcessor, Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Random rand = new Random(); + + public AnchorModifier() { + } + + /** + * Modifies an Entry object based on HTML response text. + */ + @Override + public void process() { + JMeterContext context = getThreadContext(); + Sampler sam = context.getCurrentSampler(); + SampleResult res = context.getPreviousResult(); + HTTPSamplerBase sampler = null; + HTTPSampleResult result = null; + if (res == null || !(sam instanceof HTTPSamplerBase) || !(res instanceof HTTPSampleResult)) { + log.info("Can't apply HTML Link Parser when the previous" + " sampler run is not an HTTP Request."); + return; + } else { + sampler = (HTTPSamplerBase) sam; + result = (HTTPSampleResult) res; + } + List potentialLinks = new ArrayList(); + String responseText = ""; // $NON-NLS-1$ + responseText = result.getResponseDataAsString(); + Document html; + int index = responseText.indexOf('<'); // $NON-NLS-1$ + if (index == -1) { + index = 0; + } + if (log.isDebugEnabled()) { + log.debug("Check for matches against: "+sampler.toString()); + } + html = (Document) HtmlParsingUtils.getDOM(responseText.substring(index)); + addAnchorUrls(html, result, sampler, potentialLinks); + addFormUrls(html, result, sampler, potentialLinks); + addFramesetUrls(html, result, sampler, potentialLinks); + if (potentialLinks.size() > 0) { + HTTPSamplerBase url = potentialLinks.get(rand.nextInt(potentialLinks.size())); + if (log.isDebugEnabled()) { + log.debug("Selected: "+url.toString()); + } + sampler.setDomain(url.getDomain()); + sampler.setPath(url.getPath()); + if (url.getMethod().equals(HTTPConstants.POST)) { + PropertyIterator iter = sampler.getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + modifyArgument(arg, url.getArguments()); + } + } else { + sampler.setArguments(url.getArguments()); + // config.parseArguments(url.getQueryString()); + } + sampler.setProtocol(url.getProtocol()); + return; + } else { + log.debug("No matches found"); + } + return; + } + + private void modifyArgument(Argument arg, Arguments args) { + if (log.isDebugEnabled()) { + log.debug("Modifying argument: " + arg); + } + List possibleReplacements = new ArrayList(); + PropertyIterator iter = args.iterator(); + Argument replacementArg; + while (iter.hasNext()) { + replacementArg = (Argument) iter.next().getObjectValue(); + try { + if (HtmlParsingUtils.isArgumentMatched(replacementArg, arg)) { + possibleReplacements.add(replacementArg); + } + } catch (Exception ex) { + log.error("Problem adding Argument", ex); + } + } + + if (possibleReplacements.size() > 0) { + replacementArg = possibleReplacements.get(rand.nextInt(possibleReplacements.size())); + arg.setName(replacementArg.getName()); + arg.setValue(replacementArg.getValue()); + if (log.isDebugEnabled()) { + log.debug("Just set argument to values: " + arg.getName() + " = " + arg.getValue()); + } + args.removeArgument(replacementArg); + } + } + + public void addConfigElement(ConfigElement config) { + } + + private void addFormUrls(Document html, HTTPSampleResult result, HTTPSamplerBase config, + List potentialLinks) { + NodeList rootList = html.getChildNodes(); + List urls = new LinkedList(); + for (int x = 0; x < rootList.getLength(); x++) { + urls.addAll(HtmlParsingUtils.createURLFromForm(rootList.item(x), result.getURL())); + } + for (HTTPSamplerBase newUrl : urls) { + newUrl.setMethod(HTTPConstants.POST); + if (log.isDebugEnabled()) { + log.debug("Potential Form match: " + newUrl.toString()); + } + if (HtmlParsingUtils.isAnchorMatched(newUrl, config)) { + log.debug("Matched!"); + potentialLinks.add(newUrl); + } + } + } + + private void addAnchorUrls(Document html, HTTPSampleResult result, HTTPSamplerBase config, + List potentialLinks) { + String base = ""; + NodeList baseList = html.getElementsByTagName("base"); // $NON-NLS-1$ + if (baseList.getLength() > 0) { + base = baseList.item(0).getAttributes().getNamedItem("href").getNodeValue(); // $NON-NLS-1$ + } + NodeList nodeList = html.getElementsByTagName("a"); // $NON-NLS-1$ + for (int i = 0; i < nodeList.getLength(); i++) { + Node tempNode = nodeList.item(i); + NamedNodeMap nnm = tempNode.getAttributes(); + Node namedItem = nnm.getNamedItem("href"); // $NON-NLS-1$ + if (namedItem == null) { + continue; + } + String hrefStr = namedItem.getNodeValue(); + if (hrefStr.startsWith("javascript:")) { // $NON-NLS-1$ + continue; // No point trying these + } + try { + HTTPSamplerBase newUrl = HtmlParsingUtils.createUrlFromAnchor(hrefStr, ConversionUtils.makeRelativeURL(result.getURL(), base)); + newUrl.setMethod(HTTPConstants.GET); + if (log.isDebugEnabled()) { + log.debug("Potential match: " + newUrl); + } + if (HtmlParsingUtils.isAnchorMatched(newUrl, config)) { + log.debug("Matched!"); + potentialLinks.add(newUrl); + } + } catch (MalformedURLException e) { + log.warn("Bad URL "+e); + } + } + } + + private void addFramesetUrls(Document html, HTTPSampleResult result, + HTTPSamplerBase config, List potentialLinks) { + String base = ""; + NodeList baseList = html.getElementsByTagName("base"); // $NON-NLS-1$ + if (baseList.getLength() > 0) { + base = baseList.item(0).getAttributes().getNamedItem("href") // $NON-NLS-1$ + .getNodeValue(); + } + NodeList nodeList = html.getElementsByTagName("frame"); // $NON-NLS-1$ + for (int i = 0; i < nodeList.getLength(); i++) { + Node tempNode = nodeList.item(i); + NamedNodeMap nnm = tempNode.getAttributes(); + Node namedItem = nnm.getNamedItem("src"); // $NON-NLS-1$ + if (namedItem == null) { + continue; + } + String hrefStr = namedItem.getNodeValue(); + try { + HTTPSamplerBase newUrl = HtmlParsingUtils.createUrlFromAnchor( + hrefStr, ConversionUtils.makeRelativeURL(result.getURL(), base)); + newUrl.setMethod(HTTPConstants.GET); + if (log.isDebugEnabled()) { + log.debug("Potential match: " + newUrl); + } + if (HtmlParsingUtils.isAnchorMatched(newUrl, config)) { + log.debug("Matched!"); + potentialLinks.add(newUrl); + } + } catch (MalformedURLException e) { + log.warn("Bad URL "+e); + } + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/ParamMask.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/ParamMask.java new file mode 100644 index 00000000000..89d3dd713c1 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/ParamMask.java @@ -0,0 +1,248 @@ +/* + * 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.protocol.http.modifier; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.LongProperty; + +/** + * This object defines with what a parameter has its value replaced, and the + * policies for how that value changes. Used in {@link ParamModifier}. + * + * @version $Revision$ + */ +public class ParamMask extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + private String PREFIX = "ParamModifier.prefix"; + + private String FIELD_NAME = "ParamModifier.field_name"; + + private String UPPER_BOUND = "ParamModifier.upper_bound"; + + private String LOWER_BOUND = "ParamModifier.lower_bound"; + + private String INCREMENT = "ParamModifier.increment"; + + private String SUFFIX = "ParamModifier.suffix"; + + private long _value = 0; + + /** + * Default constructor. + */ + public ParamMask() { + setFieldName(""); + setPrefix(""); + setLowerBound(0); + setUpperBound(0); + setIncrement(0); + setSuffix(""); + } + + /** + * Sets the prefix for the long value. The prefix, the value + * and the suffix are concatenated to give the parameter value. This allows + * a wider range of posibilities for the parameter values. + * + * @param prefix + * a string to prefix to the parameter value + */ + public void setPrefix(String prefix) { + setProperty(PREFIX, prefix); + } + + /** + * Set the current value of the long portion of the parameter + * value to replace. This is usually not used, as the method + * {@link #resetValue} is used to define a policy for the starting value. + * + * @param val the new parameter value + */ + public void setValue(long val) { + _value = val; + } + + public void setFieldName(String fieldName) { + setProperty(FIELD_NAME, fieldName); + } + + /** + * Sets the lowest possible value that the long portion of + * the parameter value may be. + * + * @param val + * the new lowest possible parameter value + */ + public void setLowerBound(long val) { + setProperty(new LongProperty(LOWER_BOUND, val)); + } + + /** + * Sets the highest possible value that the long portion of + * the parameter value may be. + * + * @param val + * the new highest possible parameter value + */ + public void setUpperBound(long val) { + setProperty(new LongProperty(UPPER_BOUND, val)); + } + + /** + * Sets the number by which the parameter value is incremented between + * loops. + * + * @param incr + * the new increment for the parameter value + */ + public void setIncrement(long incr) { + setProperty(new LongProperty(INCREMENT, incr)); + } + + /** + * Sets the suffix for the long value. The prefix, the value + * and the suffix are concatenated to give the parameter value. This allows + * a wider range of posibilities for the parameter values. + * + * @param suffix + * a string to suffix to the parameter value + */ + public void setSuffix(String suffix) { + setProperty(SUFFIX, suffix); + } + + /** + * Accessor method to return the String that will be prefixed + * to the long value. + * + * @return the parameter value prefix + */ + public String getPrefix() { + return getPropertyAsString(PREFIX); + } + + /** + * Accessor method, returns the lowest possible value that the + * long portion of the parameter value may be. + * + * @return the lower bound of the possible values + */ + public long getLowerBound() { + return getPropertyAsLong(LOWER_BOUND); + } + + /** + * Accessor method, returns the highest possible value that the + * long portion of the parameter value may be. + * + * @return the higher bound of the possible values + */ + public long getUpperBound() { + return getPropertyAsLong(UPPER_BOUND); + } + + /** + * Accessor method, returns the number by which the parameter value is + * incremented between loops. + * + * @return the increment + */ + public long getIncrement() { + return getPropertyAsLong(INCREMENT); + } + + /** + * Accessor method to return the String that will be suffixed + * to the long value. + * + * @return the parameter value suffix + */ + public String getSuffix() { + return getPropertyAsString(SUFFIX); + } + + /* + * ----------------------------------------------------------------------- + * Methods + * ----------------------------------------------------------------------- + */ + + /** + * Returns the current value, prefixed and suffixed, as a string, then + * increments it. If the incremented value is above the upper bound, the + * value is reset to the lower bound.
+ *

+ * This method determines the policy of what happens when an upper bound is + * reached/surpassed. + * + * @return a String representing the current + * long value + */ + public String getNextValue() { + // return the current value (don't forget the prefix!) + String retval = getPrefix() + Long.toString(_value) + getSuffix(); + + // increment the value + _value += getIncrement(); + if (_value > getUpperBound()) { + _value = getLowerBound(); + } + + return retval; + } + + /** + * This method determines the policy of what value to start (and re-start) + * at. + */ + public void resetValue() { + _value = getLowerBound(); + } + + public String getFieldName() { + return getPropertyAsString(FIELD_NAME); + } + + /** + * For debugging purposes. + * + * @return a String representing the object + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("-------------------------------\n"); + sb.append("Dumping ParamMask Object\n"); + sb.append("-------------------------------\n"); + sb.append("Name = " + getFieldName() + "\n"); + sb.append("Prefix = " + getPrefix() + "\n"); + sb.append("Current Value = " + _value + "\n"); + sb.append("Lower Bound = " + getLowerBound() + "\n"); + sb.append("Upper Bound = " + getUpperBound() + "\n"); + sb.append("Increment = " + getIncrement() + "\n"); + sb.append("Suffix = " + getSuffix() + "\n"); + sb.append("-------------------------------\n"); + + return sb.toString(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/ParamModifier.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/ParamModifier.java new file mode 100644 index 00000000000..0674240820a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/ParamModifier.java @@ -0,0 +1,157 @@ +/* + * 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.protocol.http.modifier; + +import java.io.Serializable; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * This modifier will replace any single http sampler's url parameter value with + * a value from a given range - thereby "masking" the value set in the http + * sampler. The parameter names must match exactly, and the parameter value must + * be preset to "*" to diferentiate between duplicate parameter names. + *

+ * For example, if you set up the modifier with a lower bound of 1, an upper + * bound of 10, and an increment of 2, and run the loop 12 times, the parameter + * will have the following values (one per loop): 1, 3, 5, 7, 9, 1, 3, 5, 7, 9, + * 1, 3 + *

+ * The {@link ParamMask} object contains most of the logic for stepping through + * this loop. You can make large modifications to this modifier's behaviour by + * changing one or two method implementations there. + * + * @see ParamMask + * @version $Revision$ + */ +public class ParamModifier extends AbstractTestElement implements TestStateListener, PreProcessor, Serializable { + + private static final long serialVersionUID = 240L; + + /* + * ------------------------------------------------------------------------ + * Fields + * ------------------------------------------------------------------------ + */ + + /** + * The key used to find the ParamMask object in the HashMap. + */ + private static final String MASK = "ParamModifier.mask"; + + /* + * ------------------------------------------------------------------------ + * Constructors + * ------------------------------------------------------------------------ + */ + + /** + * Default constructor. + */ + public ParamModifier() { + setProperty(new TestElementProperty(MASK, new ParamMask())); + } + + public ParamMask getMask() { + return (ParamMask) getProperty(MASK).getObjectValue(); + } + + @Override + public void testStarted() { + getMask().resetValue(); + } + + @Override + public void testStarted(String host) { + getMask().resetValue(); + } + + @Override + public void testEnded() { + } + + @Override + public void testEnded(String host) { + } + + /* + * ------------------------------------------------------------------------ + * Methods implemented from interface org.apache.jmeter.config.Modifier + * ------------------------------------------------------------------------ + */ + + /** + * Modifies an entry object to replace the value of any url parameter that + * matches a defined mask. + * + */ + @Override + public void process() { + Sampler sam = getThreadContext().getCurrentSampler(); + HTTPSamplerBase sampler = null; + if (!(sam instanceof HTTPSamplerBase)) { + return; + } else { + sampler = (HTTPSamplerBase) sam; + } + boolean modified = false; + PropertyIterator iter = sampler.getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + modified = modifyArgument(arg); + if (modified) { + break; + } + } + } + + /* + * ------------------------------------------------------------------------ + * Methods + * ------------------------------------------------------------------------ + */ + + /** + * Helper method for {@link #modifyEntry} Replaces a parameter's value if + * the parameter name matches the mask name and the value is a '*'. + * + * @param arg + * an {@link Argument} representing a http parameter + * @return trueif the value was replaced + */ + private boolean modifyArgument(Argument arg) { + // if a mask for this argument exists + if (arg.getName().equals(getMask().getFieldName())) { + // values to be masked must be set in the WebApp to "*" + if ("*".equals(arg.getValue())) { + arg.setValue(getMask().getNextValue()); + return true; + } + } + return false; + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/RegExUserParameters.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/RegExUserParameters.java new file mode 100644 index 00000000000..502d73db9fa --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/RegExUserParameters.java @@ -0,0 +1,147 @@ +/* + * 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.protocol.http.modifier; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This component allows you to specify reference name of a regular expression that extracts names and values of HTTP request parameters. + * Regular expression group numbers must be specified for parameter's name and also for parameter's value. + * Replacement will only occur for parameters in the Sampler that uses this RegEx User Parameters which name matches + */ +public class RegExUserParameters extends AbstractTestElement implements Serializable, PreProcessor { + private static final String REGEX_GROUP_SUFFIX = "_g"; + + private static final String MATCH_NR = "matchNr"; + + /** + * + */ + private static final long serialVersionUID = 5486502839185386122L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String REG_EX_REF_NAME = "RegExUserParameters.regex_ref_name";// $NON-NLS-1$ + + public static final String REG_EX_PARAM_NAMES_GR_NR = "RegExUserParameters.param_names_gr_nr";// $NON-NLS-1$ + + public static final String REG_EX_PARAM_VALUES_GR_NR = "RegExUserParameters.param_values_gr_nr";// $NON-NLS-1$ + + @Override + public void process() { + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " Running up named: " + getName());//$NON-NLS-1$ + } + Sampler entry = getThreadContext().getCurrentSampler(); + if (!(entry instanceof HTTPSamplerBase)) { + return; + } + + Map paramMap = buildParamsMap(); + if(paramMap == null || paramMap.isEmpty()){ + log.info("RegExUserParameters element:"+getName()+" => Referenced RegExp was not found, no parameter will be changed"); + return; + } + + HTTPSamplerBase sampler = (HTTPSamplerBase) entry; + PropertyIterator iter = sampler.getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + String oldValue = arg.getValue(); + // if parameter name exists in http request + // then change its value with value obtained with regular expression + String val = paramMap.get(arg.getName()); + if (val != null) { + arg.setValue(val); + } + if (log.isDebugEnabled()){ + log.debug("RegExUserParameters element:"+getName()+" => changed parameter: "+arg.getName() +" = "+ arg.getValue()+", was:"+oldValue); + } + } + } + + private Map buildParamsMap(){ + String regExRefName = getRegExRefName()+"_"; + String grNames = getRegParamNamesGrNr(); + String grValues = getRegExParamValuesGrNr(); + JMeterVariables jmvars = getThreadContext().getVariables(); + // verify if regex groups exists + if(jmvars.get(regExRefName + MATCH_NR) == null + || jmvars.get(regExRefName + 1 + REGEX_GROUP_SUFFIX + grNames) == null + || jmvars.get(regExRefName + 1 + REGEX_GROUP_SUFFIX + grValues) == null){ + return null; + } + int n = Integer.parseInt(jmvars.get(regExRefName + MATCH_NR)); + Map map = new HashMap(n); + for(int i=1; i<=n; i++){ + map.put(jmvars.get(regExRefName + i + REGEX_GROUP_SUFFIX + grNames), + jmvars.get(regExRefName + i + REGEX_GROUP_SUFFIX + grValues)); + } + return map; + } + + /** + * A new instance is created for each thread group, and the + * clone() method is then called to create copies for each thread in a + * thread group. + * + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + RegExUserParameters up = (RegExUserParameters) super.clone(); + return up; + } + + public void setRegExRefName(String str) { + setProperty(REG_EX_REF_NAME, str); + } + + public String getRegExRefName() { + return getPropertyAsString(REG_EX_REF_NAME); + } + + public void setRegExParamNamesGrNr(String str) { + setProperty(REG_EX_PARAM_NAMES_GR_NR, str); + } + + public String getRegParamNamesGrNr() { + return getPropertyAsString(REG_EX_PARAM_NAMES_GR_NR); + } + + public void setRegExParamValuesGrNr(String str) { + setProperty(REG_EX_PARAM_VALUES_GR_NR, str); + } + + public String getRegExParamValuesGrNr() { + return getPropertyAsString(REG_EX_PARAM_VALUES_GR_NR); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/URLRewritingModifier.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/URLRewritingModifier.java new file mode 100644 index 00000000000..61475d96672 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/URLRewritingModifier.java @@ -0,0 +1,238 @@ +/* + * 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.protocol.http.modifier; + +import java.io.Serializable; + +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +//For unit tests, @see TestURLRewritingModifier + +public class URLRewritingModifier extends AbstractTestElement implements Serializable, PreProcessor { + + private static final long serialVersionUID = 233L; + + private static final String SEMI_COLON = ";"; // $NON-NLS-1$ + + private transient Pattern pathExtensionEqualsQuestionmarkRegexp; + + private transient Pattern pathExtensionEqualsNoQuestionmarkRegexp; + + private transient Pattern parameterRegexp; + + private transient Pattern pathExtensionNoEqualsQuestionmarkRegexp; + + private transient Pattern pathExtensionNoEqualsNoQuestionmarkRegexp; + + // transient Perl5Compiler compiler = new Perl5Compiler(); + private static final String ARGUMENT_NAME = "argument_name"; // $NON-NLS-1$ + + private static final String PATH_EXTENSION = "path_extension"; // $NON-NLS-1$ + + private static final String PATH_EXTENSION_NO_EQUALS = "path_extension_no_equals"; // $NON-NLS-1$ + + private static final String PATH_EXTENSION_NO_QUESTIONMARK = "path_extension_no_questionmark"; // $NON-NLS-1$ + + private static final String SHOULD_CACHE = "cache_value"; // $NON-NLS-1$ + + private static final String ENCODE = "encode"; // $NON-NLS-1$ + + // PreProcessors are cloned per-thread, so this will be saved per-thread + private transient String savedValue = ""; // $NON-NLS-1$ + + @Override + public void process() { + JMeterContext ctx = getThreadContext(); + Sampler sampler = ctx.getCurrentSampler(); + if (!(sampler instanceof HTTPSamplerBase)) {// Ignore non-HTTP samplers + return; + } + SampleResult responseText = ctx.getPreviousResult(); + if (responseText == null) { + return; + } + initRegex(getArgumentName()); + String text = responseText.getResponseDataAsString(); + Perl5Matcher matcher = JMeterUtils.getMatcher(); + String value = ""; + if (isPathExtension() && isPathExtensionNoEquals() && isPathExtensionNoQuestionmark()) { + if (matcher.contains(text, pathExtensionNoEqualsNoQuestionmarkRegexp)) { + MatchResult result = matcher.getMatch(); + value = result.group(1); + } + } else if (isPathExtension() && isPathExtensionNoEquals()) // && !isPathExtensionNoQuestionmark() + { + if (matcher.contains(text, pathExtensionNoEqualsQuestionmarkRegexp)) { + MatchResult result = matcher.getMatch(); + value = result.group(1); + } + } else if (isPathExtension() && isPathExtensionNoQuestionmark()) // && !isPathExtensionNoEquals() + { + if (matcher.contains(text, pathExtensionEqualsNoQuestionmarkRegexp)) { + MatchResult result = matcher.getMatch(); + value = result.group(1); + } + } else if (isPathExtension()) // && !isPathExtensionNoEquals() && !isPathExtensionNoQuestionmark() + { + if (matcher.contains(text, pathExtensionEqualsQuestionmarkRegexp)) { + MatchResult result = matcher.getMatch(); + value = result.group(1); + } + } else // if ! isPathExtension() + { + if (matcher.contains(text, parameterRegexp)) { + MatchResult result = matcher.getMatch(); + for (int i = 1; i < result.groups(); i++) { + value = result.group(i); + if (value != null) { + break; + } + } + } + } + + // Bug 15025 - save session value across samplers + if (shouldCache()){ + if (value == null || value.length() == 0) { + value = savedValue; + } else { + savedValue = value; + } + } + modify((HTTPSamplerBase) sampler, value); + } + + private void modify(HTTPSamplerBase sampler, String value) { + if (isPathExtension()) { + if (isPathExtensionNoEquals()) { + sampler.setPath(sampler.getPath() + SEMI_COLON + getArgumentName() + value); // $NON-NLS-1$ + } else { + sampler.setPath(sampler.getPath() + SEMI_COLON + getArgumentName() + "=" + value); // $NON-NLS-1$ // $NON-NLS-2$ + } + } else { + sampler.getArguments().removeArgument(getArgumentName()); + sampler.getArguments().addArgument(new HTTPArgument(getArgumentName(), value, !encode())); + } + } + + public void setArgumentName(String argName) { + setProperty(ARGUMENT_NAME, argName); + } + + private void initRegex(String argName) { + String quotedArg = Perl5Compiler.quotemeta(argName);// Don't get tripped up by RE chars in the arg name + pathExtensionEqualsQuestionmarkRegexp = JMeterUtils.getPatternCache().getPattern( + SEMI_COLON + quotedArg + "=([^\"'<>&\\s;]*)", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + + pathExtensionEqualsNoQuestionmarkRegexp = JMeterUtils.getPatternCache().getPattern( + SEMI_COLON + quotedArg + "=([^\"'<>&\\s;?]*)", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + + pathExtensionNoEqualsQuestionmarkRegexp = JMeterUtils.getPatternCache().getPattern( + SEMI_COLON + quotedArg + "([^\"'<>&\\s;]*)", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + + pathExtensionNoEqualsNoQuestionmarkRegexp = JMeterUtils.getPatternCache().getPattern( + SEMI_COLON + quotedArg + "([^\"'<>&\\s;?]*)", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + + parameterRegexp = JMeterUtils.getPatternCache().getPattern( + // ;sessionid=value + "[;\\?&]" + quotedArg + "=([^\"'<>&\\s;\\\\]*)" + // $NON-NLS-1$ + + // name="sessionid" value="value" + "|\\s[Nn][Aa][Mm][Ee]\\s*=\\s*[\"']" + quotedArg + + "[\"']" + "[^>]*" // $NON-NLS-1$ + + "\\s[vV][Aa][Ll][Uu][Ee]\\s*=\\s*[\"']" // $NON-NLS-1$ + + "([^\"']*)" + "[\"']" // $NON-NLS-1$ + + // value="value" name="sessionid" + + "|\\s[vV][Aa][Ll][Uu][Ee]\\s*=\\s*[\"']" // $NON-NLS-1$ + + "([^\"']*)" + "[\"']" + "[^>]*" // $NON-NLS-1$ // $NON-NLS-2$ // $NON-NLS-3$ + + "\\s[Nn][Aa][Mm][Ee]\\s*=\\s*[\"']" // $NON-NLS-1$ + + quotedArg + "[\"']", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + // NOTE: the handling of simple- vs. double-quotes could be formally + // more accurate, but I can't imagine a session id containing + // either, so we should be OK. The whole set of expressions is a + // quick hack anyway, so who cares. + } + + public String getArgumentName() { + return getPropertyAsString(ARGUMENT_NAME); + } + + public void setPathExtension(boolean pathExt) { + setProperty(new BooleanProperty(PATH_EXTENSION, pathExt)); + } + + public void setPathExtensionNoEquals(boolean pathExtNoEquals) { + setProperty(new BooleanProperty(PATH_EXTENSION_NO_EQUALS, pathExtNoEquals)); + } + + public void setPathExtensionNoQuestionmark(boolean pathExtNoQuestionmark) { + setProperty(new BooleanProperty(PATH_EXTENSION_NO_QUESTIONMARK, pathExtNoQuestionmark)); + } + + public void setShouldCache(boolean b) { + setProperty(new BooleanProperty(SHOULD_CACHE, b)); + } + + public boolean isPathExtension() { + return getPropertyAsBoolean(PATH_EXTENSION); + } + + public boolean isPathExtensionNoEquals() { + return getPropertyAsBoolean(PATH_EXTENSION_NO_EQUALS); + } + + public boolean isPathExtensionNoQuestionmark() { + return getPropertyAsBoolean(PATH_EXTENSION_NO_QUESTIONMARK); + } + + public boolean shouldCache() { + return getPropertyAsBoolean(SHOULD_CACHE,true); + } + + protected Object readResolve(){ + savedValue = ""; + return this; + } + + public boolean encode() { + return getPropertyAsBoolean(ENCODE, false); + } + public void setEncode(boolean b) { + setProperty(new BooleanProperty(ENCODE, b)); + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserParameterXMLContentHandler.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserParameterXMLContentHandler.java new file mode 100644 index 00000000000..b87fec17827 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserParameterXMLContentHandler.java @@ -0,0 +1,147 @@ +/* + * 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.protocol.http.modifier; + +import java.io.CharArrayWriter; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; + +/** + * The handler used to read in XML parameter data. + * + * @version $Revision$ + */ +public class UserParameterXMLContentHandler implements ContentHandler { + // ------------------------------------------- + // Constants and Data Members + // ------------------------------------------- + + // Note UserParameterXML accesses this variable + // to obtain the Set data via method getParsedParameters() + private List> userThreads = new LinkedList>(); + + private String paramname = ""; + + private String paramvalue = ""; + + private Map nameValuePair = new HashMap(); + + /** Buffer for collecting data from the "characters" SAX event. */ + private CharArrayWriter contents = new CharArrayWriter(); + + // ------------------------------------------- + // Methods + // ------------------------------------------- + + /*------------------------------------------------------------------------- + * Methods implemented from org.xml.sax.ContentHandler + *----------------------------------------------------------------------- */ + @Override + public void setDocumentLocator(Locator locator) { + } + + @Override + public void startDocument() throws SAXException { + } + + @Override + public void endDocument() throws SAXException { + } + + @Override + public void startPrefixMapping(String prefix, String uri) throws SAXException { + } + + @Override + public void endPrefixMapping(String prefix) throws SAXException { + } + + @Override + public void startElement(String namespaceURL, String localName, String qName, Attributes atts) throws SAXException { + + contents.reset(); + + // haven't got to reset paramname & paramvalue + // but did it to keep the code looking correct + if (qName.equals("parameter")) { + paramname = ""; + paramvalue = ""; + } + + // must create a new object, + // or else end up with a set full of the same Map object + if (qName.equals("thread")) { + nameValuePair = new HashMap(); + } + + } + + @Override + public void endElement(String namespaceURI, String localName, String qName) throws SAXException { + if (qName.equals("paramname")) { + paramname = contents.toString(); + } + if (qName.equals("paramvalue")) { + paramvalue = contents.toString(); + } + if (qName.equals("parameter")) { + nameValuePair.put(paramname, paramvalue); + } + if (qName.equals("thread")) { + userThreads.add(nameValuePair); + } + } + + @Override + public void characters(char ch[], int start, int length) throws SAXException { + contents.write(ch, start, length); + } + + @Override + public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { + } + + @Override + public void processingInstruction(String target, String date) throws SAXException { + } + + @Override + public void skippedEntity(String name) throws SAXException { + } + + /*------------------------------------------------------------------------- + * Methods (used by UserParameterXML to get XML parameters from XML file) + *----------------------------------------------------------------------- */ + + /** + * results of parsing all user parameter data defined in XML file. + * + * @return all users name value pairs obtained from XML file + */ + public List> getParsedParameters() { + return userThreads; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserParameterXMLErrorHandler.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserParameterXMLErrorHandler.java new file mode 100644 index 00000000000..1e51a2b0f2e --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserParameterXMLErrorHandler.java @@ -0,0 +1,54 @@ +/* + * 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.protocol.http.modifier; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * XML Parseing errors for XML parameters file are handled here. + * + */ +public class UserParameterXMLErrorHandler implements ErrorHandler { + private static final Logger log = LoggingManager.getLoggerForClass(); + + @Override + public void warning(SAXParseException exception) throws SAXException { + log.warn("**Parsing Warning**\n" + " line: " + exception.getLineNumber() + "\n" + " URI: :" + + exception.getSystemId() + "\n" + " Message: " + exception.getMessage()); + throw new SAXException("Warning encountered"); + } + + @Override + public void error(SAXParseException exception) throws SAXException { + log.error("**Parsing Warning**\n" + " line: " + exception.getLineNumber() + "\n" + " URI: :" + + exception.getSystemId() + "\n" + " Message: " + exception.getMessage()); + throw new SAXException("Error encountered"); + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + log.error("**Parsing Warning**\n" + " line: " + exception.getLineNumber() + "\n" + " URI: :" + + exception.getSystemId() + "\n" + " Message: " + exception.getMessage()); + throw new SAXException("Fatal Error encountered"); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserParameterXMLParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserParameterXMLParser.java new file mode 100644 index 00000000000..0ba36954f86 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserParameterXMLParser.java @@ -0,0 +1,72 @@ +/* + * 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.protocol.http.modifier; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.util.JMeterUtils; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +/** + * Parse an XML file to obtain parameter name and value information for all + * users defined in the XML file. + * + * @deprecated This test element is deprecated. Test plans should use User Parameters instead. + */ +@Deprecated +public class UserParameterXMLParser { + + /** + * Parse all user parameter data defined in XML file. + * + * @param xmlURI + * name of the XML to load users parameter data + * @return all users name value pairs obtained from XML file + * @throws SAXException + * when XML pointed to by xmlURI is not valid + * @throws IOException + * when XML pointed to by xmlURI can not be read + */ + public List> getXMLParameters(String xmlURI) throws SAXException, IOException { + // create instances needed for parsing + XMLReader reader = JMeterUtils.getXMLParser(); + // XMLReaderFactory.createXMLReader(vendorParseClass); + UserParameterXMLContentHandler threadParametersContentHandler = new UserParameterXMLContentHandler(); + UserParameterXMLErrorHandler parameterErrorHandler = new UserParameterXMLErrorHandler(); + + // register content handler + reader.setContentHandler(threadParametersContentHandler); + + // register error handler + reader.setErrorHandler(parameterErrorHandler); + + // Request validation + reader.setFeature("http://xml.org/sax/features/validation", true); // $NON-NLS-1$ + + // parse + InputSource inputSource = new InputSource(xmlURI); + reader.parse(inputSource); + + return threadParametersContentHandler.getParsedParameters(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserSequence.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserSequence.java new file mode 100644 index 00000000000..25eb1a8c8b6 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/UserSequence.java @@ -0,0 +1,97 @@ +/* + * 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.protocol.http.modifier; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This module controls the Sequence in which user details are returned. This + * module uses round robin allocation of users. + * + * @version $Revision$ + */ +public class UserSequence implements Serializable { + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // ------------------------------------------- + // Constants and Data Members + // ------------------------------------------- + private List> allUsers; + + private transient Iterator> indexOfUsers; + + // ------------------------------------------- + // Constructors + // ------------------------------------------- + + public UserSequence() { + } + + /** + * Load all user and parameter data into the sequence module. + *

+ * ie a Set of Mapped "parameter names and parameter values" for each user + * to be loaded into the sequencer. + * + * @param allUsers users and parameter data to be used + */ + public UserSequence(List> allUsers) { + this.allUsers = allUsers; + + // initalise pointer to first user + indexOfUsers = allUsers.iterator(); + } + + // ------------------------------------------- + // Methods + // ------------------------------------------- + + /** + * Returns the parameter data for the next user in the sequence + * + * @return a Map object of parameter names and matching parameter values for + * the next user + */ + public synchronized Map getNextUserMods() { + // Use round robin allocation of user details + if (!indexOfUsers.hasNext()) { + indexOfUsers = allUsers.iterator(); + } + + Map user; + if (indexOfUsers.hasNext()) { + user = indexOfUsers.next(); + log.debug("UserSequence.getNextuserMods(): current parameters will be " + "changed to: " + user); + } else { + // no entries in all users, therefore create an empty Map object + user = new HashMap(); + } + + return user; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/AnchorModifierGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/AnchorModifierGui.java new file mode 100644 index 00000000000..bed41b58bdf --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/AnchorModifierGui.java @@ -0,0 +1,62 @@ +/* + * 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.protocol.http.modifier.gui; + +import java.awt.BorderLayout; + +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.protocol.http.modifier.AnchorModifier; +import org.apache.jmeter.testelement.TestElement; + +public class AnchorModifierGui extends AbstractPreProcessorGui { + private static final long serialVersionUID = 240L; + + public AnchorModifierGui() { + init(); + } + + @Override + public String getLabelResource() { + return "anchor_modifier_title"; //$NON-NLS-1$ + } + + @Override + public TestElement createTestElement() { + AnchorModifier modifier = new AnchorModifier(); + modifyTestElement(modifier); + return modifier; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement modifier) { + configureTestElement(modifier); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/ParamModifierGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/ParamModifierGui.java new file mode 100644 index 00000000000..c0244b20fc0 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/ParamModifierGui.java @@ -0,0 +1,243 @@ +/* + * 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.protocol.http.modifier.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.protocol.http.modifier.ParamMask; +import org.apache.jmeter.protocol.http.modifier.ParamModifier; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * A swing panel to allow UI with the ParamModifier class. + * + * Created Jan 18, 2002 + * + */ +public class ParamModifierGui extends AbstractPreProcessorGui implements FocusListener { + + private static final long serialVersionUID = 240L; + + /* + * These are used as GUI item names; + * LOWERBOUND, UPPERBOUND and INCREMENT are used in the focusLost() method + */ + + private static final String NAME = "name"; + + private static final String PREFIX = "prefix"; + + private static final String LOWERBOUND = "lowerBound"; + + private static final String UPPERBOUND = "upperBound"; + + private static final String INCREMENT = "increment"; + + private static final String SUFFIX = "suffix"; + + private static final String ZERO = "0"; //$NON-NLS-1$ + + private JTextField _fieldName; + + private JTextField _prefix; + + private JTextField _lowerBound; + + private JTextField _upperBound; + + private JTextField _increment; + + private JTextField _suffix; + + public ParamModifierGui() { + init(); + } + + @Override + public String getLabelResource() { + return "html_parameter_mask"; //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + ParamModifier model = (ParamModifier) el; + updateGui(model); + } + + @Override + public TestElement createTestElement() { + ParamModifier modifier = new ParamModifier(); + modifyTestElement(modifier); + return modifier; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement m) { + configureTestElement(m); + if (m instanceof ParamModifier) { + ParamModifier modifier = (ParamModifier) m; + ParamMask mask = modifier.getMask(); + mask.setFieldName(_fieldName.getText()); + mask.setPrefix(_prefix.getText()); + mask.setLowerBound(Long.parseLong(_lowerBound.getText())); + mask.setIncrement(Long.parseLong(_increment.getText())); + mask.setUpperBound(Long.parseLong(_upperBound.getText())); + mask.setSuffix(_suffix.getText()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + _fieldName.setText(""); //$NON-NLS-1$ + _prefix.setText(""); //$NON-NLS-1$ + _lowerBound.setText(ZERO); + _upperBound.setText("10"); //$NON-NLS-1$ + _increment.setText("1"); //$NON-NLS-1$ + _suffix.setText(""); //$NON-NLS-1$ + } + + @Override + public void focusGained(FocusEvent evt) { + } + + @Override + public void focusLost(FocusEvent evt) { + String name = ((Component) evt.getSource()).getName(); + if (evt.isTemporary()) { + return; + } else if (name.equals(LOWERBOUND)) { + checkTextField(evt, ZERO); + } else if (name.equals(UPPERBOUND)) { + checkTextField(evt, ZERO); + } else if (name.equals(INCREMENT)) { + checkTextField(evt, ZERO); + } + } + + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + add(getParameterMaskPanel(), BorderLayout.CENTER); + // this.updateUI(); + } + + private void updateGui(ParamModifier model) { + _fieldName.setText(model.getMask().getFieldName()); + _prefix.setText(model.getMask().getPrefix()); + _lowerBound.setText(Long.toString(model.getMask().getLowerBound())); + _upperBound.setText(Long.toString(model.getMask().getUpperBound())); + _increment.setText(Long.toString(model.getMask().getIncrement())); + _suffix.setText(model.getMask().getSuffix()); + } + + private JPanel createLabeledField(String labelResName, JTextField field) { + JLabel label = new JLabel(JMeterUtils.getResString(labelResName)); + label.setLabelFor(field); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(label, BorderLayout.NORTH); + panel.add(field, BorderLayout.CENTER); + return panel; + } + + private JPanel getParameterMaskPanel() { + HorizontalPanel panel = new HorizontalPanel(10, Component.TOP_ALIGNMENT); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("html_parameter_mask"))); //$NON-NLS-1$ + + _fieldName = new JTextField(10); + _fieldName.setName(NAME); + panel.add(createLabeledField("name", _fieldName)); //$NON-NLS-1$ resource name + + _prefix = new JTextField(5); + _prefix.setName(PREFIX); + panel.add(createLabeledField("id_prefix", _prefix)); //$NON-NLS-1$ resource name + + _lowerBound = new JTextField(ZERO, 5); + _lowerBound.addFocusListener(this); + _lowerBound.setName(LOWERBOUND); + panel.add(createLabeledField("lower_bound", _lowerBound)); //$NON-NLS-1$ resource name + + _upperBound = new JTextField("10", 5); + _upperBound.addFocusListener(this); + _upperBound.setName(UPPERBOUND); + panel.add(createLabeledField("upper_bound", _upperBound)); //$NON-NLS-1$ resource name + + _increment = new JTextField("1", 3); + _increment.addFocusListener(this); + _increment.setName(INCREMENT); + panel.add(createLabeledField("increment", _increment)); //$NON-NLS-1$ resource name + + _suffix = new JTextField(5); + _suffix.setName(SUFFIX); + panel.add(createLabeledField("id_suffix", _suffix)); //$NON-NLS-1$ resource name + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(panel, BorderLayout.NORTH); + return mainPanel; + } + + /** + * Used to validate a text field that requires a long input. + * Returns the long if valid, else creates a pop-up error + * message and throws a NumberFormatException. + * + * @return the number entered in the text field + */ + private long checkTextField(FocusEvent evt, String defaultValue) { + JTextField temp = (JTextField) evt.getSource(); + // boolean pass = true; + long longVal = 0; + + try { + longVal = Long.parseLong(temp.getText()); + } catch (NumberFormatException err) { + JOptionPane.showMessageDialog(this, "This field must have a long value!", "Value Required", + JOptionPane.ERROR_MESSAGE); + temp.setText(defaultValue); + temp.requestFocusInWindow(); + } + return longVal; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/RegExUserParametersGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/RegExUserParametersGui.java new file mode 100644 index 00000000000..ce348b1f220 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/RegExUserParametersGui.java @@ -0,0 +1,165 @@ +/* + * 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.protocol.http.modifier.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.List; + +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JPanel; + +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.protocol.http.modifier.RegExUserParameters; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * GUI for {@link RegExUserParameters} + */ +public class RegExUserParametersGui extends AbstractPreProcessorGui { + + /** + * + */ + private static final long serialVersionUID = 3080808672311046276L; + + private JLabeledTextField refRegExRefNameField; + + private JLabeledTextField paramNamesGrNrField; + + private JLabeledTextField paramValuesGrNrField; + + public RegExUserParametersGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "regex_params_title"; //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof RegExUserParameters){ + RegExUserParameters re = (RegExUserParameters) el; + paramNamesGrNrField.setText(re.getRegParamNamesGrNr()); + paramValuesGrNrField.setText(re.getRegExParamValuesGrNr()); + refRegExRefNameField.setText(re.getRegExRefName()); + } + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + RegExUserParameters regExUserParams = new RegExUserParameters(); + modifyTestElement(regExUserParams); + return regExUserParams; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement extractor) { + super.configureTestElement(extractor); + if (extractor instanceof RegExUserParameters) { + RegExUserParameters regExUserParams = (RegExUserParameters) extractor; + regExUserParams.setRegExRefName(refRegExRefNameField.getText()); + regExUserParams.setRegExParamNamesGrNr(paramNamesGrNrField.getText()); + regExUserParams.setRegExParamValuesGrNr(paramValuesGrNrField.getText()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + paramNamesGrNrField.setText(""); //$NON-NLS-1$ + paramValuesGrNrField.setText(""); //$NON-NLS-1$ + refRegExRefNameField.setText(""); //$NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + add(box, BorderLayout.NORTH); + add(makeParameterPanel(), BorderLayout.CENTER); + } + + private JPanel makeParameterPanel() { + refRegExRefNameField = new JLabeledTextField(JMeterUtils.getResString("regex_params_ref_name_field")); //$NON-NLS-1$ + paramNamesGrNrField = new JLabeledTextField(JMeterUtils.getResString("regex_params_names_field")); //$NON-NLS-1$ + paramValuesGrNrField = new JLabeledTextField(JMeterUtils.getResString("regex_params_values_field")); //$NON-NLS-1$ + + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + initConstraints(gbc); + addField(panel, refRegExRefNameField, gbc); + resetContraints(gbc); + addField(panel, paramNamesGrNrField, gbc); + resetContraints(gbc); + gbc.weighty = 1; + addField(panel, paramValuesGrNrField, gbc); + return panel; + } + + private void addField(JPanel panel, JLabeledTextField field, GridBagConstraints gbc) { + List item = field.getComponentList(); + panel.add(item.get(0), gbc.clone()); + gbc.gridx++; + gbc.weightx = 1; + gbc.fill=GridBagConstraints.HORIZONTAL; + panel.add(item.get(1), gbc.clone()); + } + + // Next line + private void resetContraints(GridBagConstraints gbc) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0; + gbc.fill=GridBagConstraints.NONE; + } + + private void initConstraints(GridBagConstraints gbc) { + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.NONE; + gbc.gridheight = 1; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0; + gbc.weighty = 0; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/URLRewritingModifierGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/URLRewritingModifierGui.java new file mode 100644 index 00000000000..c4c4a4cf044 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/modifier/gui/URLRewritingModifierGui.java @@ -0,0 +1,143 @@ +/* + * 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.protocol.http.modifier.gui; + +import java.awt.BorderLayout; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.protocol.http.modifier.URLRewritingModifier; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; + +public class URLRewritingModifierGui extends AbstractPreProcessorGui { + private static final long serialVersionUID = 240L; + + private JLabeledTextField argumentName; + + private JCheckBox pathExt; + + private JCheckBox pathExtNoEquals; + + private JCheckBox pathExtNoQuestionmark; + + private JCheckBox shouldCache; + + private JCheckBox encode; + + @Override + public String getLabelResource() { + return "http_url_rewriting_modifier_title"; // $NON-NLS-1$ + } + + public URLRewritingModifierGui() { + init(); + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + VerticalPanel mainPanel = new VerticalPanel(); + + argumentName = new JLabeledTextField(JMeterUtils.getResString("session_argument_name"), 10); // $NON-NLS-1$ + mainPanel.add(argumentName); + + pathExt = new JCheckBox(JMeterUtils.getResString("path_extension_choice")); // $NON-NLS-1$ + mainPanel.add(pathExt); + + pathExtNoEquals = new JCheckBox(JMeterUtils.getResString("path_extension_dont_use_equals")); // $NON-NLS-1$ + mainPanel.add(pathExtNoEquals); + + pathExtNoQuestionmark = new JCheckBox(JMeterUtils.getResString("path_extension_dont_use_questionmark")); // $NON-NLS-1$ + mainPanel.add(pathExtNoQuestionmark); + + shouldCache = new JCheckBox(JMeterUtils.getResString("cache_session_id")); // $NON-NLS-1$ + shouldCache.setSelected(true); + mainPanel.add(shouldCache); + + encode = new JCheckBox(JMeterUtils.getResString("encode")); // $NON-NLS-1$ + encode.setSelected(false); + mainPanel.add(encode); + + add(mainPanel, BorderLayout.CENTER); + } + + /** + * {@inheritDoc} + */ + @Override + public TestElement createTestElement() { + URLRewritingModifier modifier = new URLRewritingModifier(); + modifyTestElement(modifier); + return modifier; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + *

+ * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement modifier) { + this.configureTestElement(modifier); + URLRewritingModifier rewritingModifier = ((URLRewritingModifier) modifier); + rewritingModifier.setArgumentName(argumentName.getText()); + rewritingModifier.setPathExtension(pathExt.isSelected()); + rewritingModifier.setPathExtensionNoEquals(pathExtNoEquals.isSelected()); + rewritingModifier.setPathExtensionNoQuestionmark(pathExtNoQuestionmark.isSelected()); + rewritingModifier.setShouldCache((shouldCache.isSelected())); + rewritingModifier.setEncode(encode.isSelected()); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + + argumentName.setText(""); //$NON-NLS-1$ + pathExt.setSelected(false); + pathExtNoEquals.setSelected(false); + pathExtNoQuestionmark.setSelected(false); + shouldCache.setSelected(false); + encode.setSelected(false); + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement el) { + URLRewritingModifier rewritingModifier = ((URLRewritingModifier) el); + argumentName.setText(rewritingModifier.getArgumentName()); + pathExt.setSelected(rewritingModifier.isPathExtension()); + pathExtNoEquals.setSelected(rewritingModifier.isPathExtensionNoEquals()); + pathExtNoQuestionmark.setSelected(rewritingModifier.isPathExtensionNoQuestionmark()); + shouldCache.setSelected(rewritingModifier.shouldCache()); + encode.setSelected(rewritingModifier.encode()); + super.configure(el); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/HTMLParseError.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HTMLParseError.java new file mode 100644 index 00000000000..b29e5f3c697 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HTMLParseError.java @@ -0,0 +1,46 @@ +/* + * 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.protocol.http.parser; + +/** + * Error class for use with HTMLParser classes. + * The main rationale for the class + * was to support chained Errors in JDK 1.3, + * however it is now used in its own right. + * + * @version $Revision$ + */ +public class HTMLParseError extends Error { + private static final long serialVersionUID = 240L; + + public HTMLParseError() { + super(); + } + + public HTMLParseError(String message) { + super(message); + } + + public HTMLParseError(Throwable cause) { + super(cause); + } + + public HTMLParseError(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/HTMLParseException.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HTMLParseException.java new file mode 100644 index 00000000000..df900cad50a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HTMLParseException.java @@ -0,0 +1,46 @@ +/* + * 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.protocol.http.parser; + +/** + * Exception class for use with HTMLParser classes. + * The main rationale for the class + * was to support chained Exceptions in JDK 1.3, + * however it is now used in its own right. + * + * @version $Revision$ + */ +public class HTMLParseException extends Exception { + private static final long serialVersionUID = 240L; + + public HTMLParseException() { + super(); + } + + public HTMLParseException(String message) { + super(message); + } + + public HTMLParseException(Throwable cause) { + super(cause); + } + + public HTMLParseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/HTMLParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HTMLParser.java new file mode 100644 index 00000000000..e79252a946f --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HTMLParser.java @@ -0,0 +1,270 @@ +/* + * 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.protocol.http.parser; + + +import java.net.URL; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HtmlParsers can parse HTML content to obtain URLs. + * + */ +public abstract class HTMLParser { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected static final String ATT_BACKGROUND = "background";// $NON-NLS-1$ + protected static final String ATT_CODE = "code";// $NON-NLS-1$ + protected static final String ATT_CODEBASE = "codebase";// $NON-NLS-1$ + protected static final String ATT_DATA = "data";// $NON-NLS-1$ + protected static final String ATT_HREF = "href";// $NON-NLS-1$ + protected static final String ATT_REL = "rel";// $NON-NLS-1$ + protected static final String ATT_SRC = "src";// $NON-NLS-1$ + protected static final String ATT_STYLE = "style";// $NON-NLS-1$ + protected static final String ATT_TYPE = "type";// $NON-NLS-1$ + protected static final String ATT_IS_IMAGE = "image";// $NON-NLS-1$ + protected static final String TAG_APPLET = "applet";// $NON-NLS-1$ + protected static final String TAG_BASE = "base";// $NON-NLS-1$ + protected static final String TAG_BGSOUND = "bgsound";// $NON-NLS-1$ + protected static final String TAG_BODY = "body";// $NON-NLS-1$ + protected static final String TAG_EMBED = "embed";// $NON-NLS-1$ + protected static final String TAG_FRAME = "frame";// $NON-NLS-1$ + protected static final String TAG_IFRAME = "iframe";// $NON-NLS-1$ + protected static final String TAG_IMAGE = "img";// $NON-NLS-1$ + protected static final String TAG_INPUT = "input";// $NON-NLS-1$ + protected static final String TAG_LINK = "link";// $NON-NLS-1$ + protected static final String TAG_OBJECT = "object";// $NON-NLS-1$ + protected static final String TAG_SCRIPT = "script";// $NON-NLS-1$ + protected static final String STYLESHEET = "stylesheet";// $NON-NLS-1$ + + protected static final String IE_UA = "MSIE ([0-9]+.[0-9]+)";// $NON-NLS-1$ + protected static final Pattern IE_UA_PATTERN = Pattern.compile(IE_UA); + private static final float IE_10 = 10.0f; + + // Cache of parsers - parsers must be re-usable + private static final Map parsers = new ConcurrentHashMap(4); + + public static final String PARSER_CLASSNAME = "htmlParser.className"; // $NON-NLS-1$ + + public static final String DEFAULT_PARSER = + "org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser"; // $NON-NLS-1$ + + /** + * Protected constructor to prevent instantiation except from within + * subclasses. + */ + protected HTMLParser() { + } + + public static final HTMLParser getParser() { + return getParser(JMeterUtils.getPropDefault(PARSER_CLASSNAME, DEFAULT_PARSER)); + } + + public static final HTMLParser getParser(String htmlParserClassName) { + + // Is there a cached parser? + HTMLParser pars = parsers.get(htmlParserClassName); + if (pars != null) { + log.debug("Fetched " + htmlParserClassName); + return pars; + } + + try { + Object clazz = Class.forName(htmlParserClassName).newInstance(); + if (clazz instanceof HTMLParser) { + pars = (HTMLParser) clazz; + } else { + throw new HTMLParseError(new ClassCastException(htmlParserClassName)); + } + } catch (InstantiationException e) { + throw new HTMLParseError(e); + } catch (IllegalAccessException e) { + throw new HTMLParseError(e); + } catch (ClassNotFoundException e) { + throw new HTMLParseError(e); + } + log.info("Created " + htmlParserClassName); + if (pars.isReusable()) { + parsers.put(htmlParserClassName, pars);// cache the parser + } + + return pars; + } + + /** + * Get the URLs for all the resources that a browser would automatically + * download following the download of the HTML content, that is: images, + * stylesheets, javascript files, applets, etc... + *

+ * URLs should not appear twice in the returned iterator. + *

+ * Malformed URLs can be reported to the caller by having the Iterator + * return the corresponding RL String. Overall problems parsing the html + * should be reported by throwing an HTMLParseException. + * @param userAgent + * User Agent + * + * @param html + * HTML code + * @param baseUrl + * Base URL from which the HTML code was obtained + * @param encoding Charset + * @return an Iterator for the resource URLs + * @throws HTMLParseException when parsing the html fails + */ + public Iterator getEmbeddedResourceURLs(String userAgent, byte[] html, URL baseUrl, String encoding) throws HTMLParseException { + // The Set is used to ignore duplicated binary files. + // Using a LinkedHashSet to avoid unnecessary overhead in iterating + // the elements in the set later on. As a side-effect, this will keep + // them roughly in order, which should be a better model of browser + // behaviour. + + Collection col = new LinkedHashSet(); + return getEmbeddedResourceURLs(userAgent, html, baseUrl, new URLCollection(col),encoding); + + // An additional note on using HashSets to store URLs: I just + // discovered that obtaining the hashCode of a java.net.URL implies + // a domain-name resolution process. This means significant delays + // can occur, even more so if the domain name is not resolvable. + // Whether this can be a problem in practical situations I can't tell, + // but + // thought I'd keep a note just in case... + // BTW, note that using a List and removing duplicates via scan + // would not help, since URL.equals requires name resolution too. + // The above problem has now been addressed with the URLString and + // URLCollection classes. + + } + + /** + * Get the URLs for all the resources that a browser would automatically + * download following the download of the HTML content, that is: images, + * stylesheets, javascript files, applets, etc... + *

+ * All URLs should be added to the Collection. + *

+ * Malformed URLs can be reported to the caller by having the Iterator + * return the corresponding RL String. Overall problems parsing the html + * should be reported by throwing an HTMLParseException. + *

+ * N.B. The Iterator returns URLs, but the Collection will contain objects + * of class URLString. + * + * @param userAgent + * User Agent + * @param html + * HTML code + * @param baseUrl + * Base URL from which the HTML code was obtained + * @param coll + * URLCollection + * @param encoding Charset + * @return an Iterator for the resource URLs + * @throws HTMLParseException when parsing the html fails + */ + public abstract Iterator getEmbeddedResourceURLs(String userAgent, byte[] html, URL baseUrl, URLCollection coll, String encoding) + throws HTMLParseException; + + /** + * Get the URLs for all the resources that a browser would automatically + * download following the download of the HTML content, that is: images, + * stylesheets, javascript files, applets, etc... + *

+ * N.B. The Iterator returns URLs, but the Collection will contain objects + * of class URLString. + * + * @param userAgent + * User Agent + * @param html + * HTML code + * @param baseUrl + * Base URL from which the HTML code was obtained + * @param coll + * Collection - will contain URLString objects, not URLs + * @param encoding Charset + * @return an Iterator for the resource URLs + * @throws HTMLParseException when parsing the html fails + */ + public Iterator getEmbeddedResourceURLs(String userAgent, byte[] html, URL baseUrl, Collection coll, String encoding) throws HTMLParseException { + return getEmbeddedResourceURLs(userAgent, html, baseUrl, new URLCollection(coll), encoding); + } + + /** + * Parsers should over-ride this method if the parser class is re-usable, in + * which case the class will be cached for the next getParser() call. + * + * @return true if the Parser is reusable + */ + protected boolean isReusable() { + return false; + } + + /** + * + * @param ieVersion Float IE version + * @return true if IE version < IE v10 + */ + protected final boolean isEnableConditionalComments(Float ieVersion) { + if(ieVersion == null) { + return false; + } + // Conditionnal comment have been dropped in IE10 + // http://msdn.microsoft.com/en-us/library/ie/hh801214%28v=vs.85%29.aspx + return ieVersion.floatValue() < IE_10; + } + + /** + * + * @param userAgent User Agent + * @return version null if not IE or the version after MSIE + */ + protected Float extractIEVersion(String userAgent) { + if(StringUtils.isEmpty(userAgent)) { + log.info("userAgent is null"); + return null; + } + Matcher matcher = IE_UA_PATTERN.matcher(userAgent); + String ieVersion = null; + while (matcher.find()) { + if (matcher.groupCount() > 0) { + ieVersion = matcher.group(1); + } else { + ieVersion = matcher.group(); + } + break; + } + if(ieVersion != null) { + return Float.valueOf(ieVersion); + } else { + return null; + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/HtmlParserHTMLParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HtmlParserHTMLParser.java new file mode 100644 index 00000000000..65f505838bc --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HtmlParserHTMLParser.java @@ -0,0 +1,206 @@ +/* + * 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.protocol.http.parser; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.htmlparser.Node; +import org.htmlparser.Parser; +import org.htmlparser.Tag; +import org.htmlparser.tags.AppletTag; +import org.htmlparser.tags.BaseHrefTag; +import org.htmlparser.tags.BodyTag; +import org.htmlparser.tags.CompositeTag; +import org.htmlparser.tags.FrameTag; +import org.htmlparser.tags.ImageTag; +import org.htmlparser.tags.InputTag; +import org.htmlparser.tags.ObjectTag; +import org.htmlparser.tags.ScriptTag; +import org.htmlparser.util.NodeIterator; +import org.htmlparser.util.ParserException; + +/** + * HtmlParser implementation using SourceForge's HtmlParser. + * + */ +class HtmlParserHTMLParser extends HTMLParser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + static{ + org.htmlparser.scanners.ScriptScanner.STRICT = false; // Try to ensure that more javascript code is processed OK ... + } + + protected HtmlParserHTMLParser() { + super(); + log.info("Using htmlparser version: "+Parser.getVersion()); + } + + @Override + protected boolean isReusable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator getEmbeddedResourceURLs(String userAgent, byte[] html, URL baseUrl, URLCollection urls, String encoding) throws HTMLParseException { + + if (log.isDebugEnabled()) { + log.debug("Parsing html of: " + baseUrl); + } + + Parser htmlParser = null; + try { + String contents = new String(html,encoding); + htmlParser = new Parser(); + htmlParser.setInputHTML(contents); + } catch (Exception e) { + throw new HTMLParseException(e); + } + + // Now parse the DOM tree + try { + // we start to iterate through the elements + parseNodes(htmlParser.elements(), new URLPointer(baseUrl), urls); + log.debug("End : parseNodes"); + } catch (ParserException e) { + throw new HTMLParseException(e); + } + + return urls.iterator(); + } + + /* + * A dummy class to pass the pointer of URL. + */ + private static class URLPointer { + private URLPointer(URL newUrl) { + url = newUrl; + } + private URL url; + } + + /** + * Recursively parse all nodes to pick up all URL s. + * @see e the nodes to be parsed + * @see baseUrl Base URL from which the HTML code was obtained + * @see urls URLCollection + */ + private void parseNodes(final NodeIterator e, + final URLPointer baseUrl, final URLCollection urls) + throws HTMLParseException, ParserException { + while(e.hasMoreNodes()) { + Node node = e.nextNode(); + // a url is always in a Tag. + if (!(node instanceof Tag)) { + continue; + } + Tag tag = (Tag) node; + String tagname=tag.getTagName(); + String binUrlStr = null; + + // first we check to see if body tag has a + // background set + if (tag instanceof BodyTag) { + binUrlStr = tag.getAttribute(ATT_BACKGROUND); + } else if (tag instanceof BaseHrefTag) { + BaseHrefTag baseHref = (BaseHrefTag) tag; + String baseref = baseHref.getBaseUrl(); + try { + if (!baseref.equals(""))// Bugzilla 30713 + { + baseUrl.url = ConversionUtils.makeRelativeURL(baseUrl.url, baseref); + } + } catch (MalformedURLException e1) { + throw new HTMLParseException(e1); + } + } else if (tag instanceof ImageTag) { + ImageTag image = (ImageTag) tag; + binUrlStr = image.getImageURL(); + } else if (tag instanceof AppletTag) { + // look for applets + + // This will only work with an Applet .class file. + // Ideally, this should be upgraded to work with Objects (IE) + // and archives (.jar and .zip) files as well. + AppletTag applet = (AppletTag) tag; + binUrlStr = applet.getAppletClass(); + } else if (tag instanceof ObjectTag) { + // look for Objects + ObjectTag applet = (ObjectTag) tag; + String data = applet.getAttribute(ATT_CODEBASE); + if(!StringUtils.isEmpty(data)) { + binUrlStr = data; + } + + data = applet.getAttribute(ATT_DATA); + if(!StringUtils.isEmpty(data)) { + binUrlStr = data; + } + + } else if (tag instanceof InputTag) { + // we check the input tag type for image + if (ATT_IS_IMAGE.equalsIgnoreCase(tag.getAttribute(ATT_TYPE))) { + // then we need to download the binary + binUrlStr = tag.getAttribute(ATT_SRC); + } + } else if (tag instanceof ScriptTag) { + binUrlStr = tag.getAttribute(ATT_SRC); + // Bug 51750 + } else if (tag instanceof FrameTag || tagname.equalsIgnoreCase(TAG_IFRAME)) { + binUrlStr = tag.getAttribute(ATT_SRC); + } else if (tagname.equalsIgnoreCase(TAG_EMBED) + || tagname.equalsIgnoreCase(TAG_BGSOUND)){ + binUrlStr = tag.getAttribute(ATT_SRC); + } else if (tagname.equalsIgnoreCase(TAG_LINK)) { + // Putting the string first means it works even if the attribute is null + if (STYLESHEET.equalsIgnoreCase(tag.getAttribute(ATT_REL))) { + binUrlStr = tag.getAttribute(ATT_HREF); + } + } else { + binUrlStr = tag.getAttribute(ATT_BACKGROUND); + } + + if (binUrlStr != null) { + urls.addURL(binUrlStr, baseUrl.url); + } + + // Now look for URLs in the STYLE attribute + String styleTagStr = tag.getAttribute(ATT_STYLE); + if(styleTagStr != null) { + HtmlParsingUtils.extractStyleURLs(baseUrl.url, urls, styleTagStr); + } + + // second, if the tag was a composite tag, + // recursively parse its children. + if (tag instanceof CompositeTag) { + CompositeTag composite = (CompositeTag) tag; + parseNodes(composite.elements(), baseUrl, urls); + } + } + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/HtmlParsingUtils.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HtmlParsingUtils.java new file mode 100644 index 00000000000..bf55eb4e95c --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/HtmlParsingUtils.java @@ -0,0 +1,403 @@ +/* + * 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.protocol.http.parser; + +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.PatternCacheLRU; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.tidy.Tidy; + +// For Junit tests @see TestHtmlParsingUtils + +public final class HtmlParsingUtils { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Private constructor to prevent instantiation. + */ + private HtmlParsingUtils() { + } + + /** + * Check if anchor matches by checking against: + * - protocol + * - domain + * - path + * - parameter names + * + * @param newLink target to match + * @param config pattern to match against + * + * @return true if target URL matches pattern URL + */ + public static boolean isAnchorMatched(HTTPSamplerBase newLink, HTTPSamplerBase config) + { + String query = null; + try { + query = URLDecoder.decode(newLink.getQueryString(), "UTF-8"); // $NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + // UTF-8 unsupported? You must be joking! + log.error("UTF-8 encoding not supported!"); + throw new Error("Should not happen: " + e.toString(), e); + } + + final Arguments arguments = config.getArguments(); + + final Perl5Matcher matcher = JMeterUtils.getMatcher(); + final PatternCacheLRU patternCache = JMeterUtils.getPatternCache(); + + if (!isEqualOrMatches(newLink.getProtocol(), config.getProtocol(), matcher, patternCache)){ + return false; + } + + final String domain = config.getDomain(); + if (domain != null && domain.length() > 0) { + if (!isEqualOrMatches(newLink.getDomain(), domain, matcher, patternCache)){ + return false; + } + } + + final String path = config.getPath(); + if (!newLink.getPath().equals(path) + && !matcher.matches(newLink.getPath(), patternCache.getPattern("[/]*" + path, // $NON-NLS-1$ + Perl5Compiler.READ_ONLY_MASK))) { + return false; + } + + PropertyIterator iter = arguments.iterator(); + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + final String name = item.getName(); + if (query.indexOf(name + "=") == -1) { // $NON-NLS-1$ + if (!(matcher.contains(query, patternCache.getPattern(name, Perl5Compiler.READ_ONLY_MASK)))) { + return false; + } + } + } + + return true; + } + + /** + * Arguments match if the input name matches the corresponding pattern name + * and the input value matches the pattern value, where the matching is done + * first using String equals, and then Regular Expression matching if the equals test fails. + * + * @param arg - input Argument + * @param patternArg - pattern to match against + * @return true if both name and value match + */ + public static boolean isArgumentMatched(Argument arg, Argument patternArg) { + final Perl5Matcher matcher = JMeterUtils.getMatcher(); + final PatternCacheLRU patternCache = JMeterUtils.getPatternCache(); + return + isEqualOrMatches(arg.getName(), patternArg.getName(), matcher, patternCache) + && + isEqualOrMatches(arg.getValue(), patternArg.getValue(), matcher, patternCache); + } + + /** + * Match the input argument against the pattern using String.equals() or pattern matching if that fails. + * + * @param arg input string + * @param pat pattern string + * @param matcher Perl5Matcher + * @param cache PatternCache + * + * @return true if input matches the pattern + */ + public static boolean isEqualOrMatches(String arg, String pat, Perl5Matcher matcher, PatternCacheLRU cache){ + return + arg.equals(pat) + || + matcher.matches(arg,cache.getPattern(pat,Perl5Compiler.READ_ONLY_MASK)); + } + + /** + * Match the input argument against the pattern using String.equals() or pattern matching if that fails + * using case-insenssitive matching. + * + * @param arg input string + * @param pat pattern string + * @param matcher Perl5Matcher + * @param cache PatternCache + * + * @return true if input matches the pattern + */ + public static boolean isEqualOrMatchesCaseBlind(String arg, String pat, Perl5Matcher matcher, PatternCacheLRU cache){ + return + arg.equalsIgnoreCase(pat) + || + matcher.matches(arg,cache.getPattern(pat,Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK)); + } + + /** + * Match the input argument against the pattern using String.equals() or pattern matching if that fails + * using case-insensitive matching. + * + * @param arg input string + * @param pat pattern string + * + * @return true if input matches the pattern + */ + public static boolean isEqualOrMatches(String arg, String pat){ + return isEqualOrMatches(arg, pat, JMeterUtils.getMatcher(), JMeterUtils.getPatternCache()); + } + + /** + * Match the input argument against the pattern using String.equals() or pattern matching if that fails + * using case-insensitive matching. + * + * @param arg input string + * @param pat pattern string + * + * @return true if input matches the pattern + */ + public static boolean isEqualOrMatchesCaseBlind(String arg, String pat){ + return isEqualOrMatchesCaseBlind(arg, pat, JMeterUtils.getMatcher(), JMeterUtils.getPatternCache()); + } + + /** + * Returns tidy as HTML parser. + * + * @return a tidy HTML parser + */ + public static Tidy getParser() { + log.debug("Start : getParser1"); + Tidy tidy = new Tidy(); + tidy.setInputEncoding("UTF8"); + tidy.setOutputEncoding("UTF8"); + tidy.setQuiet(true); + tidy.setShowWarnings(false); + + if (log.isDebugEnabled()) { + log.debug("getParser1 : tidy parser created - " + tidy); + } + + log.debug("End : getParser1"); + + return tidy; + } + + /** + * Returns a node representing a whole xml given an xml document. + * + * @param text + * an xml document + * @return a node representing a whole xml + */ + public static Node getDOM(String text) { + log.debug("Start : getDOM1"); + + try { + Node node = getParser().parseDOM(new ByteArrayInputStream(text.getBytes("UTF-8")), null);// $NON-NLS-1$ + + if (log.isDebugEnabled()) { + log.debug("node : " + node); + } + + log.debug("End : getDOM1"); + + return node; + } catch (UnsupportedEncodingException e) { + log.error("getDOM1 : Unsupported encoding exception - " + e); + log.debug("End : getDOM1"); + throw new RuntimeException("UTF-8 encoding failed", e); + } + } + + public static Document createEmptyDoc() { + return Tidy.createEmptyDocument(); + } + + /** + * Create a new Sampler based on an HREF string plus a contextual URL + * object. Given that an HREF string might be of three possible forms, some + * processing is required. + * + * @param parsedUrlString + * the url from the href + * @param context + * the context in which the href was found. This is used to + * extract url information that might be missing in + * parsedUrlString + * @return sampler with filled in information about the fully parsed url + * @throws MalformedURLException + * when the given url (parsedUrlString plus + * context is malformed) + */ + public static HTTPSamplerBase createUrlFromAnchor(String parsedUrlString, URL context) throws MalformedURLException { + if (log.isDebugEnabled()) { + log.debug("Creating URL from Anchor: " + parsedUrlString + ", base: " + context); + } + URL url = ConversionUtils.makeRelativeURL(context, parsedUrlString); + HTTPSamplerBase sampler =HTTPSamplerFactory.newInstance(); + sampler.setDomain(url.getHost()); + sampler.setProtocol(url.getProtocol()); + sampler.setPort(url.getPort()); + sampler.setPath(url.getPath()); + sampler.parseArguments(url.getQuery()); + + return sampler; + } + + public static List createURLFromForm(Node doc, URL context) { + String selectName = null; + LinkedList urlConfigs = new LinkedList(); + recurseForm(doc, urlConfigs, context, selectName, false); + /* + * NamedNodeMap atts = formNode.getAttributes(); + * if(atts.getNamedItem("action") == null) { throw new + * MalformedURLException(); } String action = + * atts.getNamedItem("action").getNodeValue(); UrlConfig url = + * createUrlFromAnchor(action, context); recurseForm(doc, url, + * selectName,true,formStart); + */ + return urlConfigs; + } + + // N.B. Since the tags are extracted from an HTML Form, any values must already have been encoded + private static boolean recurseForm(Node tempNode, LinkedList urlConfigs, URL context, String selectName, + boolean inForm) { + NamedNodeMap nodeAtts = tempNode.getAttributes(); + String tag = tempNode.getNodeName(); + try { + if (inForm) { + HTTPSamplerBase url = urlConfigs.getLast(); + if (tag.equalsIgnoreCase("form")) { // $NON-NLS-1$ + try { + urlConfigs.add(createFormUrlConfig(tempNode, context)); + } catch (MalformedURLException e) { + inForm = false; + } + } else if (tag.equalsIgnoreCase("input")) { // $NON-NLS-1$ + url.addEncodedArgument(getAttributeValue(nodeAtts, "name"), // $NON-NLS-1$ + getAttributeValue(nodeAtts, "value")); // $NON-NLS-1$ + } else if (tag.equalsIgnoreCase("textarea")) { // $NON-NLS-1$ + try { + url.addEncodedArgument(getAttributeValue(nodeAtts, "name"), // $NON-NLS-1$ + tempNode.getFirstChild().getNodeValue()); + } catch (NullPointerException e) { + url.addArgument(getAttributeValue(nodeAtts, "name"), ""); // $NON-NLS-1$ + } + } else if (tag.equalsIgnoreCase("select")) { // $NON-NLS-1$ + selectName = getAttributeValue(nodeAtts, "name"); // $NON-NLS-1$ + } else if (tag.equalsIgnoreCase("option")) { // $NON-NLS-1$ + String value = getAttributeValue(nodeAtts, "value"); // $NON-NLS-1$ + if (value == null) { + try { + value = tempNode.getFirstChild().getNodeValue(); + } catch (NullPointerException e) { + value = ""; // $NON-NLS-1$ + } + } + url.addEncodedArgument(selectName, value); + } + } else if (tag.equalsIgnoreCase("form")) { // $NON-NLS-1$ + try { + urlConfigs.add(createFormUrlConfig(tempNode, context)); + inForm = true; + } catch (MalformedURLException e) { + inForm = false; + } + } + } catch (Exception ex) { + log.warn("Some bad HTML " + printNode(tempNode), ex); + } + NodeList childNodes = tempNode.getChildNodes(); + for (int x = 0; x < childNodes.getLength(); x++) { + inForm = recurseForm(childNodes.item(x), urlConfigs, context, selectName, inForm); + } + return inForm; + } + + private static String getAttributeValue(NamedNodeMap att, String attName) { + try { + return att.getNamedItem(attName).getNodeValue(); + } catch (Exception ex) { + return ""; // $NON-NLS-1$ + } + } + + private static String printNode(Node node) { + StringBuilder buf = new StringBuilder(); + buf.append("<"); // $NON-NLS-1$ + buf.append(node.getNodeName()); + NamedNodeMap atts = node.getAttributes(); + for (int x = 0; x < atts.getLength(); x++) { + buf.append(" "); // $NON-NLS-1$ + buf.append(atts.item(x).getNodeName()); + buf.append("=\""); // $NON-NLS-1$ + buf.append(atts.item(x).getNodeValue()); + buf.append("\""); // $NON-NLS-1$ + } + + buf.append(">"); // $NON-NLS-1$ + + return buf.toString(); + } + + private static HTTPSamplerBase createFormUrlConfig(Node tempNode, URL context) throws MalformedURLException { + NamedNodeMap atts = tempNode.getAttributes(); + if (atts.getNamedItem("action") == null) { // $NON-NLS-1$ + throw new MalformedURLException(); + } + String action = atts.getNamedItem("action").getNodeValue(); // $NON-NLS-1$ + return createUrlFromAnchor(action, context); + } + + public static void extractStyleURLs(final URL baseUrl, final URLCollection urls, String styleTagStr) { + Perl5Matcher matcher = JMeterUtils.getMatcher(); + Pattern pattern = JMeterUtils.getPatternCache().getPattern( + "URL\\(\\s*('|\")(.*)('|\")\\s*\\)", // $NON-NLS-1$ + Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.SINGLELINE_MASK | Perl5Compiler.READ_ONLY_MASK); + PatternMatcherInput input = null; + input = new PatternMatcherInput(styleTagStr); + while (matcher.contains(input, pattern)) { + MatchResult match = matcher.getMatch(); + // The value is in the second group + String styleUrl = match.group(2); + urls.addURL(styleUrl, baseUrl); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/JTidyHTMLParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/JTidyHTMLParser.java new file mode 100644 index 00000000000..27b7fb43548 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/JTidyHTMLParser.java @@ -0,0 +1,248 @@ +/* + * 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.protocol.http.parser; + +import java.io.ByteArrayInputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.tidy.Tidy; +import org.xml.sax.SAXException; + +/** + * HtmlParser implementation using JTidy. + * + */ +class JTidyHTMLParser extends HTMLParser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected JTidyHTMLParser() { + super(); + } + + @Override + protected boolean isReusable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator getEmbeddedResourceURLs(String userAgent, byte[] html, URL baseUrl, URLCollection urls, String encoding) throws HTMLParseException { + Document dom = null; + try { + dom = (Document) getDOM(html, encoding); + } catch (SAXException se) { + throw new HTMLParseException(se); + } + + // Now parse the DOM tree + + scanNodes(dom, urls, baseUrl); + + return urls.iterator(); + } + + /** + * Scan nodes recursively, looking for embedded resources + * + * @param node - + * initial node + * @param urls - + * container for URLs + * @param baseUrl - + * used to create absolute URLs + * + * @return new base URL + */ + private URL scanNodes(Node node, URLCollection urls, URL baseUrl) throws HTMLParseException { + if (node == null) { + return baseUrl; + } + + String name = node.getNodeName(); + + int type = node.getNodeType(); + + switch (type) { + + case Node.DOCUMENT_NODE: + scanNodes(((Document) node).getDocumentElement(), urls, baseUrl); + break; + + case Node.ELEMENT_NODE: + + NamedNodeMap attrs = node.getAttributes(); + if (name.equalsIgnoreCase(TAG_BASE)) { + String tmp = getValue(attrs, ATT_HREF); + if (tmp != null) { + try { + baseUrl = ConversionUtils.makeRelativeURL(baseUrl, tmp); + } catch (MalformedURLException e) { + throw new HTMLParseException(e); + } + } + break; + } + + if (name.equalsIgnoreCase(TAG_IMAGE) || name.equalsIgnoreCase(TAG_EMBED)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + + if (name.equalsIgnoreCase(TAG_APPLET)) { + urls.addURL(getValue(attrs, "code"), baseUrl); + break; + } + + if (name.equalsIgnoreCase(TAG_OBJECT)) { + String data = getValue(attrs, "codebase"); + if(!StringUtils.isEmpty(data)) { + urls.addURL(data, baseUrl); + } + + data = getValue(attrs, "data"); + if(!StringUtils.isEmpty(data)) { + urls.addURL(data, baseUrl); + } + break; + } + + if (name.equalsIgnoreCase(TAG_INPUT)) { + String src = getValue(attrs, ATT_SRC); + String typ = getValue(attrs, ATT_TYPE); + if ((src != null) && (typ.equalsIgnoreCase(ATT_IS_IMAGE))) { + urls.addURL(src, baseUrl); + } + break; + } + if (name.equalsIgnoreCase(TAG_LINK) && getValue(attrs, ATT_REL).equalsIgnoreCase(STYLESHEET)) { + urls.addURL(getValue(attrs, ATT_HREF), baseUrl); + break; + } + if (name.equalsIgnoreCase(TAG_SCRIPT)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + if (name.equalsIgnoreCase(TAG_FRAME)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + if (name.equalsIgnoreCase(TAG_IFRAME)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + String back = getValue(attrs, ATT_BACKGROUND); + if (back != null) { + urls.addURL(back, baseUrl); + } + if (name.equalsIgnoreCase(TAG_BGSOUND)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + + String style = getValue(attrs, ATT_STYLE); + if (style != null) { + HtmlParsingUtils.extractStyleURLs(baseUrl, urls, style); + } + + NodeList children = node.getChildNodes(); + if (children != null) { + int len = children.getLength(); + for (int i = 0; i < len; i++) { + baseUrl = scanNodes(children.item(i), urls, baseUrl); + } + } + + break; + + // case Node.TEXT_NODE: + // break; + + default: + // ignored + break; + } + + return baseUrl; + + } + + /* + * Helper method to get an attribute value, if it exists @param attrs list + * of attributs @param attname attribute name @return + */ + private String getValue(NamedNodeMap attrs, String attname) { + String v = null; + Node n = attrs.getNamedItem(attname); + if (n != null) { + v = n.getNodeValue(); + } + return v; + } + + /** + * Returns tidy as HTML parser. + * + * @return a tidy HTML parser + */ + private static Tidy getTidyParser(String encoding) { + log.debug("Start : getParser"); + Tidy tidy = new Tidy(); + tidy.setInputEncoding(encoding); + tidy.setOutputEncoding("UTF8"); + tidy.setQuiet(true); + tidy.setShowWarnings(false); + if (log.isDebugEnabled()) { + log.debug("getParser : tidy parser created - " + tidy); + } + log.debug("End : getParser"); + return tidy; + } + + /** + * Returns a node representing a whole xml given an xml document. + * + * @param text + * an xml document (as a byte array) + * @return a node representing a whole xml + * + * @throws SAXException + * indicates an error parsing the xml document + */ + private static Node getDOM(byte[] text, String encoding) throws SAXException { + log.debug("Start : getDOM"); + Node node = getTidyParser(encoding).parseDOM(new ByteArrayInputStream(text), null); + if (log.isDebugEnabled()) { + log.debug("node : " + node); + } + log.debug("End : getDOM"); + return node; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/JsoupBasedHtmlParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/JsoupBasedHtmlParser.java new file mode 100644 index 00000000000..d91e9778a86 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/JsoupBasedHtmlParser.java @@ -0,0 +1,163 @@ +/* + * 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.protocol.http.parser; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +//import org.apache.jorphan.logging.LoggingManager; +//import org.apache.log.Logger; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.select.NodeTraversor; +import org.jsoup.select.NodeVisitor; + +/** + * Parser based on JSOUP + * @since 2.10 + * TODO Factor out common code between {@link LagartoBasedHtmlParser} and this one (adapter pattern) + */ +public class JsoupBasedHtmlParser extends HTMLParser { +// private static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * A dummy class to pass the pointer of URL. + */ + private static class URLPointer { + private URLPointer(URL newUrl) { + url = newUrl; + } + private URL url; + } + + private static final class JMeterNodeVisitor implements NodeVisitor { + + private URLCollection urls; + private URLPointer baseUrl; + + /** + * @param baseUrl base url to extract possibly missing information from urls found in urls + * @param urls collection of urls to consider + */ + public JMeterNodeVisitor(final URLPointer baseUrl, URLCollection urls) { + this.urls = urls; + this.baseUrl = baseUrl; + } + + private final void extractAttribute(Element tag, String attributeName) { + String url = tag.attr(attributeName); + if (!StringUtils.isEmpty(url)) { + urls.addURL(url, baseUrl.url); + } + } + + @Override + public void head(Node node, int depth) { + if (!(node instanceof Element)) { + return; + } + Element tag = (Element) node; + String tagName = tag.tagName().toLowerCase(); + if (tagName.equals(TAG_BODY)) { + extractAttribute(tag, ATT_BACKGROUND); + } else if (tagName.equals(TAG_SCRIPT)) { + extractAttribute(tag, ATT_SRC); + } else if (tagName.equals(TAG_BASE)) { + String baseref = tag.attr(ATT_HREF); + try { + if (!StringUtils.isEmpty(baseref))// Bugzilla 30713 + { + baseUrl.url = ConversionUtils.makeRelativeURL(baseUrl.url, baseref); + } + } catch (MalformedURLException e1) { + throw new RuntimeException(e1); + } + } else if (tagName.equals(TAG_IMAGE)) { + extractAttribute(tag, ATT_SRC); + } else if (tagName.equals(TAG_APPLET)) { + extractAttribute(tag, ATT_CODE); + } else if (tagName.equals(TAG_OBJECT)) { + extractAttribute(tag, ATT_CODEBASE); + extractAttribute(tag, ATT_DATA); + } else if (tagName.equals(TAG_INPUT)) { + // we check the input tag type for image + if (ATT_IS_IMAGE.equalsIgnoreCase(tag.attr(ATT_TYPE))) { + // then we need to download the binary + extractAttribute(tag, ATT_SRC); + } + } else if (tagName.equals(TAG_SCRIPT)) { + extractAttribute(tag, ATT_SRC); + // Bug 51750 + } else if (tagName.equals(TAG_FRAME) || tagName.equals(TAG_IFRAME)) { + extractAttribute(tag, ATT_SRC); + } else if (tagName.equals(TAG_EMBED)) { + extractAttribute(tag, ATT_SRC); + } else if (tagName.equals(TAG_BGSOUND)){ + extractAttribute(tag, ATT_SRC); + } else if (tagName.equals(TAG_LINK)) { + // Putting the string first means it works even if the attribute is null + if (STYLESHEET.equalsIgnoreCase(tag.attr(ATT_REL))) { + extractAttribute(tag, ATT_HREF); + } + } else { + extractAttribute(tag, ATT_BACKGROUND); + } + + + // Now look for URLs in the STYLE attribute + String styleTagStr = tag.attr(ATT_STYLE); + if(styleTagStr != null) { + HtmlParsingUtils.extractStyleURLs(baseUrl.url, urls, styleTagStr); + } + } + + @Override + public void tail(Node arg0, int arg1) { + // Noop + } + } + + @Override + public Iterator getEmbeddedResourceURLs(String userAgent, byte[] html, URL baseUrl, + URLCollection coll, String encoding) throws HTMLParseException { + try { + // TODO Handle conditional comments for IE + String contents = new String(html,encoding); + Document doc = Jsoup.parse(contents); + JMeterNodeVisitor nodeVisitor = new JMeterNodeVisitor(new URLPointer(baseUrl), coll); + new NodeTraversor(nodeVisitor).traverse(doc); + return coll.iterator(); + } catch (Exception e) { + throw new HTMLParseException(e); + } + } + + /* (non-Javadoc) + * @see org.apache.jmeter.protocol.http.parser.HTMLParser#isReusable() + */ + @Override + protected boolean isReusable() { + return true; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/LagartoBasedHtmlParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/LagartoBasedHtmlParser.java new file mode 100644 index 00000000000..97cbd09ba3d --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/LagartoBasedHtmlParser.java @@ -0,0 +1,242 @@ +/* + * 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.protocol.http.parser; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import java.util.Iterator; +import java.util.Stack; + +import jodd.lagarto.EmptyTagVisitor; +import jodd.lagarto.LagartoException; +import jodd.lagarto.LagartoParser; +import jodd.lagarto.LagartoParserConfig; +import jodd.lagarto.Tag; +import jodd.lagarto.TagType; +import jodd.lagarto.TagUtil; +import jodd.lagarto.dom.HtmlCCommentExpressionMatcher; +import jodd.log.LoggerFactory; +import jodd.log.impl.Slf4jLoggerFactory; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Parser based on Lagarto + * @since 2.10 + */ +public class LagartoBasedHtmlParser extends HTMLParser { + private static final Logger log = LoggingManager.getLoggerForClass(); + static { + LoggerFactory.setLoggerFactory(new Slf4jLoggerFactory()); + } + + /* + * A dummy class to pass the pointer of URL. + */ + private static class URLPointer { + private URLPointer(URL newUrl) { + url = newUrl; + } + private URL url; + } + + private static final class JMeterTagVisitor extends EmptyTagVisitor { + private HtmlCCommentExpressionMatcher htmlCCommentExpressionMatcher; + private URLCollection urls; + private URLPointer baseUrl; + private Float ieVersion; + private Stack enabled = new Stack(); + + /** + * @param baseUrl base url to add possibly missing information to urls found in urls + * @param urls collection of urls to consider + * @param ieVersion version number of IE to emulate + */ + public JMeterTagVisitor(final URLPointer baseUrl, URLCollection urls, Float ieVersion) { + this.urls = urls; + this.baseUrl = baseUrl; + this.ieVersion = ieVersion; + } + + private final void extractAttribute(Tag tag, String attributeName) { + CharSequence url = tag.getAttributeValue(attributeName); + if (!StringUtils.isEmpty(url)) { + urls.addURL(url.toString(), baseUrl.url); + } + } + /* + * (non-Javadoc) + * + * @see jodd.lagarto.EmptyTagVisitor#script(jodd.lagarto.Tag, + * java.lang.CharSequence) + */ + @Override + public void script(Tag tag, CharSequence body) { + if (!enabled.peek().booleanValue()) { + return; + } + extractAttribute(tag, ATT_SRC); + } + + /* + * (non-Javadoc) + * + * @see jodd.lagarto.EmptyTagVisitor#tag(jodd.lagarto.Tag) + */ + @Override + public void tag(Tag tag) { + if (!enabled.peek().booleanValue()) { + return; + } + TagType tagType = tag.getType(); + switch (tagType) { + case START: + case SELF_CLOSING: + if (tag.nameEquals(TAG_BODY)) { + extractAttribute(tag, ATT_BACKGROUND); + } else if (tag.nameEquals(TAG_BASE)) { + CharSequence baseref = tag.getAttributeValue(ATT_HREF); + try { + if (!StringUtils.isEmpty(baseref))// Bugzilla 30713 + { + baseUrl.url = ConversionUtils.makeRelativeURL(baseUrl.url, baseref.toString()); + } + } catch (MalformedURLException e1) { + throw new RuntimeException(e1); + } + } else if (tag.nameEquals(TAG_IMAGE)) { + extractAttribute(tag, ATT_SRC); + } else if (tag.nameEquals(TAG_APPLET)) { + extractAttribute(tag, ATT_CODE); + } else if (tag.nameEquals(TAG_OBJECT)) { + extractAttribute(tag, ATT_CODEBASE); + extractAttribute(tag, ATT_DATA); + } else if (tag.nameEquals(TAG_INPUT)) { + // we check the input tag type for image + CharSequence type = tag.getAttributeValue(ATT_TYPE); + if (type != null && TagUtil.equalsIgnoreCase(ATT_IS_IMAGE, type)) { + // then we need to download the binary + extractAttribute(tag, ATT_SRC); + } + } else if (tag.nameEquals(TAG_SCRIPT)) { + extractAttribute(tag, ATT_SRC); + // Bug 51750 + } else if (tag.nameEquals(TAG_FRAME) || tag.nameEquals(TAG_IFRAME)) { + extractAttribute(tag, ATT_SRC); + } else if (tag.nameEquals(TAG_EMBED)) { + extractAttribute(tag, ATT_SRC); + } else if (tag.nameEquals(TAG_BGSOUND)){ + extractAttribute(tag, ATT_SRC); + } else if (tag.nameEquals(TAG_LINK)) { + CharSequence relAttribute = tag.getAttributeValue(ATT_REL); + // Putting the string first means it works even if the attribute is null + if (relAttribute != null && TagUtil.equalsIgnoreCase(STYLESHEET,relAttribute)) { + extractAttribute(tag, ATT_HREF); + } + } else { + extractAttribute(tag, ATT_BACKGROUND); + } + + + // Now look for URLs in the STYLE attribute + CharSequence styleTagStr = tag.getAttributeValue(ATT_STYLE); + if(!StringUtils.isEmpty(styleTagStr)) { + HtmlParsingUtils.extractStyleURLs(baseUrl.url, urls, styleTagStr.toString()); + } + break; + case END: + break; + } + } + + /* (non-Javadoc) + * @see jodd.lagarto.EmptyTagVisitor#condComment(java.lang.CharSequence, boolean, boolean, boolean) + */ + @Override + public void condComment(CharSequence expression, boolean isStartingTag, + boolean isHidden, boolean isHiddenEndTag) { + // See http://css-tricks.com/how-to-create-an-ie-only-stylesheet/ + if(!isStartingTag) { + enabled.pop(); + } else { + if (htmlCCommentExpressionMatcher == null) { + htmlCCommentExpressionMatcher = new HtmlCCommentExpressionMatcher(); + } + String expressionString = expression.toString().trim(); + enabled.push(Boolean.valueOf(htmlCCommentExpressionMatcher.match(ieVersion.floatValue(), + expressionString))); + } + } + + /* (non-Javadoc) + * @see jodd.lagarto.EmptyTagVisitor#start() + */ + @Override + public void start() { + super.start(); + enabled.clear(); + enabled.push(Boolean.TRUE); + } + } + + @Override + public Iterator getEmbeddedResourceURLs(String userAgent, byte[] html, URL baseUrl, + URLCollection coll, String encoding) throws HTMLParseException { + try { + Float ieVersion = extractIEVersion(userAgent); + + String contents = new String(html,encoding); + // As per Jodd javadocs, emitStrings should be false for visitor for better performances + LagartoParser lagartoParser = new LagartoParser(contents, false); + LagartoParserConfig> config = new LagartoParserConfig>(); + config.setCaseSensitive(false); + // Conditional comments only apply for IE < 10 + config.setEnableConditionalComments(isEnableConditionalComments(ieVersion)); + + lagartoParser.setConfig(config); + JMeterTagVisitor tagVisitor = new JMeterTagVisitor(new URLPointer(baseUrl), coll, ieVersion); + lagartoParser.parse(tagVisitor); + return coll.iterator(); + } catch (LagartoException e) { + // TODO is it the best way ? https://bz.apache.org/bugzilla/show_bug.cgi?id=55634 + if(log.isDebugEnabled()) { + log.debug("Error extracting embedded resource URLs from:'"+baseUrl+"', probably not text content, message:"+e.getMessage()); + } + return Collections.emptyList().iterator(); + } catch (Exception e) { + throw new HTMLParseException(e); + } + } + + + + + + /* (non-Javadoc) + * @see org.apache.jmeter.protocol.http.parser.HTMLParser#isReusable() + */ + @Override + protected boolean isReusable() { + return true; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/RegexpHTMLParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/RegexpHTMLParser.java new file mode 100644 index 00000000000..eadcf93790d --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/RegexpHTMLParser.java @@ -0,0 +1,205 @@ +/* + * 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.protocol.http.parser; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; + +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +//NOTE: Also looked at using Java 1.4 regexp instead of ORO. The change was +//trivial. Performance did not improve -- at least not significantly. +//Finally decided for ORO following advise from Stefan Bodewig (message +//to jmeter-dev dated 25 Nov 2003 8:52 CET) [Jordi] +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * HtmlParser implementation using regular expressions. + *

+ * This class will find RLs specified in the following ways (where url + * represents the RL being found: + *

+ * + *

+ * This class will take into account the following construct: + *

    + *
  • <base href=url> + *
+ * + *

+ * But not the following: + *

    + *
  • < ... codebase=url ... > + *
+ * + */ +class RegexpHTMLParser extends HTMLParser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Regexp fragment matching a tag attribute's value (including the equals + * sign and any spaces before it). Note it matches unquoted values, which to + * my understanding, are not conformant to any of the HTML specifications, + * but are still quite common in the web and all browsers seem to understand + * them. + */ + private static final String VALUE = "\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\"'\\s>\\\\][^\\s>]*)(?=[\\s>]))"; + + // Note there's 3 capturing groups per value + + /** + * Regexp fragment matching the separation between two tag attributes. + */ + private static final String SEP = "\\s(?:[^>]*\\s)?"; + + /** + * Regular expression used against the HTML code to find the URIs of images, + * etc.: + */ + private static final String REGEXP = + "<(?:" + "!--.*?-->" + + "|BASE" + SEP + "HREF" + VALUE + + "|(?:IMG|SCRIPT|FRAME|IFRAME|BGSOUND)" + SEP + "SRC" + VALUE + + "|APPLET" + SEP + "CODE(?:BASE)?" + VALUE + + "|(?:EMBED|OBJECT)" + SEP + "(?:SRC|CODEBASE|DATA)" + VALUE + + "|(?:BODY|TABLE|TR|TD)" + SEP + "BACKGROUND" + VALUE + + "|[^<]+?STYLE\\s*=['\"].*?URL\\(\\s*['\"](.+?)['\"]\\s*\\)" + + "|INPUT(?:" + SEP + "(?:SRC" + VALUE + + "|TYPE\\s*=\\s*(?:\"image\"|'image'|image(?=[\\s>])))){2,}" + + "|LINK(?:" + SEP + "(?:HREF" + VALUE + + "|REL\\s*=\\s*(?:\"stylesheet\"|'stylesheet'|stylesheet(?=[\\s>])))){2,}" + ")"; + + // Number of capturing groups possibly containing Base HREFs: + private static final int NUM_BASE_GROUPS = 3; + + /** + * Thread-local input: + */ + private static final ThreadLocal localInput = + new ThreadLocal() { + @Override + protected PatternMatcherInput initialValue() { + return new PatternMatcherInput(new char[0]); + } + }; + + /** + * {@inheritDoc} + */ + @Override + protected boolean isReusable() { + return true; + } + + /** + * Make sure to compile the regular expression upon instantiation: + */ + protected RegexpHTMLParser() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator getEmbeddedResourceURLs(String userAgent, byte[] html, URL baseUrl, URLCollection urls, String encoding) throws HTMLParseException { + Pattern pattern= null; + Perl5Matcher matcher = null; + try { + matcher = JMeterUtils.getMatcher(); + PatternMatcherInput input = localInput.get(); + // TODO: find a way to avoid the cost of creating a String here -- + // probably a new PatternMatcherInput working on a byte[] would do + // better. + input.setInput(new String(html, encoding)); + pattern=JMeterUtils.getPatternCache().getPattern( + REGEXP, + Perl5Compiler.CASE_INSENSITIVE_MASK + | Perl5Compiler.SINGLELINE_MASK + | Perl5Compiler.READ_ONLY_MASK); + + while (matcher.contains(input, pattern)) { + MatchResult match = matcher.getMatch(); + String s; + if (log.isDebugEnabled()) { + log.debug("match groups " + match.groups() + " " + match.toString()); + } + // Check for a BASE HREF: + for (int g = 1; g <= NUM_BASE_GROUPS && g <= match.groups(); g++) { + s = match.group(g); + if (s != null) { + if (log.isDebugEnabled()) { + log.debug("new baseUrl: " + s + " - " + baseUrl.toString()); + } + try { + baseUrl = ConversionUtils.makeRelativeURL(baseUrl, s); + } catch (MalformedURLException e) { + // Doesn't even look like a URL? + // Maybe it isn't: Ignore the exception. + if (log.isDebugEnabled()) { + log.debug("Can't build base URL from RL " + s + " in page " + baseUrl, e); + } + } + } + } + for (int g = NUM_BASE_GROUPS + 1; g <= match.groups(); g++) { + s = match.group(g); + if (s != null) { + if (log.isDebugEnabled()) { + log.debug("group " + g + " - " + match.group(g)); + } + urls.addURL(s, baseUrl); + } + } + } + return urls.iterator(); + } catch (UnsupportedEncodingException e) { + throw new HTMLParseException(e.getMessage(), e); + } catch (MalformedCachePatternException e) { + throw new HTMLParseException(e.getMessage(), e); + } finally { + JMeterUtils.clearMatcherMemory(matcher, pattern); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/URLCollection.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/URLCollection.java new file mode 100644 index 00000000000..fb041844c75 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/URLCollection.java @@ -0,0 +1,134 @@ +/* + * 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.protocol.http.parser; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collection; +import java.util.Iterator; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Collection class designed for handling URLs + *

+ * Before a URL is added to the collection, it is wrapped in a URLString class. + * The iterator unwraps the URL before return. + *

+ * N.B. Designed for use by HTMLParser, so is not a full implementation - e.g. + * does not support remove() + * + */ +public class URLCollection { + private static final Logger log = LoggingManager.getLoggerForClass(); + private final Collection coll; + + /** + * Creates a new URLCollection from an existing Collection + * + * @param c collection to start with + */ + public URLCollection(Collection c) { + coll = c; + } + + /** + * Adds the URL to the Collection, first wrapping it in the URLString class + * + * @param u + * URL to add + * @return boolean condition returned by the add() method of the underlying + * collection + */ + public boolean add(URL u) { + return coll.add(new URLString(u)); + } + + /** + * Convenience method for adding URLs to the collection. If the url + * parameter is null, empty or URL is malformed, nothing is + * done + * + * @param url + * String, may be null or empty + * @param baseUrl + * base for url to add information, which might be + * missing in url + * @return boolean condition returned by the add() method of the underlying + * collection + */ + public boolean addURL(String url, URL baseUrl) { + if (url == null || url.length() == 0) { + return false; + } + //url.replace('+',' '); + url=StringEscapeUtils.unescapeXml(url); + boolean b = false; + try { + b = this.add(ConversionUtils.makeRelativeURL(baseUrl, url)); + } catch (MalformedURLException mfue) { + // No WARN message to avoid performance impact + if(log.isDebugEnabled()) { + log.debug("Error occured building relative url for:"+url+", message:"+mfue.getMessage()); + } + // No point in adding the URL as String as it will result in null + // returned during iteration, see URLString + // See https://bz.apache.org/bugzilla/show_bug.cgi?id=55092 + return false; + } + return b; + } + + public Iterator iterator() { + return new UrlIterator(coll.iterator()); + } + + /* + * Private iterator used to unwrap the URL from the URLString class + * + */ + private static class UrlIterator implements Iterator { + private final Iterator iter; + + UrlIterator(Iterator i) { + iter = i; + } + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + /* + * Unwraps the URLString class to return the URL + */ + @Override + public URL next() { + return iter.next().getURL(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/parser/URLString.java b/src/protocol/http/org/apache/jmeter/protocol/http/parser/URLString.java new file mode 100644 index 00000000000..3a7b593313e --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/parser/URLString.java @@ -0,0 +1,77 @@ +/* + * 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.protocol.http.parser; + +import java.net.URL; + +/** + * Helper class to allow URLs to be stored in Collections without incurring the + * cost of the hostname lookup performed by the URL methods equals() and + * hashCode() URL is a final class, so cannot be extended ... + * + * @version $Revision$ + */ +public class URLString implements Comparable { + + private final URL url; + + private final String urlAsString; + + private final int hashCode; + + public URLString(URL u) { + url = u; + urlAsString = u.toExternalForm(); + /* + * TODO improve string version to better match browser behaviour? e.g. + * do browsers regard http://host/ and http://Host:80/ as the same? If + * so, it would be better to reflect this in the string + */ + + hashCode = urlAsString.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return urlAsString; + } + + public URL getURL() { + return url; + } + + /** {@inheritDoc} */ + @Override + public int compareTo(URLString o) { + return urlAsString.compareTo(o.toString()); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + return (o instanceof URLString && urlAsString.equals(o.toString())); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return hashCode; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/AbstractSamplerCreator.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/AbstractSamplerCreator.java new file mode 100644 index 00000000000..e31f8a7471a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/AbstractSamplerCreator.java @@ -0,0 +1,157 @@ +/* + * 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.protocol.http.proxy; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Base class for SamplerCreator + */ +public abstract class AbstractSamplerCreator implements SamplerCreator { + + protected static final String HTTP = "http"; // $NON-NLS-1$ + protected static final String HTTPS = "https"; // $NON-NLS-1$ + + /** Filetype to be used for the temporary binary files*/ + private static final String binaryFileSuffix = + JMeterUtils.getPropDefault("proxy.binary.filesuffix",// $NON-NLS-1$ + ".binary"); // $NON-NLS-1$ + + /** Which content-types will be treated as binary (exact match) */ + private static final Set binaryContentTypes = new HashSet(); + + /** Where to store the temporary binary files */ + private static final String binaryDirectory = + JMeterUtils.getPropDefault("proxy.binary.directory",// $NON-NLS-1$ + System.getProperty("user.dir")); // $NON-NLS-1$ proxy.binary.filetype=binary + + static { + String binaries = JMeterUtils.getPropDefault("proxy.binary.types", // $NON-NLS-1$ + "application/x-amf,application/x-java-serialized-object"); // $NON-NLS-1$ + if (binaries.length() > 0){ + StringTokenizer s = new StringTokenizer(binaries,"|, ");// $NON-NLS-1$ + while (s.hasMoreTokens()){ + binaryContentTypes.add(s.nextToken()); + } + } + } + + /* + * Optionally number the requests + */ + private static final boolean numberRequests = + JMeterUtils.getPropDefault("proxy.number.requests", true); // $NON-NLS-1$ + + private static volatile int requestNumber = 0;// running number + + + /** + * + */ + /** + * + */ + public AbstractSamplerCreator() { + super(); + } + + /** + * @return int request number + */ + protected static int getRequestNumber() { + return requestNumber; + } + + /** + * Increment request number + */ + protected static void incrementRequestNumber() { + requestNumber++; + } + + /** + * @return boolean is numbering requests is required + */ + protected static boolean isNumberRequests() { + return numberRequests; + } + + /** + * @param contentType String content type + * @return true if contentType is part of binary declared types + */ + protected boolean isBinaryContent(String contentType) { + if (contentType == null) { + return false; + } + return binaryContentTypes.contains(contentType); + } + + /** + * @return String binary file suffix + */ + protected String getBinaryFileSuffix() { + return binaryFileSuffix; + } + + /** + * @return String binary directory + */ + protected String getBinaryDirectory() { + return binaryDirectory; + } + + /** + * @see org.apache.jmeter.protocol.http.proxy.SamplerCreator#postProcessSampler(org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase, org.apache.jmeter.samplers.SampleResult) + */ + @Override + public void postProcessSampler(HTTPSamplerBase sampler, SampleResult result) { + // NOOP + } + + /** + * @see org.apache.jmeter.protocol.http.proxy.SamplerCreator#createAndPopulateSampler(org.apache.jmeter.protocol.http.proxy.HttpRequestHdr, java.util.Map, java.util.Map) + */ + @Override + public HTTPSamplerBase createAndPopulateSampler(HttpRequestHdr request, + Map pageEncodings, Map formEncodings) throws Exception { + HTTPSamplerBase sampler = createSampler(request, pageEncodings, formEncodings); + populateSampler(sampler, request, pageEncodings, formEncodings); + return sampler; + } + + /** + * Default implementation returns an empty list + * @see SamplerCreator#createChildren(HTTPSamplerBase, SampleResult) + */ + @Override + public List createChildren(HTTPSamplerBase sampler, SampleResult result) { + return Collections.emptyList(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/Daemon.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/Daemon.java new file mode 100644 index 00000000000..7ca22dfc64b --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/Daemon.java @@ -0,0 +1,164 @@ +/* + * 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.protocol.http.proxy; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.gui.Stoppable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Web daemon thread. Creates main socket on port 8080 and listens on it + * forever. For each client request, creates a Proxy thread to handle the + * request. + * + */ +public class Daemon extends Thread implements Stoppable { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The time (in milliseconds) to wait when accepting a client connection. + * The accept will be retried until the Daemon is told to stop. So this + * interval is the longest time that the Daemon will have to wait after + * being told to stop. + */ + private static final int ACCEPT_TIMEOUT = 1000; + + /** The port to listen on. */ + private final int daemonPort; + + private final ServerSocket mainSocket; + + /** True if the Daemon is currently running. */ + private volatile boolean running; + + /** The target which will receive the generated JMeter test components. */ + private final ProxyControl target; + + /** + * The proxy class which will be used to handle individual requests. This + * class must be the {@link Proxy} class or a subclass. + */ + private final Class proxyClass; + + /** + * Create a new Daemon with the specified port and target. + * + * @param port + * the port to listen on. + * @param target + * the target which will receive the generated JMeter test + * components. + * @throws IOException if an I/O error occurs opening the socket + * @throws IllegalArgumentException if port is outside the allowed range from 0 to 65535 + * @throws SocketException when something is wrong on the underlying protocol layer + */ + public Daemon(int port, ProxyControl target) throws IOException { + this(port, target, Proxy.class); + } + + /** + * Create a new Daemon with the specified port and target, using the + * specified class to handle individual requests. + * + * @param port + * the port to listen on. + * @param target + * the target which will receive the generated JMeter test + * components. + * @param proxyClass + * the proxy class to use to handle individual requests. This + * class must be the {@link Proxy} class or a subclass. + * @throws IOException if an I/O error occurs opening the socket + * @throws IllegalArgumentException if port is outside the allowed range from 0 to 65535 + * @throws SocketException when something is wrong on the underlying protocol layer + */ + public Daemon(int port, ProxyControl target, Class proxyClass) throws IOException { + super("HTTP Proxy Daemon"); + this.target = target; + this.daemonPort = port; + this.proxyClass = proxyClass; + log.info("Creating Daemon Socket on port: " + daemonPort); + mainSocket = new ServerSocket(daemonPort); + mainSocket.setSoTimeout(ACCEPT_TIMEOUT); + } + + /** + * Listen on the daemon port and handle incoming requests. This method will + * not exit until {@link #stopServer()} is called or an error occurs. + */ + @Override + public void run() { + running = true; + log.info("Test Script Recorder up and running!"); + + // Maps to contain page and form encodings + // TODO - do these really need to be shared between all Proxy instances? + Map pageEncodings = Collections.synchronizedMap(new HashMap()); + Map formEncodings = Collections.synchronizedMap(new HashMap()); + + try { + while (running) { + try { + // Listen on main socket + Socket clientSocket = mainSocket.accept(); + if (running) { + // Pass request to new proxy thread + Proxy thd = proxyClass.newInstance(); + thd.configure(clientSocket, target, pageEncodings, formEncodings); + thd.start(); + } + } catch (InterruptedIOException e) { + continue; + // Timeout occurred. Ignore, and keep looping until we're + // told to stop running. + } + } + log.info("HTTP(S) Test Script Recorder stopped"); + } catch (Exception e) { + log.warn("HTTP(S) Test Script Recorder stopped", e); + } finally { + JOrphanUtils.closeQuietly(mainSocket); + } + + // Clear maps + pageEncodings = null; + formEncodings = null; + } + + /** + * Stop the proxy daemon. The daemon may not stop immediately. + * + * see #ACCEPT_TIMEOUT + */ + @Override + public void stopServer() { + running = false; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java new file mode 100644 index 00000000000..db6735d655a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java @@ -0,0 +1,425 @@ +/* + * 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.protocol.http.proxy; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.config.MultipartUrlConfig; +import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jmeter.protocol.http.sampler.PostWriter; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Default implementation that handles classical HTTP textual + Multipart requests + */ +public class DefaultSamplerCreator extends AbstractSamplerCreator { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * + */ + public DefaultSamplerCreator() { + } + + /** + * @see org.apache.jmeter.protocol.http.proxy.SamplerCreator#getManagedContentTypes() + */ + @Override + public String[] getManagedContentTypes() { + return new String[0]; + } + + /** + * + * @see org.apache.jmeter.protocol.http.proxy.SamplerCreator#createSampler(org.apache.jmeter.protocol.http.proxy.HttpRequestHdr, java.util.Map, java.util.Map) + */ + @Override + public HTTPSamplerBase createSampler(HttpRequestHdr request, + Map pageEncodings, Map formEncodings) { + // Instantiate the sampler + HTTPSamplerBase sampler = HTTPSamplerFactory.newInstance(request.getHttpSamplerName()); + + sampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName()); + + // Defaults + sampler.setFollowRedirects(false); + sampler.setUseKeepAlive(true); + + if (log.isDebugEnabled()) { + log.debug("getSampler: sampler path = " + sampler.getPath()); + } + return sampler; + } + + /** + * @see org.apache.jmeter.protocol.http.proxy.SamplerCreator#populateSampler(org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase, org.apache.jmeter.protocol.http.proxy.HttpRequestHdr, java.util.Map, java.util.Map) + */ + @Override + public final void populateSampler(HTTPSamplerBase sampler, + HttpRequestHdr request, Map pageEncodings, + Map formEncodings) throws Exception{ + computeFromHeader(sampler, request, pageEncodings, formEncodings); + + computeFromPostBody(sampler, request); + if (log.isDebugEnabled()) { + log.debug("sampler path = " + sampler.getPath()); + } + Arguments arguments = sampler.getArguments(); + if(arguments.getArgumentCount() == 1 && arguments.getArgument(0).getName().length()==0) { + sampler.setPostBodyRaw(true); + } + } + + /** + * Compute sampler informations from Request Header + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map of page encodings + * @param formEncodings Map of form encodings + * @throws Exception when something fails + */ + protected void computeFromHeader(HTTPSamplerBase sampler, + HttpRequestHdr request, Map pageEncodings, + Map formEncodings) throws Exception { + computeDomain(sampler, request); + + computeMethod(sampler, request); + + computePort(sampler, request); + + computeProtocol(sampler, request); + + computeContentEncoding(sampler, request, + pageEncodings, formEncodings); + + computePath(sampler, request); + + computeSamplerName(sampler, request); + } + + /** + * Compute sampler informations from Request Header + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + * @throws Exception when something fails + */ + protected void computeFromPostBody(HTTPSamplerBase sampler, + HttpRequestHdr request) throws Exception { + // If it was a HTTP GET request, then all parameters in the URL + // has been handled by the sampler.setPath above, so we just need + // to do parse the rest of the request if it is not a GET request + if((!HTTPConstants.CONNECT.equals(request.getMethod())) && (!HTTPConstants.GET.equals(request.getMethod()))) { + // Check if it was a multipart http post request + final String contentType = request.getContentType(); + MultipartUrlConfig urlConfig = request.getMultipartConfig(contentType); + String contentEncoding = sampler.getContentEncoding(); + // Get the post data using the content encoding of the request + String postData = null; + if (log.isDebugEnabled()) { + if(!StringUtils.isEmpty(contentEncoding)) { + log.debug("Using encoding " + contentEncoding + " for request body"); + } + else { + log.debug("No encoding found, using JRE default encoding for request body"); + } + } + + + if (!StringUtils.isEmpty(contentEncoding)) { + postData = new String(request.getRawPostData(), contentEncoding); + } else { + // Use default encoding + postData = new String(request.getRawPostData(), PostWriter.ENCODING); + } + + if (urlConfig != null) { + urlConfig.parseArguments(postData); + // Tell the sampler to do a multipart post + sampler.setDoMultipartPost(true); + // Remove the header for content-type and content-length, since + // those values will most likely be incorrect when the sampler + // performs the multipart request, because the boundary string + // will change + request.getHeaderManager().removeHeaderNamed(HttpRequestHdr.CONTENT_TYPE); + request.getHeaderManager().removeHeaderNamed(HttpRequestHdr.CONTENT_LENGTH); + + // Set the form data + sampler.setArguments(urlConfig.getArguments()); + // Set the file uploads + sampler.setHTTPFiles(urlConfig.getHTTPFileArgs().asArray()); + sampler.setDoBrowserCompatibleMultipart(true); // we are parsing browser input here + // used when postData is pure xml (eg. an xml-rpc call) or for PUT + } else if (postData.trim().startsWith(" 0) { + if (isBinaryContent(contentType)) { + try { + File tempDir = new File(getBinaryDirectory()); + File out = File.createTempFile(request.getMethod(), getBinaryFileSuffix(), tempDir); + FileUtils.writeByteArrayToFile(out,request.getRawPostData()); + HTTPFileArg [] files = {new HTTPFileArg(out.getPath(),"",contentType)}; + sampler.setHTTPFiles(files); + } catch (IOException e) { + log.warn("Could not create binary file: "+e); + } + } else { + // Just put the whole postbody as the value of a parameter + sampler.addNonEncodedArgument("", postData, ""); //used when postData is pure xml (ex. an xml-rpc call) + } + } + } + } + + /** + * Tries parsing to see if content is xml + * @param postData String + * @return boolean + */ + private static final boolean isPotentialXml(String postData) { + try { + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser saxParser = spf.newSAXParser(); + XMLReader xmlReader = saxParser.getXMLReader(); + ErrorDetectionHandler detectionHandler = + new ErrorDetectionHandler(); + xmlReader.setContentHandler(detectionHandler); + xmlReader.setErrorHandler(detectionHandler); + xmlReader.parse(new InputSource(new StringReader(postData))); + return !detectionHandler.isErrorDetected(); + } catch (ParserConfigurationException e) { + return false; + } catch (SAXException e) { + return false; + } catch (IOException e) { + return false; + } + } + + private static final class ErrorDetectionHandler extends DefaultHandler { + private boolean errorDetected = false; + public ErrorDetectionHandler() { + super(); + } + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException) + */ + @Override + public void error(SAXParseException e) throws SAXException { + this.errorDetected = true; + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException) + */ + @Override + public void fatalError(SAXParseException e) throws SAXException { + this.errorDetected = true; + } + /** + * @return the errorDetected + */ + public boolean isErrorDetected() { + return errorDetected; + } + } + /** + * Compute sampler name + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computeSamplerName(HTTPSamplerBase sampler, + HttpRequestHdr request) { + if (!HTTPConstants.CONNECT.equals(request.getMethod()) && isNumberRequests()) { + incrementRequestNumber(); + sampler.setName(getRequestNumber() + " " + sampler.getPath()); + } else { + sampler.setName(sampler.getPath()); + } + } + + /** + * Set path on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computePath(HTTPSamplerBase sampler, HttpRequestHdr request) { + if(sampler.getContentEncoding() != null) { + sampler.setPath(request.getPath(), sampler.getContentEncoding()); + } + else { + // Although the spec says UTF-8 should be used for encoding URL parameters, + // most browser use ISO-8859-1 for default if encoding is not known. + // We use null for contentEncoding, then the url parameters will be added + // with the value in the URL, and the "encode?" flag set to false + sampler.setPath(request.getPath(), null); + } + if (log.isDebugEnabled()) { + log.debug("Proxy: setting path: " + sampler.getPath()); + } + } + + /** + * Compute content encoding + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map of page encodings + * @param formEncodings Map of form encodings + * @throws MalformedURLException when no {@link URL} could be built from + * sampler and request + */ + protected void computeContentEncoding(HTTPSamplerBase sampler, + HttpRequestHdr request, Map pageEncodings, + Map formEncodings) throws MalformedURLException { + URL pageUrl = null; + if(sampler.isProtocolDefaultPort()) { + pageUrl = new URL(sampler.getProtocol(), sampler.getDomain(), request.getPath()); + } + else { + pageUrl = new URL(sampler.getProtocol(), sampler.getDomain(), + sampler.getPort(), request.getPath()); + } + String urlWithoutQuery = request.getUrlWithoutQuery(pageUrl); + + + String contentEncoding = computeContentEncoding(request, pageEncodings, + formEncodings, urlWithoutQuery); + + // Set the content encoding + if(!StringUtils.isEmpty(contentEncoding)) { + sampler.setContentEncoding(contentEncoding); + } + } + + /** + * Computes content encoding from request and if not found uses pageEncoding + * and formEncoding to see if URL was previously computed with a content type + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map of page encodings + * @param formEncodings Map of form encodings + * @param urlWithoutQuery the request URL without the query parameters + * @return String content encoding + */ + protected String computeContentEncoding(HttpRequestHdr request, + Map pageEncodings, + Map formEncodings, String urlWithoutQuery) { + // Check if the request itself tells us what the encoding is + String contentEncoding = null; + String requestContentEncoding = ConversionUtils.getEncodingFromContentType( + request.getContentType()); + if(requestContentEncoding != null) { + contentEncoding = requestContentEncoding; + } + else { + // Check if we know the encoding of the page + if (pageEncodings != null) { + synchronized (pageEncodings) { + contentEncoding = pageEncodings.get(urlWithoutQuery); + } + } + // Check if we know the encoding of the form + if (formEncodings != null) { + synchronized (formEncodings) { + String formEncoding = formEncodings.get(urlWithoutQuery); + // Form encoding has priority over page encoding + if (formEncoding != null) { + contentEncoding = formEncoding; + } + } + } + } + return contentEncoding; + } + + /** + * Set protocol on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computeProtocol(HTTPSamplerBase sampler, + HttpRequestHdr request) { + sampler.setProtocol(request.getProtocol(sampler)); + } + + /** + * Set Port on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computePort(HTTPSamplerBase sampler, HttpRequestHdr request) { + sampler.setPort(request.serverPort()); + if (log.isDebugEnabled()) { + log.debug("Proxy: setting port: " + sampler.getPort()); + } + } + + /** + * Set method on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computeMethod(HTTPSamplerBase sampler, HttpRequestHdr request) { + sampler.setMethod(request.getMethod()); + log.debug("Proxy: setting method: " + sampler.getMethod()); + } + + /** + * Set domain on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computeDomain(HTTPSamplerBase sampler, HttpRequestHdr request) { + sampler.setDomain(request.serverName()); + if (log.isDebugEnabled()) { + log.debug("Proxy: setting server: " + sampler.getDomain()); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/FormCharSetFinder.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/FormCharSetFinder.java new file mode 100644 index 00000000000..20f78caf135 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/FormCharSetFinder.java @@ -0,0 +1,135 @@ +/* + * 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.protocol.http.proxy; + +import java.util.Map; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.jmeter.protocol.http.parser.HTMLParseException; +import org.htmlparser.Node; +import org.htmlparser.Parser; +import org.htmlparser.Tag; +import org.htmlparser.tags.CompositeTag; +import org.htmlparser.tags.FormTag; +import org.htmlparser.util.NodeIterator; +import org.htmlparser.util.ParserException; + +/** + * A parser for html, to find the form tags, and their accept-charset value + */ +// made public see Bug 49976 +public class FormCharSetFinder { + private static final Logger log = LoggingManager.getLoggerForClass(); + + static { + log.info("Using htmlparser version: "+Parser.getVersion()); + } + + public FormCharSetFinder() { + super(); + } + + /** + * Add form action urls and their corresponding encodings for all forms on the page + * + * @param html the html to parse for form encodings + * @param formEncodings the Map where form encodings should be added + * @param pageEncoding the encoding used for the whole page + * @throws HTMLParseException when parsing the html fails + */ + public void addFormActionsAndCharSet(String html, Map formEncodings, String pageEncoding) + throws HTMLParseException { + if (log.isDebugEnabled()) { + log.debug("Parsing html of: " + html); + } + + Parser htmlParser = null; + try { + htmlParser = new Parser(); + htmlParser.setInputHTML(html); + } catch (Exception e) { + throw new HTMLParseException(e); + } + + // Now parse the DOM tree + try { + // we start to iterate through the elements + parseNodes(htmlParser.elements(), formEncodings, pageEncoding); + log.debug("End : parseNodes"); + } catch (ParserException e) { + throw new HTMLParseException(e); + } + } + + /** + * Recursively parse all nodes to pick up all form encodings + * + * @param e the nodes to be parsed + * @param formEncodings the Map where we should add form encodings found + * @param pageEncoding the encoding used for the page where the nodes are present + */ + private void parseNodes(final NodeIterator e, Map formEncodings, String pageEncoding) + throws HTMLParseException, ParserException { + while(e.hasMoreNodes()) { + Node node = e.nextNode(); + // a url is always in a Tag. + if (!(node instanceof Tag)) { + continue; + } + Tag tag = (Tag) node; + + // Only check form tags + if (tag instanceof FormTag) { + // Find the action / form url + String action = tag.getAttribute("action"); + String acceptCharSet = tag.getAttribute("accept-charset"); + if(action != null && action.length() > 0) { + // We use the page encoding where the form resides, as the + // default encoding for the form + String formCharSet = pageEncoding; + // Check if we found an accept-charset attribute on the form + if(acceptCharSet != null) { + String[] charSets = JOrphanUtils.split(acceptCharSet, ","); + // Just use the first one of the possible many charsets + if(charSets.length > 0) { + formCharSet = charSets[0].trim(); + if(formCharSet.length() == 0) { + formCharSet = null; + } + } + } + if(formCharSet != null) { + synchronized (formEncodings) { + formEncodings.put(action, formCharSet); + } + } + } + } + + // second, if the tag was a composite tag, + // recursively parse its children. + if (tag instanceof CompositeTag) { + CompositeTag composite = (CompositeTag) tag; + parseNodes(composite.elements(), formEncodings, pageEncoding); + } + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/HttpReplyHdr.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/HttpReplyHdr.java new file mode 100644 index 00000000000..fcccbdc85c8 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/HttpReplyHdr.java @@ -0,0 +1,292 @@ +/* + * 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.protocol.http.proxy; + +/** + * Utility class to generate HTTP responses of various types. + * + * @version $Revision$ + */ +public final class HttpReplyHdr { + /** String representing a carriage-return/line-feed pair. */ + private static final String CR = "\r\n"; + + /** A HTTP protocol version string. */ + private static final String HTTP_PROTOCOL = "HTTP/1.0"; + + /** The HTTP server name. */ + private static final String HTTP_SERVER = "Java Proxy Server"; + + /** + * Don't allow instantiation of this utility class. + */ + private HttpReplyHdr() { + } + + /** + * Forms a http ok reply header + * + * @param contentType + * the mime-type of the content + * @param contentLength + * the length of the content + * @return a string with the header in it + */ + public static String formOk(String contentType, long contentLength) { + StringBuilder out = new StringBuilder(); + + out.append(HTTP_PROTOCOL).append(" 200 Ok").append(CR); + out.append("Server: ").append(HTTP_SERVER).append(CR); + out.append("MIME-version: 1.0").append(CR); + + if (0 < contentType.length()) { + out.append("Content-Type: ").append(contentType).append(CR); + } else { + out.append("Content-Type: text/html").append(CR); + } + + if (0 != contentLength) { + out.append("Content-Length: ").append(contentLength).append(CR); + } + + out.append(CR); + + return out.toString(); + } + + /** + * private! builds an http document describing a headers reason. + * + * @param error + * Error name. + * @param description + * Errors description. + * @return A string with the HTML description body + */ + private static String formErrorBody(String error, String description) { + StringBuilder out = new StringBuilder(); + // Generate Error Body + out.append(""); + out.append(error); + out.append(""); + out.append("

").append(error).append("

\n"); + out.append("

"); + out.append(description); + out.append(""); + return out.toString(); + } + + /** + * builds an http document describing an error. + * + * @param error + * Error name. + * @param description + * Errors description. + * @return A string with the HTML description body + */ + private static String formError(String error, String description) { + /* + * A HTTP RESPONSE HEADER LOOKS ALOT LIKE: + * + * HTTP/1.0 200 OK Date: Wednesday, 02-Feb-94 23:04:12 GMT Server: + * NCSA/1.1 MIME-version: 1.0 Last-modified: Monday, 15-Nov-93 23:33:16 + * GMT Content-Type: text/html Content-Length: 2345 \r\n + */ + + String body = formErrorBody(error, description); + StringBuilder header = new StringBuilder(); + + header.append(HTTP_PROTOCOL).append(" ").append(error).append(CR); + header.append("Server: ").append(HTTP_SERVER).append(CR); + header.append("MIME-version: 1.0").append(CR); + header.append("Content-Type: text/html").append(CR); + + header.append("Content-Length: ").append(body.length()).append(CR); + + header.append(CR); + header.append(body); + + return header.toString(); + } + + /** + * Indicates a new file was created. + * + * @return The header in a string; + */ + public static String formCreated() { + return formError("201 Created", "Object was created"); + } + + /** + * Indicates the document was accepted. + * + * @return The header in a string; + */ + public static String formAccepted() { + return formError("202 Accepted", "Object checked in"); + } + + /** + * Indicates only a partial responce was sent. + * + * @return The header in a string; + */ + public static String formPartial() { + return formError("203 Partial", "Only partail document available"); + } + + /** + * Indicates a requested URL has moved to a new address or name. + * + * @return The header in a string; + */ + public static String formMoved() { + // 300 codes tell client to do actions + return formError("301 Moved", "File has moved"); + } + + /** + * Never seen this used. + * + * @return The header in a string; + */ + public static String formFound() { + return formError("302 Found", "Object was found"); + } + + /** + * The requested method is not implemented by the server. + * + * @return The header in a string; + */ + public static String formMethod() { + return formError("303 Method unseported", "Method unseported"); + } + + /** + * Indicates remote copy of the requested object is current. + * + * @return The header in a string; + */ + public static String formNotModified() { + return formError("304 Not modified", "Use local copy"); + } + + /** + * Client not authorized for the request. + * + * @return The header in a string; + */ + public static String formUnauthorized() { + return formError("401 Unathorized", "Unathorized use of this service"); + } + + /** + * Payment is required for service. + * + * @return The header in a string; + */ + public static String formPaymentNeeded() { + return formError("402 Payment required", "Payment is required"); + } + + /** + * Client if forbidden to get the request service. + * + * @return The header in a string; + */ + public static String formForbidden() { + return formError("403 Forbidden", "You need permission for this service"); + } + + /** + * The requested object was not found. + * + * @return The header in a string; + */ + public static String formNotFound() { + return formError("404 Not_found", "Requested object was not found"); + } + + /** + * The server had a problem and could not fulfill the request. + * + * @return The header in a string; + */ + public static String formInternalError() { + return formError("500 Internal server error", "Server broke"); + } + + /** + * Server does not do the requested feature. + * + * @return The header in a string; + */ + public static String formNotImplemented() { + return formError("501 Method not implemented", "Service not implemented"); + } + + /** + * Server does not do the requested feature. + * + * @param reason detailed information for causing the failure + * @return The header in a string; + */ + public static String formNotImplemented(String reason) { + return formError("501 Method not implemented", "Service not implemented. " + reason); + } + + /** + * Server is overloaded, client should try again latter. + * + * @return The header in a string; + */ + public static String formOverloaded() { + return formError("502 Server overloaded", "Try again latter"); + } + + /** + * Indicates the request took to long. + * + * @return The header in a string; + */ + public static String formTimeout() { + return formError("503 Gateway timeout", "The connection timed out"); + } + + /** + * Indicates the client's proxies could not locate a server. + * + * @return The header in a string; + */ + public static String formServerNotFound() { + return formError("503 Gateway timeout", "The requested server was not found"); + } + + /** + * Indicates the client is not allowed to access the object. + * + * @return The header in a string; + */ + public static String formNotAllowed() { + return formError("403 Access Denied", "Access is not allowed"); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/HttpRequestHdr.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/HttpRequestHdr.java new file mode 100644 index 00000000000..07be5d3e163 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/HttpRequestHdr.java @@ -0,0 +1,460 @@ +/* + * 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.protocol.http.proxy; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.commons.lang3.CharUtils; +import org.apache.jmeter.protocol.http.config.MultipartUrlConfig; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.gui.HeaderPanel; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +//For unit tests, @see TestHttpRequestHdr + +/** + * The headers of the client HTTP request. + * + */ +public class HttpRequestHdr { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String HTTP = "http"; // $NON-NLS-1$ + private static final String HTTPS = "https"; // $NON-NLS-1$ + private static final String PROXY_CONNECTION = "proxy-connection"; // $NON-NLS-1$ + public static final String CONTENT_TYPE = "content-type"; // $NON-NLS-1$ + public static final String CONTENT_LENGTH = "content-length"; // $NON-NLS-1$ + + + /** + * Http Request method, uppercased, e.g. GET or POST. + */ + private String method = ""; // $NON-NLS-1$ + + /** CONNECT url. */ + private String paramHttps = ""; // $NON-NLS-1$ + + /** + * The requested url. The universal resource locator that hopefully uniquely + * describes the object or service the client is requesting. + */ + private String url = ""; // $NON-NLS-1$ + + /** + * Version of http being used. Such as HTTP/1.0. + */ + private String version = ""; // NOTREAD // $NON-NLS-1$ + + private byte[] rawPostData; + + private final Map headers = new HashMap(); + + private final String httpSamplerName; + + private HeaderManager headerManager; + + private String firstLine; // saved copy of first line for error reports + + public HttpRequestHdr() { + this.httpSamplerName = ""; // $NON-NLS-1$ + this.firstLine = "" ; // $NON-NLS-1$ + } + + /** + * @param httpSamplerName the http sampler name + */ + public HttpRequestHdr(String httpSamplerName) { + this.httpSamplerName = httpSamplerName; + } + + /** + * Parses a http header from a stream. + * + * @param in + * the stream to parse. + * @return array of bytes from client. + * @throws IOException when reading the input stream fails + */ + public byte[] parse(InputStream in) throws IOException { + boolean inHeaders = true; + int readLength = 0; + int dataLength = 0; + boolean firstLine = true; + ByteArrayOutputStream clientRequest = new ByteArrayOutputStream(); + ByteArrayOutputStream line = new ByteArrayOutputStream(); + int x; + while ((inHeaders || readLength < dataLength) && ((x = in.read()) != -1)) { + line.write(x); + clientRequest.write(x); + if (firstLine && !CharUtils.isAscii((char) x)){// includes \n + throw new IllegalArgumentException("Only ASCII supported in headers (perhaps SSL was used?)"); + } + if (inHeaders && (byte) x == (byte) '\n') { // $NON-NLS-1$ + if (line.size() < 3) { + inHeaders = false; + firstLine = false; // cannot be first line either + } + final String reqLine = line.toString(); + if (firstLine) { + parseFirstLine(reqLine); + firstLine = false; + } else { + // parse other header lines, looking for Content-Length + final int contentLen = parseLine(reqLine); + if (contentLen > 0) { + dataLength = contentLen; // Save the last valid content length one + } + } + if (log.isDebugEnabled()){ + log.debug("Client Request Line: '" + reqLine.replaceFirst("\r\n$", "") + "'"); + } + line.reset(); + } else if (!inHeaders) { + readLength++; + } + } + // Keep the raw post data + rawPostData = line.toByteArray(); + + if (log.isDebugEnabled()){ + log.debug("rawPostData in default JRE encoding: " + new String(rawPostData)); // TODO - charset? + log.debug("Request: '" + clientRequest.toString().replaceAll("\r\n", "") + "'"); + } + return clientRequest.toByteArray(); + } + + private void parseFirstLine(String firstLine) { + this.firstLine = firstLine; + if (log.isDebugEnabled()) { + log.debug("browser request: " + firstLine.replaceFirst("\r\n$", "")); + } + StringTokenizer tz = new StringTokenizer(firstLine); + method = getToken(tz).toUpperCase(java.util.Locale.ENGLISH); + url = getToken(tz); + version = getToken(tz); + if (log.isDebugEnabled()) { + log.debug("parsed method: " + method); + log.debug("parsed url/host: " + url); // will be host:port for CONNECT + log.debug("parsed version: " + version); + } + // SSL connection + if (getMethod().startsWith(HTTPConstants.CONNECT)) { + paramHttps = url; + return; // Don't try to adjust the host name + } + /* The next line looks odd, but proxied HTTP requests look like: + * GET http://www.apache.org/foundation/ HTTP/1.1 + * i.e. url starts with "http:", not "/" + * whereas HTTPS proxy requests look like: + * CONNECT www.google.co.uk:443 HTTP/1.1 + * followed by + * GET /?gws_rd=cr HTTP/1.1 + */ + if (url.startsWith("/")) { // it must be a proxied HTTPS request + url = HTTPS + "://" + paramHttps + url; // $NON-NLS-1$ + } + // JAVA Impl accepts URLs with unsafe characters so don't do anything + if(HTTPSamplerFactory.IMPL_JAVA.equals(httpSamplerName)) { + log.debug("First Line url: " + url); + return; + } + try { + // See Bug 54482 + URI testCleanUri = new URI(url); + if(log.isDebugEnabled()) { + log.debug("Successfully built URI from url:"+url+" => " + testCleanUri.toString()); + } + } catch (URISyntaxException e) { + log.warn("Url '" + url + "' contains unsafe characters, will escape it, message:"+e.getMessage()); + try { + String escapedUrl = ConversionUtils.escapeIllegalURLCharacters(url); + if(log.isDebugEnabled()) { + log.debug("Successfully escaped url:'"+url +"' to:'"+escapedUrl+"'"); + } + url = escapedUrl; + } catch (Exception e1) { + log.error("Error escaping URL:'"+url+"', message:"+e1.getMessage()); + } + } + log.debug("First Line url: " + url); + } + + /* + * Split line into name/value pairs and store in headers if relevant + * If name = "content-length", then return value as int, else return 0 + */ + private int parseLine(String nextLine) { + int colon = nextLine.indexOf(':'); + if (colon <= 0){ + return 0; // Nothing to do + } + String name = nextLine.substring(0, colon).trim(); + String value = nextLine.substring(colon+1).trim(); + headers.put(name.toLowerCase(java.util.Locale.ENGLISH), new Header(name, value)); + if (name.equalsIgnoreCase(CONTENT_LENGTH)) { + return Integer.parseInt(value); + } + return 0; + } + + private HeaderManager createHeaderManager() { + HeaderManager manager = new HeaderManager(); + for (Map.Entry entry : headers.entrySet()) { + final String key = entry.getKey(); + if (!key.equals(PROXY_CONNECTION) + && !key.equals(CONTENT_LENGTH) + && !key.equalsIgnoreCase(HTTPConstants.HEADER_CONNECTION)) { + manager.add(entry.getValue()); + } + } + manager.setName(JMeterUtils.getResString("header_manager_title")); // $NON-NLS-1$ + manager.setProperty(TestElement.TEST_CLASS, HeaderManager.class.getName()); + manager.setProperty(TestElement.GUI_CLASS, HeaderPanel.class.getName()); + return manager; + } + + public HeaderManager getHeaderManager() { + if(headerManager == null) { + headerManager = createHeaderManager(); + } + return headerManager; + } + + public String getContentType() { + Header contentTypeHeader = headers.get(CONTENT_TYPE); + if (contentTypeHeader != null) { + return contentTypeHeader.getValue(); + } + return null; + } + + private boolean isMultipart(String contentType) { + if (contentType != null && contentType.startsWith(HTTPConstants.MULTIPART_FORM_DATA)) { + return true; + } + return false; + } + + public MultipartUrlConfig getMultipartConfig(String contentType) { + if(isMultipart(contentType)) { + // Get the boundary string for the multiparts from the content type + String boundaryString = contentType.substring(contentType.toLowerCase(java.util.Locale.ENGLISH).indexOf("boundary=") + "boundary=".length()); + return new MultipartUrlConfig(boundaryString); + } + return null; + } + + // + // Parsing Methods + // + + /** + * Find the //server.name from an url. + * + * @return server's internet name + */ + public String serverName() { + // chop to "server.name:x/thing" + String str = url; + int i = str.indexOf("//"); // $NON-NLS-1$ + if (i > 0) { + str = str.substring(i + 2); + } + // chop to server.name:xx + i = str.indexOf('/'); // $NON-NLS-1$ + if (0 < i) { + str = str.substring(0, i); + } + // chop to server.name + i = str.lastIndexOf(':'); // $NON-NLS-1$ + if (0 < i) { + str = str.substring(0, i); + } + // Handle IPv6 urls + if(str.startsWith("[")&& str.endsWith("]")) { + return str.substring(1, str.length()-1); + } + return str; + } + + // TODO replace repeated substr() above and below with more efficient method. + + /** + * Find the :PORT from http://server.ect:PORT/some/file.xxx + * + * @return server's port (or UNSPECIFIED if not found) + */ + public int serverPort() { + String str = url; + // chop to "server.name:x/thing" + int i = str.indexOf("//"); + if (i > 0) { + str = str.substring(i + 2); + } + // chop to server.name:xx + i = str.indexOf('/'); + if (0 < i) { + str = str.substring(0, i); + } + // chop to server.name + i = str.lastIndexOf(':'); + if (0 < i) { + return Integer.parseInt(str.substring(i + 1).trim()); + } + return HTTPSamplerBase.UNSPECIFIED_PORT; + } + + /** + * Find the /some/file.xxxx from http://server.ect:PORT/some/file.xxx + * + * @return the path + */ + public String getPath() { + String str = url; + int i = str.indexOf("//"); + if (i > 0) { + str = str.substring(i + 2); + } + i = str.indexOf('/'); + if (i < 0) { + return ""; + } + return str.substring(i); + } + + /** + * Returns the url string extracted from the first line of the client request. + * + * @return the url + */ + public String getUrl(){ + return url; + } + + /** + * Returns the method string extracted from the first line of the client request. + * + * @return the method (will always be upper case) + */ + public String getMethod(){ + return method; + } + + public String getFirstLine() { + return firstLine; + } + + /** + * Returns the next token in a string. + * + * @param tk + * String that is partially tokenized. + * @return The remainder + */ + private String getToken(StringTokenizer tk) { + if (tk.hasMoreTokens()) { + return tk.nextToken(); + } + return "";// $NON-NLS-1$ + } + +// /** +// * Returns the remainder of a tokenized string. +// * +// * @param tk +// * String that is partially tokenized. +// * @return The remainder +// */ +// private String getRemainder(StringTokenizer tk) { +// StringBuilder strBuff = new StringBuilder(); +// if (tk.hasMoreTokens()) { +// strBuff.append(tk.nextToken()); +// } +// while (tk.hasMoreTokens()) { +// strBuff.append(" "); // $NON-NLS-1$ +// strBuff.append(tk.nextToken()); +// } +// return strBuff.toString(); +// } + + public String getUrlWithoutQuery(URL _url) { + String fullUrl = _url.toString(); + String urlWithoutQuery = fullUrl; + String query = _url.getQuery(); + if(query != null) { + // Get rid of the query and the ? + urlWithoutQuery = urlWithoutQuery.substring(0, urlWithoutQuery.length() - query.length() - 1); + } + return urlWithoutQuery; + } + + /** + * @return the httpSamplerName + */ + public String getHttpSamplerName() { + return httpSamplerName; + } + + /** + * @return byte[] Raw post data + */ + public byte[] getRawPostData() { + return rawPostData; + } + + /** + * @param sampler {@link HTTPSamplerBase} + * @return String Protocol (http or https) + */ + public String getProtocol(HTTPSamplerBase sampler) { + if (url.indexOf("//") > -1) { + String protocol = url.substring(0, url.indexOf(':')); + if (log.isDebugEnabled()) { + log.debug("Proxy: setting protocol to : " + protocol); + } + return protocol; + } else if (sampler.getPort() == HTTPConstants.DEFAULT_HTTPS_PORT) { + if (log.isDebugEnabled()) { + log.debug("Proxy: setting protocol to https"); + } + return HTTPS; + } else { + if (log.isDebugEnabled()) { + log.debug("Proxy setting default protocol to: http"); + } + return HTTP; + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/Proxy.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/Proxy.java new file mode 100644 index 00000000000..ce43ec8476d --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/Proxy.java @@ -0,0 +1,632 @@ +/* + * 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.protocol.http.proxy; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.Socket; +import java.net.URL; +import java.net.UnknownHostException; +import java.nio.charset.IllegalCharsetNameException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.parser.HTMLParseException; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Thread to handle one client request. Gets the request from the client and + * passes it on to the server, then sends the response back to the client. + * Information about the request and response is stored so it can be used in a + * JMeter test plan. + * + */ +public class Proxy extends Thread { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final byte[] CRLF_BYTES = { 0x0d, 0x0a }; + private static final String CRLF_STRING = "\r\n"; + + private static final String NEW_LINE = "\n"; // $NON-NLS-1$ + + private static final String[] HEADERS_TO_REMOVE; + + // Allow list of headers to be overridden + private static final String PROXY_HEADERS_REMOVE = "proxy.headers.remove"; // $NON-NLS-1$ + + private static final String PROXY_HEADERS_REMOVE_DEFAULT = "If-Modified-Since,If-None-Match,Host"; // $NON-NLS-1$ + + private static final String PROXY_HEADERS_REMOVE_SEPARATOR = ","; // $NON-NLS-1$ + + private static final String KEYMANAGERFACTORY = + JMeterUtils.getPropDefault("proxy.cert.factory", "SunX509"); // $NON-NLS-1$ $NON-NLS-2$ + + private static final String SSLCONTEXT_PROTOCOL = + JMeterUtils.getPropDefault("proxy.ssl.protocol", "TLS"); // $NON-NLS-1$ $NON-NLS-2$ + + // HashMap to save ssl connection between Jmeter proxy and browser + private static final HashMap HOST2SSL_SOCK_FAC = new HashMap(); + + private static final SamplerCreatorFactory SAMPLERFACTORY = new SamplerCreatorFactory(); + + static { + String removeList = JMeterUtils.getPropDefault(PROXY_HEADERS_REMOVE,PROXY_HEADERS_REMOVE_DEFAULT); + HEADERS_TO_REMOVE = JOrphanUtils.split(removeList,PROXY_HEADERS_REMOVE_SEPARATOR); + log.info("Proxy will remove the headers: "+removeList); + } + + // Use with SSL connection + private OutputStream outStreamClient = null; + + /** Socket to client. */ + private Socket clientSocket = null; + + /** Target to receive the generated sampler. */ + private ProxyControl target; + + /** Whether or not to capture the HTTP headers. */ + private boolean captureHttpHeaders; + + /** Reference to Deamon's Map of url string to page character encoding of that page */ + private Map pageEncodings; + /** Reference to Deamon's Map of url string to character encoding for the form */ + private Map formEncodings; + + private String port; // For identifying log messages + + private KeyStore keyStore; // keystore for SSL keys; fixed at config except for dynamic host key generation + + private String keyPassword; + + /** + * Default constructor - used by newInstance call in Daemon + */ + public Proxy() { + port = ""; + } + + /** + * Configure the Proxy. + * Intended to be called directly after construction. + * Should not be called after it has been passed to a new thread, + * otherwise the variables may not be published correctly. + * + * @param _clientSocket + * the socket connection to the client + * @param _target + * the ProxyControl which will receive the generated sampler + * @param _pageEncodings + * reference to the Map of Deamon, with mappings from page urls to encoding used + * @param _formEncodings + * reference to the Map of Deamon, with mappings from form action urls to encoding used + */ + void configure(Socket _clientSocket, ProxyControl _target, Map _pageEncodings, Map _formEncodings) { + this.target = _target; + this.clientSocket = _clientSocket; + this.captureHttpHeaders = _target.getCaptureHttpHeaders(); + this.pageEncodings = _pageEncodings; + this.formEncodings = _formEncodings; + this.port = "["+ clientSocket.getPort() + "] "; + this.keyStore = _target.getKeyStore(); + this.keyPassword = _target.getKeyPassword(); + } + + /** + * Main processing method for the Proxy object + */ + @Override + public void run() { + // Check which HTTPSampler class we should use + String httpSamplerName = target.getSamplerTypeName(); + + HttpRequestHdr request = new HttpRequestHdr(httpSamplerName); + SampleResult result = null; + HeaderManager headers = null; + HTTPSamplerBase sampler = null; + final boolean isDebug = log.isDebugEnabled(); + if (isDebug) { + log.debug(port + "===================================================================="); + } + SamplerCreator samplerCreator = null; + try { + // Now, parse initial request (in case it is a CONNECT request) + byte[] ba = request.parse(new BufferedInputStream(clientSocket.getInputStream())); + if (ba.length == 0) { + if (isDebug) { + log.debug(port + "Empty request, ignored"); + } + throw new JMeterException(); // hack to skip processing + } + if (isDebug) { + log.debug(port + "Initial request: " + new String(ba)); + } + outStreamClient = clientSocket.getOutputStream(); + + if ((request.getMethod().startsWith(HTTPConstants.CONNECT)) && (outStreamClient != null)) { + if (isDebug) { + log.debug(port + "Method CONNECT => SSL"); + } + // write a OK reponse to browser, to engage SSL exchange + outStreamClient.write(("HTTP/1.0 200 OK\r\n\r\n").getBytes(SampleResult.DEFAULT_HTTP_ENCODING)); // $NON-NLS-1$ + outStreamClient.flush(); + // With ssl request, url is host:port (without https:// or path) + String[] param = request.getUrl().split(":"); // $NON-NLS-1$ + if (param.length == 2) { + if (isDebug) { + log.debug(port + "Start to negotiate SSL connection, host: " + param[0]); + } + clientSocket = startSSL(clientSocket, param[0]); + } else { + // Should not happen, but if it does we don't want to continue + log.error("In SSL request, unable to find host and port in CONNECT request: " + request.getUrl()); + throw new JMeterException(); // hack to skip processing + } + // Re-parse (now it's the http request over SSL) + try { + ba = request.parse(new BufferedInputStream(clientSocket.getInputStream())); + } catch (IOException ioe) { // most likely this is because of a certificate error + // param.length is 2 here + final String url = " for '"+ param[0] +"'"; + log.warn(port + "Problem with SSL certificate"+url+"? Ensure browser is set to accept the JMeter proxy cert: " + ioe.getMessage()); + // won't work: writeErrorToClient(HttpReplyHdr.formInternalError()); + result = generateErrorResult(result, request, ioe, "\n**ensure browser is set to accept the JMeter proxy certificate**"); // Generate result (if nec.) and populate it + throw new JMeterException(); // hack to skip processing + } + if (isDebug) { + log.debug(port + "Reparse: " + new String(ba)); + } + if (ba.length == 0) { + log.warn(port + "Empty response to http over SSL. Probably waiting for user to authorize the certificate for " + request.getUrl()); + throw new JMeterException(); // hack to skip processing + } + } + + samplerCreator = SAMPLERFACTORY.getSamplerCreator(request, pageEncodings, formEncodings); + sampler = samplerCreator.createAndPopulateSampler(request, pageEncodings, formEncodings); + + /* + * Create a Header Manager to ensure that the browsers headers are + * captured and sent to the server + */ + headers = request.getHeaderManager(); + sampler.setHeaderManager(headers); + + sampler.threadStarted(); // Needed for HTTPSampler2 + if (isDebug) { + log.debug(port + "Execute sample: " + sampler.getMethod() + " " + sampler.getUrl()); + } + result = sampler.sample(); + + // Find the page encoding and possibly encodings for forms in the page + // in the response from the web server + String pageEncoding = addPageEncoding(result); + addFormEncodings(result, pageEncoding); + + writeToClient(result, new BufferedOutputStream(clientSocket.getOutputStream())); + samplerCreator.postProcessSampler(sampler, result); + } catch (JMeterException jme) { + // ignored, already processed + } catch (UnknownHostException uhe) { + log.warn(port + "Server Not Found.", uhe); + writeErrorToClient(HttpReplyHdr.formServerNotFound()); + result = generateErrorResult(result, request, uhe); // Generate result (if nec.) and populate it + } catch (IllegalArgumentException e) { + log.error(port + "Not implemented (probably used https)", e); + writeErrorToClient(HttpReplyHdr.formNotImplemented("Probably used https instead of http. " + + "To record https requests, see " + + "
HTTP(S) Test Script Recorder documentation")); + result = generateErrorResult(result, request, e); // Generate result (if nec.) and populate it + } catch (Exception e) { + log.error(port + "Exception when processing sample", e); + writeErrorToClient(HttpReplyHdr.formInternalError()); + result = generateErrorResult(result, request, e); // Generate result (if nec.) and populate it + } finally { + if(sampler != null && isDebug) { + log.debug(port + "Will deliver sample " + sampler.getName()); + } + /* + * We don't want to store any cookies in the generated test plan + */ + if (headers != null) { + headers.removeHeaderNamed(HTTPConstants.HEADER_COOKIE);// Always remove cookies + // See https://bz.apache.org/bugzilla/show_bug.cgi?id=25430 + // HEADER_AUTHORIZATION won't be removed, it will be used + // for creating Authorization Manager + // Remove additional headers + for(String hdr : HEADERS_TO_REMOVE){ + headers.removeHeaderNamed(hdr); + } + } + if(result != null) // deliverSampler allows sampler to be null, but result must not be null + { + List children = new ArrayList(); + if(captureHttpHeaders) { + children.add(headers); + } + if(samplerCreator != null) { + children.addAll(samplerCreator.createChildren(sampler, result)); + } + target.deliverSampler(sampler, + children.isEmpty() ? null : (TestElement[]) children + .toArray(new TestElement[children.size()]), + result); + } + try { + clientSocket.close(); + } catch (Exception e) { + log.error(port + "Failed to close client socket", e); + } + if(sampler != null) { + sampler.threadFinished(); // Needed for HTTPSampler2 + } + } + } + + /** + * Get SSL connection from hashmap, creating it if necessary. + * + * @param host + * @return a ssl socket factory, or null if keystore could not be opened/processed + * @throws IOException + */ + private SSLSocketFactory getSSLSocketFactory(String host) { + if (keyStore == null) { + log.error(port + "No keystore available, cannot record SSL"); + return null; + } + final String hashAlias; + final String keyAlias; + switch(ProxyControl.KEYSTORE_MODE) { + case DYNAMIC_KEYSTORE: + try { + keyStore = target.getKeyStore(); // pick up any recent changes from other threads + String alias = getDomainMatch(keyStore, host); + if (alias == null) { + hashAlias = host; + keyAlias = host; + keyStore = target.updateKeyStore(port, keyAlias); + } else if (alias.equals(host)) { // the host has a key already + hashAlias = host; + keyAlias = host; + } else { // the host matches a domain; use its key + hashAlias = alias; + keyAlias = alias; + } + } catch (IOException e) { + log.error(port + "Problem with keystore", e); + return null; + } catch (GeneralSecurityException e) { + log.error(port + "Problem with keystore", e); + return null; + } + break; + case JMETER_KEYSTORE: + hashAlias = keyAlias = ProxyControl.JMETER_SERVER_ALIAS; + break; + case USER_KEYSTORE: + hashAlias = keyAlias = ProxyControl.CERT_ALIAS; + break; + default: + throw new IllegalStateException("Impossible case: " + ProxyControl.KEYSTORE_MODE); + } + synchronized (HOST2SSL_SOCK_FAC) { + final SSLSocketFactory sslSocketFactory = HOST2SSL_SOCK_FAC.get(hashAlias); + if (sslSocketFactory != null) { + if (log.isDebugEnabled()) { + log.debug(port + "Good, already in map, host=" + host + " using alias " + hashAlias); + } + return sslSocketFactory; + } + try { + SSLContext sslcontext = SSLContext.getInstance(SSLCONTEXT_PROTOCOL); + sslcontext.init(getWrappedKeyManagers(keyAlias), null, null); + SSLSocketFactory sslFactory = sslcontext.getSocketFactory(); + HOST2SSL_SOCK_FAC.put(hashAlias, sslFactory); + log.info(port + "KeyStore for SSL loaded OK and put host '" + host + "' in map with key ("+hashAlias+")"); + return sslFactory; + } catch (GeneralSecurityException e) { + log.error(port + "Problem with SSL certificate", e); + } catch (IOException e) { + log.error(port + "Problem with keystore", e); + } + return null; + } + } + + /** + * Get matching alias for a host from keyStore that may contain domain aliases. + * Assumes domains must have at least 2 parts (apache.org); + * does not check if TLD requires more (google.co.uk). + * Note that DNS wildcards only apply to a single level, i.e. + * podling.incubator.apache.org matches *.incubator.apache.org + * but does not match *.apache.org + * + * @param keyStore the KeyStore to search + * @param host the hostname to match + * @return the keystore entry or {@code null} if no match found + * @throws KeyStoreException + */ + private String getDomainMatch(KeyStore keyStore, String host) throws KeyStoreException { + if (keyStore.containsAlias(host)) { + return host; + } + String parts[] = host.split("\\."); // get the component parts + // Assume domains must have at least 2 parts, e.g. apache.org + // Replace the first part with "*" + StringBuilder sb = new StringBuilder("*"); // $NON-NLS-1$ + for(int j = 1; j < parts.length ; j++) { // Skip the first part + sb.append('.'); + sb.append(parts[j]); + } + String alias = sb.toString(); + if (keyStore.containsAlias(alias)) { + return alias; + } + return null; + } + + /** + * Return the key managers, wrapped to return a specific alias + */ + private KeyManager[] getWrappedKeyManagers(final String keyAlias) + throws GeneralSecurityException, IOException { + if (!keyStore.containsAlias(keyAlias)) { + throw new IOException("Keystore does not contain alias " + keyAlias); + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEYMANAGERFACTORY); + kmf.init(keyStore, keyPassword.toCharArray()); + final KeyManager[] keyManagers = kmf.getKeyManagers(); + // Check if alias is suitable here, rather than waiting for connection to fail + final int keyManagerCount = keyManagers.length; + final KeyManager[] wrappedKeyManagers = new KeyManager[keyManagerCount]; + for (int i =0; i < keyManagerCount; i++) { + wrappedKeyManagers[i] = new ServerAliasKeyManager(keyManagers[i], keyAlias); + } + return wrappedKeyManagers; + } + + /** + * Negotiate a SSL connection. + * + * @param sock socket in + * @param host + * @return a new client socket over ssl + * @throws Exception if negotiation failed + */ + private Socket startSSL(Socket sock, String host) throws IOException { + SSLSocketFactory sslFactory = getSSLSocketFactory(host); + SSLSocket secureSocket; + if (sslFactory != null) { + try { + secureSocket = (SSLSocket) sslFactory.createSocket(sock, + sock.getInetAddress().getHostName(), sock.getPort(), true); + secureSocket.setUseClientMode(false); + if (log.isDebugEnabled()){ + log.debug(port + "SSL transaction ok with cipher: " + secureSocket.getSession().getCipherSuite()); + } + return secureSocket; + } catch (IOException e) { + log.error(port + "Error in SSL socket negotiation: ", e); + throw e; + } + } else { + log.warn(port + "Unable to negotiate SSL transaction, no keystore?"); + throw new IOException("Unable to negotiate SSL transaction, no keystore?"); + } + } + + private SampleResult generateErrorResult(SampleResult result, HttpRequestHdr request, Exception e) { + return generateErrorResult(result, request, e, ""); + } + + private static SampleResult generateErrorResult(SampleResult result, HttpRequestHdr request, Exception e, String msg) { + if (result == null) { + result = new SampleResult(); + ByteArrayOutputStream text = new ByteArrayOutputStream(200); + e.printStackTrace(new PrintStream(text)); + result.setResponseData(text.toByteArray()); + result.setSamplerData(request.getFirstLine()); + result.setSampleLabel(request.getUrl()); + } + result.setSuccessful(false); + result.setResponseMessage(e.getMessage()+msg); + return result; + } + + /** + * Write output to the output stream, then flush and close the stream. + * + * @param inBytes + * the bytes to write + * @param out + * the output stream to write to + * @param forcedHTTPS if we changed the protocol to https + * @throws IOException + * if an IOException occurs while writing + */ + private void writeToClient(SampleResult res, OutputStream out) throws IOException { + try { + String responseHeaders = messageResponseHeaders(res); + out.write(responseHeaders.getBytes(SampleResult.DEFAULT_HTTP_ENCODING)); + out.write(CRLF_BYTES); + out.write(res.getResponseData()); + out.flush(); + if (log.isDebugEnabled()) { + log.debug(port + "Done writing to client"); + } + } catch (IOException e) { + log.error("", e); + throw e; + } finally { + try { + out.close(); + } catch (Exception ex) { + log.warn(port + "Error while closing socket", ex); + } + } + } + + /** + * In the event the content was gzipped and unpacked, the content-encoding + * header must be removed and the content-length header should be corrected. + * + * The Transfer-Encoding header is also removed. + * If the protocol was changed to HTTPS then change any Location header back to http + * @param res - response + * + * @return updated headers to be sent to client + */ + private String messageResponseHeaders(SampleResult res) { + String headers = res.getResponseHeaders(); + String [] headerLines=headers.split(NEW_LINE, 0); // drop empty trailing content + int contentLengthIndex=-1; + boolean fixContentLength = false; + for (int i=0;i=0){// Fix the content length + headerLines[contentLengthIndex]=HTTPConstants.HEADER_CONTENT_LENGTH+": "+res.getResponseData().length; + } + StringBuilder sb = new StringBuilder(headers.length()); + for (int i=0;i + * This property is not persistent. + */ + private JMeterTreeNode target; + + private String storePassword; + + private String keyPassword; + + public ProxyControl() { + setPort(DEFAULT_PORT); + setExcludeList(new HashSet()); + setIncludeList(new HashSet()); + setCaptureHttpHeaders(true); // maintain original behaviour + } + + public void setPort(int port) { + this.setProperty(new IntegerProperty(PORT, port)); + } + + public void setPort(String port) { + setProperty(PORT, port); + } + + public void setSslDomains(String domains) { + setProperty(DOMAINS, domains, ""); + } + + public String getSslDomains() { + return getPropertyAsString(DOMAINS,""); + } + + public void setCaptureHttpHeaders(boolean capture) { + setProperty(new BooleanProperty(CAPTURE_HTTP_HEADERS, capture)); + } + + public void setGroupingMode(int grouping) { + this.groupingMode = grouping; + setProperty(new IntegerProperty(GROUPING_MODE, grouping)); + } + + public void setAssertions(boolean b) { + addAssertions = b; + setProperty(new BooleanProperty(ADD_ASSERTIONS, b)); + } + + @Deprecated + public void setSamplerTypeName(int samplerTypeName) { + setProperty(new IntegerProperty(SAMPLER_TYPE_NAME, samplerTypeName)); + } + + public void setSamplerTypeName(String samplerTypeName) { + setProperty(new StringProperty(SAMPLER_TYPE_NAME, samplerTypeName)); + } + public void setSamplerRedirectAutomatically(boolean b) { + samplerRedirectAutomatically = b; + setProperty(new BooleanProperty(SAMPLER_REDIRECT_AUTOMATICALLY, b)); + } + + public void setSamplerFollowRedirects(boolean b) { + samplerFollowRedirects = b; + setProperty(new BooleanProperty(SAMPLER_FOLLOW_REDIRECTS, b)); + } + + /** + * @param b flag whether keep alive should be used + */ + public void setUseKeepAlive(boolean b) { + useKeepAlive = b; + setProperty(new BooleanProperty(USE_KEEPALIVE, b)); + } + + public void setSamplerDownloadImages(boolean b) { + samplerDownloadImages = b; + setProperty(new BooleanProperty(SAMPLER_DOWNLOAD_IMAGES, b)); + } + + public void setNotifyChildSamplerListenerOfFilteredSamplers(boolean b) { + notifyChildSamplerListenersOfFilteredSamples = b; + setProperty(new BooleanProperty(NOTIFY_CHILD_SAMPLER_LISTENERS_FILTERED, b)); + } + + public void setIncludeList(Collection list) { + setProperty(new CollectionProperty(INCLUDE_LIST, new HashSet(list))); + } + + public void setExcludeList(Collection list) { + setProperty(new CollectionProperty(EXCLUDE_LIST, new HashSet(list))); + } + + /** + * @param b flag whether regex matching should be used + */ + public void setRegexMatch(boolean b) { + regexMatch = b; + setProperty(new BooleanProperty(REGEX_MATCH, b)); + } + + public void setContentTypeExclude(String contentTypeExclude) { + setProperty(new StringProperty(CONTENT_TYPE_EXCLUDE, contentTypeExclude)); + } + + public void setContentTypeInclude(String contentTypeInclude) { + setProperty(new StringProperty(CONTENT_TYPE_INCLUDE, contentTypeInclude)); + } + + public boolean getAssertions() { + return getPropertyAsBoolean(ADD_ASSERTIONS); + } + + public int getGroupingMode() { + return getPropertyAsInt(GROUPING_MODE); + } + + public int getPort() { + return getPropertyAsInt(PORT); + } + + public String getPortString() { + return getPropertyAsString(PORT); + } + + public int getDefaultPort() { + return DEFAULT_PORT; + } + + public boolean getCaptureHttpHeaders() { + return getPropertyAsBoolean(CAPTURE_HTTP_HEADERS); + } + + public String getSamplerTypeName() { + // Convert the old numeric types - just in case someone wants to reload the workbench + String type = getPropertyAsString(SAMPLER_TYPE_NAME); + if (SAMPLER_TYPE_HTTP_SAMPLER_JAVA.equals(type)){ + type = HTTPSamplerFactory.IMPL_JAVA; + } else if (SAMPLER_TYPE_HTTP_SAMPLER_HC3_1.equals(type)){ + type = HTTPSamplerFactory.IMPL_HTTP_CLIENT3_1; + } else if (SAMPLER_TYPE_HTTP_SAMPLER_HC4.equals(type)){ + type = HTTPSamplerFactory.IMPL_HTTP_CLIENT4; + } + return type; + } + + public boolean getSamplerRedirectAutomatically() { + return getPropertyAsBoolean(SAMPLER_REDIRECT_AUTOMATICALLY, false); + } + + public boolean getSamplerFollowRedirects() { + return getPropertyAsBoolean(SAMPLER_FOLLOW_REDIRECTS, true); + } + + public boolean getUseKeepalive() { + return getPropertyAsBoolean(USE_KEEPALIVE, true); + } + + public boolean getSamplerDownloadImages() { + return getPropertyAsBoolean(SAMPLER_DOWNLOAD_IMAGES, false); + } + + public boolean getNotifyChildSamplerListenerOfFilteredSamplers() { + return getPropertyAsBoolean(NOTIFY_CHILD_SAMPLER_LISTENERS_FILTERED, true); + } + + public boolean getRegexMatch() { + return getPropertyAsBoolean(REGEX_MATCH, false); + } + + public String getContentTypeExclude() { + return getPropertyAsString(CONTENT_TYPE_EXCLUDE); + } + + public String getContentTypeInclude() { + return getPropertyAsString(CONTENT_TYPE_INCLUDE); + } + + public void addConfigElement(ConfigElement config) { + // NOOP + } + + public void startProxy() throws IOException { + try { + initKeyStore(); // TODO display warning dialog as this can take some time + } catch (GeneralSecurityException e) { + log.error("Could not initialise key store", e); + throw new IOException("Could not create keystore", e); + } catch (IOException e) { // make sure we log the error + log.error("Could not initialise key store", e); + throw e; + } + notifyTestListenersOfStart(); + try { + server = new Daemon(getPort(), this); + server.start(); + GuiPackage.getInstance().register(server); + } catch (IOException e) { + log.error("Could not create Proxy daemon", e); + throw e; + } + } + + public void addExcludedPattern(String pattern) { + getExcludePatterns().addItem(pattern); + } + + public CollectionProperty getExcludePatterns() { + return (CollectionProperty) getProperty(EXCLUDE_LIST); + } + + public void addIncludedPattern(String pattern) { + getIncludePatterns().addItem(pattern); + } + + public CollectionProperty getIncludePatterns() { + return (CollectionProperty) getProperty(INCLUDE_LIST); + } + + public void clearExcludedPatterns() { + getExcludePatterns().clear(); + } + + public void clearIncludedPatterns() { + getIncludePatterns().clear(); + } + + /** + * @return the target controller node + */ + public JMeterTreeNode getTarget() { + return target; + } + + /** + * Sets the target node where the samples generated by the proxy have to be + * stored. + * + * @param target target node to store generated samples + */ + public void setTarget(JMeterTreeNode target) { + this.target = target; + } + + /** + * Receives the recorded sampler from the proxy server for placing in the + * test tree; this is skipped if the sampler is null (e.g. for recording SSL errors) + * Always sends the result to any registered sample listeners. + * + * @param sampler the sampler, may be null + * @param subConfigs the configuration elements to be added (e.g. header namager) + * @param result the sample result, not null + * TODO param serverResponse to be added to allow saving of the + * server's response while recording. + */ + public synchronized void deliverSampler(final HTTPSamplerBase sampler, final TestElement[] subConfigs, final SampleResult result) { + boolean notifySampleListeners = true; + if (sampler != null) { + if (ATTEMPT_REDIRECT_DISABLING && (samplerRedirectAutomatically || samplerFollowRedirects)) { + if (result instanceof HTTPSampleResult) { + final HTTPSampleResult httpSampleResult = (HTTPSampleResult) result; + final String urlAsString = httpSampleResult.getUrlAsString(); + if (urlAsString.equals(LAST_REDIRECT)) { // the url matches the last redirect + sampler.setEnabled(false); + sampler.setComment("Detected a redirect from the previous sample"); + } else { // this is not the result of a redirect + LAST_REDIRECT = null; // so break the chain + } + if (httpSampleResult.isRedirect()) { // Save Location so resulting sample can be disabled + if (LAST_REDIRECT == null) { + sampler.setComment("Detected the start of a redirect chain"); + } + LAST_REDIRECT = httpSampleResult.getRedirectLocation(); + } else { + LAST_REDIRECT = null; + } + } + } + if (filterContentType(result) && filterUrl(sampler)) { + JMeterTreeNode myTarget = findTargetControllerNode(); + @SuppressWarnings("unchecked") // OK, because find only returns correct element types + Collection defaultConfigurations = (Collection) findApplicableElements(myTarget, ConfigTestElement.class, false); + @SuppressWarnings("unchecked") // OK, because find only returns correct element types + Collection userDefinedVariables = (Collection) findApplicableElements(myTarget, Arguments.class, true); + + removeValuesFromSampler(sampler, defaultConfigurations); + replaceValues(sampler, subConfigs, userDefinedVariables); + sampler.setAutoRedirects(samplerRedirectAutomatically); + sampler.setFollowRedirects(samplerFollowRedirects); + sampler.setUseKeepAlive(useKeepAlive); + sampler.setImageParser(samplerDownloadImages); + + Authorization authorization = createAuthorization(subConfigs, sampler); + if (authorization != null) { + setAuthorization(authorization, myTarget); + } + placeSampler(sampler, subConfigs, myTarget); + } else { + if(log.isDebugEnabled()) { + log.debug("Sample excluded based on url or content-type: " + result.getUrlAsString() + " - " + result.getContentType()); + } + notifySampleListeners = notifyChildSamplerListenersOfFilteredSamples; + result.setSampleLabel("["+result.getSampleLabel()+"]"); + } + } + if(notifySampleListeners) { + // SampleEvent is not passed JMeterVariables, because they don't make sense for Proxy Recording + notifySampleListeners(new SampleEvent(result, "WorkBench")); // TODO - is this the correct threadgroup name? + } else { + log.debug("Sample not delivered to Child Sampler Listener based on url or content-type: " + result.getUrlAsString() + " - " + result.getContentType()); + } + } + + /** + * Detect Header manager in subConfigs, + * Find(if any) Authorization header + * Construct Authentication object + * Removes Authorization if present + * + * @param subConfigs {@link TestElement}[] + * @param sampler {@link HTTPSamplerBase} + * @return {@link Authorization} + */ + private Authorization createAuthorization(final TestElement[] subConfigs, HTTPSamplerBase sampler) { + Header authHeader = null; + Authorization authorization = null; + // Iterate over subconfig elements searching for HeaderManager + for (TestElement te : subConfigs) { + if (te instanceof HeaderManager) { + List headers = (ArrayList) ((HeaderManager) te).getHeaders().getObjectValue(); + for (Iterator iterator = headers.iterator(); iterator.hasNext();) { + TestElementProperty tep = (TestElementProperty) iterator + .next(); + if (tep.getName().equals(HTTPConstants.HEADER_AUTHORIZATION)) { + //Construct Authorization object from HEADER_AUTHORIZATION + authHeader = (Header) tep.getObjectValue(); + String[] authHeaderContent = authHeader.getValue().split(" ");//$NON-NLS-1$ + String authType = null; + String authCredentialsBase64 = null; + if(authHeaderContent.length>=2) { + authType = authHeaderContent[0]; + authCredentialsBase64 = authHeaderContent[1]; + authorization=new Authorization(); + try { + authorization.setURL(sampler.getUrl().toExternalForm()); + } catch (MalformedURLException e) { + log.error("Error filling url on authorization, message:"+e.getMessage(), e); + authorization.setURL("${AUTH_BASE_URL}");//$NON-NLS-1$ + } + // if HEADER_AUTHORIZATION contains "Basic" + // then set Mechanism.BASIC_DIGEST, otherwise Mechanism.KERBEROS + authorization.setMechanism( + authType.equals(BASIC_AUTH)||authType.equals(DIGEST_AUTH)? + AuthManager.Mechanism.BASIC_DIGEST: + AuthManager.Mechanism.KERBEROS); + if(BASIC_AUTH.equals(authType)) { + String authCred= new String(Base64.decodeBase64(authCredentialsBase64)); + String[] loginPassword = authCred.split(":"); //$NON-NLS-1$ + authorization.setUser(loginPassword[0]); + authorization.setPass(loginPassword[1]); + } else { + // Digest or Kerberos + authorization.setUser("${AUTH_LOGIN}");//$NON-NLS-1$ + authorization.setPass("${AUTH_PASSWORD}");//$NON-NLS-1$ + + } + } + // remove HEADER_AUTHORIZATION from HeaderManager + // because it's useless after creating Authorization object + iterator.remove(); + } + } + } + } + return authorization; + } + + public void stopProxy() { + if (server != null) { + server.stopServer(); + GuiPackage.getInstance().unregister(server); + try { + server.join(1000); // wait for server to stop + } catch (InterruptedException e) { + //NOOP + } + notifyTestListenersOfEnd(); + server = null; + } + } + + public String[] getCertificateDetails() { + if (isDynamicMode()) { + try { + X509Certificate caCert = (X509Certificate) keyStore.getCertificate(KeyToolUtils.getRootCAalias()); + if (caCert == null) { + return new String[]{"Could not find certificate"}; + } + return new String[] + { + caCert.getSubjectX500Principal().toString(), + "Fingerprint(SHA1): " + JOrphanUtils.baToHexString(DigestUtils.sha1(caCert.getEncoded()), ' '), + "Created: "+ caCert.getNotBefore().toString() + }; + } catch (GeneralSecurityException e) { + log.error("Problem reading root CA from keystore", e); + return new String[]{"Problem with root certificate", e.getMessage()}; + } + } + return null; // should not happen + } + // Package protected to allow test case access + boolean filterUrl(HTTPSamplerBase sampler) { + String domain = sampler.getDomain(); + if (domain == null || domain.length() == 0) { + return false; + } + + String url = generateMatchUrl(sampler); + CollectionProperty includePatterns = getIncludePatterns(); + if (includePatterns.size() > 0) { + if (!matchesPatterns(url, includePatterns)) { + return false; + } + } + + CollectionProperty excludePatterns = getExcludePatterns(); + if (excludePatterns.size() > 0) { + if (matchesPatterns(url, excludePatterns)) { + return false; + } + } + + return true; + } + + // Package protected to allow test case access + /** + * Filter the response based on the content type. + * If no include nor exclude filter is specified, the result will be included + * + * @param result the sample result to check + * @return true means result will be kept + */ + boolean filterContentType(SampleResult result) { + String includeExp = getContentTypeInclude(); + String excludeExp = getContentTypeExclude(); + // If no expressions are specified, we let the sample pass + if((includeExp == null || includeExp.length() == 0) && + (excludeExp == null || excludeExp.length() == 0) + ) + { + return true; + } + + // Check that we have a content type + String sampleContentType = result.getContentType(); + if(sampleContentType == null || sampleContentType.length() == 0) { + if(log.isDebugEnabled()) { + log.debug("No Content-type found for : " + result.getUrlAsString()); + } + + return true; + } + + if(log.isDebugEnabled()) { + log.debug("Content-type to filter : " + sampleContentType); + } + + // Check if the include pattern is matched + boolean matched = testPattern(includeExp, sampleContentType, true); + if(!matched) { + return false; + } + + // Check if the exclude pattern is matched + matched = testPattern(excludeExp, sampleContentType, false); + if(!matched) { + return false; + } + + return true; + } + + /** + * Returns true if matching pattern was different from expectedToMatch + * @param expression Expression to match + * @param sampleContentType + * @return boolean true if Matching expression + */ + private final boolean testPattern(String expression, String sampleContentType, boolean expectedToMatch) { + if(expression != null && expression.length() > 0) { + if(log.isDebugEnabled()) { + log.debug("Testing Expression : " + expression + " on sampleContentType:"+sampleContentType+", expected to match:"+expectedToMatch); + } + + Pattern pattern = null; + try { + pattern = JMeterUtils.getPatternCache().getPattern(expression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); + if(JMeterUtils.getMatcher().contains(sampleContentType, pattern) != expectedToMatch) { + return false; + } + } catch (MalformedCachePatternException e) { + log.warn("Skipped invalid content pattern: " + expression, e); + } + } + return true; + } + + /** + * Find if there is any AuthManager in JMeterTreeModel + * If there is no one, create and add it to tree + * Add authorization object to AuthManager + * @param authorization {@link Authorization} + * @param target {@link JMeterTreeNode} + */ + private void setAuthorization(Authorization authorization, JMeterTreeNode target) { + JMeterTreeModel jmeterTreeModel = GuiPackage.getInstance().getTreeModel(); + List authManagerNodes = jmeterTreeModel.getNodesOfType(AuthManager.class); + if (authManagerNodes.size() == 0) { + try { + log.debug("Creating HTTP Authentication manager for authorization:"+authorization); + AuthManager authManager = newAuthorizationManager(authorization); + jmeterTreeModel.addComponent(authManager, target); + } catch (IllegalUserActionException e) { + log.error("Failed to add Authorization Manager to target node:" + target.getName(), e); + } + } else{ + AuthManager authManager=(AuthManager)authManagerNodes.get(0).getTestElement(); + authManager.addAuth(authorization); + } + } + + /** + * Helper method to add a Response Assertion + * Called from AWT Event thread + */ + private void addAssertion(JMeterTreeModel model, JMeterTreeNode node) throws IllegalUserActionException { + ResponseAssertion ra = new ResponseAssertion(); + ra.setProperty(TestElement.GUI_CLASS, ASSERTION_GUI); + ra.setName(JMeterUtils.getResString("assertion_title")); // $NON-NLS-1$ + ra.setTestFieldResponseData(); + model.addComponent(ra, node); + } + + /** + * Construct AuthManager + * @param authorization + * @return + * @throws IllegalUserActionException + */ + private AuthManager newAuthorizationManager(Authorization authorization) throws IllegalUserActionException { + AuthManager authManager = new AuthManager(); + authManager.setProperty(TestElement.GUI_CLASS, AUTH_PANEL); + authManager.setProperty(TestElement.TEST_CLASS, AUTH_MANAGER); + authManager.setName("HTTP Authorization Manager"); + authManager.addAuth(authorization); + return authManager; + } + + /** + * Helper method to add a Divider + * Called from Application Thread that needs to update GUI (JMeterTreeModel) + */ + private void addDivider(final JMeterTreeModel model, final JMeterTreeNode node) { + final GenericController sc = new GenericController(); + sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI); + sc.setName("-------------------"); // $NON-NLS-1$ + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + try { + model.addComponent(sc, node); + } catch (IllegalUserActionException e) { + log.error("Program error", e); + throw new Error(e); + } + } + }); + } + + /** + * Helper method to add a Simple Controller to contain the samplers. + * Called from Application Thread that needs to update GUI (JMeterTreeModel) + * @param model + * Test component tree model + * @param node + * Node in the tree where we will add the Controller + * @param name + * A name for the Controller + * @throws InvocationTargetException + * @throws InterruptedException + */ + private void addSimpleController(final JMeterTreeModel model, final JMeterTreeNode node, String name) + throws InterruptedException, InvocationTargetException { + final GenericController sc = new GenericController(); + sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI); + sc.setName(name); + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + try { + model.addComponent(sc, node); + } catch (IllegalUserActionException e) { + log.error("Program error", e); + throw new Error(e); + } + } + }); + } + + /** + * Helper method to add a Transaction Controller to contain the samplers. + * Called from Application Thread that needs to update GUI (JMeterTreeModel) + * @param model + * Test component tree model + * @param node + * Node in the tree where we will add the Controller + * @param name + * A name for the Controller + * @throws InvocationTargetException + * @throws InterruptedException + */ + private void addTransactionController(final JMeterTreeModel model, final JMeterTreeNode node, String name) + throws InterruptedException, InvocationTargetException { + final TransactionController sc = new TransactionController(); + sc.setIncludeTimers(false); + sc.setProperty(TestElement.GUI_CLASS, TRANSACTION_CONTROLLER_GUI); + sc.setName(name); + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + try { + model.addComponent(sc, node); + } catch (IllegalUserActionException e) { + log.error("Program error", e); + throw new Error(e); + } + } + }); + } + /** + * Helpler method to replicate any timers found within the Proxy Controller + * into the provided sampler, while replacing any occurences of string _T_ + * in the timer's configuration with the provided deltaT. + * Called from AWT Event thread + * @param model + * Test component tree model + * @param node + * Sampler node in where we will add the timers + * @param deltaT + * Time interval from the previous request + */ + private void addTimers(JMeterTreeModel model, JMeterTreeNode node, long deltaT) { + TestPlan variables = new TestPlan(); + variables.addParameter("T", Long.toString(deltaT)); // $NON-NLS-1$ + ValueReplacer replacer = new ValueReplacer(variables); + JMeterTreeNode mySelf = model.getNodeOf(this); + Enumeration children = mySelf.children(); + while (children.hasMoreElements()) { + JMeterTreeNode templateNode = children.nextElement(); + if (templateNode.isEnabled()) { + TestElement template = templateNode.getTestElement(); + if (template instanceof Timer) { + TestElement timer = (TestElement) template.clone(); + try { + replacer.undoReverseReplace(timer); + model.addComponent(timer, node); + } catch (InvalidVariableException e) { + // Not 100% sure, but I believe this can't happen, so + // I'll log and throw an error: + log.error("Program error", e); + throw new Error(e); + } catch (IllegalUserActionException e) { + // Not 100% sure, but I believe this can't happen, so + // I'll log and throw an error: + log.error("Program error", e); + throw new Error(e); + } + } + } + } + } + + /** + * Finds the first enabled node of a given type in the tree. + * + * @param type + * class of the node to be found + * + * @return the first node of the given type in the test component tree, or + * null if none was found. + */ + private JMeterTreeNode findFirstNodeOfType(Class type) { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + List nodes = treeModel.getNodesOfType(type); + for (JMeterTreeNode node : nodes) { + if (node.isEnabled()) { + return node; + } + } + return null; + } + + /** + * Finds the controller where samplers have to be stored, that is: + *
    + *
  • The controller specified by the target property. + *
  • If none was specified, the first RecordingController in the tree. + *
  • If none is found, the first AbstractThreadGroup in the tree. + *
  • If none is found, the Workspace. + *
+ * + * @return the tree node for the controller where the proxy must store the + * generated samplers. + */ + public JMeterTreeNode findTargetControllerNode() { + JMeterTreeNode myTarget = getTarget(); + if (myTarget != null) { + return myTarget; + } + myTarget = findFirstNodeOfType(RecordingController.class); + if (myTarget != null) { + return myTarget; + } + myTarget = findFirstNodeOfType(AbstractThreadGroup.class); + if (myTarget != null) { + return myTarget; + } + myTarget = findFirstNodeOfType(WorkBench.class); + if (myTarget != null) { + return myTarget; + } + log.error("Program error: test script recording target not found."); + return null; + } + + /** + * Finds all configuration objects of the given class applicable to the + * recorded samplers, that is: + *
    + *
  • All such elements directly within the HTTP(S) Test Script Recorder (these have + * the highest priority). + *
  • All such elements directly within the target controller (higher + * priority) or directly within any containing controller (lower priority), + * including the Test Plan itself (lowest priority). + *
+ * + * @param myTarget + * tree node for the recording target controller. + * @param myClass + * Class of the elements to be found. + * @param ascending + * true if returned elements should be ordered in ascending + * priority, false if they should be in descending priority. + * + * @return a collection of applicable objects of the given class. + */ + // TODO - could be converted to generic class? + private Collection findApplicableElements(JMeterTreeNode myTarget, Class myClass, boolean ascending) { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + LinkedList elements = new LinkedList(); + + // Look for elements directly within the HTTP proxy: + Enumeration kids = treeModel.getNodeOf(this).children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = (JMeterTreeNode) kids.nextElement(); + if (subNode.isEnabled()) { + TestElement element = (TestElement) subNode.getUserObject(); + if (myClass.isInstance(element)) { + if (ascending) { + elements.addFirst(element); + } else { + elements.add(element); + } + } + } + } + + // Look for arguments elements in the target controller or higher up: + for (JMeterTreeNode controller = myTarget; controller != null; controller = (JMeterTreeNode) controller + .getParent()) { + kids = controller.children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = (JMeterTreeNode) kids.nextElement(); + if (subNode.isEnabled()) { + TestElement element = (TestElement) subNode.getUserObject(); + if (myClass.isInstance(element)) { + log.debug("Applicable: " + element.getName()); + if (ascending) { + elements.addFirst(element); + } else { + elements.add(element); + } + } + + // Special case for the TestPlan's Arguments sub-element: + if (element instanceof TestPlan) { + TestPlan tp = (TestPlan) element; + Arguments args = tp.getArguments(); + if (myClass.isInstance(args)) { + if (ascending) { + elements.addFirst(args); + } else { + elements.add(args); + } + } + } + } + } + } + + return elements; + } + + private void placeSampler(final HTTPSamplerBase sampler, final TestElement[] subConfigs, + JMeterTreeNode myTarget) { + try { + final JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + + boolean firstInBatch = false; + long now = System.currentTimeMillis(); + long deltaT = now - lastTime; + int cachedGroupingMode = groupingMode; + if (deltaT > sampleGap) { + if (!myTarget.isLeaf() && cachedGroupingMode == GROUPING_ADD_SEPARATORS) { + addDivider(treeModel, myTarget); + } + if (cachedGroupingMode == GROUPING_IN_SIMPLE_CONTROLLERS) { + addSimpleController(treeModel, myTarget, sampler.getName()); + } + if (cachedGroupingMode == GROUPING_IN_TRANSACTION_CONTROLLERS) { + addTransactionController(treeModel, myTarget, sampler.getName()); + } + firstInBatch = true;// Remember this was first in its batch + } + if (lastTime == 0) { + deltaT = 0; // Decent value for timers + } + lastTime = now; + + if (cachedGroupingMode == GROUPING_STORE_FIRST_ONLY) { + if (!firstInBatch) { + return; // Huh! don't store this one! + } + + // If we're not storing subsequent samplers, we'll need the + // first sampler to do all the work...: + sampler.setFollowRedirects(true); + sampler.setImageParser(true); + } + + if (cachedGroupingMode == GROUPING_IN_SIMPLE_CONTROLLERS || + cachedGroupingMode == GROUPING_IN_TRANSACTION_CONTROLLERS) { + // Find the last controller in the target to store the + // sampler there: + for (int i = myTarget.getChildCount() - 1; i >= 0; i--) { + JMeterTreeNode c = (JMeterTreeNode) myTarget.getChildAt(i); + if (c.getTestElement() instanceof GenericController) { + myTarget = c; + break; + } + } + } + final long deltaTFinal = deltaT; + final boolean firstInBatchFinal = firstInBatch; + final JMeterTreeNode myTargetFinal = myTarget; + JMeterUtils.runSafe(new Runnable() { + @Override + public void run() { + try { + final JMeterTreeNode newNode = treeModel.addComponent(sampler, myTargetFinal); + if (firstInBatchFinal) { + if (addAssertions) { + addAssertion(treeModel, newNode); + } + addTimers(treeModel, newNode, deltaTFinal); + } + + for (int i = 0; subConfigs != null && i < subConfigs.length; i++) { + if (subConfigs[i] instanceof HeaderManager) { + final TestElement headerManager = subConfigs[i]; + headerManager.setProperty(TestElement.GUI_CLASS, HEADER_PANEL); + treeModel.addComponent(headerManager, newNode); + } + } + } catch (IllegalUserActionException e) { + JMeterUtils.reportErrorToUser(e.getMessage()); + } + } + }); + } catch (Exception e) { + JMeterUtils.reportErrorToUser(e.getMessage()); + } + } + + /** + * Remove from the sampler all values which match the one provided by the + * first configuration in the given collection which provides a value for + * that property. + * + * @param sampler + * Sampler to remove values from. + * @param configurations + * ConfigTestElements in descending priority. + */ + private void removeValuesFromSampler(HTTPSamplerBase sampler, Collection configurations) { + for (PropertyIterator props = sampler.propertyIterator(); props.hasNext();) { + JMeterProperty prop = props.next(); + String name = prop.getName(); + String value = prop.getStringValue(); + + // There's a few properties which are excluded from this processing: + if (name.equals(TestElement.ENABLED) || name.equals(TestElement.GUI_CLASS) || name.equals(TestElement.NAME) + || name.equals(TestElement.TEST_CLASS)) { + continue; // go on with next property. + } + + for (Iterator configs = configurations.iterator(); configs.hasNext();) { + ConfigTestElement config = configs.next(); + + String configValue = config.getPropertyAsString(name); + + if (configValue != null && configValue.length() > 0) { + if (configValue.equals(value)) { + sampler.setProperty(name, ""); // $NON-NLS-1$ + } + // Property was found in a config element. Whether or not + // it matched the value in the sampler, we're done with + // this property -- don't look at lower-priority configs: + break; + } + } + } + } + + private String generateMatchUrl(HTTPSamplerBase sampler) { + StringBuilder buf = new StringBuilder(sampler.getDomain()); + buf.append(':'); // $NON-NLS-1$ + buf.append(sampler.getPort()); + buf.append(sampler.getPath()); + if (sampler.getQueryString().length() > 0) { + buf.append('?'); // $NON-NLS-1$ + buf.append(sampler.getQueryString()); + } + return buf.toString(); + } + + private boolean matchesPatterns(String url, CollectionProperty patterns) { + PropertyIterator iter = patterns.iterator(); + while (iter.hasNext()) { + String item = iter.next().getStringValue(); + Pattern pattern = null; + try { + pattern = JMeterUtils.getPatternCache().getPattern(item, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); + if (JMeterUtils.getMatcher().matches(url, pattern)) { + return true; + } + } catch (MalformedCachePatternException e) { + log.warn("Skipped invalid pattern: " + item, e); + } + } + return false; + } + + /** + * Scan all test elements passed in for values matching the value of any of + * the variables in any of the variable-holding elements in the collection. + * + * @param sampler + * A TestElement to replace values on + * @param configs + * More TestElements to replace values on + * @param variables + * Collection of Arguments to use to do the replacement, ordered + * by ascending priority. + */ + private void replaceValues(TestElement sampler, TestElement[] configs, Collection variables) { + // Build the replacer from all the variables in the collection: + ValueReplacer replacer = new ValueReplacer(); + for (Iterator vars = variables.iterator(); vars.hasNext();) { + final Map map = vars.next().getArgumentsAsMap(); + for (Iterator vals = map.values().iterator(); vals.hasNext();){ + final Object next = vals.next(); + if ("".equals(next)) {// Drop any empty values (Bug 45199) + vals.remove(); + } + } + replacer.addVariables(map); + } + + try { + boolean cachedRegexpMatch = regexMatch; + replacer.reverseReplace(sampler, cachedRegexpMatch); + for (int i = 0; i < configs.length; i++) { + if (configs[i] != null) { + replacer.reverseReplace(configs[i], cachedRegexpMatch); + } + } + } catch (InvalidVariableException e) { + log.warn("Invalid variables included for replacement into recorded " + "sample", e); + } + } + + /** + * This will notify sample listeners directly within the Proxy of the + * sampling that just occured -- so that we have a means to record the + * server's responses as we go. + * + * @param event + * sampling event to be delivered + */ + private void notifySampleListeners(SampleEvent event) { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + JMeterTreeNode myNode = treeModel.getNodeOf(this); + Enumeration kids = myNode.children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = kids.nextElement(); + if (subNode.isEnabled()) { + TestElement testElement = subNode.getTestElement(); + if (testElement instanceof SampleListener) { + ((SampleListener) testElement).sampleOccurred(event); + } + } + } + } + + /** + * This will notify test listeners directly within the Proxy that the 'test' + * (here meaning the proxy recording) has started. + */ + private void notifyTestListenersOfStart() { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + JMeterTreeNode myNode = treeModel.getNodeOf(this); + Enumeration kids = myNode.children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = kids.nextElement(); + if (subNode.isEnabled()) { + TestElement testElement = subNode.getTestElement(); + if (testElement instanceof TestStateListener) { + ((TestStateListener) testElement).testStarted(); + } + } + } + } + + /** + * This will notify test listeners directly within the Proxy that the 'test' + * (here meaning the proxy recording) has ended. + */ + private void notifyTestListenersOfEnd() { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + JMeterTreeNode myNode = treeModel.getNodeOf(this); + Enumeration kids = myNode.children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = kids.nextElement(); + if (subNode.isEnabled()) { + TestElement testElement = subNode.getTestElement(); + if (testElement instanceof TestStateListener) { // TL - TE + ((TestStateListener) testElement).testEnded(); + } + } + } + } + + @Override + public boolean canRemove() { + return null == server; + } + + private void initKeyStore() throws IOException, GeneralSecurityException { + switch(KEYSTORE_MODE) { + case DYNAMIC_KEYSTORE: + storePassword = getPassword(); + keyPassword = getPassword(); + initDynamicKeyStore(); + break; + case JMETER_KEYSTORE: + storePassword = getPassword(); + keyPassword = getPassword(); + initJMeterKeyStore(); + break; + case USER_KEYSTORE: + storePassword = JMeterUtils.getPropDefault("proxy.cert.keystorepass", DEFAULT_PASSWORD); // $NON-NLS-1$; + keyPassword = JMeterUtils.getPropDefault("proxy.cert.keypassword", DEFAULT_PASSWORD); // $NON-NLS-1$; + log.info("HTTP(S) Test Script Recorder will use the keystore '"+ CERT_PATH_ABS + "' with the alias: '" + CERT_ALIAS + "'"); + initUserKeyStore(); + break; + case NONE: + throw new IOException("Cannot find keytool application and no keystore was provided"); + default: + throw new IllegalStateException("Impossible case: " + KEYSTORE_MODE); + } + } + + /** + * Initialise the user-provided keystore + */ + private void initUserKeyStore() { + try { + keyStore = getKeyStore(storePassword.toCharArray()); + X509Certificate caCert = (X509Certificate) keyStore.getCertificate(CERT_ALIAS); + if (caCert == null) { + log.error("Could not find key with alias " + CERT_ALIAS); + keyStore = null; + } else { + caCert.checkValidity(new Date(System.currentTimeMillis()+DateUtils.MILLIS_PER_DAY)); + } + } catch (Exception e) { + keyStore = null; + log.error("Could not open keystore or certificate is not valid " + CERT_PATH_ABS + " " + e.getMessage()); + } + } + + /** + * Initialise the dynamic domain keystore + */ + private void initDynamicKeyStore() throws IOException, GeneralSecurityException { + if (storePassword != null) { // Assume we have already created the store + try { + keyStore = getKeyStore(storePassword.toCharArray()); + for(String alias : KeyToolUtils.getCAaliases()) { + X509Certificate caCert = (X509Certificate) keyStore.getCertificate(alias); + if (caCert == null) { + keyStore = null; // no CA key - probably the wrong store type. + break; // cannot continue + } else { + caCert.checkValidity(new Date(System.currentTimeMillis()+DateUtils.MILLIS_PER_DAY)); + log.info("Valid alias found for " + alias); + } + } + } catch (IOException e) { // store is faulty, we need to recreate it + keyStore = null; // if cert is not valid, flag up to recreate it + if (e.getCause() instanceof UnrecoverableKeyException) { + log.warn("Could not read key store " + e.getMessage() + "; cause: " + e.getCause().getMessage()); + } else { + log.warn("Could not open/read key store " + e.getMessage()); // message includes the file name + } + } catch (GeneralSecurityException e) { + keyStore = null; // if cert is not valid, flag up to recreate it + log.warn("Problem reading key store: " + e.getMessage()); + } + } + if (keyStore == null) { // no existing file or not valid + storePassword = RandomStringUtils.randomAlphanumeric(20); // Alphanum to avoid issues with command-line quoting + keyPassword = storePassword; // we use same password for both + setPassword(storePassword); + log.info("Creating Proxy CA in " + CERT_PATH_ABS); + KeyToolUtils.generateProxyCA(CERT_PATH, storePassword, CERT_VALIDITY); + log.info("Created keystore in " + CERT_PATH_ABS); + keyStore = getKeyStore(storePassword.toCharArray()); // This should now work + } + final String sslDomains = getSslDomains().trim(); + if (sslDomains.length() > 0) { + final String[] domains = sslDomains.split(","); + // The subject may be either a host or a domain + for(String subject : domains) { + if (isValid(subject)) { + if (!keyStore.containsAlias(subject)) { + log.info("Creating entry " + subject + " in " + CERT_PATH_ABS); + KeyToolUtils.generateHostCert(CERT_PATH, storePassword, subject, CERT_VALIDITY); + keyStore = getKeyStore(storePassword.toCharArray()); // reload to pick up new aliases + // reloading is very quick compared with creating an entry currently + } + } else { + log.warn("Attempt to create an invalid domain certificate: " + subject); + } + } + } + } + + private boolean isValid(String subject) { + String parts[] = subject.split("\\."); + if (!parts[0].endsWith("*")) { // not a wildcard + return true; + } + return parts.length >= 3 && AbstractVerifier.acceptableCountryWildcard(subject); + } + + // This should only be called for a specific host + KeyStore updateKeyStore(String port, String host) throws IOException, GeneralSecurityException { + synchronized(CERT_PATH) { // ensure Proxy threads cannot interfere with each other + if (!keyStore.containsAlias(host)) { + log.info(port + "Creating entry " + host + " in " + CERT_PATH_ABS); + KeyToolUtils.generateHostCert(CERT_PATH, storePassword, host, CERT_VALIDITY); + } + keyStore = getKeyStore(storePassword.toCharArray()); // reload after adding alias + } + return keyStore; + } + + /** + * Initialise the single key JMeter keystore (original behaviour) + */ + private void initJMeterKeyStore() throws IOException, GeneralSecurityException { + if (storePassword != null) { // Assume we have already created the store + try { + keyStore = getKeyStore(storePassword.toCharArray()); + X509Certificate caCert = (X509Certificate) keyStore.getCertificate(JMETER_SERVER_ALIAS); + caCert.checkValidity(new Date(System.currentTimeMillis()+DateUtils.MILLIS_PER_DAY)); + } catch (Exception e) { // store is faulty, we need to recreate it + keyStore = null; // if cert is not valid, flag up to recreate it + log.warn("Could not open expected file or certificate is not valid " + CERT_PATH_ABS + " " + e.getMessage()); + } + } + if (keyStore == null) { // no existing file or not valid + storePassword = RandomStringUtils.randomAlphanumeric(20); // Alphanum to avoid issues with command-line quoting + keyPassword = storePassword; // we use same password for both + setPassword(storePassword); + log.info("Generating standard keypair in " + CERT_PATH_ABS); + if(!CERT_PATH.delete()){ // safer to start afresh + log.warn("Could not delete "+CERT_PATH.getAbsolutePath()+", this could create issues, stop jmeter, ensure file is deleted and restart again"); + } + KeyToolUtils.genkeypair(CERT_PATH, JMETER_SERVER_ALIAS, storePassword, CERT_VALIDITY, null, null); + keyStore = getKeyStore(storePassword.toCharArray()); // This should now work + } + } + + private KeyStore getKeyStore(char[] password) throws GeneralSecurityException, IOException { + InputStream in = null; + try { + in = new BufferedInputStream(new FileInputStream(CERT_PATH)); + log.debug("Opened Keystore file: " + CERT_PATH_ABS); + KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE); + ks.load(in, password); + log.debug("Loaded Keystore file: " + CERT_PATH_ABS); + return ks; + } finally { + IOUtils.closeQuietly(in); + } + } + + private String getPassword() { + return PREFERENCES.get(USER_PASSWORD_KEY, null); + } + + private void setPassword(String password) { + PREFERENCES.put(USER_PASSWORD_KEY, password); + } + + // the keystore for use by the Proxy + KeyStore getKeyStore() { + return keyStore; + } + + String getKeyPassword() { + return keyPassword; + } + + public static boolean isDynamicMode() { + return KEYSTORE_MODE == KeystoreMode.DYNAMIC_KEYSTORE; + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java new file mode 100644 index 00000000000..a060162b608 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java @@ -0,0 +1,97 @@ +/* + * 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.protocol.http.proxy; + +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; + +/** + * Factory of sampler + */ +public interface SamplerCreator { + + /** + * @return String[] array of Content types managed by Factory + */ + String[] getManagedContentTypes(); + + /** + * Create HTTPSamplerBase + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map of page encodings + * @param formEncodings Map of form encodings + * @return {@link HTTPSamplerBase} + */ + HTTPSamplerBase createSampler(HttpRequestHdr request, + Map pageEncodings, Map formEncodings); + + /** + * Populate sampler from request + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map of page encodings + * @param formEncodings Map of form encodings + * @throws Exception when something fails + */ + void populateSampler(HTTPSamplerBase sampler, + HttpRequestHdr request, Map pageEncodings, + Map formEncodings) + throws Exception; + + /** + * Post process sampler + * Called after sampling + * @param sampler HTTPSamplerBase + * @param result SampleResult + * @since 2.9 + */ + void postProcessSampler(HTTPSamplerBase sampler, SampleResult result); + + /** + * Default implementation calls: + *
    + *
  1. {@link SamplerCreator}{@link #createSampler(HttpRequestHdr, Map, Map)}
  2. + *
  3. {@link SamplerCreator}{@link #populateSampler(HTTPSamplerBase, HttpRequestHdr, Map, Map)}
  4. + *
+ * @param request {@link HttpRequestHdr} + * @param pageEncodings Map of page encodings + * @param formEncodings Map of form encodings + * @return {@link HTTPSamplerBase} + * @throws Exception when something fails + * @since 2.9 + */ + HTTPSamplerBase createAndPopulateSampler(HttpRequestHdr request, + Map pageEncodings, Map formEncodings) + throws Exception; + + /** + * Create sampler children. + * This method can be used to add PostProcessor or ResponseAssertions by + * implementations of {@link SamplerCreator}. + * Return empty list if nothing to create + * @param sampler {@link HTTPSamplerBase} + * @param result {@link SampleResult} + * @return List + */ + List createChildren(HTTPSamplerBase sampler, SampleResult result); +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java new file mode 100644 index 00000000000..bea80defc32 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java @@ -0,0 +1,104 @@ +/* + * 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.protocol.http.proxy; + +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +/** + * {@link SamplerCreator} factory + */ +public class SamplerCreatorFactory { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final SamplerCreator DEFAULT_SAMPLER_CREATOR = new DefaultSamplerCreator(); + + private final Map samplerCreatorMap = new HashMap(); + + /** + * + */ + public SamplerCreatorFactory() { + init(); + } + + /** + * Initialize factory from classpath + */ + private void init() { + try { + List listClasses = ClassFinder.findClassesThatExtend( + JMeterUtils.getSearchPaths(), + new Class[] {SamplerCreator.class }); + for (String strClassName : listClasses) { + try { + if(log.isDebugEnabled()) { + log.debug("Loading class: "+ strClassName); + } + Class commandClass = Class.forName(strClassName); + if (!Modifier.isAbstract(commandClass.getModifiers())) { + if(log.isDebugEnabled()) { + log.debug("Instantiating: "+ commandClass.getName()); + } + SamplerCreator creator = (SamplerCreator) commandClass.newInstance(); + String[] contentTypes = creator.getManagedContentTypes(); + for (String contentType : contentTypes) { + if(log.isDebugEnabled()) { + log.debug("Registering samplerCreator "+commandClass.getName()+" for content type:"+contentType); + } + SamplerCreator oldSamplerCreator = samplerCreatorMap.put(contentType, creator); + if(oldSamplerCreator!=null) { + log.warn("A sampler creator was already registered for:"+contentType+", class:"+oldSamplerCreator.getClass() + + ", it will be replaced"); + } + } + } + } catch (Exception e) { + log.error("Exception registering "+SamplerCreator.class.getName() + " with implementation:"+strClassName, e); + } + } + } catch (IOException e) { + log.error("Exception finding implementations of "+SamplerCreator.class, e); + } + } + + /** + * Gets {@link SamplerCreator} for content type, if none is found returns {@link DefaultSamplerCreator} + * @param request {@link HttpRequestHdr} from which the content type should be used + * @param pageEncodings Map of pageEncodings + * @param formEncodings Map of formEncodings + * @return SamplerCreator for the content type of the request, or {@link DefaultSamplerCreator} when none is found + */ + public SamplerCreator getSamplerCreator(HttpRequestHdr request, + Map pageEncodings, Map formEncodings) { + SamplerCreator creator = samplerCreatorMap.get(request.getContentType()); + if(creator == null) { + return DEFAULT_SAMPLER_CREATOR; + } + return creator; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/ServerAliasKeyManager.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/ServerAliasKeyManager.java new file mode 100644 index 00000000000..288cee1742a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/ServerAliasKeyManager.java @@ -0,0 +1,85 @@ +/* + * 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.protocol.http.proxy; + +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.X509KeyManager; + +/** + * X509KeyManager wrapper class which returns a spacific server alias. + */ +class ServerAliasKeyManager implements X509KeyManager { + + private final X509KeyManager km; + + private final String serverAlias; + + /** + * Create a wrapper class that always returns the specified server alias + * + * @param km the key manager to wrap + * @param serverAlias the server alias which {@link #chooseServerAlias(String, Principal[], Socket)} will return + */ + ServerAliasKeyManager(KeyManager km, String serverAlias) { + this.km = (X509KeyManager) km; + this.serverAlias = serverAlias; + } + + /** + * {@inheritDoc} + * @return always returns the specified server alias + */ + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + return serverAlias; + } + + // Remaining implementations delegate to the wrapped key manager + + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { + return km.chooseClientAlias(keyType, issuers, socket); + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + return km.getCertificateChain(alias); + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return km.getClientAliases(keyType, issuers); + } + + @Override + public PrivateKey getPrivateKey(String alias) { + return km.getPrivateKey(alias); + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return km.getServerAliases(keyType, issuers); + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/proxy/gui/ProxyControlGui.java b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/gui/ProxyControlGui.java new file mode 100644 index 00000000000..6bc342029ea --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/proxy/gui/ProxyControlGui.java @@ -0,0 +1,979 @@ +/* + * 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.protocol.http.proxy.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.io.IOException; +import java.net.BindException; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.gui.LogicControllerGui; +import org.apache.jmeter.control.gui.TreeNodeWrapper; +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.http.control.RecordingController; +import org.apache.jmeter.protocol.http.proxy.ProxyControl; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.exec.KeyToolUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ProxyControlGui extends LogicControllerGui implements JMeterGUIComponent, ActionListener, ItemListener, + KeyListener, UnsharedComponent { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + private static final String NEW_LINE = "\n"; // $NON-NLS-1$ + + private static final String SPACE = " "; // $NON-NLS-1$ + + /** + * This choice means don't explicitly set Implementation and rely on default, see Bug 54154 + */ + private static final String USE_DEFAULT_HTTP_IMPL = ""; // $NON-NLS-1$ + + private static final String SUGGESTED_EXCLUSIONS = + JMeterUtils.getPropDefault("proxy.excludes.suggested", ".*\\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff)"); // $NON-NLS-1$ + + private JTextField portField; + + private JLabeledTextField sslDomains; + + /** + * Used to indicate that HTTP request headers should be captured. The + * default is to capture the HTTP request headers, which are specific to + * particular browser settings. + */ + private JCheckBox httpHeaders; + + /** + * Whether to group requests together based on inactivity separation periods -- + * and how to handle such grouping afterwards. + */ + private JComboBox groupingMode; + + /** + * Add an Assertion to the first sample of each set + */ + private JCheckBox addAssertions; + + /** + * Set/clear the Use Keep-Alive box on the samplers (default is true) + */ + private JCheckBox useKeepAlive; + + /* + * Use regexes to match the source data + */ + private JCheckBox regexMatch; + + /** + * The list of sampler type names to choose from + */ + private JComboBox samplerTypeName; + + /** + * Set/clear the Redirect automatically box on the samplers (default is false) + */ + private JCheckBox samplerRedirectAutomatically; + + /** + * Set/clear the Follow-redirects box on the samplers (default is true) + */ + private JCheckBox samplerFollowRedirects; + + /** + * Set/clear the Download images box on the samplers (default is false) + */ + private JCheckBox samplerDownloadImages; + + /** + * Regular expression to include results based on content type + */ + private JTextField contentTypeInclude; + + /** + * Regular expression to exclude results based on content type + */ + private JTextField contentTypeExclude; + + /** + * List of available target controllers + */ + private JComboBox targetNodes; + + /** + * Notify child Listener of Filtered Samplers + */ + private JCheckBox notifyChildSamplerListenerOfFilteredSamplersCB; + + private DefaultComboBoxModel targetNodesModel; + + private ProxyControl model; + + private JTable excludeTable; + + private PowerTableModel excludeModel; + + private JTable includeTable; + + private PowerTableModel includeModel; + + private static final String CHANGE_TARGET = "change_target"; // $NON-NLS-1$ + + private JButton stop, start, restart; + + //+ action names + private static final String STOP = "stop"; // $NON-NLS-1$ + + private static final String START = "start"; // $NON-NLS-1$ + + private static final String RESTART = "restart"; // $NON-NLS-1$ + + // This is applied to fields that should cause a restart when changed + private static final String ENABLE_RESTART = "enable_restart"; // $NON-NLS-1$ + + private static final String ADD_INCLUDE = "add_include"; // $NON-NLS-1$ + + private static final String ADD_EXCLUDE = "add_exclude"; // $NON-NLS-1$ + + private static final String DELETE_INCLUDE = "delete_include"; // $NON-NLS-1$ + + private static final String DELETE_EXCLUDE = "delete_exclude"; // $NON-NLS-1$ + + private static final String ADD_TO_INCLUDE_FROM_CLIPBOARD = "include_clipboard"; // $NON-NLS-1$ + + private static final String ADD_TO_EXCLUDE_FROM_CLIPBOARD = "exclude_clipboard"; // $NON-NLS-1$ + + private static final String ADD_SUGGESTED_EXCLUDES = "exclude_suggested"; + //- action names + + // Resource names for column headers + private static final String INCLUDE_COL = "patterns_to_include"; // $NON-NLS-1$ + + private static final String EXCLUDE_COL = "patterns_to_exclude"; // $NON-NLS-1$ + + // Used by itemListener + private static final String PORTFIELD = "portField"; // $NON-NLS-1$ + + public ProxyControlGui() { + super(); + log.debug("Creating ProxyControlGui"); + init(); + } + + /** {@inheritDoc} */ + @Override + public TestElement createTestElement() { + model = makeProxyControl(); + log.debug("creating/configuring model = " + model); + modifyTestElement(model); + return model; + } + + protected ProxyControl makeProxyControl() { + ProxyControl local = new ProxyControl(); + return local; + } + + /** {@inheritDoc} */ + @Override + public void modifyTestElement(TestElement el) { + GuiUtils.stopTableEditing(excludeTable); + GuiUtils.stopTableEditing(includeTable); + configureTestElement(el); + if (el instanceof ProxyControl) { + model = (ProxyControl) el; + model.setPort(portField.getText()); + model.setSslDomains(sslDomains.getText()); + setIncludeListInProxyControl(model); + setExcludeListInProxyControl(model); + model.setCaptureHttpHeaders(httpHeaders.isSelected()); + model.setGroupingMode(groupingMode.getSelectedIndex()); + model.setAssertions(addAssertions.isSelected()); + if(samplerTypeName.getSelectedIndex()< HTTPSamplerFactory.getImplementations().length) { + model.setSamplerTypeName(HTTPSamplerFactory.getImplementations()[samplerTypeName.getSelectedIndex()]); + } else { + model.setSamplerTypeName(USE_DEFAULT_HTTP_IMPL); + } + model.setSamplerRedirectAutomatically(samplerRedirectAutomatically.isSelected()); + model.setSamplerFollowRedirects(samplerFollowRedirects.isSelected()); + model.setUseKeepAlive(useKeepAlive.isSelected()); + model.setSamplerDownloadImages(samplerDownloadImages.isSelected()); + model.setNotifyChildSamplerListenerOfFilteredSamplers(notifyChildSamplerListenerOfFilteredSamplersCB.isSelected()); + model.setRegexMatch(regexMatch.isSelected()); + model.setContentTypeInclude(contentTypeInclude.getText()); + model.setContentTypeExclude(contentTypeExclude.getText()); + TreeNodeWrapper nw = (TreeNodeWrapper) targetNodes.getSelectedItem(); + if (nw == null) { + model.setTarget(null); + } else { + model.setTarget(nw.getTreeNode()); + } + } + } + + protected void setIncludeListInProxyControl(ProxyControl element) { + List includeList = getDataList(includeModel, INCLUDE_COL); + element.setIncludeList(includeList); + } + + protected void setExcludeListInProxyControl(ProxyControl element) { + List excludeList = getDataList(excludeModel, EXCLUDE_COL); + element.setExcludeList(excludeList); + } + + private List getDataList(PowerTableModel p_model, String colName) { + String[] dataArray = p_model.getData().getColumn(colName); + List list = new LinkedList(); + for (int i = 0; i < dataArray.length; i++) { + list.add(dataArray[i]); + } + return list; + } + + /** {@inheritDoc} */ + @Override + public String getLabelResource() { + return "proxy_title"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.NON_TEST_ELEMENTS }); + } + + /** {@inheritDoc} */ + @Override + public void configure(TestElement element) { + log.debug("Configuring gui with " + element); + super.configure(element); + model = (ProxyControl) element; + portField.setText(model.getPortString()); + sslDomains.setText(model.getSslDomains()); + httpHeaders.setSelected(model.getCaptureHttpHeaders()); + groupingMode.setSelectedIndex(model.getGroupingMode()); + addAssertions.setSelected(model.getAssertions()); + samplerTypeName.setSelectedItem(model.getSamplerTypeName()); + samplerRedirectAutomatically.setSelected(model.getSamplerRedirectAutomatically()); + samplerFollowRedirects.setSelected(model.getSamplerFollowRedirects()); + useKeepAlive.setSelected(model.getUseKeepalive()); + samplerDownloadImages.setSelected(model.getSamplerDownloadImages()); + notifyChildSamplerListenerOfFilteredSamplersCB.setSelected(model.getNotifyChildSamplerListenerOfFilteredSamplers()); + regexMatch.setSelected(model.getRegexMatch()); + contentTypeInclude.setText(model.getContentTypeInclude()); + contentTypeExclude.setText(model.getContentTypeExclude()); + + reinitializeTargetCombo();// Set up list of potential targets and + // enable listener + + populateTable(includeModel, model.getIncludePatterns().iterator()); + populateTable(excludeModel, model.getExcludePatterns().iterator()); + repaint(); + } + + private void populateTable(PowerTableModel p_model, PropertyIterator iter) { + p_model.clearData(); + while (iter.hasNext()) { + p_model.addRow(new Object[] { iter.next().getStringValue() }); + } + p_model.fireTableDataChanged(); + } + + /* + * Handles groupingMode. actionPerfomed is not suitable, as that seems to be + * activated whenever the Proxy is selected in the Test Plan + * Also handles samplerTypeName + */ + /** {@inheritDoc} */ + @Override + public void itemStateChanged(ItemEvent e) { + // System.err.println(e.paramString()); + enableRestart(); + } + + /** {@inheritDoc} */ + @Override + public void actionPerformed(ActionEvent action) { + String command = action.getActionCommand(); + + // Prevent both redirect types from being selected + final Object source = action.getSource(); + if (source.equals(samplerFollowRedirects) && samplerFollowRedirects.isSelected()) { + samplerRedirectAutomatically.setSelected(false); + } else if (source.equals(samplerRedirectAutomatically) && samplerRedirectAutomatically.isSelected()) { + samplerFollowRedirects.setSelected(false); + } + + // System.err.println(action.paramString()+" "+command+ " + // "+action.getModifiers()); + + if (command.equals(STOP)) { + model.stopProxy(); + stop.setEnabled(false); + start.setEnabled(true); + restart.setEnabled(false); + } else if (command.equals(START)) { + startProxy(); + } else if (command.equals(RESTART)) { + model.stopProxy(); + startProxy(); + } else if (command.equals(ENABLE_RESTART)){ + enableRestart(); + } else if (command.equals(ADD_EXCLUDE)) { + excludeModel.addNewRow(); + excludeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(ADD_INCLUDE)) { + includeModel.addNewRow(); + includeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(DELETE_EXCLUDE)) { + excludeModel.removeRow(excludeTable.getSelectedRow()); + excludeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(DELETE_INCLUDE)) { + includeModel.removeRow(includeTable.getSelectedRow()); + includeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(CHANGE_TARGET)) { + log.debug("Change target " + targetNodes.getSelectedItem()); + log.debug("In model " + model); + TreeNodeWrapper nw = (TreeNodeWrapper) targetNodes.getSelectedItem(); + model.setTarget(nw.getTreeNode()); + enableRestart(); + } else if (command.equals(ADD_TO_INCLUDE_FROM_CLIPBOARD)) { + addFromClipboard(includeTable); + includeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(ADD_TO_EXCLUDE_FROM_CLIPBOARD)) { + addFromClipboard(excludeTable); + excludeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(ADD_SUGGESTED_EXCLUDES)) { + addSuggestedExcludes(excludeTable); + excludeModel.fireTableDataChanged(); + enableRestart(); + } + } + + /** + * Add suggested excludes to exclude table + * @param table {@link JTable} + */ + protected void addSuggestedExcludes(JTable table) { + GuiUtils.stopTableEditing(table); + int rowCount = table.getRowCount(); + PowerTableModel model = null; + String[] exclusions = SUGGESTED_EXCLUSIONS.split(";"); // $NON-NLS-1$ + if (exclusions.length>0) { + model = (PowerTableModel) table.getModel(); + if(model != null) { + for (String clipboardLine : exclusions) { + model.addRow(new Object[] {clipboardLine}); + } + if (table.getRowCount() > rowCount) { + // Highlight (select) the appropriate rows. + int rowToSelect = model.getRowCount() - 1; + table.setRowSelectionInterval(rowCount, rowToSelect); + } + } + } + } + + /** + * Add values from the clipboard to table + * @param table {@link JTable} + */ + protected void addFromClipboard(JTable table) { + GuiUtils.stopTableEditing(table); + int rowCount = table.getRowCount(); + PowerTableModel model = null; + try { + String clipboardContent = GuiUtils.getPastedText(); + if (clipboardContent != null) { + String[] clipboardLines = clipboardContent.split(NEW_LINE); + for (String clipboardLine : clipboardLines) { + model = (PowerTableModel) table.getModel(); + model.addRow(new Object[] {clipboardLine}); + } + if (table.getRowCount() > rowCount) { + if(model != null) { + // Highlight (select) the appropriate rows. + int rowToSelect = model.getRowCount() - 1; + table.setRowSelectionInterval(rowCount, rowToSelect); + } + } + } + } catch (IOException ioe) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("proxy_daemon_error_read_args") // $NON-NLS-1$ + + "\n" + ioe.getLocalizedMessage(), JMeterUtils.getResString("error_title"), // $NON-NLS-1$ $NON-NLS-2$ + JOptionPane.ERROR_MESSAGE); + } catch (UnsupportedFlavorException ufe) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("proxy_daemon_error_not_retrieve") + SPACE // $NON-NLS-1$ + + DataFlavor.stringFlavor.getHumanPresentableName() + SPACE + + JMeterUtils.getResString("proxy_daemon_error_from_clipboard") // $NON-NLS-1$ + + ufe.getLocalizedMessage(), JMeterUtils.getResString("error_title"), // $NON-NLS-1$ + JOptionPane.ERROR_MESSAGE); + } + } + + private void startProxy() { + ValueReplacer replacer = GuiPackage.getInstance().getReplacer(); + modifyTestElement(model); + TreeNodeWrapper treeNodeWrapper = (TreeNodeWrapper)targetNodesModel.getSelectedItem(); + if (JMeterUtils.getResString("use_recording_controller").equals(treeNodeWrapper.getLabel())) { + JMeterTreeNode targetNode = model.findTargetControllerNode(); + if(targetNode == null || !(targetNode.getTestElement() instanceof RecordingController)) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("proxy_cl_wrong_target_cl"), // $NON-NLS-1$ + JMeterUtils.getResString("error_title"), // $NON-NLS-1$ + JOptionPane.ERROR_MESSAGE); + return; + } + } + // Proxy can take some while to start up; show a wating cursor + Cursor cursor = getCursor(); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + // TODO somehow show progress + try { + replacer.replaceValues(model); + model.startProxy(); + start.setEnabled(false); + stop.setEnabled(true); + restart.setEnabled(false); + if (ProxyControl.isDynamicMode()) { + String details[] = model.getCertificateDetails(); + StringBuilder sb = new StringBuilder(); + sb.append(JMeterUtils.getResString("proxy_daemon_msg_rootca_cert")) // $NON-NLS-1$ + .append(SPACE).append(KeyToolUtils.ROOT_CACERT_CRT_PFX) + .append(SPACE).append(JMeterUtils.getResString("proxy_daemon_msg_created_in_bin")); + sb.append(NEW_LINE).append(JMeterUtils.getResString("proxy_daemon_msg_install_as_in_doc")); // $NON-NLS-1$ + sb.append(NEW_LINE).append(JMeterUtils.getResString("proxy_daemon_msg_check_details")) // $NON-NLS-1$ + .append(NEW_LINE).append(NEW_LINE); + for(String detail : details) { + sb.append(detail).append(NEW_LINE); + } + JOptionPane.showMessageDialog(this, + sb.toString(), + JMeterUtils.getResString("proxy_daemon_msg_rootca_cert") + SPACE // $NON-NLS-1$ + + KeyToolUtils.ROOT_CACERT_CRT_PFX + SPACE + + JMeterUtils.getResString("proxy_daemon_msg_created_in_bin"), // $NON-NLS-1$ + JOptionPane.INFORMATION_MESSAGE); + } + } catch (InvalidVariableException e) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("invalid_variables")+": "+e.getMessage(), // $NON-NLS-1$ $NON-NLS-2$ + JMeterUtils.getResString("error_title"), // $NON-NLS-1$ + JOptionPane.ERROR_MESSAGE); + } catch (BindException e) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("proxy_daemon_bind_error")+": "+e.getMessage(), // $NON-NLS-1$ $NON-NLS-2$ + JMeterUtils.getResString("error_title"), // $NON-NLS-1$ + JOptionPane.ERROR_MESSAGE); + } catch (IOException e) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("proxy_daemon_error")+": "+e.getMessage(), // $NON-NLS-1$ $NON-NLS-2$ + JMeterUtils.getResString("error_title"), // $NON-NLS-1$ + JOptionPane.ERROR_MESSAGE); + } finally { + setCursor(cursor); + } + } + + private void enableRestart() { + if (stop.isEnabled()) { + // System.err.println("Enable Restart"); + restart.setEnabled(true); + } + } + + /** {@inheritDoc} */ + @Override + public void keyPressed(KeyEvent e) { + } + + /** {@inheritDoc} */ + @Override + public void keyTyped(KeyEvent e) { + } + + /** {@inheritDoc} */ + @Override + public void keyReleased(KeyEvent e) { + String fieldName = e.getComponent().getName(); + + if (fieldName.equals(PORTFIELD)) { + try { + Integer.parseInt(portField.getText()); + } catch (NumberFormatException nfe) { + int length = portField.getText().length(); + if (length > 0) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("proxy_settings_port_error_digits"), // $NON-NLS-1$ + JMeterUtils.getResString("proxy_settings_port_error_invalid_data"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + // Drop the last character: + portField.setText(portField.getText().substring(0, length-1)); + } + } + enableRestart(); + } else if (fieldName.equals(ENABLE_RESTART)){ + enableRestart(); + } + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + Box myBox = Box.createVerticalBox(); + myBox.add(createPortPanel()); + myBox.add(Box.createVerticalStrut(5)); + myBox.add(createTestPlanContentPanel()); + myBox.add(Box.createVerticalStrut(5)); + myBox.add(createHTTPSamplerPanel()); + myBox.add(Box.createVerticalStrut(5)); + myBox.add(createContentTypePanel()); + myBox.add(Box.createVerticalStrut(5)); + mainPanel.add(myBox, BorderLayout.NORTH); + + Box includeExcludePanel = Box.createVerticalBox(); + includeExcludePanel.add(createIncludePanel()); + includeExcludePanel.add(createExcludePanel()); + includeExcludePanel.add(createNotifyListenersPanel()); + mainPanel.add(includeExcludePanel, BorderLayout.CENTER); + + mainPanel.add(createControls(), BorderLayout.SOUTH); + + add(mainPanel, BorderLayout.CENTER); + } + + private JPanel createControls() { + start = new JButton(JMeterUtils.getResString("start")); // $NON-NLS-1$ + start.addActionListener(this); + start.setActionCommand(START); + start.setEnabled(true); + + stop = new JButton(JMeterUtils.getResString("stop")); // $NON-NLS-1$ + stop.addActionListener(this); + stop.setActionCommand(STOP); + stop.setEnabled(false); + + restart = new JButton(JMeterUtils.getResString("restart")); // $NON-NLS-1$ + restart.addActionListener(this); + restart.setActionCommand(RESTART); + restart.setEnabled(false); + + JPanel panel = new JPanel(); + panel.add(start); + panel.add(stop); + panel.add(restart); + return panel; + } + + private JPanel createPortPanel() { + portField = new JTextField(ProxyControl.DEFAULT_PORT_S, 5); + portField.setName(PORTFIELD); + portField.addKeyListener(this); + + JLabel label = new JLabel(JMeterUtils.getResString("port")); // $NON-NLS-1$ + label.setLabelFor(portField); + + JPanel gPane = new JPanel(new BorderLayout()); + gPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("proxy_general_settings"))); // $NON-NLS-1$ + + HorizontalPanel panel = new HorizontalPanel(); + panel.add(label); + panel.add(portField); + panel.add(Box.createHorizontalStrut(10)); + + gPane.add(panel, BorderLayout.WEST); + + sslDomains = new JLabeledTextField(JMeterUtils.getResString("proxy_domains")); // $NON-NLS-1$ + sslDomains.setEnabled(ProxyControl.isDynamicMode()); + if (ProxyControl.isDynamicMode()) { + sslDomains.setToolTipText(JMeterUtils.getResString("proxy_domains_dynamic_mode_tooltip")); + } else { + sslDomains.setToolTipText(JMeterUtils.getResString("proxy_domains_dynamic_mode_tooltip_java6")); + } + gPane.add(sslDomains, BorderLayout.CENTER); + return gPane; + } + + private JPanel createTestPlanContentPanel() { + httpHeaders = new JCheckBox(JMeterUtils.getResString("proxy_headers")); // $NON-NLS-1$ + httpHeaders.setSelected(true); // maintain original default + httpHeaders.addActionListener(this); + httpHeaders.setActionCommand(ENABLE_RESTART); + + addAssertions = new JCheckBox(JMeterUtils.getResString("proxy_assertions")); // $NON-NLS-1$ + addAssertions.setSelected(false); + addAssertions.addActionListener(this); + addAssertions.setActionCommand(ENABLE_RESTART); + + regexMatch = new JCheckBox(JMeterUtils.getResString("proxy_regex")); // $NON-NLS-1$ + regexMatch.setSelected(false); + regexMatch.addActionListener(this); + regexMatch.setActionCommand(ENABLE_RESTART); + + VerticalPanel mainPanel = new VerticalPanel(); + mainPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("proxy_test_plan_content"))); // $NON-NLS-1$ + + HorizontalPanel nodeCreationPanel = new HorizontalPanel(); + nodeCreationPanel.add(createGroupingPanel()); + nodeCreationPanel.add(httpHeaders); + nodeCreationPanel.add(addAssertions); + nodeCreationPanel.add(regexMatch); + + HorizontalPanel targetPanel = new HorizontalPanel(); + targetPanel.add(createTargetPanel()); + mainPanel.add(targetPanel); + mainPanel.add(nodeCreationPanel); + + return mainPanel; + } + + private JPanel createHTTPSamplerPanel() { + DefaultComboBoxModel m = new DefaultComboBoxModel(); + for (String s : HTTPSamplerFactory.getImplementations()){ + m.addElement(s); + } + m.addElement(USE_DEFAULT_HTTP_IMPL); + samplerTypeName = new JComboBox(m); + samplerTypeName.setPreferredSize(new Dimension(150, 20)); + samplerTypeName.setSelectedItem(USE_DEFAULT_HTTP_IMPL); + samplerTypeName.addItemListener(this); + JLabel label2 = new JLabel(JMeterUtils.getResString("proxy_sampler_type")); // $NON-NLS-1$ + label2.setLabelFor(samplerTypeName); + + samplerRedirectAutomatically = new JCheckBox(JMeterUtils.getResString("follow_redirects_auto")); // $NON-NLS-1$ + samplerRedirectAutomatically.setSelected(false); + samplerRedirectAutomatically.addActionListener(this); + samplerRedirectAutomatically.setActionCommand(ENABLE_RESTART); + + samplerFollowRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects")); // $NON-NLS-1$ + samplerFollowRedirects.setSelected(true); + samplerFollowRedirects.addActionListener(this); + samplerFollowRedirects.setActionCommand(ENABLE_RESTART); + + useKeepAlive = new JCheckBox(JMeterUtils.getResString("use_keepalive")); // $NON-NLS-1$ + useKeepAlive.setSelected(true); + useKeepAlive.addActionListener(this); + useKeepAlive.setActionCommand(ENABLE_RESTART); + + samplerDownloadImages = new JCheckBox(JMeterUtils.getResString("web_testing_retrieve_images")); // $NON-NLS-1$ + samplerDownloadImages.setSelected(false); + samplerDownloadImages.addActionListener(this); + samplerDownloadImages.setActionCommand(ENABLE_RESTART); + + HorizontalPanel panel = new HorizontalPanel(); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("proxy_sampler_settings"))); // $NON-NLS-1$ + panel.add(label2); + panel.add(samplerTypeName); + panel.add(samplerRedirectAutomatically); + panel.add(samplerFollowRedirects); + panel.add(useKeepAlive); + panel.add(samplerDownloadImages); + + return panel; + } + + private JPanel createTargetPanel() { + targetNodesModel = new DefaultComboBoxModel(); + targetNodes = new JComboBox(targetNodesModel); + targetNodes.setPrototypeDisplayValue(""); // $NON-NLS-1$ // Bug 56303 fixed the width of combo list + JPopupMenu popup = (JPopupMenu) targetNodes.getUI().getAccessibleChild(targetNodes, 0); // get popup element + JScrollPane scrollPane = findScrollPane(popup); + if(scrollPane != null) { + scrollPane.setHorizontalScrollBar(new JScrollBar(JScrollBar.HORIZONTAL)); // add scroll pane if label element is too long + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + } + targetNodes.setActionCommand(CHANGE_TARGET); + // Action listener will be added later + + JLabel label = new JLabel(JMeterUtils.getResString("proxy_target")); // $NON-NLS-1$ + label.setLabelFor(targetNodes); + + HorizontalPanel panel = new HorizontalPanel(); + panel.add(label); + panel.add(targetNodes); + + return panel; + } + + private JScrollPane findScrollPane(JPopupMenu popup) { + Component[] components = popup.getComponents(); + for (Component component : components) { + if(component instanceof JScrollPane) { + return (JScrollPane) component; + } + } + return null; + } + + private JPanel createGroupingPanel() { + DefaultComboBoxModel m = new DefaultComboBoxModel(); + // Note: position of these elements in the menu *must* match the + // corresponding ProxyControl.GROUPING_* values. + m.addElement(JMeterUtils.getResString("grouping_no_groups")); // $NON-NLS-1$ + m.addElement(JMeterUtils.getResString("grouping_add_separators")); // $NON-NLS-1$ + m.addElement(JMeterUtils.getResString("grouping_in_controllers")); // $NON-NLS-1$ + m.addElement(JMeterUtils.getResString("grouping_store_first_only")); // $NON-NLS-1$ + m.addElement(JMeterUtils.getResString("grouping_in_transaction_controllers")); // $NON-NLS-1$ + groupingMode = new JComboBox(m); + groupingMode.setPreferredSize(new Dimension(150, 20)); + groupingMode.setSelectedIndex(0); + groupingMode.addItemListener(this); + + JLabel label2 = new JLabel(JMeterUtils.getResString("grouping_mode")); // $NON-NLS-1$ + label2.setLabelFor(groupingMode); + + HorizontalPanel panel = new HorizontalPanel(); + panel.add(label2); + panel.add(groupingMode); + + return panel; + } + + private JPanel createContentTypePanel() { + contentTypeInclude = new JTextField(35); + contentTypeInclude.addKeyListener(this); + contentTypeInclude.setName(ENABLE_RESTART); + JLabel labelInclude = new JLabel(JMeterUtils.getResString("proxy_content_type_include")); // $NON-NLS-1$ + labelInclude.setLabelFor(contentTypeInclude); + // Default value + contentTypeInclude.setText(JMeterUtils.getProperty("proxy.content_type_include")); // $NON-NLS-1$ + + contentTypeExclude = new JTextField(35); + contentTypeExclude.addKeyListener(this); + contentTypeExclude.setName(ENABLE_RESTART); + JLabel labelExclude = new JLabel(JMeterUtils.getResString("proxy_content_type_exclude")); // $NON-NLS-1$ + labelExclude.setLabelFor(contentTypeExclude); + // Default value + contentTypeExclude.setText(JMeterUtils.getProperty("proxy.content_type_exclude")); // $NON-NLS-1$ + + HorizontalPanel panel = new HorizontalPanel(); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("proxy_content_type_filter"))); // $NON-NLS-1$ + panel.add(labelInclude); + panel.add(contentTypeInclude); + panel.add(labelExclude); + panel.add(contentTypeExclude); + + return panel; + } + + private JPanel createIncludePanel() { + includeModel = new PowerTableModel(new String[] { INCLUDE_COL }, new Class[] { String.class }); + includeTable = new JTable(includeModel); + includeTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + includeTable.setPreferredScrollableViewportSize(new Dimension(100, 30)); + + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("patterns_to_include"))); // $NON-NLS-1$ + + panel.add(new JScrollPane(includeTable), BorderLayout.CENTER); + panel.add(createTableButtonPanel(ADD_INCLUDE, DELETE_INCLUDE, ADD_TO_INCLUDE_FROM_CLIPBOARD, null), BorderLayout.SOUTH); + + return panel; + } + + private JPanel createExcludePanel() { + excludeModel = new PowerTableModel(new String[] { EXCLUDE_COL }, new Class[] { String.class }); + excludeTable = new JTable(excludeModel); + excludeTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + excludeTable.setPreferredScrollableViewportSize(new Dimension(100, 30)); + + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("patterns_to_exclude"))); // $NON-NLS-1$ + + panel.add(new JScrollPane(excludeTable), BorderLayout.CENTER); + panel.add(createTableButtonPanel(ADD_EXCLUDE, DELETE_EXCLUDE, ADD_TO_EXCLUDE_FROM_CLIPBOARD, ADD_SUGGESTED_EXCLUDES), BorderLayout.SOUTH); + + return panel; + } + + private JPanel createNotifyListenersPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("notify_child_listeners_fr"))); // $NON-NLS-1$ + + notifyChildSamplerListenerOfFilteredSamplersCB = new JCheckBox(JMeterUtils.getResString("notify_child_listeners_fr")); // $NON-NLS-1$ + notifyChildSamplerListenerOfFilteredSamplersCB.setSelected(true); + notifyChildSamplerListenerOfFilteredSamplersCB.addActionListener(this); + notifyChildSamplerListenerOfFilteredSamplersCB.setActionCommand(ENABLE_RESTART); + + panel.add(notifyChildSamplerListenerOfFilteredSamplersCB); + return panel; + } + + private JPanel createTableButtonPanel(String addCommand, String deleteCommand, String copyFromClipboard, String addSuggestedExcludes) { + JPanel buttonPanel = new JPanel(); + + JButton addButton = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + addButton.setActionCommand(addCommand); + addButton.addActionListener(this); + buttonPanel.add(addButton); + + JButton deleteButton = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + deleteButton.setActionCommand(deleteCommand); + deleteButton.addActionListener(this); + buttonPanel.add(deleteButton); + + /** A button for adding new excludes/includes to the table from the clipboard. */ + JButton addFromClipboard = new JButton(JMeterUtils.getResString("add_from_clipboard")); // $NON-NLS-1$ + addFromClipboard.setActionCommand(copyFromClipboard); + addFromClipboard.addActionListener(this); + buttonPanel.add(addFromClipboard); + + if(addSuggestedExcludes != null) { + /** A button for adding suggested excludes. */ + JButton addFromSuggestedExcludes = new JButton(JMeterUtils.getResString("add_from_suggested_excludes")); // $NON-NLS-1$ + addFromSuggestedExcludes.setActionCommand(addSuggestedExcludes); + addFromSuggestedExcludes.addActionListener(this); + buttonPanel.add(addFromSuggestedExcludes); + } + return buttonPanel; + } + + private void reinitializeTargetCombo() { + log.debug("Reinitializing target combo"); + + // Stop action notifications while we shuffle this around: + targetNodes.removeActionListener(this); + + targetNodesModel.removeAllElements(); + GuiPackage gp = GuiPackage.getInstance(); + JMeterTreeNode root; + if (gp != null) { + root = (JMeterTreeNode) GuiPackage.getInstance().getTreeModel().getRoot(); + targetNodesModel + .addElement(new TreeNodeWrapper(null, JMeterUtils.getResString("use_recording_controller"))); // $NON-NLS-1$ + buildNodesModel(root, "", 0); + } + TreeNodeWrapper choice = null; + for (int i = 0; i < targetNodesModel.getSize(); i++) { + choice = (TreeNodeWrapper) targetNodesModel.getElementAt(i); + log.debug("Selecting item " + choice + " for model " + model + " in " + this); + if (choice.getTreeNode() == model.getTarget()) // .equals caused NPE + { + break; + } + } + // Reinstate action notifications: + targetNodes.addActionListener(this); + // Set the current value: + targetNodesModel.setSelectedItem(choice); + + log.debug("Reinitialization complete"); + } + + private void buildNodesModel(JMeterTreeNode node, String parent_name, int level) { + String separator = " > "; + if (node != null) { + for (int i = 0; i < node.getChildCount(); i++) { + StringBuilder name = new StringBuilder(); + JMeterTreeNode cur = (JMeterTreeNode) node.getChildAt(i); + TestElement te = cur.getTestElement(); + /* + * Will never be true. Probably intended to use + * org.apache.jmeter.threads.ThreadGroup rather than + * java.lang.ThreadGroup However, that does not work correctly; + * whereas treating it as a Controller does. if (te instanceof + * ThreadGroup) { name.append(parent_name); + * name.append(cur.getName()); name.append(seperator); + * buildNodesModel(cur, name.toString(), level); } else + */ + if (te instanceof Controller) { + name.append(parent_name); + name.append(cur.getName()); + TreeNodeWrapper tnw = new TreeNodeWrapper(cur, name.toString()); + targetNodesModel.addElement(tnw); + name.append(separator); + buildNodesModel(cur, name.toString(), level + 1); + } else if (te instanceof TestPlan || te instanceof WorkBench) { + name.append(cur.getName()); + name.append(separator); + buildNodesModel(cur, name.toString(), 0); + } + // Ignore everything else + } + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSampler.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSampler.java new file mode 100644 index 00000000000..886e845c2e4 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSampler.java @@ -0,0 +1,364 @@ +/* + * 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.protocol.http.sampler; + +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.util.accesslog.Filter; +import org.apache.jmeter.protocol.http.util.accesslog.LogParser; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestCloneable; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +/** + * Description:
+ *
+ * AccessLogSampler is responsible for a couple of things: + *
    + *
  • creating instances of Generator + *
  • creating instances of Parser + *
  • triggering popup windows + *
  • calling Generator.generateRequest() + *
  • checking to make sure the classes are valid + *
  • making sure a class can be instantiated + *
+ * The intent of this sampler is it uses the generator and parser to create a + * HTTPSampler when it is needed. It does not contain logic about how to parse + * the logs. It also doesn't care how Generator is implemented, as long as it + * implements the interface. This means a person could simply implement a dummy + * parser to generate random parameters and the generator consumes the results. + * This wasn't the original intent of the sampler. I originaly wanted to write + * this sampler, so that I can take production logs to simulate production + * traffic in a test environment. Doing so is desirable to study odd or unusual + * behavior. It's also good to compare a new system against an existing system + * to get near apples-to-apples comparison. I've been asked if benchmarks are + * really fair comparisons just about every single time, so this helps me + * accomplish that task. + *

+ * Some bugs only appear under production traffic, so it is useful to generate + * traffic using production logs. This way, JMeter can record when problems + * occur and provide a way to match the server logs. + *

+ * Created on: Jun 26, 2003 + * + */ +public class AccessLogSampler extends HTTPSampler implements TestBean,ThreadListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; // Remember to change this when the class changes ... + + public static final String DEFAULT_CLASS = "org.apache.jmeter.protocol.http.util.accesslog.TCLogParser"; // $NON-NLS-1$ + + /* private members used by class */ + private transient LogParser parser = null; + + // NOTUSED private Class PARSERCLASS = null; + private String logFile, parserClassName, filterClassName; + + private transient Filter filter; + + private int count = 0; + + private boolean started = false; + + /** + * Set the path where XML messages are stored for random selection. + * + * @param path path where to store XML messages + */ + public void setLogFile(String path) { + logFile = path; + } + + /** + * Get the path where XML messages are stored. this is the directory where + * JMeter will randomly select a file. + * + * @return path where XML messages are stored + */ + public String getLogFile() { + return logFile; + } + + /** + * it's kinda obvious, but we state it anyways. Set the xml file with a + * string path. + * + * @param classname - + * parser class name + */ + public void setParserClassName(String classname) { + parserClassName = classname; + } + + /** + * Get the file location of the xml file. + * + * @return String file path. + */ + public String getParserClassName() { + return parserClassName; + } + + /** + * sample gets a new HTTPSampler from the generator and calls it's sample() + * method. + * + * @return newly generated and called sample + */ + public SampleResult sampleWithParser() { + initFilter(); + instantiateParser(); + SampleResult res = null; + try { + + if (parser == null) { + throw new JMeterException("No Parser available"); + } + /* + * samp.setDomain(this.getDomain()); samp.setPort(this.getPort()); + */ + // we call parse with 1 to get only one. + // this also means if we change the implementation + // to use 2, it would use every other entry and + // so on. Not that it is really useful, but a + // person could use it that way if they have a + // huge gigabyte log file and they only want to + // use a quarter of the entries. + int thisCount = parser.parseAndConfigure(1, this); + if (thisCount < 0) // Was there an error? + { + return errorResult(new Error("Problem parsing the log file"), new HTTPSampleResult()); + } + if (thisCount == 0) { + if (count == 0 || filter == null) { + log.info("Stopping current thread"); + JMeterContextService.getContext().getThread().stop(); + } + if (filter != null) { + filter.reset(); + } + CookieManager cm = getCookieManager(); + if (cm != null) { + cm.clear(); + } + count = 0; + return errorResult(new Error("No entries found"), new HTTPSampleResult()); + } + count = thisCount; + res = sample(); + if(res != null) { + res.setSampleLabel(toString()); + } + } catch (Exception e) { + log.warn("Sampling failure", e); + return errorResult(e, new HTTPSampleResult()); + } + return res; + } + + /** + * sample(Entry e) simply calls sample(). + * + * @param e - + * ignored + * @return the new sample + */ + @Override + public SampleResult sample(Entry e) { + return sampleWithParser(); + } + + /** + * Method will instantiate the log parser based on the class in the text + * field. This was done to make it easier for people to plugin their own log + * parser and use different log parser. + */ + public void instantiateParser() { + if (parser == null) { + try { + if (this.getParserClassName() != null && this.getParserClassName().length() > 0) { + if (this.getLogFile() != null && this.getLogFile().length() > 0) { + parser = (LogParser) Class.forName(getParserClassName()).newInstance(); + parser.setSourceFile(this.getLogFile()); + parser.setFilter(filter); + } else { + log.error("No log file specified"); + } + } + } catch (InstantiationException e) { + log.error("", e); + } catch (IllegalAccessException e) { + log.error("", e); + } catch (ClassNotFoundException e) { + log.error("", e); + } + } + } + + /** + * @return Returns the filterClassName. + */ + public String getFilterClassName() { + return filterClassName; + } + + /** + * @param filterClassName + * The filterClassName to set. + */ + public void setFilterClassName(String filterClassName) { + this.filterClassName = filterClassName; + } + + /** + * @return Returns the domain. + */ + @Override + public String getDomain() { // N.B. Must be in this class for the TestBean code to work + return super.getDomain(); + } + + /** + * @param domain + * The domain to set. + */ + @Override + public void setDomain(String domain) { // N.B. Must be in this class for the TestBean code to work + super.setDomain(domain); + } + + /** + * @return Returns the imageParsing. + */ + public boolean isImageParsing() { + return super.isImageParser(); + } + + /** + * @param imageParsing + * The imageParsing to set. + */ + public void setImageParsing(boolean imageParsing) { + super.setImageParser(imageParsing); + } + + /** + * @return Returns the port. + */ + public String getPortString() { + return super.getPropertyAsString(HTTPSamplerBase.PORT); + } + + /** + * @param port + * The port to set. + */ + public void setPortString(String port) { + super.setProperty(HTTPSamplerBase.PORT, port); + } + + /** + * + */ + public AccessLogSampler() { + super(); + } + + protected void initFilter() { + if (filter == null && filterClassName != null && filterClassName.length() > 0) { + try { + filter = (Filter) Class.forName(filterClassName).newInstance(); + } catch (Exception e) { + log.warn("Couldn't instantiate filter '" + filterClassName + "'", e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object clone() { + AccessLogSampler s = (AccessLogSampler) super.clone(); + if (started) { + if (filterClassName != null && filterClassName.length() > 0) { + + try { + if (TestCloneable.class.isAssignableFrom(Class.forName(filterClassName))) { + initFilter(); + s.filter = (Filter) ((TestCloneable) filter).clone(); + } + if (TestCloneable.class.isAssignableFrom(Class.forName(parserClassName))) + { + instantiateParser(); + s.parser = (LogParser)((TestCloneable)parser).clone(); + if (filter != null) + { + s.parser.setFilter(s.filter); + } + } + } catch (Exception e) { + log.warn("Could not clone cloneable filter", e); + } + } + } + return s; + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded() { + if (parser != null) { + parser.close(); + } + filter = null; + started = false; + super.testEnded(); + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted() { + started = true; + super.testStarted(); + } + + /** + * {@inheritDoc} + */ + @Override + public void threadFinished() { + if(parser instanceof ThreadListener) { + ((ThreadListener)parser).threadFinished(); + } + if(filter instanceof ThreadListener) { + ((ThreadListener)filter).threadFinished(); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java new file mode 100644 index 00000000000..935e07c6c0e --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java @@ -0,0 +1,99 @@ +/* + * 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. + * + */ + +/* + * Created on May 24, 2004 + * + */ +package org.apache.jmeter.protocol.http.sampler; + +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.util.List; + +import org.apache.jmeter.protocol.http.util.accesslog.Filter; +import org.apache.jmeter.protocol.http.util.accesslog.LogParser; +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.FileEditor; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +public class AccessLogSamplerBeanInfo extends BeanInfoSupport { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public AccessLogSamplerBeanInfo() { + super(AccessLogSampler.class); + log.debug("Entered access log sampler bean info"); + try { + createPropertyGroup("defaults", // $NON-NLS-1$ + new String[] { "domain", "portString", "imageParsing" });// $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + + createPropertyGroup("plugins", // $NON-NLS-1$ + new String[] { "parserClassName", "filterClassName" }); // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + + createPropertyGroup("accesslogfile", // $NON-NLS-1$ + new String[] { "logFile" }); // $NON-NLS-1$ + + PropertyDescriptor p; + + p = property("parserClassName"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, AccessLogSampler.DEFAULT_CLASS); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + final List logParserClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { LogParser.class }); + if (log.isDebugEnabled()) { + log.debug("found parsers: " + logParserClasses); + } + p.setValue(TAGS, logParserClasses.toArray(new String[logParserClasses.size()])); + + p = property("filterClassName"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.FALSE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + List classes = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), + new Class[] { Filter.class }, false); + p.setValue(TAGS, classes.toArray(new String[classes.size()])); + + p = property("logFile"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p.setPropertyEditorClass(FileEditor.class); + + p = property("domain"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("portString"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property("imageParsing"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + p.setValue(NOT_OTHER, Boolean.TRUE); + } catch (IOException e) { + log.warn("couldn't find classes and set up properties", e); + throw new RuntimeException("Could not find classes with class finder", e); + } + log.debug("Got to end of access log samper bean info init"); + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources.properties b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources.properties new file mode 100644 index 00000000000..a95b39074ed --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources.properties @@ -0,0 +1,31 @@ +# 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. + +displayName=Access Log Sampler +plugins.displayName=Plugin Classes +accesslogfile.displayName=Log File Location +defaults.displayName=Default Test Values +logFile.displayName=Log File +logFile.shortDescription=Location of log file to parse for requests +parserClassName.displayName=Parser +parserClassName.shortDescription=Choose a parser implementation to parse your log file. +filterClassName.displayName=Filter (Optional) +filterClassName.shortDescription=Choose a filter implementation to filter your log file entries (optional). +domain.displayName=Server +domain.shortDescription=Host name of the server to test against +portString.displayName=Port +portString.shortDescription=Port Number to test against +imageParsing.displayName=Parse Images +imageParsing.shortDescription=If turned on, JMeter will download images and resources contained in each web page \ No newline at end of file diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_es.properties b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_es.properties new file mode 100644 index 00000000000..76e66344096 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_es.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +accesslogfile.displayName=Ubicaci\u00F3n del Archivo de Log +defaults.displayName=Valores por defecto para Prueba +displayName=Muestreador de Acceso a Log +domain.displayName=Servidor +domain.shortDescription=Nombre del servidor contra el que probar +filterClassName.displayName=Filtro (Opcional) +filterClassName.shortDescription=Escoja una implementaci\u00F3n de filtro para filtrar sus entradas en el archivo de log (opcional) +imageParsing.displayName=Parsear Im\u00E1genes +imageParsing.shortDescription=Si lo selecciona, JMeter descargar\u00E1 las im\u00E1genes y recursos contenidos en cada p\u00E1gina web +logFile.displayName=Archivo de Log +logFile.shortDescription=Ubicaci\u00F3n del archivo de log para parsear peticiones +parserClassName.displayName=Parser +parserClassName.shortDescription=Seleccione una implementaci\u00F3n de parser para parsear su archivo de log +plugins.displayName=Classes desplegables +portString.displayName=Puerto +portString.shortDescription=N\u00FAmero de puerto contra el que probar diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_fr.properties b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_fr.properties new file mode 100644 index 00000000000..e00ed57ab1a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_fr.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +accesslogfile.displayName=Emplacement du fichier journal +defaults.displayName=Valeurs par d\u00E9faut du test +displayName=Echantillon Journal d'acc\u00E8s +domain.displayName=Serveur +domain.shortDescription=Nom d'h\u00F4te du serveur de test +filterClassName.displayName=Filtre (Optionnel) +filterClassName.shortDescription=Choisir une impl\u00E9mentation de filtre pour filtrer vos entr\u00E9es de fichier de journal (optionnel). +imageParsing.displayName=Analyser les images +imageParsing.shortDescription=Si activ\u00E9, JMeter va t\u00E9l\u00E9charger les images et les ressources contenues dans chaque page web. +logFile.displayName=Fichier journal +logFile.shortDescription=Emplacement du fichier journal \u00E0 analyser pour les requ\u00EAtes. +parserClassName.displayName=Analyseur +parserClassName.shortDescription=Choisir une impl\u00E9mentation d'analyseur pour analyser votre fichier journal. +plugins.displayName=Extension de Classes +portString.displayName=Port +portString.shortDescription=Num\u00E9ro de port de test diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_pt_BR.properties b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_pt_BR.properties new file mode 100644 index 00000000000..e2a073c8365 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_pt_BR.properties @@ -0,0 +1,31 @@ +# 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. + +accesslogfile.displayName=Localiza\u00E7\u00E3o do Arquivo de Log +defaults.displayName=Valores Padr\u00F5es de Teste +displayName=Testador de Log de Acesso +domain.displayName=Servidor +domain.shortDescription=Nome do servidor que ir\u00E1 ser testado +filterClassName.displayName=Filtro (Opcional) +filterClassName.shortDescription=Escolha uma implementa\u00E7\u00E3o de filtro para filtrar as entradas do arquivo de log (opcional) +imageParsing.displayName=Processar Imagens +imageParsing.shortDescription=Se configurado, JMeter ir\u00E1 baixar imagens e recursos contidos em cada p\u00E1gina web. +logFile.displayName=Arquivo de Log +logFile.shortDescription=Localiza\u00E7\u00E3o do arquivo de log a ser processado pelas requisi\u00E7\u00F5es +parserClassName.displayName=Processador +parserClassName.shortDescription=Escolha uma implementa\u00E7\u00E3o de processador para processar seu arquivo de log. +plugins.displayName=Classes do Plugin +portString.displayName=Porta +portString.shortDescription=N\u00FAmero da porta a ser testada diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_tr.properties b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_tr.properties new file mode 100644 index 00000000000..3995028a563 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_tr.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +accesslogfile.displayName=Log Dosyas\u0131 Yeri +defaults.displayName=\u00D6ntan\u0131ml\u0131 Test De\u011Ferleri +displayName=Log \u00D6rnekleyicisine Eri\u015Fim +domain.displayName=Sunucu +domain.shortDescription=Test edilecek sunucunun makine ismi +filterClassName.displayName=Filtre (\u0130ste\u011Fe ba\u011Fl\u0131) +filterClassName.shortDescription=Log dosyas\u0131 girdilerini filtrelemek i\u00E7in filtre uygulamas\u0131 se\u00E7 (iste\u011Fe ba\u011Fl\u0131) +imageParsing.displayName=Resimleri Ayr\u0131\u015Ft\u0131r +imageParsing.shortDescription=E\u011Fer a\u00E7\u0131ksa, JMeter web sayfas\u0131ndaki resimleri ve kaynaklar\u0131 indirecektir +logFile.displayName=Log Dosyas\u0131 +logFile.shortDescription=\u0130stekler i\u00E7in ayr\u0131\u015Ft\u0131r\u0131lacak log dosyas\u0131n\u0131n yeri +parserClassName.displayName=Ayr\u0131\u015Ft\u0131r\u0131c\u0131 +parserClassName.shortDescription=Log dosyas\u0131n\u0131 ayr\u0131\u015Ft\u0131rmak i\u00E7in bir ayr\u0131\u015Ft\u0131r\u0131c\u0131 uygulamas\u0131 kullan. +plugins.displayName=Eklenti S\u0131n\u0131flar\u0131 +portString.displayName=Port +portString.shortDescription=Test edilecek Port Numaras\u0131 diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_zh_TW.properties b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_zh_TW.properties new file mode 100644 index 00000000000..1a241028c4d --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_zh_TW.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +accesslogfile.displayName=\u6B77\u7A0B\u6A94\u4F4D\u7F6E +defaults.displayName=\u9810\u8A2D\u6E2C\u8A66\u503C +displayName=\u5B58\u53D6\u8A18\u9304\u53D6\u6A23 +domain.displayName=\u4F3A\u670D\u5668 +domain.shortDescription=\u88AB\u6E2C\u8A66\u4E3B\u6A5F\u540D\u7A31 +filterClassName.displayName=\u904E\u6FFE\u5668(\u9078\u64C7\u6027) +filterClassName.shortDescription=\u4F7F\u7528\u81EA\u8A02\u904E\u6FFE\u5668\u5C0D\u8A18\u9304\u6A94\u9032\u884C\u904E\u6FFE(\u9078\u64C7\u6027) +imageParsing.displayName=\u5256\u6790\u5716\u5F62 +imageParsing.shortDescription=\u5982\u679C\u555F\u52D5, JMeter \u6703\u4E0B\u8F09\u6240\u6709\u5716\u5F62\u548C\u76F8\u95DC\u8CC7\u6E90 +logFile.displayName=\u8A18\u9304\u6A94 +logFile.shortDescription=\u88AB\u5256\u6790\u7684\u8A18\u9304\u6A94\u4F4D\u7F6E +parserClassName.displayName=\u5256\u6790\u5668 +parserClassName.shortDescription=\u9078\u64C7\u81EA\u5DF1\u5BE6\u4F5C\u7684\u5256\u6790\u5668 +plugins.displayName=\u63D2\u4EF6 Classes +portString.displayName=\u7AEF\u53E3 +portString.shortDescription=\u88AB\u6E2C\u8A66\u7AEF\u53E3 diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AjpSampler.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AjpSampler.java new file mode 100644 index 00000000000..92016f564f3 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AjpSampler.java @@ -0,0 +1,535 @@ +/* + * 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.protocol.http.sampler; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.URL; + +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Cookie; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Selector for the AJP/1.3 protocol + * (i.e. what Tomcat uses with mod_jk) + * It allows you to test Tomcat in AJP mode without + * actually having Apache installed and configured + * + */ +public class AjpSampler extends HTTPSamplerBase implements Interruptible { + + private static final long serialVersionUID = 233L; + + private static final Logger log= LoggingManager.getLoggerForClass(); + + private static final char NEWLINE = '\n'; + private static final String COLON_SPACE = ": ";//$NON-NLS-1$ + + /** + * Translates integer codes to request header names + */ + private static final String []headerTransArray = { + "accept", //$NON-NLS-1$ + "accept-charset", //$NON-NLS-1$ + "accept-encoding", //$NON-NLS-1$ + "accept-language", //$NON-NLS-1$ + "authorization", //$NON-NLS-1$ + "connection", //$NON-NLS-1$ + "content-type", //$NON-NLS-1$ + "content-length", //$NON-NLS-1$ + "cookie", //$NON-NLS-1$ + "cookie2", //$NON-NLS-1$ + "host", //$NON-NLS-1$ + "pragma", //$NON-NLS-1$ + "referer", //$NON-NLS-1$ + "user-agent" //$NON-NLS-1$ + }; + + /** + * Base value for translated headers + */ + static final int AJP_HEADER_BASE = 0xA000; + + static final int MAX_SEND_SIZE = 8*1024 - 4 - 4; + + private transient Socket channel = null; + private transient Socket activeChannel = null; + private int lastPort = -1; + private String lastHost = null; + private String localName = null; + private String localAddress = null; + private final byte [] inbuf = new byte[8*1024]; + private final byte [] outbuf = new byte[8*1024]; + private final transient ByteArrayOutputStream responseData = new ByteArrayOutputStream(); + private int inpos = 0; + private int outpos = 0; + private transient String stringBody = null; + private transient InputStream body = null; + + public AjpSampler() { + } + + @Override + protected HTTPSampleResult sample(URL url, + String method, + boolean frd, + int fd) { + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(false); + res.setSampleLabel(url.toExternalForm()); + res.sampleStart(); + try { + setupConnection(url, method, res); + activeChannel = channel; + execute(method, res); + res.sampleEnd(); + res.setResponseData(responseData.toByteArray()); + return res; + } catch(IOException iex) { + res.sampleEnd(); + lastPort = -1; // force reopen on next sample + channel = null; + return errorResult(iex, res); + } finally { + activeChannel = null; + JOrphanUtils.closeQuietly(body); + body = null; + } + } + + @Override + public void threadFinished() { + if(channel != null) { + try { + channel.close(); + } catch(IOException iex) { + log.debug("Error closing channel",iex); + } + } + channel = null; + JOrphanUtils.closeQuietly(body); + body = null; + stringBody = null; + } + + private void setupConnection(URL url, + String method, + HTTPSampleResult res) throws IOException { + + String host = url.getHost(); + int port = url.getPort(); + if(port <= 0 || port == url.getDefaultPort()) { + port = 8009; + } + String scheme = url.getProtocol(); + if(channel == null || !host.equals(lastHost) || port != lastPort) { + if(channel != null) { + channel.close(); + } + channel = new Socket(host, port); + int timeout = JMeterUtils.getPropDefault("httpclient.timeout",0);//$NON-NLS-1$ + if(timeout > 0) { + channel.setSoTimeout(timeout); + } + localAddress = channel.getLocalAddress().getHostAddress(); + localName = channel.getLocalAddress().getHostName(); + lastHost = host; + lastPort = port; + } + res.setURL(url); + res.setHTTPMethod(method); + outpos = 4; + setByte((byte)2); + if(method.equals(HTTPConstants.POST)) { + setByte((byte)4); + } else { + setByte((byte)2); + } + if(JMeterUtils.getPropDefault("httpclient.version","1.1").equals("1.0")) {//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + setString("HTTP/1.0");//$NON-NLS-1$ + } else { + setString(HTTPConstants.HTTP_1_1); + } + setString(url.getPath()); + setString(localAddress); + setString(localName); + setString(host); + setInt(url.getDefaultPort()); + setByte(HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(scheme) ? (byte)1 : (byte)0); + setInt(getHeaderSize(method, url)); + String hdr = setConnectionHeaders(url, host, method); + res.setRequestHeaders(hdr); + res.setCookies(setConnectionCookies(url, getCookieManager())); + String query = url.getQuery(); + if (query != null) { + setByte((byte)0x05); // Marker for query string attribute + setString(query); + } + setByte((byte)0xff); // More general attributes not supported + } + + private int getHeaderSize(String method, URL url) { + HeaderManager headers = getHeaderManager(); + CookieManager cookies = getCookieManager(); + AuthManager auth = getAuthManager(); + int hsz = 1; // Host always + if(method.equals(HTTPConstants.POST)) { + HTTPFileArg[] hfa = getHTTPFiles(); + if(hfa.length > 0) { + hsz += 3; + } else { + hsz += 2; + } + } + if(headers != null) { + hsz += headers.size(); + } + if(cookies != null) { + hsz += cookies.getCookieCount(); + } + if(auth != null) { + String authHeader = auth.getAuthHeaderForURL(url); + if(authHeader != null) { + ++hsz; + } + } + return hsz; + } + + + private String setConnectionHeaders(URL url, String host, String method) + throws IOException { + HeaderManager headers = getHeaderManager(); + AuthManager auth = getAuthManager(); + StringBuilder hbuf = new StringBuilder(); + // Allow Headers to override Host setting + hbuf.append("Host").append(COLON_SPACE).append(host).append(NEWLINE);//$NON-NLS-1$ + setInt(0xA00b); //Host + setString(host); + if(headers != null) { + CollectionProperty coll = headers.getHeaders(); + PropertyIterator i = coll.iterator(); + while(i.hasNext()) { + Header header = (Header)i.next().getObjectValue(); + String n = header.getName(); + String v = header.getValue(); + hbuf.append(n).append(COLON_SPACE).append(v).append(NEWLINE); + int hc = translateHeader(n); + if(hc > 0) { + setInt(hc+AJP_HEADER_BASE); + } else { + setString(n); + } + setString(v); + } + } + if(method.equals(HTTPConstants.POST)) { + int cl = -1; + HTTPFileArg[] hfa = getHTTPFiles(); + if(hfa.length > 0) { + HTTPFileArg fa = hfa[0]; + String fn = fa.getPath(); + File input = new File(fn); + cl = (int)input.length(); + if(body != null) { + JOrphanUtils.closeQuietly(body); + body = null; + } + body = new BufferedInputStream(new FileInputStream(input)); + setString(HTTPConstants.HEADER_CONTENT_DISPOSITION); + setString("form-data; name=\""+encode(fa.getParamName())+ + "\"; filename=\"" + encode(fn) +"\""); //$NON-NLS-1$ //$NON-NLS-2$ + String mt = fa.getMimeType(); + hbuf.append(HTTPConstants.HEADER_CONTENT_TYPE).append(COLON_SPACE).append(mt).append(NEWLINE); + setInt(0xA007); // content-type + setString(mt); + } else { + hbuf.append(HTTPConstants.HEADER_CONTENT_TYPE).append(COLON_SPACE).append(HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED).append(NEWLINE); + setInt(0xA007); // content-type + setString(HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + StringBuilder sb = new StringBuilder(); + boolean first = true; + PropertyIterator args = getArguments().iterator(); + while(args.hasNext()) { + JMeterProperty arg = args.next(); + if(first) { + first = false; + } else { + sb.append('&'); + } + sb.append(arg.getStringValue()); + } + stringBody = sb.toString(); + byte [] sbody = stringBody.getBytes(); // TODO - charset? + cl = sbody.length; + body = new ByteArrayInputStream(sbody); + } + hbuf.append(HTTPConstants.HEADER_CONTENT_LENGTH).append(COLON_SPACE).append(String.valueOf(cl)).append(NEWLINE); + setInt(0xA008); // Content-length + setString(String.valueOf(cl)); + } + if(auth != null) { + String authHeader = auth.getAuthHeaderForURL(url); + if(authHeader != null) { + setInt(0xA005); // Authorization + setString(authHeader); + hbuf.append(HTTPConstants.HEADER_AUTHORIZATION).append(COLON_SPACE).append(authHeader).append(NEWLINE); + } + } + return hbuf.toString(); + } + + private String encode(String value) { + StringBuilder newValue = new StringBuilder(); + char[] chars = value.toCharArray(); + for (int i = 0; i < chars.length; i++) + { + if (chars[i] == '\\')//$NON-NLS-1$ + { + newValue.append("\\\\");//$NON-NLS-1$ + } + else + { + newValue.append(chars[i]); + } + } + return newValue.toString(); + } + + private String setConnectionCookies(URL url, CookieManager cookies) { + String cookieHeader = null; + if(cookies != null) { + cookieHeader = cookies.getCookieHeaderForURL(url); + CollectionProperty coll = cookies.getCookies(); + PropertyIterator i = coll.iterator(); + while(i.hasNext()) { + Cookie cookie = (Cookie)(i.next().getObjectValue()); + setInt(0xA009); // Cookie + setString(cookie.getName()+"="+cookie.getValue());//$NON-NLS-1$ + } + } + return cookieHeader; + } + + private int translateHeader(String n) { + for(int i=0; i < headerTransArray.length; i++) { + if(headerTransArray[i].equalsIgnoreCase(n)) { + return i+1; + } + } + return -1; + } + + private void setByte(byte b) { + outbuf[outpos++] = b; + } + + private void setInt(int n) { + outbuf[outpos++] = (byte)((n >> 8)&0xff); + outbuf[outpos++] = (byte) (n&0xff); + } + + private void setString(String s) { + if( s == null ) { + setInt(0xFFFF); + } else { + int len = s.length(); + setInt(len); + for(int i=0; i < len; i++) { + setByte((byte)s.charAt(i)); + } + setByte((byte)0); + } + } + + private void send() throws IOException { + OutputStream os = channel.getOutputStream(); + int len = outpos; + outpos = 0; + setInt(0x1234); + setInt(len-4); + os.write(outbuf, 0, len); + } + + private void execute(String method, HTTPSampleResult res) + throws IOException { + send(); + if(method.equals(HTTPConstants.POST)) { + res.setQueryString(stringBody); + sendPostBody(); + } + handshake(res); + } + + private void handshake(HTTPSampleResult res) throws IOException { + responseData.reset(); + int msg = getMessage(); + while(msg != 5) { + if(msg == 3) { + int len = getInt(); + responseData.write(inbuf, inpos, len); + } else if(msg == 4) { + parseHeaders(res); + } else if(msg == 6) { + setNextBodyChunk(); + send(); + } + msg = getMessage(); + } + } + + + private void sendPostBody() throws IOException { + setNextBodyChunk(); + send(); + } + + private void setNextBodyChunk() throws IOException { + int nr = 0; + if(body != null) { + int len = body.available(); + if(len < 0) { + len = 0; + } else if(len > MAX_SEND_SIZE) { + len = MAX_SEND_SIZE; + } + outpos = 4; + if(len > 0) { + nr = body.read(outbuf, outpos+2, len); + } + } else { + outpos = 4; + } + setInt(nr); + outpos += nr; + } + + + private void parseHeaders(HTTPSampleResult res) + throws IOException { + int status = getInt(); + res.setResponseCode(Integer.toString(status)); + res.setSuccessful(200 <= status && status <= 399); + String msg = getString(); + res.setResponseMessage(msg); + int nh = getInt(); + StringBuilder sb = new StringBuilder(); + sb.append(HTTPConstants.HTTP_1_1 ).append(status).append(" ").append(msg).append(NEWLINE);//$NON-NLS-1$//$NON-NLS-2$ + for(int i=0; i < nh; i++) { + String name; + int thn = peekInt(); + if((thn & 0xff00) == AJP_HEADER_BASE) { + name = headerTransArray[(thn&0xff)-1]; + getInt(); // we need to use up the int now + } else { + name = getString(); + } + String value = getString(); + if(HTTPConstants.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) { + res.setContentType(value); + res.setEncodingAndType(value); + } else if(HTTPConstants.HEADER_SET_COOKIE.equalsIgnoreCase(name)) { + CookieManager cookies = getCookieManager(); + if(cookies != null) { + cookies.addCookieFromHeader(value, res.getURL()); + } + } + sb.append(name).append(COLON_SPACE).append(value).append(NEWLINE); + } + res.setResponseHeaders(sb.toString()); + } + + + private int getMessage() throws IOException { + InputStream is = channel.getInputStream(); + inpos = 0; + int nr = is.read(inbuf, inpos, 4); + if(nr != 4) { + channel.close(); + channel = null; + throw new IOException("Connection Closed: "+nr); + } + //int mark = + getInt(); + int len = getInt(); + int toRead = len; + int cpos = inpos; + while(toRead > 0) { + nr = is.read(inbuf, cpos, toRead); + cpos += nr; + toRead -= nr; + } + return getByte(); + } + + private byte getByte() { + return inbuf[inpos++]; + } + + private int getInt() { + int res = (inbuf[inpos++]<<8)&0xff00; + res += inbuf[inpos++]&0xff; + return res; + } + + private int peekInt() { + int res = (inbuf[inpos]<<8)&0xff00; + res += inbuf[inpos+1]&0xff; + return res; + } + + private String getString() throws IOException { + int len = getInt(); + String s = new String(inbuf, inpos, len, "iso-8859-1");//$NON-NLS-1$ + inpos+= len+1; + return s; + } + + @Override + public boolean interrupt() { + Socket chan = activeChannel; + if (chan != null) { + activeChannel = null; + try { + chan.close(); + } catch (Exception e) { + // Ignored + } + } + return chan != null; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java new file mode 100644 index 00000000000..8d47aec0f4f --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java @@ -0,0 +1,510 @@ +/* + * 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.protocol.http.sampler; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.URL; +import java.net.UnknownHostException; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.SourceType; +import org.apache.jmeter.protocol.http.util.HTTPConstantsInterface; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Base class for HTTP implementations used by the HTTPSamplerProxy sampler. + */ +public abstract class HTTPAbstractImpl implements Interruptible, HTTPConstantsInterface { + private static enum CachedResourceMode { + RETURN_200_CACHE(), + RETURN_NO_SAMPLE(), + RETURN_CUSTOM_STATUS(); + } + + /** + * If true create a SampleResult with emply content and 204 response code + */ + private static final CachedResourceMode CACHED_RESOURCE_MODE = + CachedResourceMode.valueOf( + JMeterUtils.getPropDefault("cache_manager.cached_resource_mode", //$NON-NLS-1$ + CachedResourceMode.RETURN_NO_SAMPLE.toString())); + + /** + * SampleResult message when resource was in cache and mode is RETURN_200_CACHE + */ + private static final String RETURN_200_CACHE_MESSAGE = + JMeterUtils.getPropDefault("RETURN_200_CACHE.message","(ex cache)");//$NON-NLS-1$ $NON-NLS-2$ + + /** + * Custom response code for cached resource + */ + private static final String RETURN_CUSTOM_STATUS_CODE = + JMeterUtils.getProperty("RETURN_CUSTOM_STATUS.code");//$NON-NLS-1$ + + /** + * Custom response message for cached resource + */ + private static final String RETURN_CUSTOM_STATUS_MESSAGE = + JMeterUtils.getProperty("RETURN_CUSTOM_STATUS.message"); //$NON-NLS-1$ + + protected final HTTPSamplerBase testElement; + + protected HTTPAbstractImpl(HTTPSamplerBase testElement){ + this.testElement = testElement; + } + + protected abstract HTTPSampleResult sample(URL url, String method, boolean areFollowingRedirect, int frameDepth); + + // Allows HTTPSamplerProxy to call threadFinished; subclasses can override if necessary + protected void threadFinished() { + } + + // Allows HTTPSamplerProxy to call notifyFirstSampleAfterLoopRestart; subclasses can override if necessary + protected void notifyFirstSampleAfterLoopRestart() { + } + + // Provide access to HTTPSamplerBase methods + + /** + * Populates the provided HTTPSampleResult with details from the Exception. + * Does not create a new instance, so should not be used directly to add a + * subsample. + *

+ * See {@link HTTPSamplerBase#errorResult(Throwable, HTTPSampleResult)} + * + * @param t + * Exception representing the error. + * @param res + * SampleResult to be modified + * @return the modified sampling result containing details of the Exception. + * Invokes + */ + protected HTTPSampleResult errorResult(Throwable t, HTTPSampleResult res) { + return testElement.errorResult(t, res); + } + + /** + * Invokes {@link HTTPSamplerBase#getArguments()} + * + * @return the arguments of the associated test element + */ + protected Arguments getArguments() { + return testElement.getArguments(); + } + + /** + * Invokes {@link HTTPSamplerBase#getAuthManager()} + * + * @return the {@link AuthManager} of the associated test element + */ + protected AuthManager getAuthManager() { + return testElement.getAuthManager(); + } + + /** + * Invokes {@link HTTPSamplerBase#getAutoRedirects()} + * + * @return flag whether to do auto redirects + */ + protected boolean getAutoRedirects() { + return testElement.getAutoRedirects(); + } + + /** + * Invokes {@link HTTPSamplerBase#getCacheManager()} + * + * @return the {@link CacheManager} of the associated test element + */ + protected CacheManager getCacheManager() { + return testElement.getCacheManager(); + } + + /** + * Invokes {@link HTTPSamplerBase#getConnectTimeout()} + * + * @return the connect timeout of the associated test element + */ + protected int getConnectTimeout() { + return testElement.getConnectTimeout(); + } + + /** + * Invokes {@link HTTPSamplerBase#getContentEncoding()} + * @return the encoding of the content, i.e. its charset name + */ + protected String getContentEncoding() { + return testElement.getContentEncoding(); + } + + /** + * Invokes {@link HTTPSamplerBase#getCookieManager()} + * + * @return the {@link CookieManager} of the associated test element + */ + protected CookieManager getCookieManager() { + return testElement.getCookieManager(); + } + + /** + * Invokes {@link HTTPSamplerBase#getHeaderManager()} + * + * @return the {@link HeaderManager} of the associated test element + */ + protected HeaderManager getHeaderManager() { + return testElement.getHeaderManager(); + } + + /** + * + * Get the collection of files as a list. + * The list is built up from the filename/filefield/mimetype properties, + * plus any additional entries saved in the FILE_ARGS property. + *

+ * If there are no valid file entries, then an empty list is returned. + *

+ * Invokes {@link HTTPSamplerBase#getHTTPFiles()} + * + * @return an array of file arguments (never null) + */ + protected HTTPFileArg[] getHTTPFiles() { + return testElement.getHTTPFiles(); + } + + /** + * Invokes {@link HTTPSamplerBase#getIpSource()} + * + * @return the configured ip source for the associated test element + */ + protected String getIpSource() { + return testElement.getIpSource(); + } + + /** + * Gets the IP source address (IP spoofing) if one has been provided. + * + * @return the IP source address to use (or null, if none provided or the device address could not be found) + * @throws UnknownHostException if the hostname/ip for {@link #getIpSource()} could not be resolved or not interface was found for it + * @throws SocketException if an I/O error occurs + */ + protected InetAddress getIpSourceAddress() throws UnknownHostException, SocketException { + final String ipSource = getIpSource(); + if (ipSource.trim().length() > 0) { + Class ipClass = null; + final SourceType sourceType = HTTPSamplerBase.SourceType.values()[testElement.getIpSourceType()]; + switch (sourceType) { + case DEVICE: + ipClass = InetAddress.class; + break; + case DEVICE_IPV4: + ipClass = Inet4Address.class; + break; + case DEVICE_IPV6: + ipClass = Inet6Address.class; + break; + case HOSTNAME: + default: + return InetAddress.getByName(ipSource); + } + + NetworkInterface net = NetworkInterface.getByName(ipSource); + if (net != null) { + for (InterfaceAddress ia : net.getInterfaceAddresses()) { + final InetAddress inetAddr = ia.getAddress(); + if (ipClass.isInstance(inetAddr)) { + return inetAddr; + } + } + throw new UnknownHostException("Interface " + ipSource + + " does not have address of type " + ipClass.getSimpleName()); + } + throw new UnknownHostException("Cannot find interface " + ipSource); + } + return null; // did not want to spoof the IP address + } + + /** + * Invokes {@link HTTPSamplerBase#getProxyHost()} + * + * @return the configured host to use as a proxy + */ + protected String getProxyHost() { + return testElement.getProxyHost(); + } + + /** + * Invokes {@link HTTPSamplerBase#getProxyPass()} + * + * @return the configured password to use for the proxy + */ + protected String getProxyPass() { + return testElement.getProxyPass(); + } + + /** + * Invokes {@link HTTPSamplerBase#getProxyPortInt()} + * + * @return the configured port to use for the proxy + */ + protected int getProxyPortInt() { + return testElement.getProxyPortInt(); + } + + /** + * Invokes {@link HTTPSamplerBase#getProxyUser()} + * + * @return the configured user to use for the proxy + */ + protected String getProxyUser() { + return testElement.getProxyUser(); + } + + /** + * Invokes {@link HTTPSamplerBase#getResponseTimeout()} + * + * @return the configured timeout for responses + */ + protected int getResponseTimeout() { + return testElement.getResponseTimeout(); + } + + /** + * Determine whether to send a file as the entire body of an + * entity enclosing request such as POST, PUT or PATCH. + * + * Invokes {@link HTTPSamplerBase#getSendFileAsPostBody()} + * + * @return flag whether to send a file as POST, PUT or PATCH + */ + protected boolean getSendFileAsPostBody() { + return testElement.getSendFileAsPostBody(); + } + + /** + * Determine whether to send concatenated parameters as the entire body of an + * entity enclosing request such as POST, PUT or PATCH. + * + * Invokes {@link HTTPSamplerBase#getSendParameterValuesAsPostBody()} + * + * @return flag whether to send concatenated parameters as the entire body + */ + protected boolean getSendParameterValuesAsPostBody() { + return testElement.getSendParameterValuesAsPostBody(); + } + + /** + * Invokes {@link HTTPSamplerBase#getUseKeepAlive()} + * + * @return flag whether to use keep-alive for requests + */ + protected boolean getUseKeepAlive() { + return testElement.getUseKeepAlive(); + } + + /** + * Determine if we should use multipart/form-data or + * application/x-www-form-urlencoded for the post + *

+ * Invokes {@link HTTPSamplerBase#getUseMultipartForPost()} + * + * @return true if multipart/form-data should be + * used and method is POST + */ + protected boolean getUseMultipartForPost() { + return testElement.getUseMultipartForPost(); + } + + /** + * Invokes {@link HTTPSamplerBase#getDoBrowserCompatibleMultipart()} + * + * @return flag whether we should do browser compatible multiparts + */ + protected boolean getDoBrowserCompatibleMultipart() { + return testElement.getDoBrowserCompatibleMultipart(); + } + + /** + * Invokes {@link HTTPSamplerBase#hasArguments()} + * + * @return flag whether we have arguments to send + */ + protected boolean hasArguments() { + return testElement.hasArguments(); + } + + /** + * Invokes {@link HTTPSamplerBase#isMonitor()} + * + * @return flag whether monitor is enabled + */ + protected boolean isMonitor() { + return testElement.isMonitor(); + } + + /** + * Determine if the HTTP status code is successful or not i.e. in range 200 + * to 399 inclusive + *

+ * Invokes {@link HTTPSamplerBase#isSuccessCode(int)} + * + * @param errorLevel + * status code to check + * @return whether in range 200-399 or not + */ + protected boolean isSuccessCode(int errorLevel) { + return testElement.isSuccessCode(errorLevel); + } + + /** + * Read response from the input stream, converting to MD5 digest if the + * useMD5 property is set. + *

+ * For the MD5 case, the result byte count is set to the size of the + * original response. + *

+ * Closes the inputStream + *

+ * Invokes + * {@link HTTPSamplerBase#readResponse(SampleResult, InputStream, int)} + * + * @param res + * sample to store information about the response into + * @param instream + * input stream from which to read the response + * @param responseContentLength + * expected input length or zero + * @return the response or the MD5 of the response + * @throws IOException + * if reading the result fails + */ + protected byte[] readResponse(SampleResult res, InputStream instream, + int responseContentLength) throws IOException { + return testElement.readResponse(res, instream, responseContentLength); + } + + /** + * Read response from the input stream, converting to MD5 digest if the + * useMD5 property is set. + *

+ * For the MD5 case, the result byte count is set to the size of the + * original response. + *

+ * Closes the inputStream + *

+ * Invokes {@link HTTPSamplerBase#readResponse(SampleResult, InputStream, int)} + * + * @param res + * sample to store information about the response into + * @param in + * input stream from which to read the response + * @param contentLength + * expected input length or zero + * @return the response or the MD5 of the response + * @throws IOException + * when reading the result fails + */ + protected byte[] readResponse(SampleResult res, BufferedInputStream in, + int contentLength) throws IOException { + return testElement.readResponse(res, in, contentLength); + } + + /** + * Follow redirects and download page resources if appropriate. this works, + * but the container stuff here is what's doing it. followRedirects() is + * actually doing the work to make sure we have only one container to make + * this work more naturally, I think this method - sample() - needs to take + * an HTTPSamplerResult container parameter instead of a + * boolean:areFollowingRedirect. + *

+ * Invokes + * {@link HTTPSamplerBase#resultProcessing(boolean, int, HTTPSampleResult)} + * + * @param areFollowingRedirect + * flag whether we are getting a redirect target + * @param frameDepth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @param res + * sample result to process + * @return the sample result + */ + protected HTTPSampleResult resultProcessing(boolean areFollowingRedirect, + int frameDepth, HTTPSampleResult res) { + return testElement.resultProcessing(areFollowingRedirect, frameDepth, res); + } + + /** + * Invokes {@link HTTPSamplerBase#setUseKeepAlive(boolean)} + * + * @param b flag whether to use keep-alive for requests + */ + protected void setUseKeepAlive(boolean b) { + testElement.setUseKeepAlive(b); + } + + /** + * Called by testIterationStart if the SSL Context was reset. + * + * This implementation does nothing. + */ + protected void notifySSLContextWasReset() { + // NOOP + } + + /** + * Update HTTPSampleResult for a resource in cache + * @param res {@link HTTPSampleResult} + * @return HTTPSampleResult + */ + protected HTTPSampleResult updateSampleResultForResourceInCache(HTTPSampleResult res) { + switch (CACHED_RESOURCE_MODE) { + case RETURN_NO_SAMPLE: + return null; + case RETURN_200_CACHE: + res.sampleEnd(); + res.setResponseCodeOK(); + res.setResponseMessage(RETURN_200_CACHE_MESSAGE); + res.setSuccessful(true); + return res; + case RETURN_CUSTOM_STATUS: + res.sampleEnd(); + res.setResponseCode(RETURN_CUSTOM_STATUS_CODE); + res.setResponseMessage(RETURN_CUSTOM_STATUS_MESSAGE); + res.setSuccessful(true); + return res; + default: + // Cannot happen + throw new IllegalStateException("Unknown CACHED_RESOURCE_MODE"); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPFileImpl.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPFileImpl.java new file mode 100644 index 00000000000..4ad6f546eee --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPFileImpl.java @@ -0,0 +1,87 @@ +/* + * 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.protocol.http.sampler; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.protocol.http.util.HTTPConstants; + +/** + * HTTP Sampler which can read from file: URLs + */ +public class HTTPFileImpl extends HTTPAbstractImpl { + + protected HTTPFileImpl(HTTPSamplerBase base) { + super(base); + } + + @Override + public boolean interrupt() { + return false; + } + + @Override + protected HTTPSampleResult sample(URL url, String method, + boolean areFollowingRedirect, int frameDepth) { + + HTTPSampleResult res = new HTTPSampleResult(); + res.setHTTPMethod(HTTPConstants.GET); // Dummy + res.setURL(url); + res.setSampleLabel(url.toString()); + InputStream is = null; + res.sampleStart(); + try { + byte[] responseData; + URLConnection conn = url.openConnection(); + is = conn.getInputStream(); + responseData = IOUtils.toByteArray(is); + res.sampleEnd(); + res.setResponseData(responseData); + res.setResponseCodeOK(); + res.setResponseMessageOK(); + res.setSuccessful(true); + StringBuilder ctb=new StringBuilder("text/html"); // $NON-NLS-1$ + // TODO can this be obtained from the file somehow? + String contentEncoding = getContentEncoding(); + if (contentEncoding.length() > 0) { + ctb.append("; charset="); // $NON-NLS-1$ + ctb.append(contentEncoding); + } + String ct = ctb.toString(); + res.setContentType(ct); + res.setEncodingAndType(ct); + + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + return res; + } catch (FileNotFoundException e) { + return errorResult(e, res); + } catch (IOException e) { + return errorResult(e, res); + } finally { + IOUtils.closeQuietly(is); + } + + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC3Impl.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC3Impl.java new file mode 100644 index 00000000000..3245946c3df --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC3Impl.java @@ -0,0 +1,1156 @@ +/* + * 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.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpConnectionManager; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.HttpVersion; +import org.apache.commons.httpclient.NTCredentials; +import org.apache.commons.httpclient.ProtocolException; +import org.apache.commons.httpclient.SimpleHttpConnectionManager; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.EntityEnclosingMethod; +import org.apache.commons.httpclient.methods.FileRequestEntity; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.HeadMethod; +import org.apache.commons.httpclient.methods.OptionsMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.httpclient.methods.TraceMethod; +import org.apache.commons.httpclient.methods.multipart.FilePart; +import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; +import org.apache.commons.httpclient.methods.multipart.Part; +import org.apache.commons.httpclient.methods.multipart.PartBase; +import org.apache.commons.httpclient.methods.multipart.StringPart; +import org.apache.commons.httpclient.params.DefaultHttpParams; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.apache.commons.httpclient.params.HttpParams; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.io.input.CountingInputStream; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Authorization; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.EncoderCache; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.protocol.http.util.LoopbackHttpClientSocketFactory; +import org.apache.jmeter.protocol.http.util.SlowHttpClientSocketFactory; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.JsseSSLManager; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * HTTP sampler using Apache (Jakarta) Commons HttpClient 3.1. + */ +public class HTTPHC3Impl extends HTTPHCAbstractImpl { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** retry count to be used (default 1); 0 = disable retries */ + private static final int RETRY_COUNT = JMeterUtils.getPropDefault("httpclient3.retrycount", 0); + + private static final String HTTP_AUTHENTICATION_PREEMPTIVE = "http.authentication.preemptive"; // $NON-NLS-1$ + + private static final boolean canSetPreEmptive; // OK to set pre-emptive auth? + + private static final ThreadLocal> httpClients = + new ThreadLocal>(){ + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + // Needs to be accessible by HTTPSampler2 + volatile HttpClient savedClient; + + private volatile boolean resetSSLContext; + + static { + log.info("HTTP request retry count = "+RETRY_COUNT); + if (CPS_HTTP > 0) { + log.info("Setting up HTTP SlowProtocol, cps="+CPS_HTTP); + Protocol.registerProtocol(HTTPConstants.PROTOCOL_HTTP, + new Protocol(HTTPConstants.PROTOCOL_HTTP,new SlowHttpClientSocketFactory(CPS_HTTP),HTTPConstants.DEFAULT_HTTP_PORT)); + } + + // Now done in JsseSSLManager (which needs to register the protocol) +// cps = +// JMeterUtils.getPropDefault("httpclient.socket.https.cps", 0); // $NON-NLS-1$ +// +// if (cps > 0) { +// log.info("Setting up HTTPS SlowProtocol, cps="+cps); +// Protocol.registerProtocol(PROTOCOL_HTTPS, +// new Protocol(PROTOCOL_HTTPS,new SlowHttpClientSocketFactory(cps),DEFAULT_HTTPS_PORT)); +// } + + // Set default parameters as needed + HttpParams params = DefaultHttpParams.getDefaultParams(); + + // Process Commons HttpClient parameters file + String file=JMeterUtils.getProperty("httpclient.parameters.file"); // $NON-NLS-1$ + if (file != null) { + HttpClientDefaultParameters.load(file, params); + } + + // If the pre-emptive parameter is undefined, then we can set it as needed + // otherwise we should do what the user requested. + canSetPreEmptive = params.getParameter(HTTP_AUTHENTICATION_PREEMPTIVE) == null; + + // Handle old-style JMeter properties + try { + params.setParameter(HttpMethodParams.PROTOCOL_VERSION, HttpVersion.parse("HTTP/"+HTTP_VERSION)); + } catch (ProtocolException e) { + log.warn("Problem setting protocol version "+e.getLocalizedMessage()); + } + + if (SO_TIMEOUT >= 0){ + params.setIntParameter(HttpMethodParams.SO_TIMEOUT, SO_TIMEOUT); + } + + // This must be done last, as must not be overridden + params.setParameter(HttpMethodParams.COOKIE_POLICY,CookiePolicy.IGNORE_COOKIES); + // We do our own cookie handling + + if (USE_LOOPBACK){ + LoopbackHttpClientSocketFactory.setup(); + } + } + + protected HTTPHC3Impl(HTTPSamplerBase base) { + super(base); + } + + + /** + * Samples the URL passed in and stores the result in + * HTTPSampleResult, following redirects and downloading + * page resources as appropriate. + *

+ * When getting a redirect target, redirects are not followed and resources + * are not downloaded. The caller will take care of this. + * + * @param url + * URL to sample + * @param method + * HTTP method: GET, POST,... + * @param areFollowingRedirect + * whether we're getting a redirect target + * @param frameDepth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return results of the sampling + */ + @Override + protected HTTPSampleResult sample(URL url, String method, boolean areFollowingRedirect, int frameDepth) { + + String urlStr = url.toString(); + + if (log.isDebugEnabled()) { + log.debug("Start : sample " + urlStr); + log.debug("method " + method+ " followingRedirect " + areFollowingRedirect + " depth " + frameDepth); + } + + HttpMethodBase httpMethod = null; + + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(isMonitor()); + + res.setSampleLabel(urlStr); // May be replaced later + res.setHTTPMethod(method); + res.setURL(url); + + res.sampleStart(); // Count the retries as well in the time + try { + // May generate IllegalArgumentException + if (method.equals(HTTPConstants.POST)) { + httpMethod = new PostMethod(urlStr); + } else if (method.equals(HTTPConstants.PUT)){ + httpMethod = new PutMethod(urlStr); + } else if (method.equals(HTTPConstants.HEAD)){ + httpMethod = new HeadMethod(urlStr); + } else if (method.equals(HTTPConstants.TRACE)){ + httpMethod = new TraceMethod(urlStr); + } else if (method.equals(HTTPConstants.OPTIONS)){ + httpMethod = new OptionsMethod(urlStr); + } else if (method.equals(HTTPConstants.DELETE)){ + httpMethod = new EntityEnclosingMethod(urlStr) { + @Override + public String getName() { // HC3.1 does not have the method + return HTTPConstants.DELETE; + } + }; + } else if (method.equals(HTTPConstants.GET)){ + httpMethod = new GetMethod(urlStr); + } else if (method.equals(HTTPConstants.PATCH)){ + httpMethod = new EntityEnclosingMethod(urlStr) { + @Override + public String getName() { // HC3.1 does not have the method + return HTTPConstants.PATCH; + } + }; + } else { + throw new IllegalArgumentException("Unexpected method: '"+method+"'"); + } + + final CacheManager cacheManager = getCacheManager(); + if (cacheManager != null && HTTPConstants.GET.equalsIgnoreCase(method)) { + if (cacheManager.inCache(url)) { + return updateSampleResultForResourceInCache(res); + } + } + + // Set any default request headers + setDefaultRequestHeaders(httpMethod); + + // Setup connection + HttpClient client = setupConnection(url, httpMethod, res); + savedClient = client; + + // Handle the various methods + if (method.equals(HTTPConstants.POST)) { + String postBody = sendPostData((PostMethod)httpMethod); + res.setQueryString(postBody); + } else if (method.equals(HTTPConstants.PUT) || method.equals(HTTPConstants.PATCH) + || method.equals(HTTPConstants.DELETE)) { + String putBody = sendEntityData((EntityEnclosingMethod) httpMethod); + res.setQueryString(putBody); + } + + int statusCode = client.executeMethod(httpMethod); + + // We've finished with the request, so we can add the LocalAddress to it for display + final InetAddress localAddr = client.getHostConfiguration().getLocalAddress(); + if (localAddr != null) { + httpMethod.addRequestHeader(HEADER_LOCAL_ADDRESS, localAddr.toString()); + } + // Needs to be done after execute to pick up all the headers + res.setRequestHeaders(getConnectionHeaders(httpMethod)); + + // Request sent. Now get the response: + InputStream instream = httpMethod.getResponseBodyAsStream(); + + if (instream != null) {// will be null for HEAD + instream = new CountingInputStream(instream); + try { + Header responseHeader = httpMethod.getResponseHeader(HTTPConstants.HEADER_CONTENT_ENCODING); + if (responseHeader!= null && HTTPConstants.ENCODING_GZIP.equals(responseHeader.getValue())) { + InputStream tmpInput = new GZIPInputStream(instream); // tmp inputstream needs to have a good counting + res.setResponseData(readResponse(res, tmpInput, (int) httpMethod.getResponseContentLength())); + } else { + res.setResponseData(readResponse(res, instream, (int) httpMethod.getResponseContentLength())); + } + } finally { + JOrphanUtils.closeQuietly(instream); + } + } + + res.sampleEnd(); + // Done with the sampling proper. + + // Now collect the results into the HTTPSampleResult: + + res.setSampleLabel(httpMethod.getURI().toString()); + // Pick up Actual path (after redirects) + + res.setResponseCode(Integer.toString(statusCode)); + res.setSuccessful(isSuccessCode(statusCode)); + + res.setResponseMessage(httpMethod.getStatusText()); + + String ct = null; + Header h = httpMethod.getResponseHeader(HTTPConstants.HEADER_CONTENT_TYPE); + if (h != null)// Can be missing, e.g. on redirect + { + ct = h.getValue(); + res.setContentType(ct);// e.g. text/html; charset=ISO-8859-1 + res.setEncodingAndType(ct); + } + + res.setResponseHeaders(getResponseHeaders(httpMethod)); + if (res.isRedirect()) { + final Header headerLocation = httpMethod.getResponseHeader(HTTPConstants.HEADER_LOCATION); + if (headerLocation == null) { // HTTP protocol violation, but avoids NPE + throw new IllegalArgumentException("Missing location header"); + } + String redirectLocation = headerLocation.getValue(); + res.setRedirectLocation(redirectLocation); // in case sanitising fails + } + + // record some sizes to allow HTTPSampleResult.getBytes() with different options + if (instream != null) { + res.setBodySize(((CountingInputStream) instream).getCount()); + } + res.setHeadersSize(calculateHeadersSize(httpMethod)); + if (log.isDebugEnabled()) { + log.debug("Response headersSize=" + res.getHeadersSize() + " bodySize=" + res.getBodySize() + + " Total=" + (res.getHeadersSize() + res.getBodySize())); + } + + // If we redirected automatically, the URL may have changed + if (getAutoRedirects()){ + res.setURL(new URL(httpMethod.getURI().toString())); + } + + // Store any cookies received in the cookie manager: + saveConnectionCookies(httpMethod, res.getURL(), getCookieManager()); + + // Save cache information + if (cacheManager != null){ + cacheManager.saveDetails(httpMethod, res); + } + + // Follow redirects and download page resources if appropriate: + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + log.debug("End : sample"); + return res; + } catch (IllegalArgumentException e) { // e.g. some kinds of invalid URL + res.sampleEnd(); + // pick up headers if failed to execute the request + // httpMethod can be null if method is unexpected + if(httpMethod != null) { + res.setRequestHeaders(getConnectionHeaders(httpMethod)); + } + errorResult(e, res); + return res; + } catch (IOException e) { + res.sampleEnd(); + // pick up headers if failed to execute the request + // httpMethod cannot be null here, otherwise + // it would have been caught in the previous catch block + res.setRequestHeaders(getConnectionHeaders(httpMethod)); + errorResult(e, res); + return res; + } finally { + savedClient = null; + if (httpMethod != null) { + httpMethod.releaseConnection(); + } + } + } + + /** + * Calculate response headers size + * + * @return the size response headers (in bytes) + */ + private static int calculateHeadersSize(HttpMethodBase httpMethod) { + int headerSize = httpMethod.getStatusLine().toString().length()+2; // add a \r\n + Header[] rh = httpMethod.getResponseHeaders(); + for (int i = 0; i < rh.length; i++) { + headerSize += rh[i].toString().length(); // already include the \r\n + } + headerSize += 2; // last \r\n before response data + return headerSize; + } + + /** + * Returns an HttpConnection fully ready to attempt + * connection. This means it sets the request method (GET or POST), headers, + * cookies, and authorization for the URL request. + *

+ * The request infos are saved into the sample result if one is provided. + * + * @param u + * URL of the URL request + * @param httpMethod + * GET/PUT/HEAD etc + * @param res + * sample result to save request infos to + * @return HttpConnection ready for .connect + * @exception IOException + * if an I/O Exception occurs + */ + protected HttpClient setupConnection(URL u, HttpMethodBase httpMethod, HTTPSampleResult res) throws IOException { + + String urlStr = u.toString(); + + org.apache.commons.httpclient.URI uri = new org.apache.commons.httpclient.URI(urlStr,false); + + String schema = uri.getScheme(); + if ((schema == null) || (schema.length()==0)) { + schema = HTTPConstants.PROTOCOL_HTTP; + } + + final boolean isHTTPS = HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(schema); + if (isHTTPS){ + SSLManager.getInstance(); // ensure the manager is initialised + // we don't currently need to do anything further, as this sets the default https protocol + } + + Protocol protocol = Protocol.getProtocol(schema); + + String host = uri.getHost(); + int port = uri.getPort(); + + /* + * We use the HostConfiguration as the key to retrieve the HttpClient, + * so need to ensure that any items used in its equals/hashcode methods are + * not changed after use, i.e.: + * host, port, protocol, localAddress, proxy + * + */ + HostConfiguration hc = new HostConfiguration(); + hc.setHost(host, port, protocol); // All needed to ensure re-usablility + + // Set up the local address if one exists + final InetAddress inetAddr = getIpSourceAddress(); + if (inetAddr != null) {// Use special field ip source address (for pseudo 'ip spoofing') + hc.setLocalAddress(inetAddr); + } else { + hc.setLocalAddress(localAddress); // null means use the default + } + + final String proxyHost = getProxyHost(); + final int proxyPort = getProxyPortInt(); + + boolean useStaticProxy = isStaticProxy(host); + boolean useDynamicProxy = isDynamicProxy(proxyHost, proxyPort); + + if (useDynamicProxy){ + hc.setProxy(proxyHost, proxyPort); + useStaticProxy = false; // Dynamic proxy overrules static proxy + } else if (useStaticProxy) { + if (log.isDebugEnabled()){ + log.debug("Setting proxy: "+PROXY_HOST+":"+PROXY_PORT); + } + hc.setProxy(PROXY_HOST, PROXY_PORT); + } + + Map map = httpClients.get(); + // N.B. HostConfiguration.equals() includes proxy settings in the compare. + HttpClient httpClient = map.get(hc); + + if (httpClient != null && resetSSLContext && isHTTPS) { + httpClient.getHttpConnectionManager().closeIdleConnections(-1000); + httpClient = null; + JsseSSLManager sslMgr = (JsseSSLManager) SSLManager.getInstance(); + sslMgr.resetContext(); + resetSSLContext = false; + } + + if ( httpClient == null ) + { + httpClient = new HttpClient(new SimpleHttpConnectionManager()); + httpClient.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, + new DefaultHttpMethodRetryHandler(RETRY_COUNT, false)); + if (log.isDebugEnabled()) { + log.debug("Created new HttpClient: @"+System.identityHashCode(httpClient)); + } + httpClient.setHostConfiguration(hc); + map.put(hc, httpClient); + } else { + if (log.isDebugEnabled()) { + log.debug("Reusing the HttpClient: @"+System.identityHashCode(httpClient)); + } + } + + // Set up any required Proxy credentials + if (useDynamicProxy){ + String user = getProxyUser(); + if (user.length() > 0){ + httpClient.getState().setProxyCredentials( + new AuthScope(proxyHost,proxyPort,null,AuthScope.ANY_SCHEME), + new NTCredentials(user,getProxyPass(),localHost,PROXY_DOMAIN) + ); + } else { + httpClient.getState().clearProxyCredentials(); + } + } else { + if (useStaticProxy) { + if (PROXY_USER.length() > 0){ + httpClient.getState().setProxyCredentials( + new AuthScope(PROXY_HOST,PROXY_PORT,null,AuthScope.ANY_SCHEME), + new NTCredentials(PROXY_USER,PROXY_PASS,localHost,PROXY_DOMAIN) + ); + } + } else { + httpClient.getState().clearProxyCredentials(); + } + } + + int rto = getResponseTimeout(); + if (rto > 0){ + httpMethod.getParams().setSoTimeout(rto); + } + + int cto = getConnectTimeout(); + if (cto > 0){ + httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(cto); + } + + + // Allow HttpClient to handle the redirects: + httpMethod.setFollowRedirects(getAutoRedirects()); + + // a well-behaved browser is supposed to send 'Connection: close' + // with the last request to an HTTP server. Instead, most browsers + // leave it to the server to close the connection after their + // timeout period. Leave it to the JMeter user to decide. + if (getUseKeepAlive()) { + httpMethod.setRequestHeader(HTTPConstants.HEADER_CONNECTION, HTTPConstants.KEEP_ALIVE); + } else { + httpMethod.setRequestHeader(HTTPConstants.HEADER_CONNECTION, HTTPConstants.CONNECTION_CLOSE); + } + + setConnectionHeaders(httpMethod, u, getHeaderManager(), getCacheManager()); + String cookies = setConnectionCookie(httpMethod, u, getCookieManager()); + + setConnectionAuthorization(httpClient, u, getAuthManager()); + + if (res != null) { + res.setCookies(cookies); + } + + return httpClient; + } + + /** + * Set any default request headers to include + * + * @param httpMethod the HttpMethod used for the request + */ + protected void setDefaultRequestHeaders(HttpMethod httpMethod) { + // Method left empty here, but allows subclasses to override + } + + /** + * Gets the ResponseHeaders + * + * @param method the method used to perform the request + * @return string containing the headers, one per line + */ + protected String getResponseHeaders(HttpMethod method) { + StringBuilder headerBuf = new StringBuilder(); + org.apache.commons.httpclient.Header rh[] = method.getResponseHeaders(); + headerBuf.append(method.getStatusLine());// header[0] is not the status line... + headerBuf.append("\n"); // $NON-NLS-1$ + + for (int i = 0; i < rh.length; i++) { + String key = rh[i].getName(); + headerBuf.append(key); + headerBuf.append(": "); // $NON-NLS-1$ + headerBuf.append(rh[i].getValue()); + headerBuf.append("\n"); // $NON-NLS-1$ + } + return headerBuf.toString(); + } + + /** + * Extracts all the required cookies for that particular URL request and + * sets them in the HttpMethod passed in. + * + * @param method HttpMethod for the request + * @param u URL of the request + * @param cookieManager the CookieManager containing all the cookies + * @return a String containing the cookie details (for the response) + * May be null + */ + private String setConnectionCookie(HttpMethod method, URL u, CookieManager cookieManager) { + String cookieHeader = null; + if (cookieManager != null) { + cookieHeader = cookieManager.getCookieHeaderForURL(u); + if (cookieHeader != null) { + method.setRequestHeader(HTTPConstants.HEADER_COOKIE, cookieHeader); + } + } + return cookieHeader; + } + + /** + * Extracts all the required non-cookie headers for that particular URL request and + * sets them in the HttpMethod passed in + * + * @param method + * HttpMethod which represents the request + * @param u + * URL of the URL request + * @param headerManager + * the HeaderManager containing all the cookies + * for this UrlConfig + * @param cacheManager the CacheManager (may be null) + */ + private void setConnectionHeaders(HttpMethod method, URL u, HeaderManager headerManager, CacheManager cacheManager) { + // Set all the headers from the HeaderManager + if (headerManager != null) { + CollectionProperty headers = headerManager.getHeaders(); + if (headers != null) { + PropertyIterator i = headers.iterator(); + while (i.hasNext()) { + org.apache.jmeter.protocol.http.control.Header header + = (org.apache.jmeter.protocol.http.control.Header) + i.next().getObjectValue(); + String n = header.getName(); + // Don't allow override of Content-Length + // This helps with SoapSampler hack too + // TODO - what other headers are not allowed? + if (! HTTPConstants.HEADER_CONTENT_LENGTH.equalsIgnoreCase(n)){ + String v = header.getValue(); + if (HTTPConstants.HEADER_HOST.equalsIgnoreCase(n)) { + v = v.replaceFirst(":\\d+$",""); // remove any port specification // $NON-NLS-1$ $NON-NLS-2$ + method.getParams().setVirtualHost(v); + } else { + method.addRequestHeader(n, v); + } + } + } + } + } + if (cacheManager != null){ + cacheManager.setHeaders(u, method); + } + } + + /** + * Get all the request headers for the HttpMethod + * + * @param method + * HttpMethod which represents the request + * @return the headers as a string + */ + protected String getConnectionHeaders(HttpMethod method) { + // Get all the request headers + StringBuilder hdrs = new StringBuilder(100); + Header[] requestHeaders = method.getRequestHeaders(); + for(int i = 0; i < requestHeaders.length; i++) { + // Exclude the COOKIE header, since cookie is reported separately in the sample + if(!HTTPConstants.HEADER_COOKIE.equalsIgnoreCase(requestHeaders[i].getName())) { + hdrs.append(requestHeaders[i].getName()); + hdrs.append(": "); // $NON-NLS-1$ + hdrs.append(requestHeaders[i].getValue()); + hdrs.append("\n"); // $NON-NLS-1$ + } + } + + return hdrs.toString(); + } + + + /** + * Extracts all the required authorization for that particular URL request + * and sets it in the HttpMethod passed in. + * + * @param client the HttpClient object + * + * @param u + * URL of the URL request + * @param authManager + * the AuthManager containing all the authorisations for + * this UrlConfig + */ + private void setConnectionAuthorization(HttpClient client, URL u, AuthManager authManager) { + HttpState state = client.getState(); + if (authManager != null) { + HttpClientParams params = client.getParams(); + Authorization auth = authManager.getAuthForURL(u); + if (auth != null) { + String username = auth.getUser(); + String realm = auth.getRealm(); + String domain = auth.getDomain(); + if (log.isDebugEnabled()){ + log.debug(username + " > D="+ username + " D="+domain+" R="+realm); + } + state.setCredentials( + new AuthScope(u.getHost(),u.getPort(), + realm.length()==0 ? null : realm //"" is not the same as no realm + ,AuthScope.ANY_SCHEME), + // NT Includes other types of Credentials + new NTCredentials( + username, + auth.getPass(), + localHost, + domain + )); + // We have credentials - should we set pre-emptive authentication? + if (canSetPreEmptive){ + log.debug("Setting Pre-emptive authentication"); + params.setAuthenticationPreemptive(true); + } + } else { + state.clearCredentials(); + if (canSetPreEmptive){ + params.setAuthenticationPreemptive(false); + } + } + } else { + state.clearCredentials(); + } + } + + + /* + * Send POST data from Entry to the open connection. + * + * @param connection + * URLConnection where POST data should be sent + * @return a String show what was posted. Will not contain actual file upload content + * @exception IOException + * if an I/O exception occurs + */ + private String sendPostData(PostMethod post) throws IOException { + // Buffer to hold the post body, except file content + StringBuilder postedBody = new StringBuilder(1000); + HTTPFileArg files[] = getHTTPFiles(); + // Check if we should do a multipart/form-data or an + // application/x-www-form-urlencoded post request + if(getUseMultipartForPost()) { + // If a content encoding is specified, we use that as the + // encoding of any parameter values + String contentEncoding = getContentEncoding(); + if(isNullOrEmptyTrimmed(contentEncoding)) { + contentEncoding = null; + } + + final boolean browserCompatible = getDoBrowserCompatibleMultipart(); + // We don't know how many entries will be skipped + ArrayList partlist = new ArrayList(); + // Create the parts + // Add any parameters + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + StringPart part = new StringPart(arg.getName(), arg.getValue(), contentEncoding); + if (browserCompatible) { + part.setTransferEncoding(null); + part.setContentType(null); + } + partlist.add(part); + } + + // Add any files + for (int i=0; i < files.length; i++) { + HTTPFileArg file = files[i]; + File inputFile = new File(file.getPath()); + // We do not know the char set of the file to be uploaded, so we set it to null + ViewableFilePart filePart = new ViewableFilePart(file.getParamName(), inputFile, file.getMimeType(), null); + filePart.setCharSet(null); // We do not know what the char set of the file is + partlist.add(filePart); + } + + // Set the multipart for the post + int partNo = partlist.size(); + Part[] parts = partlist.toArray(new Part[partNo]); + MultipartRequestEntity multiPart = new MultipartRequestEntity(parts, post.getParams()); + post.setRequestEntity(multiPart); + + // Set the content type + String multiPartContentType = multiPart.getContentType(); + post.setRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE, multiPartContentType); + + // If the Multipart is repeatable, we can send it first to + // our own stream, without the actual file content, so we can return it + if(multiPart.isRepeatable()) { + // For all the file multiparts, we must tell it to not include + // the actual file content + for(int i = 0; i < partNo; i++) { + if(parts[i] instanceof ViewableFilePart) { + ((ViewableFilePart) parts[i]).setHideFileData(true); // .sendMultipartWithoutFileContent(bos); + } + } + // Write the request to our own stream + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + multiPart.writeRequest(bos); + bos.flush(); + // We get the posted bytes using the encoding used to create it + postedBody.append(new String(bos.toByteArray(), + contentEncoding == null ? "US-ASCII" // $NON-NLS-1$ this is the default used by HttpClient + : contentEncoding)); + bos.close(); + + // For all the file multiparts, we must revert the hiding of + // the actual file content + for(int i = 0; i < partNo; i++) { + if(parts[i] instanceof ViewableFilePart) { + ((ViewableFilePart) parts[i]).setHideFileData(false); + } + } + } + else { + postedBody.append(""); // $NON-NLS-1$ + } + } + else { + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a POST request + Header contentTypeHeader = post.getRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.getValue() != null && contentTypeHeader.getValue().length() > 0; + // If there are no arguments, we can send a file as the body of the request + // TODO: needs a multiple file upload scenerio + if(!hasArguments() && getSendFileAsPostBody()) { + // If getSendFileAsPostBody returned true, it's sure that file is not null + HTTPFileArg file = files[0]; + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + if(file.getMimeType() != null && file.getMimeType().length() > 0) { + post.setRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + post.setRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + FileRequestEntity fileRequestEntity = new FileRequestEntity(new File(file.getPath()),null); + post.setRequestEntity(fileRequestEntity); + + // We just add placeholder text for file content + postedBody.append(""); + } + else { + // In a post request which is not multipart, we only support + // parameters, no file upload is allowed + + // If a content encoding is specified, we set it as http parameter, so that + // the post body will be encoded in the specified content encoding + String contentEncoding = getContentEncoding(); + boolean haveContentEncoding = false; + if(isNullOrEmptyTrimmed(contentEncoding)) { + contentEncoding=null; + } else { + post.getParams().setContentCharset(contentEncoding); + haveContentEncoding = true; + } + + // If none of the arguments have a name specified, we + // just send all the values as the post body + if(getSendParameterValuesAsPostBody()) { + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + // TODO: needs a multiple file upload scenerio + if(!hasContentTypeHeader) { + HTTPFileArg file = files.length > 0? files[0] : null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + post.setRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + // TODO - is this the correct default? + post.setRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + // Just append all the parameter values, and use that as the post body + StringBuilder postBody = new StringBuilder(); + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String value; + if (haveContentEncoding){ + value = arg.getEncodedValue(contentEncoding); + } else { + value = arg.getEncodedValue(); + } + postBody.append(value); + } + StringRequestEntity requestEntity = new StringRequestEntity(postBody.toString(), post.getRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE).getValue(), contentEncoding); + post.setRequestEntity(requestEntity); + } + else { + // It is a normal post request, with parameter names and values + + // Set the content type + if(!hasContentTypeHeader) { + post.setRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + // Add the parameters + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + // The HTTPClient always urlencodes both name and value, + // so if the argument is already encoded, we have to decode + // it before adding it to the post request + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + String parameterValue = arg.getValue(); + if(!arg.isAlwaysEncoded()) { + // The value is already encoded by the user + // Must decode the value now, so that when the + // httpclient encodes it, we end up with the same value + // as the user had entered. + String urlContentEncoding = contentEncoding; + if(urlContentEncoding == null || urlContentEncoding.length() == 0) { + // Use the default encoding for urls + urlContentEncoding = EncoderCache.URL_ARGUMENT_ENCODING; + } + parameterName = URLDecoder.decode(parameterName, urlContentEncoding); + parameterValue = URLDecoder.decode(parameterValue, urlContentEncoding); + } + // Add the parameter, httpclient will urlencode it + post.addParameter(parameterName, parameterValue); + } + +/* +// // Alternative implementation, to make sure that HTTPSampler and HTTPSampler2 +// // sends the same post body. +// +// // Only include the content char set in the content-type header if it is not +// // an APPLICATION_X_WWW_FORM_URLENCODED content type +// String contentCharSet = null; +// if(!post.getRequestHeader(HEADER_CONTENT_TYPE).getValue().equals(APPLICATION_X_WWW_FORM_URLENCODED)) { +// contentCharSet = post.getRequestCharSet(); +// } +// StringRequestEntity requestEntity = new StringRequestEntity(getQueryString(contentEncoding), post.getRequestHeader(HEADER_CONTENT_TYPE).getValue(), contentCharSet); +// post.setRequestEntity(requestEntity); +*/ + } + + // If the request entity is repeatable, we can send it first to + // our own stream, so we can return it + if(post.getRequestEntity().isRepeatable()) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + post.getRequestEntity().writeRequest(bos); + bos.flush(); + // We get the posted bytes using the encoding used to create it + postedBody.append(new String(bos.toByteArray(),post.getRequestCharSet())); + bos.close(); + } + else { + postedBody.append(""); + } + } + } + // Set the content length + post.setRequestHeader(HTTPConstants.HEADER_CONTENT_LENGTH, Long.toString(post.getRequestEntity().getContentLength())); + + return postedBody.toString(); + } + + /** + * Set up the PUT/PATCH/DELETE data + */ + private String sendEntityData(EntityEnclosingMethod put) throws IOException { + // Buffer to hold the put body, except file content + StringBuilder putBody = new StringBuilder(1000); + boolean hasPutBody = false; + + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a POST request + Header contentTypeHeader = put.getRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.getValue() != null && contentTypeHeader.getValue().length() > 0; + HTTPFileArg files[] = getHTTPFiles(); + + // If there are no arguments, we can send a file as the body of the request + + if(!hasArguments() && getSendFileAsPostBody()) { + hasPutBody = true; + + // If getSendFileAsPostBody returned true, it's sure that file is not null + FileRequestEntity fileRequestEntity = new FileRequestEntity(new File(files[0].getPath()),null); + put.setRequestEntity(fileRequestEntity); + } + // If none of the arguments have a name specified, we + // just send all the values as the put body + else if(getSendParameterValuesAsPostBody()) { + hasPutBody = true; + + // If a content encoding is specified, we set it as http parameter, so that + // the post body will be encoded in the specified content encoding + String contentEncoding = getContentEncoding(); + boolean haveContentEncoding = false; + if(isNullOrEmptyTrimmed(contentEncoding)) { + contentEncoding = null; + } else { + put.getParams().setContentCharset(contentEncoding); + haveContentEncoding = true; + } + + // Just append all the parameter values, and use that as the post body + StringBuilder putBodyContent = new StringBuilder(); + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String value = null; + if (haveContentEncoding){ + value = arg.getEncodedValue(contentEncoding); + } else { + value = arg.getEncodedValue(); + } + putBodyContent.append(value); + } + String contentTypeValue = null; + if(hasContentTypeHeader) { + contentTypeValue = put.getRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE).getValue(); + } + StringRequestEntity requestEntity = new StringRequestEntity(putBodyContent.toString(), contentTypeValue, put.getRequestCharSet()); + put.setRequestEntity(requestEntity); + } + // Check if we have any content to send for body + if(hasPutBody) { + // If the request entity is repeatable, we can send it first to + // our own stream, so we can return it + if(put.getRequestEntity().isRepeatable()) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + put.getRequestEntity().writeRequest(bos); + bos.flush(); + // We get the posted bytes using the charset that was used to create them + putBody.append(new String(bos.toByteArray(),put.getRequestCharSet())); + bos.close(); + } + else { + putBody.append(""); + } + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + // TODO: needs a multiple file upload scenerio + HTTPFileArg file = files.length > 0? files[0] : null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + put.setRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + } + // Set the content length + put.setRequestHeader(HTTPConstants.HEADER_CONTENT_LENGTH, Long.toString(put.getRequestEntity().getContentLength())); + } + return putBody.toString(); + } + + /** + * Class extending FilePart, so that we can send placeholder text + * instead of the actual file content + */ + private static class ViewableFilePart extends FilePart { + private boolean hideFileData; + + public ViewableFilePart(String name, File file, String contentType, String charset) throws FileNotFoundException { + super(name, file, contentType, charset); + this.hideFileData = false; + } + + public void setHideFileData(boolean hideFileData) { + this.hideFileData = hideFileData; + } + + @Override + protected void sendData(OutputStream out) throws IOException { + // Check if we should send only placeholder text for the + // file content, or the real file content + if(hideFileData) { + out.write("".getBytes());// encoding does not really matter here + } + else { + super.sendData(out); + } + } + } + + /** + * From the HttpMethod, store all the "set-cookie" key-pair + * values in the cookieManager of the UrlConfig. + * + * @param method + * HttpMethod which represents the request + * @param u + * URL of the URL request + * @param cookieManager + * the CookieManager containing all the cookies + */ + protected void saveConnectionCookies(HttpMethod method, URL u, CookieManager cookieManager) { + if (cookieManager != null) { + Header hdr[] = method.getResponseHeaders(HTTPConstants.HEADER_SET_COOKIE); + for (int i = 0; i < hdr.length; i++) { + cookieManager.addCookieFromHeader(hdr[i].getValue(),u); + } + } + } + + + @Override + protected void threadFinished() { + log.debug("Thread Finished"); + + closeThreadLocalConnections(); + } + + @Override + protected void notifyFirstSampleAfterLoopRestart() { + log.debug("notifyFirstSampleAfterLoopRestart"); + resetSSLContext = !USE_CACHED_SSL_CONTEXT; + } + + /** + * + */ + private void closeThreadLocalConnections() { + // Does not need to be synchronised, as all access is from same thread + Map map = httpClients.get(); + + if ( map != null ) { + for (HttpClient cl : map.values()) + { + // Can cause NPE in HttpClient 3.1 + //((SimpleHttpConnectionManager)cl.getHttpConnectionManager()).shutdown();// Closes the connection + // Revert to original method: + cl.getHttpConnectionManager().closeIdleConnections(-1000);// Closes the connection + } + map.clear(); + } + } + + /** {@inheritDoc} */ + @Override + public boolean interrupt() { + HttpClient client = savedClient; + if (client != null) { + savedClient = null; + // TODO - not sure this is the best method + final HttpConnectionManager httpConnectionManager = client.getHttpConnectionManager(); + if (httpConnectionManager instanceof SimpleHttpConnectionManager) {// Should be true + ((SimpleHttpConnectionManager)httpConnectionManager).shutdown(); + } + } + return client != null; + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java new file mode 100644 index 00000000000..f99ef9b93bd --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java @@ -0,0 +1,1360 @@ +/* + * 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.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.security.auth.Subject; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.HttpConnection; +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.NameValuePair; +import org.apache.http.StatusLine; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.NTCredentials; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.HttpTrace; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.params.CookiePolicy; +import org.apache.http.client.protocol.ResponseContentEncoding; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.DnsResolver; +import org.apache.http.conn.params.ConnRoutePNames; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.FileEntity; +import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.FormBodyPart; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.entity.mime.content.StringBody; +import org.apache.http.impl.client.AbstractHttpClient; +import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; +import org.apache.http.impl.conn.SchemeRegistryFactory; +import org.apache.http.impl.conn.SystemDefaultDnsResolver; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.params.DefaultedHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.SyncBasicHttpParams; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.ExecutionContext; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.EncoderCache; +import org.apache.jmeter.protocol.http.util.HC4TrustAllSSLSocketFactory; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.protocol.http.util.SlowHC4SSLSocketFactory; +import org.apache.jmeter.protocol.http.util.SlowHC4SocketFactory; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.JsseSSLManager; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HTTP Sampler using Apache HttpClient 4.x. + * + */ +public class HTTPHC4Impl extends HTTPHCAbstractImpl { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** retry count to be used (default 0); 0 = disable retries */ + private static final int RETRY_COUNT = JMeterUtils.getPropDefault("httpclient4.retrycount", 0); + + /** Idle timeout to be applied to connections if no Keep-Alive header is sent by the server (default 0 = disable) */ + private static final int IDLE_TIMEOUT = JMeterUtils.getPropDefault("httpclient4.idletimeout", 0); + + private static final String CONTEXT_METRICS = "jmeter_metrics"; // TODO hack for metrics related to HTTPCLIENT-1081, to be removed later + + private static final ConnectionKeepAliveStrategy IDLE_STRATEGY = new DefaultConnectionKeepAliveStrategy(){ + @Override + public long getKeepAliveDuration(HttpResponse response, HttpContext context) { + long duration = super.getKeepAliveDuration(response, context); + if (duration <= 0) {// none found by the superclass + log.debug("Setting keepalive to " + IDLE_TIMEOUT); + return IDLE_TIMEOUT; + } + return duration; // return the super-class value + } + + }; + + /** + * Special interceptor made to keep metrics when connection is released for some method like HEAD + * Otherwise calling directly ((HttpConnection) localContext.getAttribute(ExecutionContext.HTTP_CONNECTION)).getMetrics(); + * would throw org.apache.http.impl.conn.ConnectionShutdownException + * See https://bz.apache.org/jira/browse/HTTPCLIENT-1081 + */ + private static final HttpResponseInterceptor METRICS_SAVER = new HttpResponseInterceptor(){ + @Override + public void process(HttpResponse response, HttpContext context) + throws HttpException, IOException { + HttpConnection conn = (HttpConnection) context.getAttribute(ExecutionContext.HTTP_CONNECTION); + HttpConnectionMetrics metrics = conn.getMetrics(); + context.setAttribute(CONTEXT_METRICS, metrics); + } + }; + private static final HttpRequestInterceptor METRICS_RESETTER = new HttpRequestInterceptor() { + @Override + public void process(HttpRequest request, HttpContext context) + throws HttpException, IOException { + HttpConnection conn = (HttpConnection) context.getAttribute(ExecutionContext.HTTP_CONNECTION); + HttpConnectionMetrics metrics = conn.getMetrics(); + metrics.reset(); + } + }; + + /** + * 1 HttpClient instance per combination of (HttpClient,HttpClientKey) + */ + private static final ThreadLocal> HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY = + new ThreadLocal>(){ + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + // Scheme used for slow HTTP sockets. Cannot be set as a default, because must be set on an HttpClient instance. + private static final Scheme SLOW_HTTP; + + // We always want to override the HTTPS scheme, because we want to trust all certificates and hosts + private static final Scheme HTTPS_SCHEME; + + /* + * Create a set of default parameters from the ones initially created. + * This allows the defaults to be overridden if necessary from the properties file. + */ + private static final HttpParams DEFAULT_HTTP_PARAMS; + + static { + log.info("HTTP request retry count = "+RETRY_COUNT); + + DEFAULT_HTTP_PARAMS = new SyncBasicHttpParams(); // Could we drop the Sync here? + DEFAULT_HTTP_PARAMS.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false); + DefaultHttpClient.setDefaultHttpParams(DEFAULT_HTTP_PARAMS); + + // Process Apache HttpClient parameters file + String file=JMeterUtils.getProperty("hc.parameters.file"); // $NON-NLS-1$ + if (file != null) { + HttpClientDefaultParameters.load(file, DEFAULT_HTTP_PARAMS); + } + + // Set up HTTP scheme override if necessary + if (CPS_HTTP > 0) { + log.info("Setting up HTTP SlowProtocol, cps="+CPS_HTTP); + SLOW_HTTP = new Scheme(HTTPConstants.PROTOCOL_HTTP, HTTPConstants.DEFAULT_HTTP_PORT, new SlowHC4SocketFactory(CPS_HTTP)); + } else { + SLOW_HTTP = null; + } + + // We always want to override the HTTPS scheme + Scheme https = null; + if (CPS_HTTPS > 0) { + log.info("Setting up HTTPS SlowProtocol, cps="+CPS_HTTPS); + try { + https = new Scheme(HTTPConstants.PROTOCOL_HTTPS, HTTPConstants.DEFAULT_HTTPS_PORT, new SlowHC4SSLSocketFactory(CPS_HTTPS)); + } catch (GeneralSecurityException e) { + log.warn("Failed to initialise SLOW_HTTPS scheme, cps="+CPS_HTTPS, e); + } + } else { + log.info("Setting up HTTPS TrustAll scheme"); + try { + https = new Scheme(HTTPConstants.PROTOCOL_HTTPS, HTTPConstants.DEFAULT_HTTPS_PORT, new HC4TrustAllSSLSocketFactory()); + } catch (GeneralSecurityException e) { + log.warn("Failed to initialise HTTPS TrustAll scheme", e); + } + } + HTTPS_SCHEME = https; + if (localAddress != null){ + DEFAULT_HTTP_PARAMS.setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress); + } + + } + + private volatile HttpUriRequest currentRequest; // Accessed from multiple threads + + private volatile boolean resetSSLContext; + + protected HTTPHC4Impl(HTTPSamplerBase testElement) { + super(testElement); + } + + public static final class HttpDelete extends HttpEntityEnclosingRequestBase { + + public HttpDelete(final URI uri) { + super(); + setURI(uri); + } + + @Override + public String getMethod() { + return HTTPConstants.DELETE; + } + } + + @Override + protected HTTPSampleResult sample(URL url, String method, + boolean areFollowingRedirect, int frameDepth) { + + if (log.isDebugEnabled()) { + log.debug("Start : sample " + url.toString()); + log.debug("method " + method+ " followingRedirect " + areFollowingRedirect + " depth " + frameDepth); + } + + HTTPSampleResult res = createSampleResult(url, method); + + HttpClient httpClient = setupClient(url, res); + + HttpRequestBase httpRequest = null; + try { + URI uri = url.toURI(); + if (method.equals(HTTPConstants.POST)) { + httpRequest = new HttpPost(uri); + } else if (method.equals(HTTPConstants.PUT)) { + httpRequest = new HttpPut(uri); + } else if (method.equals(HTTPConstants.HEAD)) { + httpRequest = new HttpHead(uri); + } else if (method.equals(HTTPConstants.TRACE)) { + httpRequest = new HttpTrace(uri); + } else if (method.equals(HTTPConstants.OPTIONS)) { + httpRequest = new HttpOptions(uri); + } else if (method.equals(HTTPConstants.DELETE)) { + httpRequest = new HttpDelete(uri); + } else if (method.equals(HTTPConstants.GET)) { + httpRequest = new HttpGet(uri); + } else if (method.equals(HTTPConstants.PATCH)) { + httpRequest = new HttpPatch(uri); + } else if (HttpWebdav.isWebdavMethod(method)) { + httpRequest = new HttpWebdav(method, uri); + } else { + throw new IllegalArgumentException("Unexpected method: '"+method+"'"); + } + setupRequest(url, httpRequest, res); // can throw IOException + } catch (Exception e) { + res.sampleStart(); + res.sampleEnd(); + errorResult(e, res); + return res; + } + + HttpContext localContext = new BasicHttpContext(); + + res.sampleStart(); + + final CacheManager cacheManager = getCacheManager(); + if (cacheManager != null && HTTPConstants.GET.equalsIgnoreCase(method)) { + if (cacheManager.inCache(url)) { + return updateSampleResultForResourceInCache(res); + } + } + + try { + currentRequest = httpRequest; + handleMethod(method, res, httpRequest, localContext); + // perform the sample + HttpResponse httpResponse = + executeRequest(httpClient, httpRequest, localContext, url); + + // Needs to be done after execute to pick up all the headers + final HttpRequest request = (HttpRequest) localContext.getAttribute(ExecutionContext.HTTP_REQUEST); + // We've finished with the request, so we can add the LocalAddress to it for display + final InetAddress localAddr = (InetAddress) httpRequest.getParams().getParameter(ConnRoutePNames.LOCAL_ADDRESS); + if (localAddr != null) { + request.addHeader(HEADER_LOCAL_ADDRESS, localAddr.toString()); + } + res.setRequestHeaders(getConnectionHeaders(request)); + + Header contentType = httpResponse.getLastHeader(HTTPConstants.HEADER_CONTENT_TYPE); + if (contentType != null){ + String ct = contentType.getValue(); + res.setContentType(ct); + res.setEncodingAndType(ct); + } + HttpEntity entity = httpResponse.getEntity(); + if (entity != null) { + InputStream instream = entity.getContent(); + res.setResponseData(readResponse(res, instream, (int) entity.getContentLength())); + } + + res.sampleEnd(); // Done with the sampling proper. + currentRequest = null; + + // Now collect the results into the HTTPSampleResult: + StatusLine statusLine = httpResponse.getStatusLine(); + int statusCode = statusLine.getStatusCode(); + res.setResponseCode(Integer.toString(statusCode)); + res.setResponseMessage(statusLine.getReasonPhrase()); + res.setSuccessful(isSuccessCode(statusCode)); + + res.setResponseHeaders(getResponseHeaders(httpResponse)); + if (res.isRedirect()) { + final Header headerLocation = httpResponse.getLastHeader(HTTPConstants.HEADER_LOCATION); + if (headerLocation == null) { // HTTP protocol violation, but avoids NPE + throw new IllegalArgumentException("Missing location header in redirect for " + httpRequest.getRequestLine()); + } + String redirectLocation = headerLocation.getValue(); + res.setRedirectLocation(redirectLocation); + } + + // record some sizes to allow HTTPSampleResult.getBytes() with different options + HttpConnectionMetrics metrics = (HttpConnectionMetrics) localContext.getAttribute(CONTEXT_METRICS); + long headerBytes = + res.getResponseHeaders().length() // condensed length (without \r) + + httpResponse.getAllHeaders().length // Add \r for each header + + 1 // Add \r for initial header + + 2; // final \r\n before data + long totalBytes = metrics.getReceivedBytesCount(); + res.setHeadersSize((int) headerBytes); + res.setBodySize((int)(totalBytes - headerBytes)); + if (log.isDebugEnabled()) { + log.debug("ResponseHeadersSize=" + res.getHeadersSize() + " Content-Length=" + res.getBodySize() + + " Total=" + (res.getHeadersSize() + res.getBodySize())); + } + + // If we redirected automatically, the URL may have changed + if (getAutoRedirects()){ + HttpUriRequest req = (HttpUriRequest) localContext.getAttribute(ExecutionContext.HTTP_REQUEST); + HttpHost target = (HttpHost) localContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST); + URI redirectURI = req.getURI(); + if (redirectURI.isAbsolute()){ + res.setURL(redirectURI.toURL()); + } else { + res.setURL(new URL(new URL(target.toURI()),redirectURI.toString())); + } + } + + // Store any cookies received in the cookie manager: + saveConnectionCookies(httpResponse, res.getURL(), getCookieManager()); + + // Save cache information + if (cacheManager != null){ + cacheManager.saveDetails(httpResponse, res); + } + + // Follow redirects and download page resources if appropriate: + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + } catch (IOException e) { + log.debug("IOException", e); + if (res.getEndTime() == 0) { + res.sampleEnd(); + } + // pick up headers if failed to execute the request + if (res.getRequestHeaders() != null) { + log.debug("Overwriting request old headers: " + res.getRequestHeaders()); + } + res.setRequestHeaders(getConnectionHeaders((HttpRequest) localContext.getAttribute(ExecutionContext.HTTP_REQUEST))); + errorResult(e, res); + return res; + } catch (RuntimeException e) { + log.debug("RuntimeException", e); + if (res.getEndTime() == 0) { + res.sampleEnd(); + } + errorResult(e, res); + return res; + } finally { + currentRequest = null; + } + return res; + } + + /** + * Calls {@link #sendPostData(HttpPost)} if method is POST and + * {@link #sendEntityData(HttpEntityEnclosingRequestBase)} if method is + * PUT or PATCH + *

+ * Field HTTPSampleResult#queryString of result is modified in the 2 cases + * + * @param method + * String HTTP method + * @param result + * {@link HTTPSampleResult} + * @param httpRequest + * {@link HttpRequestBase} + * @param localContext + * {@link HttpContext} + * @throws IOException + * when posting data fails due to I/O + */ + protected void handleMethod(String method, HTTPSampleResult result, + HttpRequestBase httpRequest, HttpContext localContext) throws IOException { + // Handle the various methods + if (method.equals(HTTPConstants.POST)) { + String postBody = sendPostData((HttpPost)httpRequest); + result.setQueryString(postBody); + } else if (method.equals(HTTPConstants.PUT) || method.equals(HTTPConstants.PATCH) + || HttpWebdav.isWebdavMethod(method) + || method.equals(HTTPConstants.DELETE)) { + String entityBody = sendEntityData(( HttpEntityEnclosingRequestBase)httpRequest); + result.setQueryString(entityBody); + } + } + + /** + * Create HTTPSampleResult filling url, method and SampleLabel. + * Monitor field is computed calling isMonitor() + * @param url URL + * @param method HTTP Method + * @return {@link HTTPSampleResult} + */ + protected HTTPSampleResult createSampleResult(URL url, String method) { + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(isMonitor()); + + res.setSampleLabel(url.toString()); // May be replaced later + res.setHTTPMethod(method); + res.setURL(url); + + return res; + } + + /** + * Execute request either as is or under PrivilegedAction + * if a Subject is available for url + * @param httpClient + * @param httpRequest + * @param localContext + * @param url + * @return + * @throws IOException + * @throws ClientProtocolException + */ + private HttpResponse executeRequest(final HttpClient httpClient, + final HttpRequestBase httpRequest, final HttpContext localContext, final URL url) + throws IOException, ClientProtocolException { + AuthManager authManager = getAuthManager(); + if (authManager != null) { + Subject subject = authManager.getSubjectForUrl(url); + if(subject != null) { + try { + return Subject.doAs(subject, + new PrivilegedExceptionAction() { + + @Override + public HttpResponse run() throws Exception { + return httpClient.execute(httpRequest, + localContext); + } + }); + } catch (PrivilegedActionException e) { + log.error( + "Can't execute httpRequest with subject:"+subject, + e); + throw new RuntimeException("Can't execute httpRequest with subject:"+subject, e); + } + } + } + return httpClient.execute(httpRequest, localContext); + } + + /** + * Holder class for all fields that define an HttpClient instance; + * used as the key to the ThreadLocal map of HttpClient instances. + */ + private static final class HttpClientKey { + + private final String target; // protocol://[user:pass@]host:[port] + private final boolean hasProxy; + private final String proxyHost; + private final int proxyPort; + private final String proxyUser; + private final String proxyPass; + + private final int hashCode; // Always create hash because we will always need it + + /** + * @param url URL Only protocol and url authority are used (protocol://[user:pass@]host:[port]) + * @param hasProxy has proxy + * @param proxyHost proxy host + * @param proxyPort proxy port + * @param proxyUser proxy user + * @param proxyPass proxy password + */ + public HttpClientKey(URL url, boolean hasProxy, String proxyHost, + int proxyPort, String proxyUser, String proxyPass) { + // N.B. need to separate protocol from authority otherwise http://server would match https://erver (<= sic, not typo error) + // could use separate fields, but simpler to combine them + this.target = url.getProtocol()+"://"+url.getAuthority(); + this.hasProxy = hasProxy; + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUser = proxyUser; + this.proxyPass = proxyPass; + this.hashCode = getHash(); + } + + private int getHash() { + int hash = 17; + hash = hash*31 + (hasProxy ? 1 : 0); + if (hasProxy) { + hash = hash*31 + getHash(proxyHost); + hash = hash*31 + proxyPort; + hash = hash*31 + getHash(proxyUser); + hash = hash*31 + getHash(proxyPass); + } + hash = hash*31 + target.hashCode(); + return hash; + } + + // Allow for null strings + private int getHash(String s) { + return s == null ? 0 : s.hashCode(); + } + + @Override + public boolean equals (Object obj){ + if (this == obj) { + return true; + } + if (!(obj instanceof HttpClientKey)) { + return false; + } + HttpClientKey other = (HttpClientKey) obj; + if (this.hasProxy) { // otherwise proxy String fields may be null + return + this.hasProxy == other.hasProxy && + this.proxyPort == other.proxyPort && + this.proxyHost.equals(other.proxyHost) && + this.proxyUser.equals(other.proxyUser) && + this.proxyPass.equals(other.proxyPass) && + this.target.equals(other.target); + } + // No proxy, so don't check proxy fields + return + this.hasProxy == other.hasProxy && + this.target.equals(other.target); + } + + @Override + public int hashCode(){ + return hashCode; + } + + // For debugging + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(target); + if (hasProxy) { + sb.append(" via "); + sb.append(proxyUser); + sb.append("@"); + sb.append(proxyHost); + sb.append(":"); + sb.append(proxyPort); + } + return sb.toString(); + } + } + + private HttpClient setupClient(URL url, SampleResult res) { + + Map mapHttpClientPerHttpClientKey = HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY.get(); + + final String host = url.getHost(); + final String proxyHost = getProxyHost(); + final int proxyPort = getProxyPortInt(); + + boolean useStaticProxy = isStaticProxy(host); + boolean useDynamicProxy = isDynamicProxy(proxyHost, proxyPort); + + // Lookup key - must agree with all the values used to create the HttpClient. + HttpClientKey key = new HttpClientKey(url, (useStaticProxy || useDynamicProxy), + useDynamicProxy ? proxyHost : PROXY_HOST, + useDynamicProxy ? proxyPort : PROXY_PORT, + useDynamicProxy ? getProxyUser() : PROXY_USER, + useDynamicProxy ? getProxyPass() : PROXY_PASS); + + HttpClient httpClient = mapHttpClientPerHttpClientKey.get(key); + + if (httpClient != null && resetSSLContext && HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(url.getProtocol())) { + ((AbstractHttpClient) httpClient).clearRequestInterceptors(); + ((AbstractHttpClient) httpClient).clearResponseInterceptors(); + httpClient.getConnectionManager().closeIdleConnections(1L, TimeUnit.MICROSECONDS); + httpClient = null; + JsseSSLManager sslMgr = (JsseSSLManager) SSLManager.getInstance(); + sslMgr.resetContext(); + resetSSLContext = false; + } + + if (httpClient == null){ // One-time init for this client + + HttpParams clientParams = new DefaultedHttpParams(new BasicHttpParams(), DEFAULT_HTTP_PARAMS); + + DnsResolver resolver = this.testElement.getDNSResolver(); + if (resolver == null) { + resolver = new SystemDefaultDnsResolver(); + } + ClientConnectionManager connManager = new MeasuringConnectionManager(SchemeRegistryFactory.createDefault(), resolver); + + httpClient = new DefaultHttpClient(connManager, clientParams) { + @Override + protected HttpRequestRetryHandler createHttpRequestRetryHandler() { + return new DefaultHttpRequestRetryHandler(RETRY_COUNT, false); // set retry count + } + }; + if (IDLE_TIMEOUT > 0) { + ((AbstractHttpClient) httpClient).setKeepAliveStrategy(IDLE_STRATEGY ); + } + ((AbstractHttpClient) httpClient).addResponseInterceptor(new ResponseContentEncoding()); + ((AbstractHttpClient) httpClient).addResponseInterceptor(METRICS_SAVER); // HACK + ((AbstractHttpClient) httpClient).addRequestInterceptor(METRICS_RESETTER); + + // Override the defualt schemes as necessary + SchemeRegistry schemeRegistry = httpClient.getConnectionManager().getSchemeRegistry(); + + if (SLOW_HTTP != null){ + schemeRegistry.register(SLOW_HTTP); + } + + if (HTTPS_SCHEME != null){ + schemeRegistry.register(HTTPS_SCHEME); + } + + // Set up proxy details + if (useDynamicProxy){ + HttpHost proxy = new HttpHost(proxyHost, proxyPort); + clientParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); + String proxyUser = getProxyUser(); + + if (proxyUser.length() > 0) { + ((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials( + new AuthScope(proxyHost, proxyPort), + new NTCredentials(proxyUser, getProxyPass(), localHost, PROXY_DOMAIN)); + } + } else if (useStaticProxy) { + HttpHost proxy = new HttpHost(PROXY_HOST, PROXY_PORT); + clientParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); + if (PROXY_USER.length() > 0) { + ((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials( + new AuthScope(PROXY_HOST, PROXY_PORT), + new NTCredentials(PROXY_USER, PROXY_PASS, localHost, PROXY_DOMAIN)); + } + } + + // Bug 52126 - we do our own cookie handling + clientParams.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES); + + if (log.isDebugEnabled()) { + log.debug("Created new HttpClient: @"+System.identityHashCode(httpClient) + " " + key.toString()); + } + + mapHttpClientPerHttpClientKey.put(key, httpClient); // save the agent for next time round + } else { + if (log.isDebugEnabled()) { + log.debug("Reusing the HttpClient: @"+System.identityHashCode(httpClient) + " " + key.toString()); + } + } + + MeasuringConnectionManager connectionManager = (MeasuringConnectionManager) httpClient.getConnectionManager(); + connectionManager.setSample(res); + + // TODO - should this be done when the client is created? + // If so, then the details need to be added as part of HttpClientKey + setConnectionAuthorization(httpClient, url, getAuthManager(), key); + + return httpClient; + } + + /** + * Setup following elements on httpRequest: + *

    + *
  • ConnRoutePNames.LOCAL_ADDRESS enabling IP-SPOOFING
  • + *
  • Socket and connection timeout
  • + *
  • Redirect handling
  • + *
  • Keep Alive header or Connection Close
  • + *
  • Calls setConnectionHeaders to setup headers
  • + *
  • Calls setConnectionCookie to setup Cookie
  • + *
+ * + * @param url + * {@link URL} of the request + * @param httpRequest + * http request for the request + * @param res + * sample result to set cookies on + * @throws IOException + * if hostname/ip to use could not be figured out + */ + protected void setupRequest(URL url, HttpRequestBase httpRequest, HTTPSampleResult res) + throws IOException { + + HttpParams requestParams = httpRequest.getParams(); + + // Set up the local address if one exists + final InetAddress inetAddr = getIpSourceAddress(); + if (inetAddr != null) {// Use special field ip source address (for pseudo 'ip spoofing') + requestParams.setParameter(ConnRoutePNames.LOCAL_ADDRESS, inetAddr); + } else if (localAddress != null){ + requestParams.setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress); + } else { // reset in case was set previously + requestParams.removeParameter(ConnRoutePNames.LOCAL_ADDRESS); + } + + int rto = getResponseTimeout(); + if (rto > 0){ + requestParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, rto); + } + + int cto = getConnectTimeout(); + if (cto > 0){ + requestParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, cto); + } + + requestParams.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, getAutoRedirects()); + + // a well-behaved browser is supposed to send 'Connection: close' + // with the last request to an HTTP server. Instead, most browsers + // leave it to the server to close the connection after their + // timeout period. Leave it to the JMeter user to decide. + if (getUseKeepAlive()) { + httpRequest.setHeader(HTTPConstants.HEADER_CONNECTION, HTTPConstants.KEEP_ALIVE); + } else { + httpRequest.setHeader(HTTPConstants.HEADER_CONNECTION, HTTPConstants.CONNECTION_CLOSE); + } + + setConnectionHeaders(httpRequest, url, getHeaderManager(), getCacheManager()); + + String cookies = setConnectionCookie(httpRequest, url, getCookieManager()); + + if (res != null) { + res.setCookies(cookies); + } + +} + + + /** + * Set any default request headers to include + * + * @param request the HttpRequest to be used + */ + protected void setDefaultRequestHeaders(HttpRequest request) { + // Method left empty here, but allows subclasses to override + } + + /** + * Gets the ResponseHeaders + * + * @param response + * containing the headers + * @return string containing the headers, one per line + */ + private String getResponseHeaders(HttpResponse response) { + StringBuilder headerBuf = new StringBuilder(); + Header[] rh = response.getAllHeaders(); + headerBuf.append(response.getStatusLine());// header[0] is not the status line... + headerBuf.append("\n"); // $NON-NLS-1$ + + for (int i = 0; i < rh.length; i++) { + headerBuf.append(rh[i].getName()); + headerBuf.append(": "); // $NON-NLS-1$ + headerBuf.append(rh[i].getValue()); + headerBuf.append("\n"); // $NON-NLS-1$ + } + return headerBuf.toString(); + } + + /** + * Extracts all the required cookies for that particular URL request and + * sets them in the HttpMethod passed in. + * + * @param request HttpRequest for the request + * @param url URL of the request + * @param cookieManager the CookieManager containing all the cookies + * @return a String containing the cookie details (for the response) + * May be null + */ + protected String setConnectionCookie(HttpRequest request, URL url, CookieManager cookieManager) { + String cookieHeader = null; + if (cookieManager != null) { + cookieHeader = cookieManager.getCookieHeaderForURL(url); + if (cookieHeader != null) { + request.setHeader(HTTPConstants.HEADER_COOKIE, cookieHeader); + } + } + return cookieHeader; + } + + /** + * Extracts all the required non-cookie headers for that particular URL request and + * sets them in the HttpMethod passed in + * + * @param request + * HttpRequest which represents the request + * @param url + * URL of the URL request + * @param headerManager + * the HeaderManager containing all the cookies + * for this UrlConfig + * @param cacheManager the CacheManager (may be null) + */ + protected void setConnectionHeaders(HttpRequestBase request, URL url, HeaderManager headerManager, CacheManager cacheManager) { + if (headerManager != null) { + CollectionProperty headers = headerManager.getHeaders(); + if (headers != null) { + PropertyIterator i = headers.iterator(); + while (i.hasNext()) { + org.apache.jmeter.protocol.http.control.Header header + = (org.apache.jmeter.protocol.http.control.Header) + i.next().getObjectValue(); + String n = header.getName(); + // Don't allow override of Content-Length + // TODO - what other headers are not allowed? + if (! HTTPConstants.HEADER_CONTENT_LENGTH.equalsIgnoreCase(n)){ + String v = header.getValue(); + if (HTTPConstants.HEADER_HOST.equalsIgnoreCase(n)) { + int port = url.getPort(); + v = v.replaceFirst(":\\d+$",""); // remove any port specification // $NON-NLS-1$ $NON-NLS-2$ + if (port != -1) { + if (port == url.getDefaultPort()) { + port = -1; // no need to specify the port if it is the default + } + } + request.getParams().setParameter(ClientPNames.VIRTUAL_HOST, new HttpHost(v, port)); + } else { + request.addHeader(n, v); + } + } + } + } + } + if (cacheManager != null){ + cacheManager.setHeaders(url, request); + } + } + + /** + * Get all the request headers for the HttpMethod + * + * @param method + * HttpMethod which represents the request + * @return the headers as a string + */ + private String getConnectionHeaders(HttpRequest method) { + if(method != null) { + // Get all the request headers + StringBuilder hdrs = new StringBuilder(100); + Header[] requestHeaders = method.getAllHeaders(); + for(int i = 0; i < requestHeaders.length; i++) { + // Exclude the COOKIE header, since cookie is reported separately in the sample + if(!HTTPConstants.HEADER_COOKIE.equalsIgnoreCase(requestHeaders[i].getName())) { + hdrs.append(requestHeaders[i].getName()); + hdrs.append(": "); // $NON-NLS-1$ + hdrs.append(requestHeaders[i].getValue()); + hdrs.append("\n"); // $NON-NLS-1$ + } + } + + return hdrs.toString(); + } + return ""; ////$NON-NLS-1$ + } + + /** + * Setup credentials for url AuthScope but keeps Proxy AuthScope credentials + * @param client HttpClient + * @param url URL + * @param authManager {@link AuthManager} + * @param key key + */ + private void setConnectionAuthorization(HttpClient client, URL url, AuthManager authManager, HttpClientKey key) { + CredentialsProvider credentialsProvider = + ((AbstractHttpClient) client).getCredentialsProvider(); + if (authManager != null) { + if(authManager.hasAuthForURL(url)) { + authManager.setupCredentials(client, url, credentialsProvider, localHost); + } else { + credentialsProvider.clear(); + } + } else { + Credentials credentials = null; + AuthScope authScope = null; + if(key.hasProxy && !StringUtils.isEmpty(key.proxyUser)) { + authScope = new AuthScope(key.proxyHost, key.proxyPort); + credentials = credentialsProvider.getCredentials(authScope); + } + credentialsProvider.clear(); + if(credentials != null) { + credentialsProvider.setCredentials(authScope, credentials); + } + } + } + + // Helper class so we can generate request data without dumping entire file contents + private static class ViewableFileBody extends FileBody { + private boolean hideFileData; + + public ViewableFileBody(File file, String mimeType) { + super(file, mimeType); + hideFileData = false; + } + + @Override + public void writeTo(final OutputStream out) throws IOException { + if (hideFileData) { + out.write("".getBytes());// encoding does not really matter here + } else { + super.writeTo(out); + } + } + } + + // TODO needs cleaning up + /** + * + * @param post {@link HttpPost} + * @return String posted body if computable + * @throws IOException if sending the data fails due to I/O + */ + protected String sendPostData(HttpPost post) throws IOException { + // Buffer to hold the post body, except file content + StringBuilder postedBody = new StringBuilder(1000); + HTTPFileArg files[] = getHTTPFiles(); + + final String contentEncoding = getContentEncodingOrNull(); + final boolean haveContentEncoding = contentEncoding != null; + + // Check if we should do a multipart/form-data or an + // application/x-www-form-urlencoded post request + if(getUseMultipartForPost()) { + // If a content encoding is specified, we use that as the + // encoding of any parameter values + Charset charset = null; + if(haveContentEncoding) { + charset = Charset.forName(contentEncoding); + } + + // Write the request to our own stream + MultipartEntity multiPart = new MultipartEntity( + getDoBrowserCompatibleMultipart() ? HttpMultipartMode.BROWSER_COMPATIBLE : HttpMultipartMode.STRICT, + null, charset); + // Create the parts + // Add any parameters + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + FormBodyPart formPart; + StringBody stringBody = new StringBody(arg.getValue(), charset); + formPart = new FormBodyPart(arg.getName(), stringBody); + multiPart.addPart(formPart); + } + + // Add any files + // Cannot retrieve parts once added to the MultiPartEntity, so have to save them here. + ViewableFileBody[] fileBodies = new ViewableFileBody[files.length]; + for (int i=0; i < files.length; i++) { + HTTPFileArg file = files[i]; + fileBodies[i] = new ViewableFileBody(new File(file.getPath()), file.getMimeType()); + multiPart.addPart(file.getParamName(),fileBodies[i]); + } + + post.setEntity(multiPart); + + if (multiPart.isRepeatable()){ + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + for(ViewableFileBody fileBody : fileBodies){ + fileBody.hideFileData = true; + } + multiPart.writeTo(bos); + for(ViewableFileBody fileBody : fileBodies){ + fileBody.hideFileData = false; + } + bos.flush(); + // We get the posted bytes using the encoding used to create it + postedBody.append(new String(bos.toByteArray(), + contentEncoding == null ? "US-ASCII" // $NON-NLS-1$ this is the default used by HttpClient + : contentEncoding)); + bos.close(); + } else { + postedBody.append(""); // $NON-NLS-1$ + } + +// // Set the content type TODO - needed? +// String multiPartContentType = multiPart.getContentType().getValue(); +// post.setHeader(HEADER_CONTENT_TYPE, multiPartContentType); + + } else { // not multipart + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a POST request + Header contentTypeHeader = post.getFirstHeader(HTTPConstants.HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.getValue() != null && contentTypeHeader.getValue().length() > 0; + // If there are no arguments, we can send a file as the body of the request + // TODO: needs a multiple file upload scenerio + if(!hasArguments() && getSendFileAsPostBody()) { + // If getSendFileAsPostBody returned true, it's sure that file is not null + HTTPFileArg file = files[0]; + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + if(file.getMimeType() != null && file.getMimeType().length() > 0) { + post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + FileEntity fileRequestEntity = new FileEntity(new File(file.getPath()),(ContentType) null);// TODO is null correct? + post.setEntity(fileRequestEntity); + + // We just add placeholder text for file content + postedBody.append(""); + } else { + // In a post request which is not multipart, we only support + // parameters, no file upload is allowed + + // If a content encoding is specified, we set it as http parameter, so that + // the post body will be encoded in the specified content encoding + if(haveContentEncoding) { + post.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, contentEncoding); + } + + // If none of the arguments have a name specified, we + // just send all the values as the post body + if(getSendParameterValuesAsPostBody()) { + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + // TODO: needs a multiple file upload scenerio + if(!hasContentTypeHeader) { + HTTPFileArg file = files.length > 0? files[0] : null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + // TODO - is this the correct default? + post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + // Just append all the parameter values, and use that as the post body + StringBuilder postBody = new StringBuilder(); + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + // Note: if "Encoded?" is not selected, arg.getEncodedValue is equivalent to arg.getValue + if (haveContentEncoding) { + postBody.append(arg.getEncodedValue(contentEncoding)); + } else { + postBody.append(arg.getEncodedValue()); + } + } + // Let StringEntity perform the encoding + StringEntity requestEntity = new StringEntity(postBody.toString(), contentEncoding); + post.setEntity(requestEntity); + postedBody.append(postBody.toString()); + } else { + // It is a normal post request, with parameter names and values + + // Set the content type + if(!hasContentTypeHeader) { + post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + // Add the parameters + PropertyIterator args = getArguments().iterator(); + List nvps = new ArrayList (); + String urlContentEncoding = contentEncoding; + if(urlContentEncoding == null || urlContentEncoding.length() == 0) { + // Use the default encoding for urls + urlContentEncoding = EncoderCache.URL_ARGUMENT_ENCODING; + } + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + // The HTTPClient always urlencodes both name and value, + // so if the argument is already encoded, we have to decode + // it before adding it to the post request + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + String parameterValue = arg.getValue(); + if(!arg.isAlwaysEncoded()) { + // The value is already encoded by the user + // Must decode the value now, so that when the + // httpclient encodes it, we end up with the same value + // as the user had entered. + parameterName = URLDecoder.decode(parameterName, urlContentEncoding); + parameterValue = URLDecoder.decode(parameterValue, urlContentEncoding); + } + // Add the parameter, httpclient will urlencode it + nvps.add(new BasicNameValuePair(parameterName, parameterValue)); + } + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nvps, urlContentEncoding); + post.setEntity(entity); + if (entity.isRepeatable()){ + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + post.getEntity().writeTo(bos); + bos.flush(); + // We get the posted bytes using the encoding used to create it + if (contentEncoding != null) { + postedBody.append(new String(bos.toByteArray(), contentEncoding)); + } else { + postedBody.append(new String(bos.toByteArray(), SampleResult.DEFAULT_HTTP_ENCODING)); + } + bos.close(); + } else { + postedBody.append(""); + } + } + } + } + return postedBody.toString(); + } + + // TODO merge put and post methods as far as possible. + // e.g. post checks for multipart form/files, and if not, invokes sendData(HttpEntityEnclosingRequestBase) + + + /** + * Creates the entity data to be sent. + *

+ * If there is a file entry with a non-empty MIME type we use that to + * set the request Content-Type header, otherwise we default to whatever + * header is present from a Header Manager. + *

+ * If the content charset {@link #getContentEncoding()} is null or empty + * we use the HC4 default provided by {@link HTTP#DEF_CONTENT_CHARSET} which is + * ISO-8859-1. + * + * @param entity to be processed, e.g. PUT or PATCH + * @return the entity content, may be empty + * @throws UnsupportedEncodingException for invalid charset name + * @throws IOException cannot really occur for ByteArrayOutputStream methods + */ + protected String sendEntityData( HttpEntityEnclosingRequestBase entity) throws IOException { + // Buffer to hold the entity body + StringBuilder entityBody = new StringBuilder(1000); + boolean hasEntityBody = false; + + final HTTPFileArg files[] = getHTTPFiles(); + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + final HTTPFileArg file = files.length > 0? files[0] : null; + String contentTypeValue = null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + contentTypeValue = file.getMimeType(); + entity.setHeader(HEADER_CONTENT_TYPE, contentTypeValue); // we provide the MIME type here + } + + // Check for local contentEncoding (charset) override; fall back to default for content body + // we do this here rather so we can use the same charset to retrieve the data + final String charset = getContentEncoding(HTTP.DEF_CONTENT_CHARSET.name()); + + // Only create this if we are overriding whatever default there may be + // If there are no arguments, we can send a file as the body of the request + + if(!hasArguments() && getSendFileAsPostBody()) { + hasEntityBody = true; + + // If getSendFileAsPostBody returned true, it's sure that file is not null + FileEntity fileRequestEntity = new FileEntity(new File(files[0].getPath())); // no need for content-type here + entity.setEntity(fileRequestEntity); + } + // If none of the arguments have a name specified, we + // just send all the values as the entity body + else if(getSendParameterValuesAsPostBody()) { + hasEntityBody = true; + + // Just append all the parameter values, and use that as the entity body + StringBuilder entityBodyContent = new StringBuilder(); + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + // Note: if "Encoded?" is not selected, arg.getEncodedValue is equivalent to arg.getValue + if (charset!= null) { + entityBodyContent.append(arg.getEncodedValue(charset)); + } else { + entityBodyContent.append(arg.getEncodedValue()); + } + } + StringEntity requestEntity = new StringEntity(entityBodyContent.toString(), charset); + entity.setEntity(requestEntity); + } + // Check if we have any content to send for body + if(hasEntityBody) { + // If the request entity is repeatable, we can send it first to + // our own stream, so we can return it + final HttpEntity entityEntry = entity.getEntity(); + if(entityEntry.isRepeatable()) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + entityEntry.writeTo(bos); + bos.flush(); + // We get the posted bytes using the charset that was used to create them + entityBody.append(new String(bos.toByteArray(), charset)); + bos.close(); + } + else { // this probably cannot happen + entityBody.append(""); + } + } + return entityBody.toString(); // may be the empty string + } + + /** + * + * @return the value of {@link #getContentEncoding()}; forced to null if empty + */ + private String getContentEncodingOrNull() { + return getContentEncoding(null); + } + + /** + * @param dflt the default to be used + * @return the value of {@link #getContentEncoding()}; default if null or empty + */ + private String getContentEncoding(String dflt) { + String ce = getContentEncoding(); + if (isNullOrEmptyTrimmed(ce)) { + return dflt; + } else { + return ce; + } + } + + /** + * If contentEncoding is not set by user, then Platform encoding will be used to convert to String + * @param putParams {@link HttpParams} + * @return String charset + */ + protected String getCharsetWithDefault(HttpParams putParams) { + String charset =(String) putParams.getParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET); + if(StringUtils.isEmpty(charset)) { + charset = Charset.defaultCharset().name(); + } + return charset; + } + + private void saveConnectionCookies(HttpResponse method, URL u, CookieManager cookieManager) { + if (cookieManager != null) { + Header[] hdrs = method.getHeaders(HTTPConstants.HEADER_SET_COOKIE); + for (Header hdr : hdrs) { + cookieManager.addCookieFromHeader(hdr.getValue(),u); + } + } + } + + @Override + protected void notifyFirstSampleAfterLoopRestart() { + log.debug("notifyFirstSampleAfterLoopRestart"); + resetSSLContext = !USE_CACHED_SSL_CONTEXT; + } + + @Override + protected void threadFinished() { + log.debug("Thread Finished"); + closeThreadLocalConnections(); + } + + /** + * + */ + private void closeThreadLocalConnections() { + // Does not need to be synchronised, as all access is from same thread + Map mapHttpClientPerHttpClientKey = HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY.get(); + if ( mapHttpClientPerHttpClientKey != null ) { + for ( HttpClient cl : mapHttpClientPerHttpClientKey.values() ) { + ((AbstractHttpClient) cl).clearRequestInterceptors(); + ((AbstractHttpClient) cl).clearResponseInterceptors(); + cl.getConnectionManager().shutdown(); + } + mapHttpClientPerHttpClientKey.clear(); + } + } + + @Override + public boolean interrupt() { + HttpUriRequest request = currentRequest; + if (request != null) { + currentRequest = null; // don't try twice + try { + request.abort(); + } catch (UnsupportedOperationException e) { + log.warn("Could not abort pending request", e); + } + } + return request != null; + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java new file mode 100644 index 00000000000..ee3c3aad368 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java @@ -0,0 +1,168 @@ +/* + * 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.protocol.http.sampler; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.JMeter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Common parent class for HttpClient implementations. + * + * Includes system property settings that are handled internally by the Java HTTP implementation, + * but which need to be explicitly configured in HttpClient implementations. + */ +public abstract class HTTPHCAbstractImpl extends HTTPAbstractImpl { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected static final String PROXY_HOST = System.getProperty("http.proxyHost",""); + + protected static final String NONPROXY_HOSTS = System.getProperty("http.nonProxyHosts",""); + + protected static final int PROXY_PORT = Integer.parseInt(System.getProperty("http.proxyPort","0")); + + protected static final boolean PROXY_DEFINED = PROXY_HOST.length() > 0 && PROXY_PORT > 0; + + protected static final String PROXY_USER = JMeterUtils.getPropDefault(JMeter.HTTP_PROXY_USER,""); + + protected static final String PROXY_PASS = JMeterUtils.getPropDefault(JMeter.HTTP_PROXY_PASS,""); + + protected static final String PROXY_DOMAIN = JMeterUtils.getPropDefault("http.proxyDomain",""); + + protected static final InetAddress localAddress; + + protected static final String localHost; + + protected static final Set nonProxyHostFull = new HashSet(); + + protected static final List nonProxyHostSuffix = new ArrayList(); + + protected static final int nonProxyHostSuffixSize; + + protected static final int CPS_HTTP = JMeterUtils.getPropDefault("httpclient.socket.http.cps", 0); + + protected static final int CPS_HTTPS = JMeterUtils.getPropDefault("httpclient.socket.https.cps", 0); + + protected static final boolean USE_LOOPBACK = JMeterUtils.getPropDefault("httpclient.loopback", false); + + protected static final String HTTP_VERSION = JMeterUtils.getPropDefault("httpclient.version", "1.1"); + + // -1 means not defined + protected static final int SO_TIMEOUT = JMeterUtils.getPropDefault("httpclient.timeout", -1); + + // Control reuse of cached SSL Context in subsequent iterations + protected static final boolean USE_CACHED_SSL_CONTEXT = + JMeterUtils.getPropDefault("https.use.cached.ssl.context", true);//$NON-NLS-1$ + + static { + if(!StringUtils.isEmpty(JMeterUtils.getProperty("httpclient.timeout"))) { //$NON-NLS-1$ + log.warn("You're using property 'httpclient.timeout' that will soon be deprecated for HttpClient3.1, you should either set " + + "timeout in HTTP Request GUI, HTTP Request Defaults or set http.socket.timeout in httpclient.parameters"); + } + if (NONPROXY_HOSTS.length() > 0){ + StringTokenizer s = new StringTokenizer(NONPROXY_HOSTS,"|");// $NON-NLS-1$ + while (s.hasMoreTokens()){ + String t = s.nextToken(); + if (t.indexOf('*') ==0){// e.g. *.apache.org // $NON-NLS-1$ + nonProxyHostSuffix.add(t.substring(1)); + } else { + nonProxyHostFull.add(t);// e.g. www.apache.org + } + } + } + nonProxyHostSuffixSize=nonProxyHostSuffix.size(); + + InetAddress inet=null; + String localHostOrIP = + JMeterUtils.getPropDefault("httpclient.localaddress",""); // $NON-NLS-1$ + if (localHostOrIP.length() > 0){ + try { + inet = InetAddress.getByName(localHostOrIP); + log.info("Using localAddress "+inet.getHostAddress()); + } catch (UnknownHostException e) { + log.warn(e.getLocalizedMessage()); + } + } else { + // Get hostname + localHostOrIP = JMeterUtils.getLocalHostName(); + } + localAddress = inet; + localHost = localHostOrIP; + log.info("Local host = "+localHost); + + } + + protected HTTPHCAbstractImpl(HTTPSamplerBase testElement) { + super(testElement); + } + + protected static boolean isNonProxy(String host){ + return nonProxyHostFull.contains(host) || isPartialMatch(host); + } + + protected static boolean isPartialMatch(String host) { + for (int i=0;i 0); + } + + /** + * Is a static proxy defined? + * + * @param host to check against non-proxy hosts + * @return {@code true} iff a static proxy has been defined. + */ + protected static boolean isStaticProxy(String host){ + return PROXY_DEFINED && !isNonProxy(host); + } + + /** + * @param value String value to test + * @return true if value is null or empty trimmed + */ + protected static boolean isNullOrEmptyTrimmed(String value) { + return JOrphanUtils.isBlank(value); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java new file mode 100644 index 00000000000..7afbb0e187f --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java @@ -0,0 +1,656 @@ +/* + * 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.protocol.http.sampler; + +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.BindException; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +import org.apache.commons.io.input.CountingInputStream; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Authorization; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler which understands all the parts necessary to read statistics about + * HTTP requests, including cookies and authentication. + * + */ +public class HTTPJavaImpl extends HTTPAbstractImpl { + private static final boolean OBEY_CONTENT_LENGTH = + JMeterUtils.getPropDefault("httpsampler.obey_contentlength", false); // $NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int MAX_CONN_RETRIES = + JMeterUtils.getPropDefault("http.java.sampler.retries" // $NON-NLS-1$ + ,10); // Maximum connection retries + + static { + log.info("Maximum connection retries = "+MAX_CONN_RETRIES); // $NON-NLS-1$ + // Temporary copies, so can set the final ones + } + + private static final byte[] NULL_BA = new byte[0];// can share these + + /** Handles writing of a post or put request */ + private transient PostWriter postOrPutWriter; + + private volatile HttpURLConnection savedConn; + + protected HTTPJavaImpl(HTTPSamplerBase base) { + super(base); + } + + /** + * Set request headers in preparation to opening a connection. + * + * @param conn + * URLConnection to set headers on + * @exception IOException + * if an I/O exception occurs + */ + protected void setPostHeaders(URLConnection conn) throws IOException { + postOrPutWriter = new PostWriter(); + postOrPutWriter.setHeaders(conn, testElement); + } + + private void setPutHeaders(URLConnection conn) throws IOException { + postOrPutWriter = new PutWriter(); + postOrPutWriter.setHeaders(conn, testElement); + } + + /** + * Send POST data from Entry to the open connection. + * This also handles sending data for PUT requests + * + * @param connection + * URLConnection where POST data should be sent + * @return a String show what was posted. Will not contain actual file upload content + * @exception IOException + * if an I/O exception occurs + */ + protected String sendPostData(URLConnection connection) throws IOException { + return postOrPutWriter.sendPostData(connection, testElement); + } + + private String sendPutData(URLConnection connection) throws IOException { + return postOrPutWriter.sendPostData(connection, testElement); + } + + /** + * Returns an HttpURLConnection fully ready to attempt + * connection. This means it sets the request method (GET or POST), headers, + * cookies, and authorization for the URL request. + *

+ * The request infos are saved into the sample result if one is provided. + * + * @param u + * URL of the URL request + * @param method + * GET, POST etc + * @param res + * sample result to save request infos to + * @return HttpURLConnection ready for .connect + * @exception IOException + * if an I/O Exception occurs + */ + protected HttpURLConnection setupConnection(URL u, String method, HTTPSampleResult res) throws IOException { + SSLManager sslmgr = null; + if (HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(u.getProtocol())) { + try { + sslmgr=SSLManager.getInstance(); // N.B. this needs to be done before opening the connection + } catch (Exception e) { + log.warn("Problem creating the SSLManager: ", e); + } + } + + final HttpURLConnection conn; + final String proxyHost = getProxyHost(); + final int proxyPort = getProxyPortInt(); + if (proxyHost.length() > 0 && proxyPort > 0){ + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); + //TODO - how to define proxy authentication for a single connection? + // It's not clear if this is possible +// String user = getProxyUser(); +// if (user.length() > 0){ +// Authenticator auth = new ProxyAuthenticator(user, getProxyPass()); +// } + conn = (HttpURLConnection) u.openConnection(proxy); + } else { + conn = (HttpURLConnection) u.openConnection(); + } + + // Update follow redirects setting just for this connection + conn.setInstanceFollowRedirects(getAutoRedirects()); + + int cto = getConnectTimeout(); + if (cto > 0){ + conn.setConnectTimeout(cto); + } + + int rto = getResponseTimeout(); + if (rto > 0){ + conn.setReadTimeout(rto); + } + + if (HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(u.getProtocol())) { + try { + if (null != sslmgr){ + sslmgr.setContext(conn); // N.B. must be done after opening connection + } + } catch (Exception e) { + log.warn("Problem setting the SSLManager for the connection: ", e); + } + } + + // a well-bahaved browser is supposed to send 'Connection: close' + // with the last request to an HTTP server. Instead, most browsers + // leave it to the server to close the connection after their + // timeout period. Leave it to the JMeter user to decide. + if (getUseKeepAlive()) { + conn.setRequestProperty(HTTPConstants.HEADER_CONNECTION, HTTPConstants.KEEP_ALIVE); + } else { + conn.setRequestProperty(HTTPConstants.HEADER_CONNECTION, HTTPConstants.CONNECTION_CLOSE); + } + + conn.setRequestMethod(method); + setConnectionHeaders(conn, u, getHeaderManager(), getCacheManager()); + String cookies = setConnectionCookie(conn, u, getCookieManager()); + + setConnectionAuthorization(conn, u, getAuthManager()); + + if (method.equals(HTTPConstants.POST)) { + setPostHeaders(conn); + } else if (method.equals(HTTPConstants.PUT)) { + setPutHeaders(conn); + } + + if (res != null) { + res.setRequestHeaders(getConnectionHeaders(conn)); + res.setCookies(cookies); + } + + return conn; + } + + /** + * Reads the response from the URL connection. + * + * @param conn + * URL from which to read response + * @param res + * {@link SampleResult} to read response into + * @return response content + * @exception IOException + * if an I/O exception occurs + */ + protected byte[] readResponse(HttpURLConnection conn, SampleResult res) throws IOException { + BufferedInputStream in; + + final int contentLength = conn.getContentLength(); + if ((contentLength == 0) + && OBEY_CONTENT_LENGTH) { + log.info("Content-Length: 0, not reading http-body"); + res.setResponseHeaders(getResponseHeaders(conn)); + res.latencyEnd(); + return NULL_BA; + } + + // works OK even if ContentEncoding is null + boolean gzipped = HTTPConstants.ENCODING_GZIP.equals(conn.getContentEncoding()); + InputStream instream = null; + try { + instream = new CountingInputStream(conn.getInputStream()); + if (gzipped) { + in = new BufferedInputStream(new GZIPInputStream(instream)); + } else { + in = new BufferedInputStream(instream); + } + } catch (IOException e) { + if (! (e.getCause() instanceof FileNotFoundException)) + { + log.error("readResponse: "+e.toString()); + Throwable cause = e.getCause(); + if (cause != null){ + log.error("Cause: "+cause); + if(cause instanceof Error) { + throw (Error)cause; + } + } + } + // Normal InputStream is not available + InputStream errorStream = conn.getErrorStream(); + if (errorStream == null) { + log.info("Error Response Code: "+conn.getResponseCode()+", Server sent no Errorpage"); + res.setResponseHeaders(getResponseHeaders(conn)); + res.latencyEnd(); + return NULL_BA; + } + + log.info("Error Response Code: "+conn.getResponseCode()); + + if (gzipped) { + in = new BufferedInputStream(new GZIPInputStream(errorStream)); + } else { + in = new BufferedInputStream(errorStream); + } + } catch (Exception e) { + log.error("readResponse: "+e.toString()); + Throwable cause = e.getCause(); + if (cause != null){ + log.error("Cause: "+cause); + if(cause instanceof Error) { + throw (Error)cause; + } + } + in = new BufferedInputStream(conn.getErrorStream()); + } + // N.B. this closes 'in' + byte[] responseData = readResponse(res, in, contentLength); + if (instream != null) { + res.setBodySize(((CountingInputStream) instream).getCount()); + instream.close(); + } + return responseData; + } + + /** + * Gets the ResponseHeaders from the URLConnection + * + * @param conn + * connection from which the headers are read + * @return string containing the headers, one per line + */ + protected String getResponseHeaders(HttpURLConnection conn) { + StringBuilder headerBuf = new StringBuilder(); + headerBuf.append(conn.getHeaderField(0));// Leave header as is + // headerBuf.append(conn.getHeaderField(0).substring(0, 8)); + // headerBuf.append(" "); + // headerBuf.append(conn.getResponseCode()); + // headerBuf.append(" "); + // headerBuf.append(conn.getResponseMessage()); + headerBuf.append("\n"); //$NON-NLS-1$ + + String hfk; + for (int i = 1; (hfk=conn.getHeaderFieldKey(i)) != null; i++) { + headerBuf.append(hfk); + headerBuf.append(": "); // $NON-NLS-1$ + headerBuf.append(conn.getHeaderField(i)); + headerBuf.append("\n"); // $NON-NLS-1$ + } + return headerBuf.toString(); + } + + /** + * Extracts all the required cookies for that particular URL request and + * sets them in the HttpURLConnection passed in. + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @param u + * URL of the URL request + * @param cookieManager + * the CookieManager containing all the cookies + * for this UrlConfig + */ + private String setConnectionCookie(HttpURLConnection conn, URL u, CookieManager cookieManager) { + String cookieHeader = null; + if (cookieManager != null) { + cookieHeader = cookieManager.getCookieHeaderForURL(u); + if (cookieHeader != null) { + conn.setRequestProperty(HTTPConstants.HEADER_COOKIE, cookieHeader); + } + } + return cookieHeader; + } + + /** + * Extracts all the required headers for that particular URL request and + * sets them in the HttpURLConnection passed in + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @param u + * URL of the URL request + * @param headerManager + * the HeaderManager containing all the cookies + * for this UrlConfig + * @param cacheManager the CacheManager (may be null) + */ + private void setConnectionHeaders(HttpURLConnection conn, URL u, HeaderManager headerManager, CacheManager cacheManager) { + // Add all the headers from the HeaderManager + if (headerManager != null) { + CollectionProperty headers = headerManager.getHeaders(); + if (headers != null) { + PropertyIterator i = headers.iterator(); + while (i.hasNext()) { + Header header = (Header) i.next().getObjectValue(); + String n = header.getName(); + String v = header.getValue(); + conn.addRequestProperty(n, v); + } + } + } + if (cacheManager != null){ + cacheManager.setHeaders(conn, u); + } + } + + /** + * Get all the headers for the HttpURLConnection passed in + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @return the headers as a string + */ + private String getConnectionHeaders(HttpURLConnection conn) { + // Get all the request properties, which are the headers set on the connection + StringBuilder hdrs = new StringBuilder(100); + Map> requestHeaders = conn.getRequestProperties(); + for(Map.Entry> entry : requestHeaders.entrySet()) { + String headerKey=entry.getKey(); + // Exclude the COOKIE header, since cookie is reported separately in the sample + if(!HTTPConstants.HEADER_COOKIE.equalsIgnoreCase(headerKey)) { + // value is a List of Strings + for (String value : entry.getValue()){ + hdrs.append(headerKey); + hdrs.append(": "); // $NON-NLS-1$ + hdrs.append(value); + hdrs.append("\n"); // $NON-NLS-1$ + } + } + } + return hdrs.toString(); + } + + /** + * Extracts all the required authorization for that particular URL request + * and sets it in the HttpURLConnection passed in. + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @param u + * URL of the URL request + * @param authManager + * the AuthManager containing all the cookies for + * this UrlConfig + */ + private void setConnectionAuthorization(HttpURLConnection conn, URL u, AuthManager authManager) { + if (authManager != null) { + Authorization auth = authManager.getAuthForURL(u); + if (auth != null) { + conn.setRequestProperty(HTTPConstants.HEADER_AUTHORIZATION, auth.toBasicHeader()); + } + } + } + + /** + * Samples the URL passed in and stores the result in + * HTTPSampleResult, following redirects and downloading + * page resources as appropriate. + *

+ * When getting a redirect target, redirects are not followed and resources + * are not downloaded. The caller will take care of this. + * + * @param url + * URL to sample + * @param method + * HTTP method: GET, POST,... + * @param areFollowingRedirect + * whether we're getting a redirect target + * @param frameDepth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return results of the sampling + */ + @Override + protected HTTPSampleResult sample(URL url, String method, boolean areFollowingRedirect, int frameDepth) { + HttpURLConnection conn = null; + + String urlStr = url.toString(); + if (log.isDebugEnabled()) { + log.debug("Start : sample " + urlStr); + log.debug("method " + method+ " followingRedirect " + areFollowingRedirect + " depth " + frameDepth); + } + + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(isMonitor()); + + res.setSampleLabel(urlStr); + res.setURL(url); + res.setHTTPMethod(method); + + res.sampleStart(); // Count the retries as well in the time + + // Check cache for an entry with an Expires header in the future + final CacheManager cacheManager = getCacheManager(); + if (cacheManager != null && HTTPConstants.GET.equalsIgnoreCase(method)) { + if (cacheManager.inCache(url)) { + return updateSampleResultForResourceInCache(res); + } + } + + try { + // Sampling proper - establish the connection and read the response: + // Repeatedly try to connect: + int retry; + // Start with 0 so tries at least once, and retries at most MAX_CONN_RETRIES times + for (retry = 0; retry <= MAX_CONN_RETRIES; retry++) { + try { + conn = setupConnection(url, method, res); + // Attempt the connection: + savedConn = conn; + conn.connect(); + break; + } catch (BindException e) { + if (retry >= MAX_CONN_RETRIES) { + log.error("Can't connect after "+retry+" retries, "+e); + throw e; + } + log.debug("Bind exception, try again"); + if (conn!=null) { + savedConn = null; // we don't want interrupt to try disconnection again + conn.disconnect(); + } + setUseKeepAlive(false); + continue; // try again + } catch (IOException e) { + log.debug("Connection failed, giving up"); + throw e; + } + } + if (retry > MAX_CONN_RETRIES) { + // This should never happen, but... + throw new BindException(); + } + // Nice, we've got a connection. Finish sending the request: + if (method.equals(HTTPConstants.POST)) { + String postBody = sendPostData(conn); + res.setQueryString(postBody); + } + else if (method.equals(HTTPConstants.PUT)) { + String putBody = sendPutData(conn); + res.setQueryString(putBody); + } + // Request sent. Now get the response: + byte[] responseData = readResponse(conn, res); + + res.sampleEnd(); + // Done with the sampling proper. + + // Now collect the results into the HTTPSampleResult: + + res.setResponseData(responseData); + + @SuppressWarnings("null") // Cannot be null here + int errorLevel = conn.getResponseCode(); + String respMsg = conn.getResponseMessage(); + String hdr=conn.getHeaderField(0); + if (hdr == null) { + hdr="(null)"; // $NON-NLS-1$ + } + if (errorLevel == -1){// Bug 38902 - sometimes -1 seems to be returned unnecessarily + if (respMsg != null) {// Bug 41902 - NPE + try { + errorLevel = Integer.parseInt(respMsg.substring(0, 3)); + log.warn("ResponseCode==-1; parsed "+respMsg+ " as "+errorLevel); + } catch (NumberFormatException e) { + log.warn("ResponseCode==-1; could not parse "+respMsg+" hdr: "+hdr); + } + } else { + respMsg=hdr; // for result + log.warn("ResponseCode==-1 & null ResponseMessage. Header(0)= "+hdr); + } + } + if (errorLevel == -1) { + res.setResponseCode("(null)"); // $NON-NLS-1$ + } else { + res.setResponseCode(Integer.toString(errorLevel)); + } + res.setSuccessful(isSuccessCode(errorLevel)); + + if (respMsg == null) {// has been seen in a redirect + respMsg=hdr; // use header (if possible) if no message found + } + res.setResponseMessage(respMsg); + + String ct = conn.getContentType(); + if (ct != null){ + res.setContentType(ct);// e.g. text/html; charset=ISO-8859-1 + res.setEncodingAndType(ct); + } + + String responseHeaders = getResponseHeaders(conn); + res.setResponseHeaders(responseHeaders); + if (res.isRedirect()) { + res.setRedirectLocation(conn.getHeaderField(HTTPConstants.HEADER_LOCATION)); + } + + // record headers size to allow HTTPSampleResult.getBytes() with different options + res.setHeadersSize(responseHeaders.replaceAll("\n", "\r\n") // $NON-NLS-1$ $NON-NLS-2$ + .length() + 2); // add 2 for a '\r\n' at end of headers (before data) + if (log.isDebugEnabled()) { + log.debug("Response headersSize=" + res.getHeadersSize() + " bodySize=" + res.getBodySize() + + " Total=" + (res.getHeadersSize() + res.getBodySize())); + } + + // If we redirected automatically, the URL may have changed + if (getAutoRedirects()){ + res.setURL(conn.getURL()); + } + + // Store any cookies received in the cookie manager: + saveConnectionCookies(conn, url, getCookieManager()); + + // Save cache information + if (cacheManager != null){ + cacheManager.saveDetails(conn, res); + } + + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + log.debug("End : sample"); + return res; + } catch (IOException e) { + res.sampleEnd(); + savedConn = null; // we don't want interrupt to try disconnection again + // We don't want to continue using this connection, even if KeepAlive is set + if (conn != null) { // May not exist + conn.disconnect(); + } + conn=null; // Don't process again + return errorResult(e, res); + } finally { + // calling disconnect doesn't close the connection immediately, + // but indicates we're through with it. The JVM should close + // it when necessary. + savedConn = null; // we don't want interrupt to try disconnection again + disconnect(conn); // Disconnect unless using KeepAlive + } + } + + protected void disconnect(HttpURLConnection conn) { + if (conn != null) { + String connection = conn.getHeaderField(HTTPConstants.HEADER_CONNECTION); + String protocol = conn.getHeaderField(0); + if ((connection == null && (protocol == null || !protocol.startsWith(HTTPConstants.HTTP_1_1))) + || (connection != null && connection.equalsIgnoreCase(HTTPConstants.CONNECTION_CLOSE))) { + conn.disconnect(); + } // TODO ? perhaps note connection so it can be disconnected at end of test? + } + } + + /** + * From the HttpURLConnection, store all the "set-cookie" + * key-pair values in the cookieManager of the UrlConfig. + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @param u + * URL of the URL request + * @param cookieManager + * the CookieManager containing all the cookies + * for this UrlConfig + */ + private void saveConnectionCookies(HttpURLConnection conn, URL u, CookieManager cookieManager) { + if (cookieManager != null) { + for (int i = 1; conn.getHeaderFieldKey(i) != null; i++) { + if (conn.getHeaderFieldKey(i).equalsIgnoreCase(HTTPConstants.HEADER_SET_COOKIE)) { + cookieManager.addCookieFromHeader(conn.getHeaderField(i), u); + } + } + } + } + + /** {@inheritDoc} */ + @Override + public boolean interrupt() { + HttpURLConnection conn = savedConn; + if (conn != null) { + savedConn = null; + conn.disconnect(); + } + return conn != null; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSampleResult.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSampleResult.java new file mode 100644 index 00000000000..f21a6b06e00 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSampleResult.java @@ -0,0 +1,257 @@ +/* + * 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.protocol.http.sampler; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; + +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.SampleResult; + +/** + * This is a specialisation of the SampleResult class for the HTTP protocol. + * + */ +public class HTTPSampleResult extends SampleResult { + + private static final long serialVersionUID = 240L; + + private String cookies = ""; // never null + + private String method; + + /** + * The raw value of the Location: header; may be null. + * This is supposed to be an absolute URL: + * RFC2616 sec14.30 + * but is often relative. + */ + private String redirectLocation; + + private String queryString = ""; // never null + + private static final String HTTP_NO_CONTENT_CODE = Integer.toString(HttpURLConnection.HTTP_NO_CONTENT); + private static final String HTTP_NO_CONTENT_MSG = "No Content"; // $NON-NLS-1$ + + public HTTPSampleResult() { + super(); + } + + public HTTPSampleResult(long elapsed) { + super(elapsed, true); + } + + /** + * Construct a 'parent' result for an already-existing result, essentially + * cloning it + * + * @param res + * existing sample result + */ + public HTTPSampleResult(HTTPSampleResult res) { + super(res); + method=res.method; + cookies=res.cookies; + queryString=res.queryString; + redirectLocation=res.redirectLocation; + } + + public void setHTTPMethod(String method) { + this.method = method; + } + + public String getHTTPMethod() { + return method; + } + + public void setRedirectLocation(String redirectLocation) { + this.redirectLocation = redirectLocation; + } + + public String getRedirectLocation() { + return redirectLocation; + } + + /** + * Determine whether this result is a redirect. + * Returns true for: 301,302,303 and 307(GET or HEAD) + * @return true iff res is an HTTP redirect response + */ + public boolean isRedirect() { + /* + * Don't redirect the following: + * 300 = Multiple choice + * 304 = Not Modified + * 305 = Use Proxy + * 306 = (Unused) + */ + final String[] REDIRECT_CODES = { "301", "302", "303" }; + String code = getResponseCode(); + for (int i = 0; i < REDIRECT_CODES.length; i++) { + if (REDIRECT_CODES[i].equals(code)) { + return true; + } + } + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + // If the 307 status code is received in response to a request other than GET or HEAD, + // the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, + // since this might change the conditions under which the request was issued. + // See Bug 54119 + if ("307".equals(code) && + (HTTPConstants.GET.equals(getHTTPMethod()) || HTTPConstants.HEAD.equals(getHTTPMethod()))) { + return true; + } + return false; + } + + /** + * Overrides version in Sampler data to provide more details + *

+ * {@inheritDoc} + */ + @Override + public String getSamplerData() { + StringBuilder sb = new StringBuilder(); + sb.append(method); + URL u = super.getURL(); + if (u != null) { + sb.append(' '); + sb.append(u.toString()); + sb.append("\n"); + // Include request body if it is a post or put or patch + if (HTTPConstants.POST.equals(method) || HTTPConstants.PUT.equals(method) + || HTTPConstants.PATCH.equals(method) + || HttpWebdav.isWebdavMethod(method) + || HTTPConstants.DELETE.equals(method)) { + sb.append("\n"+method+" data:\n"); + sb.append(queryString); + sb.append("\n"); + } + if (cookies.length()>0){ + sb.append("\nCookie Data:\n"); + sb.append(cookies); + } else { + sb.append("\n[no cookies]"); + } + sb.append("\n"); + } + final String sampData = super.getSamplerData(); + if (sampData != null){ + sb.append(sampData); + } + return sb.toString(); + } + + /** + * @return cookies as a string + */ + public String getCookies() { + return cookies; + } + + /** + * @param string + * representing the cookies + */ + public void setCookies(String string) { + if (string == null) { + cookies="";// $NON-NLS-1$ + } else { + cookies = string; + } + } + + /** + * Fetch the query string + * + * @return the query string + */ + public String getQueryString() { + return queryString; + } + + /** + * Save the query string + * + * @param string + * the query string + */ + public void setQueryString(String string) { + if (string == null ) { + queryString="";// $NON-NLS-1$ + } else { + queryString = string; + } + } + + /** + * Overrides the method from SampleResult - so the encoding can be extracted from + * the Meta content-type if necessary. + * + * Updates the dataEncoding field if the content-type is found. + * @param defaultEncoding Default encoding used if there is no data encoding + * @return the dataEncoding value as a String + */ + @Override + public String getDataEncodingWithDefault(String defaultEncoding) { + String dataEncodingNoDefault = getDataEncodingNoDefault(); + if(dataEncodingNoDefault != null && dataEncodingNoDefault.length()> 0) { + return dataEncodingNoDefault; + } + return defaultEncoding; + } + + /** + * Overrides the method from SampleResult - so the encoding can be extracted from + * the Meta content-type if necessary. + * + * Updates the dataEncoding field if the content-type is found. + * + * @return the dataEncoding value as a String + */ + @Override + public String getDataEncodingNoDefault() { + if (super.getDataEncodingNoDefault() == null && getContentType().startsWith("text/html")){ // $NON-NLS-1$ + byte[] bytes=getResponseData(); + // get the start of the file + String prefix = new String(bytes, 0, Math.min(bytes.length, 2000), Charset.forName(DEFAULT_HTTP_ENCODING)); + // Preserve original case + String matchAgainst = prefix.toLowerCase(java.util.Locale.ENGLISH); + // Extract the content-type if present + final String METATAG = " APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.LoginConfigGui", + "org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui", + "org.apache.jmeter.config.gui.SimpleConfigGui", + "org.apache.jmeter.protocol.http.gui.HeaderPanel", + "org.apache.jmeter.protocol.http.control.DNSCacheManager", + "org.apache.jmeter.protocol.http.gui.DNSCachePanel", + "org.apache.jmeter.protocol.http.gui.AuthPanel", + "org.apache.jmeter.protocol.http.gui.CacheManagerGui", + "org.apache.jmeter.protocol.http.gui.CookiePanel"})); + + //+ JMX names - do not change + public static final String ARGUMENTS = "HTTPsampler.Arguments"; // $NON-NLS-1$ + + public static final String AUTH_MANAGER = "HTTPSampler.auth_manager"; // $NON-NLS-1$ + + public static final String COOKIE_MANAGER = "HTTPSampler.cookie_manager"; // $NON-NLS-1$ + + public static final String CACHE_MANAGER = "HTTPSampler.cache_manager"; // $NON-NLS-1$ + + public static final String HEADER_MANAGER = "HTTPSampler.header_manager"; // $NON-NLS-1$ + + public static final String DNS_CACHE_MANAGER = "HTTPSampler.dns_cache_manager"; // $NON-NLS-1$ + + public static final String DOMAIN = "HTTPSampler.domain"; // $NON-NLS-1$ + + public static final String PORT = "HTTPSampler.port"; // $NON-NLS-1$ + + public static final String PROXYHOST = "HTTPSampler.proxyHost"; // $NON-NLS-1$ + + public static final String PROXYPORT = "HTTPSampler.proxyPort"; // $NON-NLS-1$ + + public static final String PROXYUSER = "HTTPSampler.proxyUser"; // $NON-NLS-1$ + + public static final String PROXYPASS = "HTTPSampler.proxyPass"; // $NON-NLS-1$ + + public static final String CONNECT_TIMEOUT = "HTTPSampler.connect_timeout"; // $NON-NLS-1$ + + public static final String RESPONSE_TIMEOUT = "HTTPSampler.response_timeout"; // $NON-NLS-1$ + + public static final String METHOD = "HTTPSampler.method"; // $NON-NLS-1$ + + /** This is the encoding used for the content, i.e. the charset name, not the header "Content-Encoding" */ + public static final String CONTENT_ENCODING = "HTTPSampler.contentEncoding"; // $NON-NLS-1$ + + public static final String IMPLEMENTATION = "HTTPSampler.implementation"; // $NON-NLS-1$ + + public static final String PATH = "HTTPSampler.path"; // $NON-NLS-1$ + + public static final String FOLLOW_REDIRECTS = "HTTPSampler.follow_redirects"; // $NON-NLS-1$ + + public static final String AUTO_REDIRECTS = "HTTPSampler.auto_redirects"; // $NON-NLS-1$ + + public static final String PROTOCOL = "HTTPSampler.protocol"; // $NON-NLS-1$ + + static final String PROTOCOL_FILE = "file"; // $NON-NLS-1$ + + private static final String DEFAULT_PROTOCOL = HTTPConstants.PROTOCOL_HTTP; + + public static final String URL = "HTTPSampler.URL"; // $NON-NLS-1$ + + /** + * IP source to use - does not apply to Java HTTP implementation currently + */ + public static final String IP_SOURCE = "HTTPSampler.ipSource"; // $NON-NLS-1$ + + public static final String IP_SOURCE_TYPE = "HTTPSampler.ipSourceType"; // $NON-NLS-1$ + + public static final String USE_KEEPALIVE = "HTTPSampler.use_keepalive"; // $NON-NLS-1$ + + public static final String DO_MULTIPART_POST = "HTTPSampler.DO_MULTIPART_POST"; // $NON-NLS-1$ + + public static final String BROWSER_COMPATIBLE_MULTIPART = "HTTPSampler.BROWSER_COMPATIBLE_MULTIPART"; // $NON-NLS-1$ + + public static final String CONCURRENT_DWN = "HTTPSampler.concurrentDwn"; // $NON-NLS-1$ + + public static final String CONCURRENT_POOL = "HTTPSampler.concurrentPool"; // $NON-NLS-1$ + + private static final String CONCURRENT_POOL_DEFAULT = "4"; // default for concurrent pool (do not change) + + private static final String USER_AGENT = "User-Agent"; // $NON-NLS-1$ + + //- JMX names + + public static final boolean BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT = false; // The default setting to be used (i.e. historic) + + private static final long KEEPALIVETIME = 0; // for Thread Pool for resources but no need to use a special value? + + private static final long AWAIT_TERMINATION_TIMEOUT = + JMeterUtils.getPropDefault("httpsampler.await_termination_timeout", 60); // $NON-NLS-1$ // default value: 60 secs + + private static final boolean IGNORE_FAILED_EMBEDDED_RESOURCES = + JMeterUtils.getPropDefault("httpsampler.ignore_failed_embedded_resources", false); // $NON-NLS-1$ // default value: false + + public static final int CONCURRENT_POOL_SIZE = 4; // Default concurrent pool size for download embedded resources + + public static enum SourceType { + HOSTNAME("web_testing_source_ip_hostname"), //$NON-NLS-1$ + DEVICE("web_testing_source_ip_device"), //$NON-NLS-1$ + DEVICE_IPV4("web_testing_source_ip_device_ipv4"), //$NON-NLS-1$ + DEVICE_IPV6("web_testing_source_ip_device_ipv6"); //$NON-NLS-1$ + + public final String propertyName; + SourceType(String propertyName) { + this.propertyName = propertyName; + } + } + + private static final int SOURCE_TYPE_DEFAULT = HTTPSamplerBase.SourceType.HOSTNAME.ordinal(); + + // Use for ComboBox Source Address Type. Preserve order (specially with localization) + public static final String[] getSourceTypeList() { + final SourceType[] types = SourceType.values(); + final String[] displayStrings = new String[types.length]; + for(int i = 0; i < types.length; i++) { + displayStrings[i] = JMeterUtils.getResString(types[i].propertyName); + } + return displayStrings; + } + + public static final String DEFAULT_METHOD = HTTPConstants.GET; // $NON-NLS-1$ + // Supported methods: + private static final String [] METHODS = { + DEFAULT_METHOD, // i.e. GET + HTTPConstants.POST, + HTTPConstants.HEAD, + HTTPConstants.PUT, + HTTPConstants.OPTIONS, + HTTPConstants.TRACE, + HTTPConstants.DELETE, + HTTPConstants.PATCH, + HTTPConstants.PROPFIND, + HTTPConstants.PROPPATCH, + HTTPConstants.MKCOL, + HTTPConstants.COPY, + HTTPConstants.MOVE, + HTTPConstants.LOCK, + HTTPConstants.UNLOCK, + HTTPConstants.REPORT, + HTTPConstants.MKCALENDAR + }; + + private static final List METHODLIST = Collections.unmodifiableList(Arrays.asList(METHODS)); + + // @see mergeFileProperties + // Must be private, as the file list needs special handling + private static final String FILE_ARGS = "HTTPsampler.Files"; // $NON-NLS-1$ + // MIMETYPE is kept for backward compatibility with old test plans + private static final String MIMETYPE = "HTTPSampler.mimetype"; // $NON-NLS-1$ + // FILE_NAME is kept for backward compatibility with old test plans + private static final String FILE_NAME = "HTTPSampler.FILE_NAME"; // $NON-NLS-1$ + /* Shown as Parameter Name on the GUI */ + // FILE_FIELD is kept for backward compatibility with old test plans + private static final String FILE_FIELD = "HTTPSampler.FILE_FIELD"; // $NON-NLS-1$ + + public static final String CONTENT_TYPE = "HTTPSampler.CONTENT_TYPE"; // $NON-NLS-1$ + + // IMAGE_PARSER now really means EMBEDDED_PARSER + public static final String IMAGE_PARSER = "HTTPSampler.image_parser"; // $NON-NLS-1$ + + // Embedded URLs must match this RE (if provided) + public static final String EMBEDDED_URL_RE = "HTTPSampler.embedded_url_re"; // $NON-NLS-1$ + + public static final String MONITOR = "HTTPSampler.monitor"; // $NON-NLS-1$ + + // Store MD5 hash instead of storing response + private static final String MD5 = "HTTPSampler.md5"; // $NON-NLS-1$ + + /** A number to indicate that the port has not been set. */ + public static final int UNSPECIFIED_PORT = 0; + public static final String UNSPECIFIED_PORT_AS_STRING = "0"; // $NON-NLS-1$ + // TODO - change to use URL version? Will this affect test plans? + + /** If the port is not present in a URL, getPort() returns -1 */ + public static final int URL_UNSPECIFIED_PORT = -1; + public static final String URL_UNSPECIFIED_PORT_AS_STRING = "-1"; // $NON-NLS-1$ + + protected static final String NON_HTTP_RESPONSE_CODE = "Non HTTP response code"; + + protected static final String NON_HTTP_RESPONSE_MESSAGE = "Non HTTP response message"; + + public static final String POST_BODY_RAW = "HTTPSampler.postBodyRaw"; // TODO - belongs elsewhere + + public static final boolean POST_BODY_RAW_DEFAULT = false; + + private static final String ARG_VAL_SEP = "="; // $NON-NLS-1$ + + private static final String QRY_SEP = "&"; // $NON-NLS-1$ + + private static final String QRY_PFX = "?"; // $NON-NLS-1$ + + protected static final int MAX_REDIRECTS = JMeterUtils.getPropDefault("httpsampler.max_redirects", 5); // $NON-NLS-1$ + + protected static final int MAX_FRAME_DEPTH = JMeterUtils.getPropDefault("httpsampler.max_frame_depth", 5); // $NON-NLS-1$ + + + // Derive the mapping of content types to parsers + private static final Map parsersForType = new HashMap(); + // Not synch, but it is not modified after creation + + private static final String RESPONSE_PARSERS= // list of parsers + JMeterUtils.getProperty("HTTPResponse.parsers");//$NON-NLS-1$ + + static{ + String []parsers = JOrphanUtils.split(RESPONSE_PARSERS, " " , true);// returns empty array for null + for (int i=0;i 0) + && (files[0].getParamName().length() == 0); + } + + /** + * Determine if none of the parameters have a name, and if that + * is the case, it means that the parameter values should be sent + * as the entity body + * + * @return true if none of the parameters have a name specified + */ + public boolean getSendParameterValuesAsPostBody() { + if(getPostBodyRaw()) { + return true; + } else { + boolean noArgumentsHasName = true; + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + if(arg.getName() != null && arg.getName().length() > 0) { + noArgumentsHasName = false; + break; + } + } + return noArgumentsHasName; + } + } + + /** + * Determine if we should use multipart/form-data or + * application/x-www-form-urlencoded for the post + * + * @return true if multipart/form-data should be used and method is POST + */ + public boolean getUseMultipartForPost(){ + // We use multipart if we have been told so, or files are present + // and the files should not be send as the post body + HTTPFileArg[] files = getHTTPFiles(); + if(HTTPConstants.POST.equals(getMethod()) && (getDoMultipartPost() || (files.length > 0 && !getSendFileAsPostBody()))) { + return true; + } + return false; + } + + public void setProtocol(String value) { + setProperty(PROTOCOL, value.toLowerCase(java.util.Locale.ENGLISH)); + } + + /** + * Gets the protocol, with default. + * + * @return the protocol + */ + public String getProtocol() { + String protocol = getPropertyAsString(PROTOCOL); + if (protocol == null || protocol.length() == 0 ) { + return DEFAULT_PROTOCOL; + } + return protocol; + } + + /** + * Sets the Path attribute of the UrlConfig object Also calls parseArguments + * to extract and store any query arguments + * + * @param path + * The new Path value + */ + public void setPath(String path) { + // We know that URL arguments should always be encoded in UTF-8 according to spec + setPath(path, EncoderCache.URL_ARGUMENT_ENCODING); + } + + /** + * Sets the PATH property; if the request is a GET or DELETE (and the path + * does not start with http[s]://) it also calls {@link #parseArguments(String, String)} + * to extract and store any query arguments. + * + * @param path + * The new Path value + * @param contentEncoding + * The encoding used for the querystring parameter values + */ + public void setPath(String path, String contentEncoding) { + boolean fullUrl = path.startsWith(HTTP_PREFIX) || path.startsWith(HTTPS_PREFIX); + if (!fullUrl && (HTTPConstants.GET.equals(getMethod()) || HTTPConstants.DELETE.equals(getMethod()))) { + int index = path.indexOf(QRY_PFX); + if (index > -1) { + setProperty(PATH, path.substring(0, index)); + // Parse the arguments in querystring, assuming specified encoding for values + parseArguments(path.substring(index + 1), contentEncoding); + } else { + setProperty(PATH, path); + } + } else { + setProperty(PATH, path); + } + } + + public String getPath() { + String p = getPropertyAsString(PATH); + return encodeSpaces(p); + } + + public void setFollowRedirects(boolean value) { + setProperty(new BooleanProperty(FOLLOW_REDIRECTS, value)); + } + + public boolean getFollowRedirects() { + return getPropertyAsBoolean(FOLLOW_REDIRECTS); + } + + public void setAutoRedirects(boolean value) { + setProperty(new BooleanProperty(AUTO_REDIRECTS, value)); + } + + public boolean getAutoRedirects() { + return getPropertyAsBoolean(AUTO_REDIRECTS); + } + + public void setMethod(String value) { + setProperty(METHOD, value); + } + + public String getMethod() { + return getPropertyAsString(METHOD); + } + + /** + * Sets the value of the encoding to be used for the content. + * + * @param charsetName the name of the encoding to be used + */ + public void setContentEncoding(String charsetName) { + setProperty(CONTENT_ENCODING, charsetName); + } + + /** + * + * @return the encoding of the content, i.e. its charset name + */ + public String getContentEncoding() { + return getPropertyAsString(CONTENT_ENCODING); + } + + public void setUseKeepAlive(boolean value) { + setProperty(new BooleanProperty(USE_KEEPALIVE, value)); + } + + public boolean getUseKeepAlive() { + return getPropertyAsBoolean(USE_KEEPALIVE); + } + + public void setDoMultipartPost(boolean value) { + setProperty(new BooleanProperty(DO_MULTIPART_POST, value)); + } + + public boolean getDoMultipartPost() { + return getPropertyAsBoolean(DO_MULTIPART_POST, false); + } + + public void setDoBrowserCompatibleMultipart(boolean value) { + setProperty(BROWSER_COMPATIBLE_MULTIPART, value, BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + } + + public boolean getDoBrowserCompatibleMultipart() { + return getPropertyAsBoolean(BROWSER_COMPATIBLE_MULTIPART, BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + } + + public void setMonitor(String value) { + this.setProperty(MONITOR, value); + } + + public void setMonitor(boolean truth) { + this.setProperty(MONITOR, truth); + } + + public String getMonitor() { + return this.getPropertyAsString(MONITOR); + } + + public boolean isMonitor() { + return this.getPropertyAsBoolean(MONITOR); + } + + public void setImplementation(String value) { + this.setProperty(IMPLEMENTATION, value); + } + + public String getImplementation() { + return this.getPropertyAsString(IMPLEMENTATION); + } + + public boolean useMD5() { + return this.getPropertyAsBoolean(MD5, false); + } + + public void setMD5(boolean truth) { + this.setProperty(MD5, truth, false); + } + + /** + * Add an argument which has already been encoded + * + * @param name name of the argument + * @param value value of the argument + */ + public void addEncodedArgument(String name, String value) { + this.addEncodedArgument(name, value, ARG_VAL_SEP); + } + + /** + * Creates an HTTPArgument and adds it to the current set {@link #getArguments()} of arguments. + * + * @param name - the parameter name + * @param value - the parameter value + * @param metaData - normally just '=' + * @param contentEncoding - the encoding, may be null + */ + public void addEncodedArgument(String name, String value, String metaData, String contentEncoding) { + if (log.isDebugEnabled()){ + log.debug("adding argument: name: " + name + " value: " + value + " metaData: " + metaData + " contentEncoding: " + contentEncoding); + } + + HTTPArgument arg = null; + final boolean nonEmptyEncoding = !StringUtils.isEmpty(contentEncoding); + if(nonEmptyEncoding) { + arg = new HTTPArgument(name, value, metaData, true, contentEncoding); + } + else { + arg = new HTTPArgument(name, value, metaData, true); + } + + // Check if there are any difference between name and value and their encoded name and value + String valueEncoded = null; + if(nonEmptyEncoding) { + try { + valueEncoded = arg.getEncodedValue(contentEncoding); + } + catch (UnsupportedEncodingException e) { + log.warn("Unable to get encoded value using encoding " + contentEncoding); + valueEncoded = arg.getEncodedValue(); + } + } + else { + valueEncoded = arg.getEncodedValue(); + } + // If there is no difference, we mark it as not needing encoding + if (arg.getName().equals(arg.getEncodedName()) && arg.getValue().equals(valueEncoded)) { + arg.setAlwaysEncoded(false); + } + this.getArguments().addArgument(arg); + } + + public void addEncodedArgument(String name, String value, String metaData) { + this.addEncodedArgument(name, value, metaData, null); + } + + public void addNonEncodedArgument(String name, String value, String metadata) { + HTTPArgument arg = new HTTPArgument(name, value, metadata, false); + arg.setAlwaysEncoded(false); + this.getArguments().addArgument(arg); + } + + public void addArgument(String name, String value) { + this.getArguments().addArgument(new HTTPArgument(name, value)); + } + + public void addArgument(String name, String value, String metadata) { + this.getArguments().addArgument(new HTTPArgument(name, value, metadata)); + } + + public boolean hasArguments() { + return getArguments().getArgumentCount() > 0; + } + + @Override + public void addTestElement(TestElement el) { + if (el instanceof CookieManager) { + setCookieManager((CookieManager) el); + } else if (el instanceof CacheManager) { + setCacheManager((CacheManager) el); + } else if (el instanceof HeaderManager) { + setHeaderManager((HeaderManager) el); + } else if (el instanceof AuthManager) { + setAuthManager((AuthManager) el); + } else if (el instanceof DNSCacheManager) { + setDNSResolver((DNSCacheManager) el); + } else { + super.addTestElement(el); + } + } + + /** + * {@inheritDoc} + *

+ * Clears the Header Manager property so subsequent loops don't keep merging more elements + */ + @Override + public void clearTestElementChildren(){ + removeProperty(HEADER_MANAGER); + } + + public void setPort(int value) { + setProperty(new IntegerProperty(PORT, value)); + } + + /** + * Get the port number for a URL, applying defaults if necessary. + * (Called by CookieManager.) + * @param protocol from {@link URL#getProtocol()} + * @param port number from {@link URL#getPort()} + * @return the default port for the protocol + */ + public static int getDefaultPort(String protocol,int port){ + if (port==URL_UNSPECIFIED_PORT){ + return + protocol.equalsIgnoreCase(HTTPConstants.PROTOCOL_HTTP) ? HTTPConstants.DEFAULT_HTTP_PORT : + protocol.equalsIgnoreCase(HTTPConstants.PROTOCOL_HTTPS) ? HTTPConstants.DEFAULT_HTTPS_PORT : + port; + } + return port; + } + + /** + * Get the port number from the port string, allowing for trailing blanks. + * + * @return port number or UNSPECIFIED_PORT (== 0) + */ + public int getPortIfSpecified() { + String port_s = getPropertyAsString(PORT, UNSPECIFIED_PORT_AS_STRING); + try { + return Integer.parseInt(port_s.trim()); + } catch (NumberFormatException e) { + return UNSPECIFIED_PORT; + } + } + + /** + * Tell whether the default port for the specified protocol is used + * + * @return true if the default port number for the protocol is used, false otherwise + */ + public boolean isProtocolDefaultPort() { + final int port = getPortIfSpecified(); + final String protocol = getProtocol(); + if (port == UNSPECIFIED_PORT || + (HTTPConstants.PROTOCOL_HTTP.equalsIgnoreCase(protocol) && port == HTTPConstants.DEFAULT_HTTP_PORT) || + (HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(protocol) && port == HTTPConstants.DEFAULT_HTTPS_PORT)) { + return true; + } + return false; + } + + /** + * Get the port; apply the default for the protocol if necessary. + * + * @return the port number, with default applied if required. + */ + public int getPort() { + final int port = getPortIfSpecified(); + if (port == UNSPECIFIED_PORT) { + String prot = getProtocol(); + if (HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(prot)) { + return HTTPConstants.DEFAULT_HTTPS_PORT; + } + if (!HTTPConstants.PROTOCOL_HTTP.equalsIgnoreCase(prot)) { + log.warn("Unexpected protocol: "+prot); + // TODO - should this return something else? + } + return HTTPConstants.DEFAULT_HTTP_PORT; + } + return port; + } + + public void setDomain(String value) { + setProperty(DOMAIN, value); + } + + public String getDomain() { + return getPropertyAsString(DOMAIN); + } + + public void setConnectTimeout(String value) { + setProperty(CONNECT_TIMEOUT, value, ""); + } + + public int getConnectTimeout() { + return getPropertyAsInt(CONNECT_TIMEOUT, 0); + } + + public void setResponseTimeout(String value) { + setProperty(RESPONSE_TIMEOUT, value, ""); + } + + public int getResponseTimeout() { + return getPropertyAsInt(RESPONSE_TIMEOUT, 0); + } + + public String getProxyHost() { + return getPropertyAsString(PROXYHOST); + } + + public int getProxyPortInt() { + return getPropertyAsInt(PROXYPORT, 0); + } + + public String getProxyUser() { + return getPropertyAsString(PROXYUSER); + } + + public String getProxyPass() { + return getPropertyAsString(PROXYPASS); + } + + public void setArguments(Arguments value) { + setProperty(new TestElementProperty(ARGUMENTS, value)); + } + + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /** + * @param value Boolean that indicates body will be sent as is + */ + public void setPostBodyRaw(boolean value) { + setProperty(POST_BODY_RAW, value, POST_BODY_RAW_DEFAULT); + } + + /** + * @return boolean that indicates body will be sent as is + */ + public boolean getPostBodyRaw() { + return getPropertyAsBoolean(POST_BODY_RAW, POST_BODY_RAW_DEFAULT); + } + + public void setAuthManager(AuthManager value) { + AuthManager mgr = getAuthManager(); + if (mgr != null) { + log.warn("Existing AuthManager " + mgr.getName() + " superseded by " + value.getName()); + } + setProperty(new TestElementProperty(AUTH_MANAGER, value)); + } + + public AuthManager getAuthManager() { + return (AuthManager) getProperty(AUTH_MANAGER).getObjectValue(); + } + + public void setHeaderManager(HeaderManager value) { + HeaderManager mgr = getHeaderManager(); + if (mgr != null) { + value = mgr.merge(value, true); + if (log.isDebugEnabled()) { + log.debug("Existing HeaderManager '" + mgr.getName() + "' merged with '" + value.getName() + "'"); + for (int i=0; i < value.getHeaders().size(); i++) { + log.debug(" " + value.getHeader(i).getName() + "=" + value.getHeader(i).getValue()); + } + } + } + setProperty(new TestElementProperty(HEADER_MANAGER, value)); + } + + public HeaderManager getHeaderManager() { + return (HeaderManager) getProperty(HEADER_MANAGER).getObjectValue(); + } + + // private method to allow AsyncSample to reset the value without performing checks + private void setCookieManagerProperty(CookieManager value) { + setProperty(new TestElementProperty(COOKIE_MANAGER, value)); + } + + public void setCookieManager(CookieManager value) { + CookieManager mgr = getCookieManager(); + if (mgr != null) { + log.warn("Existing CookieManager " + mgr.getName() + " superseded by " + value.getName()); + } + setCookieManagerProperty(value); + } + + public CookieManager getCookieManager() { + return (CookieManager) getProperty(COOKIE_MANAGER).getObjectValue(); + } + + // private method to allow AsyncSample to reset the value without performing checks + private void setCacheManagerProperty(CacheManager value) { + setProperty(new TestElementProperty(CACHE_MANAGER, value)); + } + + public void setCacheManager(CacheManager value) { + CacheManager mgr = getCacheManager(); + if (mgr != null) { + log.warn("Existing CacheManager " + mgr.getName() + " superseded by " + value.getName()); + } + setCacheManagerProperty(value); + } + + public CacheManager getCacheManager() { + return (CacheManager) getProperty(CACHE_MANAGER).getObjectValue(); + } + + public DNSCacheManager getDNSResolver() { + return (DNSCacheManager) getProperty(DNS_CACHE_MANAGER).getObjectValue(); + } + + public void setDNSResolver(DNSCacheManager cacheManager) { + DNSCacheManager mgr = getDNSResolver(); + if (mgr != null) { + log.warn("Existing DNSCacheManager " + mgr.getName() + " superseded by " + cacheManager.getName()); + } + setProperty(new TestElementProperty(DNS_CACHE_MANAGER, cacheManager)); + } + + public boolean isImageParser() { + return getPropertyAsBoolean(IMAGE_PARSER, false); + } + + public void setImageParser(boolean parseImages) { + setProperty(IMAGE_PARSER, parseImages, false); + } + + /** + * Get the regular expression URLs must match. + * + * @return regular expression (or empty) string + */ + public String getEmbeddedUrlRE() { + return getPropertyAsString(EMBEDDED_URL_RE,""); + } + + public void setEmbeddedUrlRE(String regex) { + setProperty(new StringProperty(EMBEDDED_URL_RE, regex)); + } + + /** + * Populates the provided HTTPSampleResult with details from the Exception. + * Does not create a new instance, so should not be used directly to add a subsample. + * + * @param e + * Exception representing the error. + * @param res + * SampleResult to be modified + * @return the modified sampling result containing details of the Exception. + */ + protected HTTPSampleResult errorResult(Throwable e, HTTPSampleResult res) { + res.setSampleLabel(res.getSampleLabel()); + res.setDataType(SampleResult.TEXT); + ByteArrayOutputStream text = new ByteArrayOutputStream(200); + e.printStackTrace(new PrintStream(text)); + res.setResponseData(text.toByteArray()); + res.setResponseCode(NON_HTTP_RESPONSE_CODE+": "+e.getClass().getName()); + res.setResponseMessage(NON_HTTP_RESPONSE_MESSAGE+": "+e.getMessage()); + res.setSuccessful(false); + res.setMonitor(this.isMonitor()); + return res; + } + + private static final String HTTP_PREFIX = HTTPConstants.PROTOCOL_HTTP+"://"; // $NON-NLS-1$ + private static final String HTTPS_PREFIX = HTTPConstants.PROTOCOL_HTTPS+"://"; // $NON-NLS-1$ + + // Bug 51939 + private static final boolean SEPARATE_CONTAINER = + JMeterUtils.getPropDefault("httpsampler.separate.container", true); // $NON-NLS-1$ + + /** + * Get the URL, built from its component parts. + * + *

+ * As a special case, if the path starts with "http[s]://", + * then the path is assumed to be the entire URL. + *

+ * + * @return The URL to be requested by this sampler. + * @throws MalformedURLException if url is malformed + */ + public URL getUrl() throws MalformedURLException { + StringBuilder pathAndQuery = new StringBuilder(100); + String path = this.getPath(); + // Hack to allow entire URL to be provided in host field + if (path.startsWith(HTTP_PREFIX) + || path.startsWith(HTTPS_PREFIX)){ + return new URL(path); + } + String domain = getDomain(); + String protocol = getProtocol(); + if (PROTOCOL_FILE.equalsIgnoreCase(protocol)) { + domain=null; // allow use of relative file URLs + } else { + // HTTP URLs must be absolute, allow file to be relative + if (!path.startsWith("/")){ // $NON-NLS-1$ + pathAndQuery.append("/"); // $NON-NLS-1$ + } + } + pathAndQuery.append(path); + + // Add the query string if it is a HTTP GET or DELETE request + if(HTTPConstants.GET.equals(getMethod()) || HTTPConstants.DELETE.equals(getMethod())) { + // Get the query string encoded in specified encoding + // If no encoding is specified by user, we will get it + // encoded in UTF-8, which is what the HTTP spec says + String queryString = getQueryString(getContentEncoding()); + if(queryString.length() > 0) { + if (path.indexOf(QRY_PFX) > -1) {// Already contains a prefix + pathAndQuery.append(QRY_SEP); + } else { + pathAndQuery.append(QRY_PFX); + } + pathAndQuery.append(queryString); + } + } + // If default port for protocol is used, we do not include port in URL + if(isProtocolDefaultPort()) { + return new URL(protocol, domain, pathAndQuery.toString()); + } + return new URL(protocol, domain, getPort(), pathAndQuery.toString()); + } + + /** + * Gets the QueryString attribute of the UrlConfig object, using + * UTF-8 to encode the URL + * + * @return the QueryString value + */ + public String getQueryString() { + // We use the encoding which should be used according to the HTTP spec, which is UTF-8 + return getQueryString(EncoderCache.URL_ARGUMENT_ENCODING); + } + + /** + * Gets the QueryString attribute of the UrlConfig object, using the + * specified encoding to encode the parameter values put into the URL + * + * @param contentEncoding the encoding to use for encoding parameter values + * @return the QueryString value + */ + public String getQueryString(String contentEncoding) { + // Check if the sampler has a specified content encoding + if(JOrphanUtils.isBlank(contentEncoding)) { + // We use the encoding which should be used according to the HTTP spec, which is UTF-8 + contentEncoding = EncoderCache.URL_ARGUMENT_ENCODING; + } + StringBuilder buf = new StringBuilder(); + PropertyIterator iter = getArguments().iterator(); + boolean first = true; + while (iter.hasNext()) { + HTTPArgument item = null; + /* + * N.B. Revision 323346 introduced the ClassCast check, but then used iter.next() + * to fetch the item to be cast, thus skipping the element that did not cast. + * Reverted to work more like the original code, but with the check in place. + * Added a warning message so can track whether it is necessary + */ + Object objectValue = iter.next().getObjectValue(); + try { + item = (HTTPArgument) objectValue; + } catch (ClassCastException e) { + log.warn("Unexpected argument type: "+objectValue.getClass().getName()); + item = new HTTPArgument((Argument) objectValue); + } + final String encodedName = item.getEncodedName(); + if (encodedName.length() == 0) { + continue; // Skip parameters with a blank name (allows use of optional variables in parameter lists) + } + if (!first) { + buf.append(QRY_SEP); + } else { + first = false; + } + buf.append(encodedName); + if (item.getMetaData() == null) { + buf.append(ARG_VAL_SEP); + } else { + buf.append(item.getMetaData()); + } + + // Encode the parameter value in the specified content encoding + try { + buf.append(item.getEncodedValue(contentEncoding)); + } + catch(UnsupportedEncodingException e) { + log.warn("Unable to encode parameter in encoding " + contentEncoding + ", parameter value not included in query string"); + } + } + return buf.toString(); + } + + // Mark Walsh 2002-08-03, modified to also parse a parameter name value + // string, where string contains only the parameter name and no equal sign. + /** + * This method allows a proxy server to send over the raw text from a + * browser's output stream to be parsed and stored correctly into the + * UrlConfig object. + * + * For each name found, addArgument() is called + * + * @param queryString - + * the query string, might be the post body of a http post request. + * @param contentEncoding - + * the content encoding of the query string; + * if non-null then it is used to decode the + */ + public void parseArguments(String queryString, String contentEncoding) { + String[] args = JOrphanUtils.split(queryString, QRY_SEP); + final boolean isDebug = log.isDebugEnabled(); + for (int i = 0; i < args.length; i++) { + if (isDebug) { + log.debug("Arg: " + args[i]); + } + // need to handle four cases: + // - string contains name=value + // - string contains name= + // - string contains name + // - empty string + + String metaData; // records the existance of an equal sign + String name; + String value; + int length = args[i].length(); + int endOfNameIndex = args[i].indexOf(ARG_VAL_SEP); + if (endOfNameIndex != -1) {// is there a separator? + // case of name=value, name= + metaData = ARG_VAL_SEP; + name = args[i].substring(0, endOfNameIndex); + value = args[i].substring(endOfNameIndex + 1, length); + } else { + metaData = ""; + name=args[i]; + value=""; + } + if (name.length() > 0) { + if (isDebug) { + log.debug("Name: " + name+ " Value: " + value+ " Metadata: " + metaData); + } + // If we know the encoding, we can decode the argument value, + // to make it easier to read for the user + if(!StringUtils.isEmpty(contentEncoding)) { + addEncodedArgument(name, value, metaData, contentEncoding); + } + else { + // If we do not know the encoding, we just use the encoded value + // The browser has already done the encoding, so save the values as is + addNonEncodedArgument(name, value, metaData); + } + } + } + } + + public void parseArguments(String queryString) { + // We do not know the content encoding of the query string + parseArguments(queryString, null); + } + + @Override + public String toString() { + try { + StringBuilder stringBuffer = new StringBuilder(); + stringBuffer.append(this.getUrl().toString()); + // Append body if it is a post or put + if(HTTPConstants.POST.equals(getMethod()) || HTTPConstants.PUT.equals(getMethod())) { + stringBuffer.append("\nQuery Data: "); + stringBuffer.append(getQueryString()); + } + return stringBuffer.toString(); + } catch (MalformedURLException e) { + return ""; + } + } + + /** + * Do a sampling and return its results. + * + * @param e + * Entry to be sampled + * @return results of the sampling + */ + @Override + public SampleResult sample(Entry e) { + return sample(); + } + + /** + * Perform a sample, and return the results + * + * @return results of the sampling + */ + public SampleResult sample() { + SampleResult res = null; + try { + res = sample(getUrl(), getMethod(), false, 0); + if(res != null) { + res.setSampleLabel(getName()); + } + return res; + } catch (Exception e) { + return errorResult(e, new HTTPSampleResult()); + } + } + + /** + * Samples the URL passed in and stores the result in + * HTTPSampleResult, following redirects and downloading + * page resources as appropriate. + *

+ * When getting a redirect target, redirects are not followed and resources + * are not downloaded. The caller will take care of this. + * + * @param u + * URL to sample + * @param method + * HTTP method: GET, POST,... + * @param areFollowingRedirect + * whether we're getting a redirect target + * @param depth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return results of the sampling, can be null if u is in CacheManager + */ + protected abstract HTTPSampleResult sample(URL u, + String method, boolean areFollowingRedirect, int depth); + + /** + * Download the resources of an HTML page. + * + * @param res + * result of the initial request - must contain an HTML response + * @param container + * for storing the results, if any + * @param frameDepth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return res if no resources exist, otherwise the "Container" result with one subsample per request issued + */ + protected HTTPSampleResult downloadPageResources(HTTPSampleResult res, HTTPSampleResult container, int frameDepth) { + Iterator urls = null; + try { + final byte[] responseData = res.getResponseData(); + if (responseData.length > 0){ // Bug 39205 + String parserName = getParserClass(res); + if(parserName != null) + { + final HTMLParser parser = + parserName.length() > 0 ? // we have a name + HTMLParser.getParser(parserName) + : + HTMLParser.getParser(); // we don't; use the default parser + String userAgent = getUserAgent(res); + urls = parser.getEmbeddedResourceURLs(userAgent, responseData, res.getURL(), res.getDataEncodingWithDefault()); + } + } + } catch (HTMLParseException e) { + // Don't break the world just because this failed: + res.addSubResult(errorResult(e, new HTTPSampleResult(res))); + setParentSampleSuccess(res, false); + } + + // Iterate through the URLs and download each image: + if (urls != null && urls.hasNext()) { + if (container == null) { + container = new HTTPSampleResult(res); + container.addRawSubResult(res); + } + res = container; + + // Get the URL matcher + String re=getEmbeddedUrlRE(); + Perl5Matcher localMatcher = null; + Pattern pattern = null; + if (re.length()>0){ + try { + pattern = JMeterUtils.getPattern(re); + localMatcher = JMeterUtils.getMatcher();// don't fetch unless pattern compiles + } catch (MalformedCachePatternException e) { + log.warn("Ignoring embedded URL match string: "+e.getMessage()); + } + } + + // For concurrent get resources + final List> liste = new ArrayList>(); + + while (urls.hasNext()) { + Object binURL = urls.next(); // See catch clause below + try { + URL url = (URL) binURL; + if (url == null) { + log.warn("Null URL detected (should not happen)"); + } else { + String urlstr = url.toString(); + String urlStrEnc=encodeSpaces(urlstr); + if (!urlstr.equals(urlStrEnc)){// There were some spaces in the URL + try { + url = new URL(urlStrEnc); + } catch (MalformedURLException e) { + res.addSubResult(errorResult(new Exception(urlStrEnc + " is not a correct URI"), new HTTPSampleResult(res))); + setParentSampleSuccess(res, false); + continue; + } + } + // I don't think localMatcher can be null here, but check just in case + if (pattern != null && localMatcher != null && !localMatcher.matches(urlStrEnc, pattern)) { + continue; // we have a pattern and the URL does not match, so skip it + } + + if (isConcurrentDwn()) { + // if concurrent download emb. resources, add to a list for async gets later + liste.add(new ASyncSample(url, HTTPConstants.GET, false, frameDepth + 1, getCookieManager(), this)); + } else { + // default: serial download embedded resources + HTTPSampleResult binRes = sample(url, HTTPConstants.GET, false, frameDepth + 1); + res.addSubResult(binRes); + setParentSampleSuccess(res, res.isSuccessful() && (binRes != null ? binRes.isSuccessful() : true)); + } + + } + } catch (ClassCastException e) { // TODO can this happen? + res.addSubResult(errorResult(new Exception(binURL + " is not a correct URI"), new HTTPSampleResult(res))); + setParentSampleSuccess(res, false); + continue; + } + } + // IF for download concurrent embedded resources + if (isConcurrentDwn()) { + int poolSize = CONCURRENT_POOL_SIZE; // init with default value + try { + poolSize = Integer.parseInt(getConcurrentPool()); + } catch (NumberFormatException nfe) { + log.warn("Concurrent download resources selected, "// $NON-NLS-1$ + + "but pool size value is bad. Use default value");// $NON-NLS-1$ + } + // Thread pool Executor to get resources + // use a LinkedBlockingQueue, note: max pool size doesn't effect + final ThreadPoolExecutor exec = new ThreadPoolExecutor( + poolSize, poolSize, KEEPALIVETIME, TimeUnit.SECONDS, + new LinkedBlockingQueue(), + new ThreadFactory() { + @Override + public Thread newThread(final Runnable r) { + Thread t = new CleanerThread(new Runnable() { + @Override + public void run() { + try { + r.run(); + } finally { + ((CleanerThread)Thread.currentThread()).notifyThreadEnd(); + } + } + }); + return t; + } + }); + + boolean tasksCompleted = false; + try { + // sample all resources with threadpool + final List> retExec = exec.invokeAll(liste); + // call normal shutdown (wait ending all tasks) + exec.shutdown(); + // put a timeout if tasks couldn't terminate + exec.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.SECONDS); + CookieManager cookieManager = getCookieManager(); + // add result to main sampleResult + for (Future future : retExec) { + AsynSamplerResultHolder binRes; + try { + binRes = future.get(1, TimeUnit.MILLISECONDS); + if(cookieManager != null) { + CollectionProperty cookies = binRes.getCookies(); + PropertyIterator iter = cookies.iterator(); + while (iter.hasNext()) { + Cookie cookie = (Cookie) iter.next().getObjectValue(); + cookieManager.add(cookie) ; + } + } + res.addSubResult(binRes.getResult()); + setParentSampleSuccess(res, res.isSuccessful() && (binRes.getResult() != null ? binRes.getResult().isSuccessful():true)); + } catch (TimeoutException e) { + errorResult(e, res); + } + } + tasksCompleted = exec.awaitTermination(1, TimeUnit.MILLISECONDS); // did all the tasks finish? + } catch (InterruptedException ie) { + log.warn("Interruped fetching embedded resources", ie); // $NON-NLS-1$ + } catch (ExecutionException ee) { + log.warn("Execution issue when fetching embedded resources", ee); // $NON-NLS-1$ + } finally { + if (!tasksCompleted) { + exec.shutdownNow(); // kill any remaining tasks + } + } + } + } + return res; + } + + /** + * Extract User-Agent header value + * @param sampleResult HTTPSampleResult + * @return User Agent part + */ + private String getUserAgent(HTTPSampleResult sampleResult) { + String res = sampleResult.getRequestHeaders(); + int index = res.indexOf(USER_AGENT); + if(index >=0) { + // see HTTPHC3Impl#getConnectionHeaders + // see HTTPHC4Impl#getConnectionHeaders + // see HTTPJavaImpl#getConnectionHeaders + //': ' is used by JMeter to fill-in requestHeaders, see getConnectionHeaders + final String userAgentPrefix = USER_AGENT+": "; + String userAgentHdr = res.substring( + index+userAgentPrefix.length(), + res.indexOf('\n',// '\n' is used by JMeter to fill-in requestHeaders, see getConnectionHeaders + index+userAgentPrefix.length()+1)); + return userAgentHdr.trim(); + } else { + if(log.isInfoEnabled()) { + log.info("No user agent extracted from requestHeaders:"+res); + } + return null; + } + } + + /** + * Set parent successful attribute based on IGNORE_FAILED_EMBEDDED_RESOURCES parameter + * @param res {@link HTTPSampleResult} + * @param initialValue boolean + */ + private void setParentSampleSuccess(HTTPSampleResult res, boolean initialValue) { + if(!IGNORE_FAILED_EMBEDDED_RESOURCES) { + res.setSuccessful(initialValue); + if(!initialValue) { + res.setResponseMessage("Embedded resource download error"); //$NON-NLS-1$ + } + } + } + + /* + * @param res HTTPSampleResult to check + * @return parser class name (may be "") or null if entry does not exist + */ + private String getParserClass(HTTPSampleResult res) { + final String ct = res.getMediaType(); + return parsersForType.get(ct); + } + + // TODO: make static? + protected String encodeSpaces(String path) { + return JOrphanUtils.replaceAllChars(path, ' ', "%20"); // $NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded() { + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded(String host) { + testEnded(); + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted() { + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted(String host) { + testStarted(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object clone() { + HTTPSamplerBase base = (HTTPSamplerBase) super.clone(); + return base; + } + + /** + * Iteratively download the redirect targets of a redirect response. + *

+ * The returned result will contain one subsample for each request issued, + * including the original one that was passed in. It will be an + * HTTPSampleResult that should mostly look as if the final destination of + * the redirect chain had been obtained in a single shot. + * + * @param res + * result of the initial request - must be a redirect response + * @param frameDepth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return "Container" result with one subsample per request issued + */ + protected HTTPSampleResult followRedirects(HTTPSampleResult res, int frameDepth) { + HTTPSampleResult totalRes = new HTTPSampleResult(res); + totalRes.addRawSubResult(res); + HTTPSampleResult lastRes = res; + + int redirect; + for (redirect = 0; redirect < MAX_REDIRECTS; redirect++) { + boolean invalidRedirectUrl = false; + String location = lastRes.getRedirectLocation(); + if (log.isDebugEnabled()) { + log.debug("Initial location: " + location); + } + if (REMOVESLASHDOTDOT) { + location = ConversionUtils.removeSlashDotDot(location); + } + // Browsers seem to tolerate Location headers with spaces, + // replacing them automatically with %20. We want to emulate + // this behaviour. + location = encodeSpaces(location); + if (log.isDebugEnabled()) { + log.debug("Location after /. and space transforms: " + location); + } + // Change all but HEAD into GET (Bug 55450) + String method = lastRes.getHTTPMethod(); + if (!HTTPConstants.HEAD.equalsIgnoreCase(method)) { + method = HTTPConstants.GET; + } + try { + URL url = ConversionUtils.makeRelativeURL(lastRes.getURL(), location); + url = ConversionUtils.sanitizeUrl(url).toURL(); + if (log.isDebugEnabled()) { + log.debug("Location as URL: " + url.toString()); + } + HTTPSampleResult tempRes = sample(url, method, true, frameDepth); + if(tempRes != null) { + lastRes = tempRes; + } else { + // Last url was in cache so tempRes is null + break; + } + } catch (MalformedURLException e) { + errorResult(e, lastRes); + // The redirect URL we got was not a valid URL + invalidRedirectUrl = true; + } catch (URISyntaxException e) { + errorResult(e, lastRes); + // The redirect URL we got was not a valid URL + invalidRedirectUrl = true; + } + if (lastRes.getSubResults() != null && lastRes.getSubResults().length > 0) { + SampleResult[] subs = lastRes.getSubResults(); + for (int i = 0; i < subs.length; i++) { + totalRes.addSubResult(subs[i]); + } + } else { + // Only add sample if it is a sample of valid url redirect, i.e. that + // we have actually sampled the URL + if(!invalidRedirectUrl) { + totalRes.addSubResult(lastRes); + } + } + + if (!lastRes.isRedirect()) { + break; + } + } + if (redirect >= MAX_REDIRECTS) { + lastRes = errorResult(new IOException("Exceeeded maximum number of redirects: " + MAX_REDIRECTS), new HTTPSampleResult(lastRes)); + totalRes.addSubResult(lastRes); + } + + // Now populate the any totalRes fields that need to + // come from lastRes: + totalRes.setSampleLabel(totalRes.getSampleLabel() + "->" + lastRes.getSampleLabel()); + // The following three can be discussed: should they be from the + // first request or from the final one? I chose to do it this way + // because that's what browsers do: they show the final URL of the + // redirect chain in the location field. + totalRes.setURL(lastRes.getURL()); + totalRes.setHTTPMethod(lastRes.getHTTPMethod()); + totalRes.setQueryString(lastRes.getQueryString()); + totalRes.setRequestHeaders(lastRes.getRequestHeaders()); + + totalRes.setResponseData(lastRes.getResponseData()); + totalRes.setResponseCode(lastRes.getResponseCode()); + totalRes.setSuccessful(lastRes.isSuccessful()); + totalRes.setResponseMessage(lastRes.getResponseMessage()); + totalRes.setDataType(lastRes.getDataType()); + totalRes.setResponseHeaders(lastRes.getResponseHeaders()); + totalRes.setContentType(lastRes.getContentType()); + totalRes.setDataEncoding(lastRes.getDataEncodingNoDefault()); + return totalRes; + } + + /** + * Follow redirects and download page resources if appropriate. this works, + * but the container stuff here is what's doing it. followRedirects() is + * actually doing the work to make sure we have only one container to make + * this work more naturally, I think this method - sample() - needs to take + * an HTTPSamplerResult container parameter instead of a + * boolean:areFollowingRedirect. + * + * @param areFollowingRedirect flag whether we are getting a redirect target + * @param frameDepth Depth of this target in the frame structure. Used only to prevent infinite recursion. + * @param res sample result to process + * @return the sample result + */ + protected HTTPSampleResult resultProcessing(boolean areFollowingRedirect, int frameDepth, HTTPSampleResult res) { + boolean wasRedirected = false; + if (!areFollowingRedirect) { + if (res.isRedirect()) { + log.debug("Location set to - " + res.getRedirectLocation()); + + if (getFollowRedirects()) { + res = followRedirects(res, frameDepth); + areFollowingRedirect = true; + wasRedirected = true; + } + } + } + if (isImageParser() && (SampleResult.TEXT).equals(res.getDataType()) && res.isSuccessful()) { + if (frameDepth > MAX_FRAME_DEPTH) { + res.addSubResult(errorResult(new Exception("Maximum frame/iframe nesting depth exceeded."), new HTTPSampleResult(res))); + } else { + // Only download page resources if we were not redirected. + // If we were redirected, the page resources have already been + // downloaded for the sample made for the redirected url + // otherwise, use null so the container is created if necessary unless + // the flag is false, in which case revert to broken 2.1 behaviour + // Bug 51939 - https://bz.apache.org/bugzilla/show_bug.cgi?id=51939 + if(!wasRedirected) { + HTTPSampleResult container = (HTTPSampleResult) ( + areFollowingRedirect ? res.getParent() : SEPARATE_CONTAINER ? null : res); + res = downloadPageResources(res, container, frameDepth); + } + } + } + return res; + } + + /** + * Determine if the HTTP status code is successful or not + * i.e. in range 200 to 399 inclusive + * + * @param code status code to check + * @return whether in range 200-399 or not + */ + protected boolean isSuccessCode(int code){ + return (code >= 200 && code <= 399); + } + + protected static String encodeBackSlashes(String value) { + StringBuilder newValue = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char charAt = value.charAt(i); + if (charAt == '\\') { // $NON-NLS-1$ + newValue.append("\\\\"); // $NON-NLS-1$ + } else { + newValue.append(charAt); + } + } + return newValue.toString(); + } + + /* + * Method to set files list to be uploaded. + * + * @param value + * HTTPFileArgs object that stores file list to be uploaded. + */ + private void setHTTPFileArgs(HTTPFileArgs value) { + if (value.getHTTPFileArgCount() > 0){ + setProperty(new TestElementProperty(FILE_ARGS, value)); + } else { + removeProperty(FILE_ARGS); // no point saving an empty list + } + } + + /* + * Method to get files list to be uploaded. + */ + private HTTPFileArgs getHTTPFileArgs() { + return (HTTPFileArgs) getProperty(FILE_ARGS).getObjectValue(); + } + + /** + * Get the collection of files as a list. + * The list is built up from the filename/filefield/mimetype properties, + * plus any additional entries saved in the FILE_ARGS property. + * + * If there are no valid file entries, then an empty list is returned. + * + * @return an array of file arguments (never null) + */ + public HTTPFileArg[] getHTTPFiles() { + final HTTPFileArgs fileArgs = getHTTPFileArgs(); + return fileArgs == null ? new HTTPFileArg[] {} : fileArgs.asArray(); + } + + public int getHTTPFileCount(){ + return getHTTPFiles().length; + } + /** + * Saves the list of files. + * The first file is saved in the Filename/field/mimetype properties. + * Any additional files are saved in the FILE_ARGS array. + * + * @param files list of files to save + */ + public void setHTTPFiles(HTTPFileArg[] files) { + HTTPFileArgs fileArgs = new HTTPFileArgs(); + // Weed out the empty files + if (files.length > 0) { + for(int i=0; i < files.length; i++){ + HTTPFileArg file = files[i]; + if (file.isNotEmpty()){ + fileArgs.addHTTPFileArg(file); + } + } + } + setHTTPFileArgs(fileArgs); + } + + public static String[] getValidMethodsAsArray(){ + return METHODLIST.toArray(new String[METHODLIST.size()]); + } + + public static boolean isSecure(String protocol){ + return HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(protocol); + } + + public static boolean isSecure(URL url){ + return isSecure(url.getProtocol()); + } + + // Implement these here, to avoid re-implementing for sub-classes + // (previously these were implemented in all TestElements) + @Override + public void threadStarted(){ + } + + @Override + public void threadFinished(){ + } + + @Override + public void testIterationStart(LoopIterationEvent event) { + // NOOP to provide based empty impl and avoid breaking existing implementations + } + + /** + * Read response from the input stream, converting to MD5 digest if the useMD5 property is set. + *

+ * For the MD5 case, the result byte count is set to the size of the original response. + *

+ * Closes the inputStream + * + * @param sampleResult sample to store information about the response into + * @param in input stream from which to read the response + * @param length expected input length or zero + * @return the response or the MD5 of the response + * @throws IOException if reading the result fails + */ + public byte[] readResponse(SampleResult sampleResult, InputStream in, int length) throws IOException { + try { + byte[] readBuffer = new byte[8192]; // 8kB is the (max) size to have the latency ('the first packet') + int bufferSize=32;// Enough for MD5 + + MessageDigest md=null; + boolean asMD5 = useMD5(); + if (asMD5) { + try { + md = MessageDigest.getInstance("MD5"); //$NON-NLS-1$ + } catch (NoSuchAlgorithmException e) { + log.error("Should not happen - could not find MD5 digest", e); + asMD5=false; + } + } else { + if (length <= 0) {// may also happen if long value > int.max + bufferSize = 4 * 1024; + } else { + bufferSize = length; + } + } + ByteArrayOutputStream w = new ByteArrayOutputStream(bufferSize); + int bytesRead = 0; + int totalBytes = 0; + boolean first = true; + while ((bytesRead = in.read(readBuffer)) > -1) { + if (first) { + sampleResult.latencyEnd(); + first = false; + } + if (asMD5 && md != null) { + md.update(readBuffer, 0 , bytesRead); + totalBytes += bytesRead; + } else { + w.write(readBuffer, 0, bytesRead); + } + } + if (first){ // Bug 46838 - if there was no data, still need to set latency + sampleResult.latencyEnd(); + } + in.close(); + w.flush(); + if (asMD5 && md != null) { + byte[] md5Result = md.digest(); + w.write(JOrphanUtils.baToHexBytes(md5Result)); + sampleResult.setBytes(totalBytes); + } + w.close(); + return w.toByteArray(); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** + * JMeter 2.3.1 and earlier only had fields for one file on the GUI: + *

    + *
  • FILE_NAME
  • + *
  • FILE_FIELD
  • + *
  • MIMETYPE
  • + *
+ * These were stored in their own individual properties. + *

+ * Version 2.3.3 introduced a list of files, each with their own path, name and mimetype. + *

+ * In order to maintain backwards compatibility of test plans, the 3 original properties + * were retained; additional file entries are stored in an HTTPFileArgs class. + * The HTTPFileArgs class was only present if there is more than 1 file; this means that + * such test plans are backward compatible. + *

+ * Versions after 2.3.4 dispense with the original set of 3 properties. + * Test plans that use them are converted to use a single HTTPFileArgs list. + * + * @see HTTPSamplerBaseConverter + */ + void mergeFileProperties() { + JMeterProperty fileName = getProperty(FILE_NAME); + JMeterProperty paramName = getProperty(FILE_FIELD); + JMeterProperty mimeType = getProperty(MIMETYPE); + HTTPFileArg oldStyleFile = new HTTPFileArg(fileName, paramName, mimeType); + + HTTPFileArgs fileArgs = getHTTPFileArgs(); + + HTTPFileArgs allFileArgs = new HTTPFileArgs(); + if(oldStyleFile.isNotEmpty()) { // OK, we have an old-style file definition + allFileArgs.addHTTPFileArg(oldStyleFile); // save it + // Now deal with any additional file arguments + if(fileArgs != null) { + HTTPFileArg[] infiles = fileArgs.asArray(); + for (int i = 0; i < infiles.length; i++){ + allFileArgs.addHTTPFileArg(infiles[i]); + } + } + } else { + if(fileArgs != null) { // for new test plans that don't have FILE/PARAM/MIME properties + allFileArgs = fileArgs; + } + } + // Updated the property lists + setHTTPFileArgs(allFileArgs); + removeProperty(FILE_FIELD); + removeProperty(FILE_NAME); + removeProperty(MIMETYPE); + } + + /** + * set IP source to use - does not apply to Java HTTP implementation currently + * + * @param value IP source to use + */ + public void setIpSource(String value) { + setProperty(IP_SOURCE, value, ""); + } + + /** + * get IP source to use - does not apply to Java HTTP implementation currently + * + * @return IP source to use + */ + public String getIpSource() { + return getPropertyAsString(IP_SOURCE,""); + } + + /** + * set IP/address source type to use + * + * @param value type of the IP/address source + */ + public void setIpSourceType(int value) { + setProperty(IP_SOURCE_TYPE, value, SOURCE_TYPE_DEFAULT); + } + + /** + * get IP/address source type to use + * + * @return address source type + */ + public int getIpSourceType() { + return getPropertyAsInt(IP_SOURCE_TYPE, SOURCE_TYPE_DEFAULT); + } + + /** + * Return if used a concurrent thread pool to get embedded resources. + * + * @return true if used + */ + public boolean isConcurrentDwn() { + return getPropertyAsBoolean(CONCURRENT_DWN, false); + } + + public void setConcurrentDwn(boolean concurrentDwn) { + setProperty(CONCURRENT_DWN, concurrentDwn, false); + } + + /** + * Get the pool size for concurrent thread pool to get embedded resources. + * + * @return the pool size + */ + public String getConcurrentPool() { + return getPropertyAsString(CONCURRENT_POOL,CONCURRENT_POOL_DEFAULT); + } + + public void setConcurrentPool(String poolSize) { + setProperty(CONCURRENT_POOL, poolSize, CONCURRENT_POOL_DEFAULT); + } + + + /** + * Callable class to sample asynchronously resources embedded + * + */ + private static class ASyncSample implements Callable { + final private URL url; + final private String method; + final private boolean areFollowingRedirect; + final private int depth; + private final HTTPSamplerBase sampler; + private final JMeterContext jmeterContextOfParentThread; + + ASyncSample(URL url, String method, + boolean areFollowingRedirect, int depth, CookieManager cookieManager, HTTPSamplerBase base){ + this.url = url; + this.method = method; + this.areFollowingRedirect = areFollowingRedirect; + this.depth = depth; + this.sampler = (HTTPSamplerBase) base.clone(); + // We don't want to use CacheManager clone but the parent one, and CacheManager is Thread Safe + CacheManager cacheManager = base.getCacheManager(); + if (cacheManager != null) { + this.sampler.setCacheManagerProperty(cacheManager); + } + + if(cookieManager != null) { + CookieManager clonedCookieManager = (CookieManager) cookieManager.clone(); + this.sampler.setCookieManagerProperty(clonedCookieManager); + } + this.jmeterContextOfParentThread = JMeterContextService.getContext(); + } + + @Override + public AsynSamplerResultHolder call() { + JMeterContextService.replaceContext(jmeterContextOfParentThread); + ((CleanerThread) Thread.currentThread()).registerSamplerForEndNotification(sampler); + HTTPSampleResult httpSampleResult = sampler.sample(url, method, areFollowingRedirect, depth); + if(sampler.getCookieManager() != null) { + CollectionProperty cookies = sampler.getCookieManager().getCookies(); + return new AsynSamplerResultHolder(httpSampleResult, cookies); + } else { + return new AsynSamplerResultHolder(httpSampleResult, new CollectionProperty()); + } + } + } + + /** + * Custom thread implementation that + * + */ + private static class CleanerThread extends Thread { + private final List samplersToNotify = new ArrayList(); + /** + * @param runnable Runnable + */ + public CleanerThread(Runnable runnable) { + super(runnable); + } + + /** + * Notify of thread end + */ + public void notifyThreadEnd() { + for (HTTPSamplerBase samplerBase : samplersToNotify) { + samplerBase.threadFinished(); + } + samplersToNotify.clear(); + } + + /** + * Register sampler to be notify at end of thread + * @param sampler {@link HTTPSamplerBase} + */ + public void registerSamplerForEndNotification(HTTPSamplerBase sampler) { + this.samplersToNotify.add(sampler); + } + } + + /** + * Holder of AsynSampler result + */ + private static class AsynSamplerResultHolder { + private final HTTPSampleResult result; + private final CollectionProperty cookies; + /** + * @param result {@link HTTPSampleResult} to hold + * @param cookies cookies to hold + */ + public AsynSamplerResultHolder(HTTPSampleResult result, CollectionProperty cookies) { + super(); + this.result = result; + this.cookies = cookies; + } + /** + * @return the result + */ + public HTTPSampleResult getResult() { + return result; + } + /** + * @return the cookies + */ + public CollectionProperty getCookies() { + return cookies; + } + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseBeanInfo.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseBeanInfo.java new file mode 100644 index 00000000000..3960afdb8ac --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseBeanInfo.java @@ -0,0 +1,32 @@ +/* + * 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. + * + */ + +/* + * Created on May 24, 2004 + */ +package org.apache.jmeter.protocol.http.sampler; + +import org.apache.jmeter.testelement.AbstractTestElementBeanInfo; + +/** + * This is the BeanInfo class for the TestBean HTTPSamplerBase. + * (every TestBean has to have a BeanInfo class) + */ +public class HTTPSamplerBaseBeanInfo extends AbstractTestElementBeanInfo { + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java new file mode 100644 index 00000000000..718e5aa6abe --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java @@ -0,0 +1,77 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 14, 2004 + * + */ +package org.apache.jmeter.protocol.http.sampler; + +import org.apache.jmeter.save.converters.TestElementConverter; + +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.mapper.Mapper; + +/** + * Class for XStream conversion of HTTPResult + * + */ +public class HTTPSamplerBaseConverter extends TestElementConverter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + * + * @return the version of this component + */ + public static String getVersion() { + return "$Revision$"; //$NON-NLS-1$ + } + + public HTTPSamplerBaseConverter(Mapper arg0) { + super(arg0); + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not support types + return HTTPSamplerBase.class.isAssignableFrom(arg0); + } + + /** + * Override TestElementConverter; convert HTTPSamplerBase to merge + * the two means of providing file names into a single list. + * + * {@inheritDoc} + */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final HTTPSamplerBase httpSampler = (HTTPSamplerBase) super.unmarshal(reader, context); + // Help convert existing JMX files which use HTTPSampler[2] nodes + String nodeName = reader.getNodeName(); + if (nodeName.equals(HTTPSamplerFactory.HTTP_SAMPLER_JAVA)){ + httpSampler.setImplementation(HTTPSamplerFactory.IMPL_JAVA); + } + if (nodeName.equals(HTTPSamplerFactory.HTTP_SAMPLER_APACHE)){ + httpSampler.setImplementation(HTTPSamplerFactory.IMPL_HTTP_CLIENT3_1); + } + httpSampler.mergeFileProperties(); + return httpSampler; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerFactory.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerFactory.java new file mode 100644 index 00000000000..3de17ba8a32 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerFactory.java @@ -0,0 +1,108 @@ +/* + * 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.protocol.http.sampler; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Factory to return the appropriate HTTPSampler for use with classes that need + * an HTTPSampler; also creates the implementations for use with HTTPSamplerProxy. + * + */ +public final class HTTPSamplerFactory { + + // N.B. These values are used in jmeter.properties (jmeter.httpsampler) - do not change + // They can alse be used as the implementation name + /** Use the the default Java HTTP implementation */ + public static final String HTTP_SAMPLER_JAVA = "HTTPSampler"; //$NON-NLS-1$ + + /** Use Apache HTTPClient HTTP implementation */ + public static final String HTTP_SAMPLER_APACHE = "HTTPSampler2"; //$NON-NLS-1$ + + //+ JMX implementation attribute values (also displayed in GUI) - do not change + public static final String IMPL_HTTP_CLIENT4 = "HttpClient4"; // $NON-NLS-1$ + + public static final String IMPL_HTTP_CLIENT3_1 = "HttpClient3.1"; // $NON-NLS-1$ + + public static final String IMPL_JAVA = "Java"; // $NON-NLS-1$ + //- JMX + + public static final String DEFAULT_CLASSNAME = + JMeterUtils.getPropDefault("jmeter.httpsampler", IMPL_HTTP_CLIENT4); //$NON-NLS-1$ + + private HTTPSamplerFactory() { + // Not intended to be instantiated + } + + /** + * Create a new instance of the default sampler + * + * @return instance of default sampler + */ + public static HTTPSamplerBase newInstance() { + return newInstance(DEFAULT_CLASSNAME); + } + + /** + * Create a new instance of the required sampler type + * + * @param alias HTTP_SAMPLER or HTTP_SAMPLER_APACHE or IMPL_HTTP_CLIENT3_1 or IMPL_HTTP_CLIENT4 + * @return the appropriate sampler + * @throws UnsupportedOperationException if alias is not recognised + */ + public static HTTPSamplerBase newInstance(String alias) { + if (alias ==null || alias.length() == 0) { + return new HTTPSamplerProxy(); + } + if (alias.equals(HTTP_SAMPLER_JAVA) || alias.equals(IMPL_JAVA)) { + return new HTTPSamplerProxy(IMPL_JAVA); + } + if (alias.equals(HTTP_SAMPLER_APACHE) || alias.equals(IMPL_HTTP_CLIENT3_1)) { + return new HTTPSamplerProxy(IMPL_HTTP_CLIENT3_1); + } + if (alias.equals(IMPL_HTTP_CLIENT4)) { + return new HTTPSamplerProxy(IMPL_HTTP_CLIENT4); + } + throw new IllegalArgumentException("Unknown sampler type: '" + alias+"'"); + } + + public static String[] getImplementations(){ + return new String[]{IMPL_HTTP_CLIENT4,IMPL_HTTP_CLIENT3_1,IMPL_JAVA}; + } + + public static HTTPAbstractImpl getImplementation(String impl, HTTPSamplerBase base){ + if (HTTPSamplerBase.PROTOCOL_FILE.equals(base.getProtocol())) { + return new HTTPFileImpl(base); + } + if (JOrphanUtils.isBlank(impl)){ + impl = DEFAULT_CLASSNAME; + } + if (IMPL_JAVA.equals(impl) || HTTP_SAMPLER_JAVA.equals(impl)) { + return new HTTPJavaImpl(base); + } else if (IMPL_HTTP_CLIENT3_1.equals(impl) || HTTP_SAMPLER_APACHE.equals(impl)) { + return new HTTPHC3Impl(base); + } else if (IMPL_HTTP_CLIENT4.equals(impl)) { + return new HTTPHC4Impl(base); + } else { + throw new IllegalArgumentException("Unknown implementation type: '"+impl+"'"); + } + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java new file mode 100644 index 00000000000..6893ee8a47f --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java @@ -0,0 +1,102 @@ +/* + * 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.protocol.http.sampler; + +import java.net.URL; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.samplers.Interruptible; + +/** + * Proxy class that dispatches to the appropriate HTTP sampler. + *

+ * This class is stored in the test plan, and holds all the configuration settings. + * The actual implementation is created at run-time, and is passed a reference to this class + * so it can get access to all the settings stored by HTTPSamplerProxy. + */ +public final class HTTPSamplerProxy extends HTTPSamplerBase implements Interruptible { + + private static final long serialVersionUID = 1L; + + private transient HTTPAbstractImpl impl; + + private transient volatile boolean notifyFirstSampleAfterLoopRestart; + + public HTTPSamplerProxy(){ + super(); + } + + /** + * Convenience method used to initialise the implementation. + * + * @param impl the implementation to use. + */ + public HTTPSamplerProxy(String impl){ + super(); + setImplementation(impl); + } + + /** {@inheritDoc} */ + @Override + protected HTTPSampleResult sample(URL u, String method, boolean areFollowingRedirect, int depth) { + // When Retrieve Embedded resources + Concurrent Pool is used + // as the instance of Proxy is cloned, we end up with impl being null + // testIterationStart will not be executed but it's not a problem for 51380 as it's download of resources + // so SSL context is to be reused + if (impl == null) { // Not called from multiple threads, so this is OK + try { + impl = HTTPSamplerFactory.getImplementation(getImplementation(), this); + } catch (Exception ex) { + return errorResult(ex, new HTTPSampleResult()); + } + } + // see https://bz.apache.org/bugzilla/show_bug.cgi?id=51380 + if(notifyFirstSampleAfterLoopRestart) { + impl.notifyFirstSampleAfterLoopRestart(); + notifyFirstSampleAfterLoopRestart = false; + } + return impl.sample(u, method, areFollowingRedirect, depth); + } + + // N.B. It's not po ssible to forward threadStarted() to the implementation class. + // This is because Config items are not processed until later, and HTTPDefaults may define the implementation + + @Override + public void threadFinished(){ + if (impl != null){ + impl.threadFinished(); // Forward to sampler + } + } + + @Override + public boolean interrupt() { + if (impl != null) { + return impl.interrupt(); // Forward to sampler + } + return false; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent) + */ + @Override + public void testIterationStart(LoopIterationEvent event) { + notifyFirstSampleAfterLoopRestart = true; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HttpClientDefaultParameters.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HttpClientDefaultParameters.java new file mode 100644 index 00000000000..ec35c5eac37 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HttpClientDefaultParameters.java @@ -0,0 +1,151 @@ +/* + * 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.protocol.http.sampler; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/* + * Utility class to set up default HttpClient parameters from a file. + * + * Supports both Commons HttpClient and Apache HttpClient. + * + */ +public class HttpClientDefaultParameters { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Non-instantiable + private HttpClientDefaultParameters(){ + } + + // Helper class (callback) for applying parameter definitions + private static abstract class GenericHttpParams { + public abstract void setParameter(String name, Object value); + public abstract void setVersion(String name, String value) throws Exception; + } + + /** + * Loads a property file and converts parameters as necessary. + * + * @param file the file to load + * @param params Commons HttpClient parameter instance + */ + public static void load(String file, + final org.apache.commons.httpclient.params.HttpParams params){ + load(file, + new GenericHttpParams (){ + @Override + public void setParameter(String name, Object value) { + params.setParameter(name, value); + } + @Override + public void setVersion(String name, String value) throws Exception { + params.setParameter(name, + org.apache.commons.httpclient.HttpVersion.parse("HTTP/"+value)); + } + } + ); + } + + /** + * Loads a property file and converts parameters as necessary. + * + * @param file the file to load + * @param params Apache HttpClient parameter instance + */ + public static void load(String file, + final org.apache.http.params.HttpParams params){ + load(file, + new GenericHttpParams (){ + @Override + public void setParameter(String name, Object value) { + params.setParameter(name, value); + } + + @Override + public void setVersion(String name, String value) { + String parts[] = value.split("\\."); + if (parts.length != 2){ + throw new IllegalArgumentException("Version must have form m.n"); + } + params.setParameter(name, + new org.apache.http.HttpVersion( + Integer.parseInt(parts[0]), Integer.parseInt(parts[1]))); + } + } + ); + } + + private static void load(String file, GenericHttpParams params){ + log.info("Reading httpclient parameters from "+file); + File f = new File(file); + InputStream is = null; + Properties props = new Properties(); + try { + is = new FileInputStream(f); + props.load(is); + for (Map.Entry me : props.entrySet()){ + String key = (String) me.getKey(); + String value = (String)me.getValue(); + int typeSep = key.indexOf('$'); // $NON-NLS-1$ + try { + if (typeSep > 0){ + String type = key.substring(typeSep+1);// get past separator + String name=key.substring(0,typeSep); + log.info("Defining "+name+ " as "+value+" ("+type+")"); + if (type.equals("Integer")){ + params.setParameter(name, Integer.valueOf(value)); + } else if (type.equals("Long")){ + params.setParameter(name, Long.valueOf(value)); + } else if (type.equals("Boolean")){ + params.setParameter(name, Boolean.valueOf(value)); + } else if (type.equals("HttpVersion")){ // Commons HttpClient only + params.setVersion(name, value); + } else { + log.warn("Unexpected type: "+type+" for name "+name); + } + } else { + log.info("Defining "+key+ " as "+value); + params.setParameter(key, value); + } + } catch (Exception e) { + log.error("Error in property: "+key+"="+value+" "+e.toString()); + } + } + } catch (FileNotFoundException e) { + log.error("Problem loading properties "+e.toString()); + } catch (IOException e) { + log.error("Problem loading properties "+e.toString()); + } finally { + JOrphanUtils.closeQuietly(is); + } + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HttpWebdav.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HttpWebdav.java new file mode 100644 index 00000000000..d3bdee8ef93 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HttpWebdav.java @@ -0,0 +1,72 @@ +/* + * 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.protocol.http.sampler; + +import java.net.URI; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.jmeter.protocol.http.util.HTTPConstants; + +/** + * WebDav request + * @since 2.12 + */ +public final class HttpWebdav extends HttpEntityEnclosingRequestBase { + private static final Set WEBDAV_METHODS = + new HashSet(Arrays.asList(new String[] { + HTTPConstants.PROPFIND, + HTTPConstants.PROPPATCH, + HTTPConstants.MKCOL, + HTTPConstants.COPY, + HTTPConstants.MOVE, + HTTPConstants.LOCK, + HTTPConstants.UNLOCK, + HTTPConstants.REPORT, + HTTPConstants.MKCALENDAR + })); + + private String davMethod; + + /** + * + * @param davMethod method to use (has to be a Webdav method as identified by {@link #isWebdavMethod(String)}) + * @param uri {@link URI} to use + */ + public HttpWebdav(final String davMethod, final URI uri) { + super(); + this.davMethod = davMethod; + setURI(uri); + } + + @Override + public String getMethod() { + return davMethod; + } + + /** + * @param method Http Method + * @return true if method is a Webdav one + */ + public static boolean isWebdavMethod(String method) { + return WEBDAV_METHODS.contains(method); + } +} \ No newline at end of file diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/MeasuringConnectionManager.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/MeasuringConnectionManager.java new file mode 100644 index 00000000000..887e8bfb8cb --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/MeasuringConnectionManager.java @@ -0,0 +1,269 @@ +/* + * 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.protocol.http.sampler; + +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.HttpRequest; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.DnsResolver; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.impl.conn.PoolingClientConnectionManager; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.jmeter.samplers.SampleResult; + +import javax.net.ssl.SSLSession; +import java.io.IOException; +import java.net.InetAddress; +import java.util.concurrent.TimeUnit; + +/** + * Adapter for {@link PoolingClientConnectionManager} + * that wraps all connection requests into time-measured implementation a private + * MeasuringConnectionRequest + */ +public class MeasuringConnectionManager extends PoolingClientConnectionManager { + + private MeasuringConnectionRequest measuredConnection; + private SampleResult sample; + + public MeasuringConnectionManager(SchemeRegistry schemeRegistry, DnsResolver resolver) { + super(schemeRegistry, resolver); + } + + @Override + public ClientConnectionRequest requestConnection(final HttpRoute route, final Object state) { + ClientConnectionRequest res = super.requestConnection(route, state); + this.measuredConnection = new MeasuringConnectionRequest(res, this.sample); + return this.measuredConnection; + } + + public void setSample(SampleResult sample) { + this.sample = sample; + } + + /** + * An adapter class to pass {@link SampleResult} into {@link MeasuredConnection} + */ + private static class MeasuringConnectionRequest implements ClientConnectionRequest { + private final ClientConnectionRequest handler; + private final SampleResult sample; + + public MeasuringConnectionRequest(ClientConnectionRequest res, SampleResult sample) { + handler = res; + this.sample = sample; + } + + @Override + public ManagedClientConnection getConnection(long timeout, TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException { + ManagedClientConnection res = handler.getConnection(timeout, tunit); + return new MeasuredConnection(res, this.sample); + } + + @Override + public void abortRequest() { + handler.abortRequest(); + } + } + + /** + * An adapter for {@link ManagedClientConnection} + * that calls SampleResult.connectEnd after calling ManagedClientConnection.open + */ + private static class MeasuredConnection implements ManagedClientConnection { + private final ManagedClientConnection handler; + private final SampleResult sample; + + public MeasuredConnection(ManagedClientConnection res, SampleResult sample) { + handler = res; + this.sample = sample; + } + + @Override + public void open(HttpRoute route, HttpContext context, HttpParams params) throws IOException { + handler.open(route, context, params); + if (sample != null) { + sample.connectEnd(); + } + } + + // ================= all following methods just wraps handler's ================= + @Override + public boolean isSecure() { + return handler.isSecure(); + } + + @Override + public HttpRoute getRoute() { + return handler.getRoute(); + } + + @Override + public SSLSession getSSLSession() { + return handler.getSSLSession(); + } + + @Override + public void tunnelTarget(boolean secure, HttpParams params) throws IOException { + handler.tunnelTarget(secure, params); + } + + @Override + public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) throws IOException { + handler.tunnelProxy(next, secure, params); + } + + @Override + public void layerProtocol(HttpContext context, HttpParams params) throws IOException { + handler.layerProtocol(context, params); + } + + @Override + public void markReusable() { + handler.markReusable(); + } + + @Override + public void unmarkReusable() { + handler.unmarkReusable(); + } + + @Override + public boolean isMarkedReusable() { + return handler.isMarkedReusable(); + } + + @Override + public void setState(Object state) { + handler.setState(state); + } + + @Override + public Object getState() { + return handler.getState(); + } + + @Override + public void setIdleDuration(long duration, TimeUnit unit) { + handler.setIdleDuration(duration, unit); + } + + @Override + public void releaseConnection() throws IOException { + handler.releaseConnection(); + } + + @Override + public void abortConnection() throws IOException { + handler.abortConnection(); + } + + @Override + public boolean isResponseAvailable(int timeout) throws IOException { + return handler.isResponseAvailable(timeout); + } + + @Override + public void sendRequestHeader(HttpRequest request) throws HttpException, IOException { + handler.sendRequestHeader(request); + } + + @Override + public void sendRequestEntity(HttpEntityEnclosingRequest request) throws HttpException, IOException { + handler.sendRequestEntity(request); + } + + @Override + public HttpResponse receiveResponseHeader() throws HttpException, IOException { + return handler.receiveResponseHeader(); + } + + @Override + public void receiveResponseEntity(HttpResponse response) throws HttpException, IOException { + handler.receiveResponseEntity(response); + } + + @Override + public void flush() throws IOException { + handler.flush(); + } + + @Override + public InetAddress getLocalAddress() { + return handler.getLocalAddress(); + } + + @Override + public int getLocalPort() { + return handler.getLocalPort(); + } + + @Override + public InetAddress getRemoteAddress() { + return handler.getRemoteAddress(); + } + + @Override + public int getRemotePort() { + return handler.getRemotePort(); + } + + @Override + public void close() throws IOException { + handler.close(); + } + + @Override + public boolean isOpen() { + return handler.isOpen(); + } + + @Override + public boolean isStale() { + return handler.isStale(); + } + + @Override + public void setSocketTimeout(int timeout) { + handler.setSocketTimeout(timeout); + } + + @Override + public int getSocketTimeout() { + return handler.getSocketTimeout(); + } + + @Override + public void shutdown() throws IOException { + handler.shutdown(); + } + + @Override + public HttpConnectionMetrics getMetrics() { + return handler.getMetrics(); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/PostWriter.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/PostWriter.java new file mode 100644 index 00000000000..c03b17656e2 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/PostWriter.java @@ -0,0 +1,463 @@ +/* + * 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.protocol.http.sampler; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLConnection; + +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Class for setting the necessary headers for a POST request, and sending the + * body of the POST. + */ +public class PostWriter { + + private static final String DASH_DASH = "--"; // $NON-NLS-1$ + private static final byte[] DASH_DASH_BYTES = {'-', '-'}; + + /** The boundary string between multiparts */ + protected static final String BOUNDARY = "---------------------------7d159c1302d0y0"; // $NON-NLS-1$ + + private static final byte[] CRLF = { 0x0d, 0x0A }; + + public static final String ENCODING = "ISO-8859-1"; // $NON-NLS-1$ + + /** The form data that is going to be sent as url encoded */ + protected byte[] formDataUrlEncoded; + /** The form data that is going to be sent in post body */ + protected byte[] formDataPostBody; + /** The boundary string for multipart */ + private final String boundary; + + /** + * Constructor for PostWriter. + * Uses the PostWriter.BOUNDARY as the boundary string + * + */ + public PostWriter() { + this(BOUNDARY); + } + + /** + * Constructor for PostWriter + * + * @param boundary the boundary string to use as marker between multipart parts + */ + public PostWriter(String boundary) { + this.boundary = boundary; + } + + /** + * Send POST data from Entry to the open connection. + * + * @param connection + * the open connection to use for sending data + * @param sampler + * sampler to get information about what to send + * @return the post body sent. Actual file content is not returned, it is + * just shown as a placeholder text "actual file content" + * @throws IOException when writing data fails + */ + public String sendPostData(URLConnection connection, HTTPSamplerBase sampler) throws IOException { + // Buffer to hold the post body, except file content + StringBuilder postedBody = new StringBuilder(1000); + + HTTPFileArg files[] = sampler.getHTTPFiles(); + + String contentEncoding = sampler.getContentEncoding(); + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = ENCODING; + } + + // Check if we should do a multipart/form-data or an + // application/x-www-form-urlencoded post request + if(sampler.getUseMultipartForPost()) { + OutputStream out = connection.getOutputStream(); + + // Write the form data post body, which we have constructed + // in the setHeaders. This contains the multipart start divider + // and any form data, i.e. arguments + out.write(formDataPostBody); + // Retrieve the formatted data using the same encoding used to create it + postedBody.append(new String(formDataPostBody, contentEncoding)); + + // Add any files + for (int i=0; i < files.length; i++) { + HTTPFileArg file = files[i]; + // First write the start multipart file + byte[] header = file.getHeader().getBytes(); // TODO - charset? + out.write(header); + // Retrieve the formatted data using the same encoding used to create it + postedBody.append(new String(header)); // TODO - charset? + // Write the actual file content + writeFileToStream(file.getPath(), out); + // We just add placeholder text for file content + postedBody.append(""); // $NON-NLS-1$ + // Write the end of multipart file + byte[] fileMultipartEndDivider = getFileMultipartEndDivider(); + out.write(fileMultipartEndDivider); + // Retrieve the formatted data using the same encoding used to create it + postedBody.append(new String(fileMultipartEndDivider, ENCODING)); + if(i + 1 < files.length) { + out.write(CRLF); + postedBody.append(new String(CRLF, SampleResult.DEFAULT_HTTP_ENCODING)); + } + } + // Write end of multipart + byte[] multipartEndDivider = getMultipartEndDivider(); + out.write(multipartEndDivider); + postedBody.append(new String(multipartEndDivider, ENCODING)); + + out.flush(); + out.close(); + } + else { + // If there are no arguments, we can send a file as the body of the request + if(sampler.getArguments() != null && !sampler.hasArguments() && sampler.getSendFileAsPostBody()) { + OutputStream out = connection.getOutputStream(); + // we're sure that there is at least one file because of + // getSendFileAsPostBody method's return value. + HTTPFileArg file = files[0]; + writeFileToStream(file.getPath(), out); + out.flush(); + out.close(); + + // We just add placeholder text for file content + postedBody.append(""); // $NON-NLS-1$ + } + else if (formDataUrlEncoded != null){ // may be null for PUT + // In an application/x-www-form-urlencoded request, we only support + // parameters, no file upload is allowed + OutputStream out = connection.getOutputStream(); + out.write(formDataUrlEncoded); + out.flush(); + out.close(); + + postedBody.append(new String(formDataUrlEncoded, contentEncoding)); + } + } + return postedBody.toString(); + } + + public void setHeaders(URLConnection connection, HTTPSamplerBase sampler) throws IOException { + // Get the encoding to use for the request + String contentEncoding = sampler.getContentEncoding(); + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = ENCODING; + } + long contentLength = 0L; + HTTPFileArg files[] = sampler.getHTTPFiles(); + + // Check if we should do a multipart/form-data or an + // application/x-www-form-urlencoded post request + if(sampler.getUseMultipartForPost()) { + // Set the content type + connection.setRequestProperty( + HTTPConstants.HEADER_CONTENT_TYPE, + HTTPConstants.MULTIPART_FORM_DATA + "; boundary=" + getBoundary()); // $NON-NLS-1$ + + // Write the form section + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + // First the multipart start divider + bos.write(getMultipartDivider()); + // Add any parameters + PropertyIterator args = sampler.getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + // End the previous multipart + bos.write(CRLF); + // Write multipart for parameter + writeFormMultipart(bos, parameterName, arg.getValue(), contentEncoding, sampler.getDoBrowserCompatibleMultipart()); + } + // If there are any files, we need to end the previous multipart + if(files.length > 0) { + // End the previous multipart + bos.write(CRLF); + } + bos.flush(); + // Keep the content, will be sent later + formDataPostBody = bos.toByteArray(); + bos.close(); + contentLength = formDataPostBody.length; + + // Now we just construct any multipart for the files + // We only construct the file multipart start, we do not write + // the actual file content + for (int i=0; i < files.length; i++) { + HTTPFileArg file = files[i]; + // Write multipart for file + bos = new ByteArrayOutputStream(); + writeStartFileMultipart(bos, file.getPath(), file.getParamName(), file.getMimeType()); + bos.flush(); + String header = bos.toString(contentEncoding);// TODO is this correct? + // If this is not the first file we can't write its header now + // for simplicity we always save it, even if there is only one file + file.setHeader(header); + bos.close(); + contentLength += header.length(); + // Add also the length of the file content + File uploadFile = new File(file.getPath()); + contentLength += uploadFile.length(); + // And the end of the file multipart + contentLength += getFileMultipartEndDivider().length; + if(i+1 < files.length) { + contentLength += CRLF.length; + } + } + + // Add the end of multipart + contentLength += getMultipartEndDivider().length; + + // Set the content length + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_LENGTH, Long.toString(contentLength)); + + // Make the connection ready for sending post data + connection.setDoOutput(true); + connection.setDoInput(true); + } + else { + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a POST request + String contentTypeHeader = connection.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.length() > 0; + + // If there are no arguments, we can send a file as the body of the request + if(sampler.getArguments() != null && sampler.getArguments().getArgumentCount() == 0 && sampler.getSendFileAsPostBody()) { + // we're sure that there is one file because of + // getSendFileAsPostBody method's return value. + HTTPFileArg file = files[0]; + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + if(file.getMimeType() != null && file.getMimeType().length() > 0) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + } + // Create the content length we are going to write + File inputFile = new File(file.getPath()); + contentLength = inputFile.length(); + } + else { + // We create the post body content now, so we know the size + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + // If none of the arguments have a name specified, we + // just send all the values as the post body + String postBody = null; + if(!sampler.getSendParameterValuesAsPostBody()) { + // Set the content type + if(!hasContentTypeHeader) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + + // It is a normal post request, with parameter names and values + postBody = sampler.getQueryString(contentEncoding); + } + else { + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + // TODO: needs a multiple file upload scenerio + if(!hasContentTypeHeader) { + HTTPFileArg file = files.length > 0? files[0] : null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + // TODO: is this the correct default? + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + // Just append all the parameter values, and use that as the post body + StringBuilder postBodyBuffer = new StringBuilder(); + PropertyIterator args = sampler.getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + postBodyBuffer.append(arg.getEncodedValue(contentEncoding)); + } + postBody = postBodyBuffer.toString(); + } + + bos.write(postBody.getBytes(contentEncoding)); + bos.flush(); + bos.close(); + + // Keep the content, will be sent later + formDataUrlEncoded = bos.toByteArray(); + contentLength = bos.toByteArray().length; + } + + // Set the content length + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_LENGTH, Long.toString(contentLength)); + + // Make the connection ready for sending post data + connection.setDoOutput(true); + } + } + + /** + * Get the boundary string, used to separate multiparts + * + * @return the boundary string + */ + protected String getBoundary() { + return boundary; + } + + /** + * Get the bytes used to separate multiparts + * Encoded using ENCODING + * + * @return the bytes used to separate multiparts + * @throws IOException + */ + private byte[] getMultipartDivider() throws IOException { + return (DASH_DASH + getBoundary()).getBytes(ENCODING); + } + + /** + * Get the bytes used to end a file multipart + * Encoded using ENCODING + * + * @return the bytes used to end a file multipart + * @throws IOException + */ + private byte[] getFileMultipartEndDivider() throws IOException{ + byte[] ending = getMultipartDivider(); + byte[] completeEnding = new byte[ending.length + CRLF.length]; + System.arraycopy(CRLF, 0, completeEnding, 0, CRLF.length); + System.arraycopy(ending, 0, completeEnding, CRLF.length, ending.length); + return completeEnding; + } + + /** + * Get the bytes used to end the multipart request + * + * @return the bytes used to end the multipart request + */ + private byte[] getMultipartEndDivider(){ + byte[] ending = DASH_DASH_BYTES; + byte[] completeEnding = new byte[ending.length + CRLF.length]; + System.arraycopy(ending, 0, completeEnding, 0, ending.length); + System.arraycopy(CRLF, 0, completeEnding, ending.length, CRLF.length); + return completeEnding; + } + + /** + * Write the start of a file multipart, up to the point where the + * actual file content should be written + */ + private void writeStartFileMultipart(OutputStream out, String filename, + String nameField, String mimetype) + throws IOException { + write(out, "Content-Disposition: form-data; name=\""); // $NON-NLS-1$ + write(out, nameField); + write(out, "\"; filename=\"");// $NON-NLS-1$ + write(out, new File(filename).getName()); + writeln(out, "\""); // $NON-NLS-1$ + writeln(out, "Content-Type: " + mimetype); // $NON-NLS-1$ + writeln(out, "Content-Transfer-Encoding: binary"); // $NON-NLS-1$ + out.write(CRLF); + } + + /** + * Write the content of a file to the output stream + * + * @param filename the filename of the file to write to the stream + * @param out the stream to write to + * @throws IOException + */ + private static void writeFileToStream(String filename, OutputStream out) throws IOException { + byte[] buf = new byte[1024]; + // 1k - the previous 100k made no sense (there's tons of buffers + // elsewhere in the chain) and it caused OOM when many concurrent + // uploads were being done. Could be fixed by increasing the evacuation + // ratio in bin/jmeter[.bat], but this is better. + InputStream in = new BufferedInputStream(new FileInputStream(filename)); + int read; + boolean noException = false; + try { + while ((read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + noException = true; + } + finally { + if(!noException) { + // Exception in progress + JOrphanUtils.closeQuietly(in); + } else { + in.close(); + } + } + } + + /** + * Writes form data in multipart format. + */ + private void writeFormMultipart(OutputStream out, String name, String value, String charSet, + boolean browserCompatibleMultipart) + throws IOException { + writeln(out, "Content-Disposition: form-data; name=\"" + name + "\""); // $NON-NLS-1$ // $NON-NLS-2$ + if (!browserCompatibleMultipart){ + writeln(out, "Content-Type: text/plain; charset=" + charSet); // $NON-NLS-1$ + writeln(out, "Content-Transfer-Encoding: 8bit"); // $NON-NLS-1$ + } + out.write(CRLF); + out.write(value.getBytes(charSet)); + out.write(CRLF); + // Write boundary end marker + out.write(getMultipartDivider()); + } + + private void write(OutputStream out, String value) + throws UnsupportedEncodingException, IOException + { + out.write(value.getBytes(ENCODING)); + } + + + private void writeln(OutputStream out, String value) + throws UnsupportedEncodingException, IOException + { + out.write(value.getBytes(ENCODING)); + out.write(CRLF); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/PutWriter.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/PutWriter.java new file mode 100644 index 00000000000..3e91554fe56 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/PutWriter.java @@ -0,0 +1,113 @@ +/* + * 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.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.URLConnection; + +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.testelement.property.PropertyIterator; + +/** + * Class for setting the necessary headers for a PUT request, and sending the + * body of the PUT. + */ +public class PutWriter extends PostWriter { + /** + * Constructor for PutWriter. + */ + public PutWriter() { + // Put request does not use multipart, so no need for boundary + super(null); + } + + @Override + public void setHeaders(URLConnection connection, HTTPSamplerBase sampler) throws IOException { + // Get the encoding to use for the request + String contentEncoding = sampler.getContentEncoding(); + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = ENCODING; + } + long contentLength = 0L; + boolean hasPutBody = false; + + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a PUT request + String contentTypeHeader = connection.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.length() > 0; + + HTTPFileArg files[] = sampler.getHTTPFiles(); + + // If there are no arguments, we can send a file as the body of the request + if(sampler.getArguments() != null && sampler.getArguments().getArgumentCount() == 0 && sampler.getSendFileAsPostBody()) { + // If getSendFileAsPostBody returned true, it's sure that file is not null + HTTPFileArg file = files[0]; + hasPutBody = true; + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + if(file.getMimeType().length() > 0) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + } + + // Create the content length we are going to write + File inputFile = new File(file.getPath()); + contentLength = inputFile.length(); + } + else if(sampler.getSendParameterValuesAsPostBody()) { + hasPutBody = true; + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + if(!hasContentTypeHeader && files.length == 1 && files[0].getMimeType().length() > 0) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, files[0].getMimeType()); + } + + // We create the post body content now, so we know the size + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + // Just append all the parameter values, and use that as the put body + StringBuilder putBodyBuffer = new StringBuilder(); + PropertyIterator args = sampler.getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + putBodyBuffer.append(arg.getEncodedValue(contentEncoding)); + } + + bos.write(putBodyBuffer.toString().getBytes(contentEncoding)); + bos.flush(); + bos.close(); + + // Keep the content, will be sent later + formDataUrlEncoded = bos.toByteArray(); + contentLength = bos.toByteArray().length; + } + if(hasPutBody) { + // Set the content length + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_LENGTH, Long.toString(contentLength)); + + // Make the connection ready for sending post data + connection.setDoOutput(true); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/SoapSampler.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/SoapSampler.java new file mode 100644 index 00000000000..1f75e2f9570 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/SoapSampler.java @@ -0,0 +1,377 @@ +/* + * 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.protocol.http.sampler; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.zip.GZIPInputStream; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.RequestEntity; +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Commons HTTPClient based soap sampler + */ +public class SoapSampler extends HTTPSampler2 implements Interruptible { // Implemented by parent class + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + public static final String XML_DATA = "HTTPSamper.xml_data"; //$NON-NLS-1$ + + public static final String URL_DATA = "SoapSampler.URL_DATA"; //$NON-NLS-1$ + + public static final String SOAP_ACTION = "SoapSampler.SOAP_ACTION"; //$NON-NLS-1$ + + public static final String SEND_SOAP_ACTION = "SoapSampler.SEND_SOAP_ACTION"; //$NON-NLS-1$ + + public static final String XML_DATA_FILE = "SoapSampler.xml_data_file"; //$NON-NLS-1$ + + private static final String DOUBLE_QUOTE = "\""; //$NON-NLS-1$ + + private static final String SOAPACTION = "SOAPAction"; //$NON-NLS-1$ + + private static final String ENCODING = "utf-8"; //$NON-NLS-1$ TODO should this be variable? + + private static final String DEFAULT_CONTENT_TYPE = "text/xml"; //$NON-NLS-1$ + + public void setXmlData(String data) { + setProperty(XML_DATA, data); + } + + public String getXmlData() { + return getPropertyAsString(XML_DATA); + } + + /** + * it's kinda obvious, but we state it anyways. Set the xml file with a + * string path. + * + * @param filename path to the xml file + */ + public void setXmlFile(String filename) { + setProperty(XML_DATA_FILE, filename); + } + + /** + * Get the file location of the xml file. + * + * @return String file path. + */ + public String getXmlFile() { + return getPropertyAsString(XML_DATA_FILE); + } + + public String getURLData() { + return getPropertyAsString(URL_DATA); + } + + public void setURLData(String url) { + setProperty(URL_DATA, url); + } + + public String getSOAPAction() { + return getPropertyAsString(SOAP_ACTION); + } + + public String getSOAPActionQuoted() { + String action = getSOAPAction(); + StringBuilder sb = new StringBuilder(action.length()+2); + sb.append(DOUBLE_QUOTE); + sb.append(action); + sb.append(DOUBLE_QUOTE); + return sb.toString(); + } + + public void setSOAPAction(String action) { + setProperty(SOAP_ACTION, action); + } + + public boolean getSendSOAPAction() { + return getPropertyAsBoolean(SEND_SOAP_ACTION); + } + + public void setSendSOAPAction(boolean action) { + setProperty(SEND_SOAP_ACTION, String.valueOf(action)); + } + + protected int setPostHeaders(PostMethod post) { + int length=0;// Take length from file + if (getHeaderManager() != null) { + // headerManager was set, so let's set the connection + // to use it. + HeaderManager mngr = getHeaderManager(); + int headerSize = mngr.size(); + for (int idx = 0; idx < headerSize; idx++) { + Header hd = mngr.getHeader(idx); + if (HTTPConstants.HEADER_CONTENT_LENGTH.equalsIgnoreCase(hd.getName())) {// Use this to override file length + length = Integer.parseInt(hd.getValue()); + break; + } + // All the other headers are set up by HTTPSampler2.setupConnection() + } + } else { + // otherwise we use "text/xml" as the default + post.setRequestHeader(HTTPConstants.HEADER_CONTENT_TYPE, DEFAULT_CONTENT_TYPE); //$NON-NLS-1$ + } + if (getSendSOAPAction()) { + post.setRequestHeader(SOAPACTION, getSOAPActionQuoted()); + } + return length; + } + + /** + * Send POST data from Entry to the open connection. + * + * @param post + * @throws IOException if an I/O exception occurs + */ + private String sendPostData(PostMethod post, final int length) { + // Buffer to hold the post body, except file content + StringBuilder postedBody = new StringBuilder(1000); + final String xmlFile = getXmlFile(); + if (xmlFile != null && xmlFile.length() > 0) { + File xmlFileAsFile = new File(xmlFile); + if(!(xmlFileAsFile.exists() && xmlFileAsFile.canRead())) { + throw new IllegalArgumentException(JMeterUtils.getResString("soap_sampler_file_invalid") // $NON-NLS-1$ + + xmlFileAsFile.getAbsolutePath()); + } + // We just add placeholder text for file content + postedBody.append("Filename: ").append(xmlFile).append("\n"); + postedBody.append(""); + post.setRequestEntity(new RequestEntity() { + @Override + public boolean isRepeatable() { + return true; + } + + @Override + public void writeRequest(OutputStream out) throws IOException { + InputStream in = null; + try{ + in = new BufferedInputStream(new FileInputStream(xmlFile)); + IOUtils.copy(in, out); + out.flush(); + } finally { + IOUtils.closeQuietly(in); + } + } + + @Override + public long getContentLength() { + switch(length){ + case -1: + return -1; + case 0: // No header provided + return new File(xmlFile).length(); + default: + return length; + } + } + + @Override + public String getContentType() { + // TODO do we need to add a charset for the file contents? + return DEFAULT_CONTENT_TYPE; // $NON-NLS-1$ + } + }); + } else { + postedBody.append(getXmlData()); + post.setRequestEntity(new RequestEntity() { + @Override + public boolean isRepeatable() { + return true; + } + + @Override + public void writeRequest(OutputStream out) throws IOException { + // charset must agree with content-type below + IOUtils.write(getXmlData(), out, ENCODING); // $NON-NLS-1$ + out.flush(); + } + + @Override + public long getContentLength() { + try { + return getXmlData().getBytes(ENCODING).length; // so we don't generate chunked encoding + } catch (UnsupportedEncodingException e) { + log.warn(e.getLocalizedMessage()); + return -1; // will use chunked encoding + } + } + + @Override + public String getContentType() { + return DEFAULT_CONTENT_TYPE+"; charset="+ENCODING; // $NON-NLS-1$ + } + }); + } + return postedBody.toString(); + } + + @Override + protected HTTPSampleResult sample(URL url, String method, boolean areFollowingRedirect, int frameDepth) { + + String urlStr = url.toString(); + + log.debug("Start : sample " + urlStr); + + PostMethod httpMethod; + httpMethod = new PostMethod(urlStr); + + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(false); + + res.setSampleLabel(urlStr); // May be replaced later + res.setHTTPMethod(HTTPConstants.POST); + res.setURL(url); + res.sampleStart(); // Count the retries as well in the time + HttpClient client = null; + InputStream instream = null; + try { + int content_len = setPostHeaders(httpMethod); + client = setupConnection(url, httpMethod, res); + setSavedClient(client); + + res.setQueryString(sendPostData(httpMethod,content_len)); + int statusCode = client.executeMethod(httpMethod); + // Some headers are set by executeMethod() + res.setRequestHeaders(getConnectionHeaders(httpMethod)); + + // Request sent. Now get the response: + instream = httpMethod.getResponseBodyAsStream(); + + if (instream != null) {// will be null for HEAD + + org.apache.commons.httpclient.Header responseHeader = httpMethod.getResponseHeader(HTTPConstants.HEADER_CONTENT_ENCODING); + if (responseHeader != null && HTTPConstants.ENCODING_GZIP.equals(responseHeader.getValue())) { + instream = new GZIPInputStream(instream); + } + + //int contentLength = httpMethod.getResponseContentLength();Not visible ... + //TODO size ouststream according to actual content length + ByteArrayOutputStream outstream = new ByteArrayOutputStream(4 * 1024); + //contentLength > 0 ? contentLength : DEFAULT_INITIAL_BUFFER_SIZE); + byte[] buffer = new byte[4096]; + int len; + boolean first = true;// first response + while ((len = instream.read(buffer)) > 0) { + if (first) { // save the latency + res.latencyEnd(); + first = false; + } + outstream.write(buffer, 0, len); + } + + res.setResponseData(outstream.toByteArray()); + outstream.close(); + + } + + res.sampleEnd(); + // Done with the sampling proper. + + // Now collect the results into the HTTPSampleResult: + + res.setSampleLabel(httpMethod.getURI().toString()); + // Pick up Actual path (after redirects) + + res.setResponseCode(Integer.toString(statusCode)); + res.setSuccessful(isSuccessCode(statusCode)); + + res.setResponseMessage(httpMethod.getStatusText()); + + // Set up the defaults (may be overridden below) + res.setDataEncoding(ENCODING); + res.setContentType(DEFAULT_CONTENT_TYPE); + String ct = null; + org.apache.commons.httpclient.Header h + = httpMethod.getResponseHeader(HTTPConstants.HEADER_CONTENT_TYPE); + if (h != null)// Can be missing, e.g. on redirect + { + ct = h.getValue(); + res.setContentType(ct);// e.g. text/html; charset=ISO-8859-1 + res.setEncodingAndType(ct); + } + + res.setResponseHeaders(getResponseHeaders(httpMethod)); + if (res.isRedirect()) { + res.setRedirectLocation(httpMethod.getResponseHeader(HTTPConstants.HEADER_LOCATION).getValue()); + } + + // If we redirected automatically, the URL may have changed + if (getAutoRedirects()) { + res.setURL(new URL(httpMethod.getURI().toString())); + } + + // Store any cookies received in the cookie manager: + saveConnectionCookies(httpMethod, res.getURL(), getCookieManager()); + + // Save cache information + final CacheManager cacheManager = getCacheManager(); + if (cacheManager != null){ + cacheManager.saveDetails(httpMethod, res); + } + + // Follow redirects and download page resources if appropriate: + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + log.debug("End : sample"); + httpMethod.releaseConnection(); + return res; + } catch (IllegalArgumentException e)// e.g. some kinds of invalid URL + { + res.sampleEnd(); + errorResult(e, res); + return res; + } catch (IOException e) { + res.sampleEnd(); + errorResult(e, res); + return res; + } finally { + JOrphanUtils.closeQuietly(instream); + setSavedClient(null); + httpMethod.releaseConnection(); + } + } + + @Override + public URL getUrl() throws MalformedURLException { + return new URL(getURLData()); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/sampler/WebServiceSampler.java b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/WebServiceSampler.java new file mode 100644 index 00000000000..c370b8d4008 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/sampler/WebServiceSampler.java @@ -0,0 +1,710 @@ +/* + * 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.protocol.http.sampler; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Hashtable; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; + +import javax.xml.parsers.DocumentBuilder; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.JMeter; +import org.apache.jmeter.gui.JMeterFileFilter; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Authorization; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.DOMPool; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.io.TextFile; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.soap.Envelope; +import org.apache.soap.SOAPException; +import org.apache.soap.rpc.SOAPContext; +import org.apache.soap.transport.http.SOAPHTTPConnection; +import org.apache.soap.util.xml.XMLParserUtils; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Sampler to handle Web Service requests. It uses Apache SOAP drivers to + * perform the XML generation, connection, SOAP encoding and other SOAP + * functions. + *

+ * Created on: Jun 26, 2003 + * + */ +public class WebServiceSampler extends HTTPSamplerBase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + //+ JMX file attribute names - do not change! + private static final String XML_DATA = "HTTPSamper.xml_data"; //$NON-NLS-1$ + + private static final String SOAP_ACTION = "Soap.Action"; //$NON-NLS-1$ + + private static final String XML_DATA_FILE = "WebServiceSampler.xml_data_file"; //$NON-NLS-1$ + + private static final String XML_PATH_LOC = "WebServiceSampler.xml_path_loc"; //$NON-NLS-1$ + + private static final String MEMORY_CACHE = "WebServiceSampler.memory_cache"; //$NON-NLS-1$ + + private static final String MAINTAIN_SESSION = "WebServiceSampler.maintain_session"; //$NON-NLS-1$ + + private static final String READ_RESPONSE = "WebServiceSampler.read_response"; //$NON-NLS-1$ + + private static final String USE_PROXY = "WebServiceSampler.use_proxy"; //$NON-NLS-1$ + + private static final String PROXY_HOST = "WebServiceSampler.proxy_host"; //$NON-NLS-1$ + + private static final String PROXY_PORT = "WebServiceSampler.proxy_port"; //$NON-NLS-1$ + + private static final String WSDL_URL = "WebserviceSampler.wsdl_url"; //$NON-NLS-1$ + + private static final String TIMEOUT = "WebserviceSampler.timeout"; //$NON-NLS-1$ + //- JMX file attribute names - do not change! + + private static final String PROXY_USER = + JMeterUtils.getPropDefault(JMeter.HTTP_PROXY_USER,""); // $NON-NLS-1$ + + private static final String PROXY_PASS = + JMeterUtils.getPropDefault(JMeter.HTTP_PROXY_PASS,""); // $NON-NLS-1$ + + private static final String ENCODING = "UTF-8"; // $NON-NLS-1$ TODO should this be a variable? + + public static final boolean MAINTAIN_SESSION_DEFAULT = true; + + /* + * Random class for generating random numbers. + */ + private final Random RANDOM = new Random(); + + private String fileContents = null; + + /** + * Set the path where XML messages are stored for random selection. + * + * @param path where XML messages are stored + */ + public void setXmlPathLoc(String path) { + setProperty(XML_PATH_LOC, path); + } + + /** + * Get the path where XML messages are stored. This is the directory where + * JMeter will randomly select a file. + * + * @return path where XML messages are stored + */ + public String getXmlPathLoc() { + return getPropertyAsString(XML_PATH_LOC); + } + + /** + * it's kinda obvious, but we state it anyways. Set the xml file with a + * string path. + * + * @param filename path to xml file + */ + public void setXmlFile(String filename) { + setProperty(XML_DATA_FILE, filename); + } + + /** + * Get the file location of the xml file. + * + * @return String file path. + */ + public String getXmlFile() { + return getPropertyAsString(XML_DATA_FILE); + } + + /** + * Method is used internally to check if a random file should be used for + * the message. Messages must be valid. This is one way to load test with + * different messages. The limitation of this approach is parsing XML takes + * CPU resources, so it could affect JMeter GUI responsiveness. + * + * @return String filename + */ + protected String getRandomFileName() { + if (this.getXmlPathLoc() != null) { + File src = new File(this.getXmlPathLoc()); + if (src.isDirectory() && src.list() != null) { + File [] fileList = src.listFiles(new JMeterFileFilter(new String[] { ".xml" }, false)); + File one = fileList[RANDOM.nextInt(fileList.length)]; + // return the absolutePath of the file + return one.getAbsolutePath(); + } + return getXmlFile(); + } + return getXmlFile(); + } + + /** + * Set the XML data. + * + * @param data xml data + */ + public void setXmlData(String data) { + setProperty(XML_DATA, data); + } + + /** + * Get the XML data as a string. + * + * @return String data + */ + public String getXmlData() { + return getPropertyAsString(XML_DATA); + } + + /** + * Set the soap action which should be in the form of an URN. + * + * @param data soap action + */ + public void setSoapAction(String data) { + setProperty(SOAP_ACTION, data); + } + + /** + * Return the soap action string. + * + * @return String soap action + */ + public String getSoapAction() { + return getPropertyAsString(SOAP_ACTION); + } + + /** + * Set the maintain session option. + * + * @param maintainSession flag whether to maintain a session + */ + public void setMaintainSession(boolean maintainSession) { + setProperty(MAINTAIN_SESSION, maintainSession, MAINTAIN_SESSION_DEFAULT); + } + + /** + * Get the maintain session option. + * + * @return flag whether to maintain a session + */ + public boolean getMaintainSession() { + return getPropertyAsBoolean(MAINTAIN_SESSION, MAINTAIN_SESSION_DEFAULT); + } + + /** + * Set the memory cache. + * + * @param cache flag whether to use the memory cache + */ + public void setMemoryCache(boolean cache) { + setProperty(MEMORY_CACHE, String.valueOf(cache)); + } + + /** + * Get the memory cache. + * + * @return flag whether to use the memory cache + */ + public boolean getMemoryCache() { + return getPropertyAsBoolean(MEMORY_CACHE); + } + + /** + * Set whether the sampler should read the response or not. + * + * @param read + * flag whether the response should be read + */ + public void setReadResponse(boolean read) { + setProperty(READ_RESPONSE, String.valueOf(read)); + } + + /** + * Return whether or not to read the response. + * + * @return flag whether the response should be read + */ + public boolean getReadResponse() { + return this.getPropertyAsBoolean(READ_RESPONSE); + } + + /** + * Set whether or not to use a proxy + * + * @param proxy flag whether to use a proxy + */ + public void setUseProxy(boolean proxy) { + setProperty(USE_PROXY, String.valueOf(proxy)); + } + + /** + * Return whether or not to use proxy + * + * @return true if a proxy should be used + */ + public boolean getUseProxy() { + return this.getPropertyAsBoolean(USE_PROXY); + } + + /** + * Set the proxy hostname + * + * @param host the hostname of the proxy + */ + public void setProxyHost(String host) { + setProperty(PROXY_HOST, host); + } + + /** + * Return the proxy hostname + * + * @return the proxy hostname + */ + @Override + public String getProxyHost() { + this.checkProxy(); + return this.getPropertyAsString(PROXY_HOST); + } + + /** + * Set the proxy port + * + * @param port the port of the proxy + */ + public void setProxyPort(String port) { + setProperty(PROXY_PORT, port); + } + + /** + * Return the proxy port + * + * @return the proxy port + */ + public int getProxyPort() { + this.checkProxy(); + return this.getPropertyAsInt(PROXY_PORT); + } + + /** + * + * @param url the URL of the WSDL + */ + public void setWsdlURL(String url) { + this.setProperty(WSDL_URL, url); + } + + /** + * method returns the WSDL URL + * + * @return the WSDL URL + */ + public String getWsdlURL() { + return getPropertyAsString(WSDL_URL); + } + + /* + * The method will check to see if JMeter was started in NonGui mode. If it + * was, it will try to pick up the proxy host and port values if they were + * passed to JMeter.java. + */ + private void checkProxy() { + if (JMeter.isNonGUI()) { + this.setUseProxy(true); + // we check to see if the proxy host and port are set + String port = this.getPropertyAsString(PROXY_PORT); + String host = this.getPropertyAsString(PROXY_HOST); + if (host == null || host.length() == 0) { + // it's not set, lets check if the user passed + // proxy host and port from command line + host = System.getProperty("http.proxyHost"); + if (host != null) { + this.setProxyHost(host); + } + } + if (port == null || port.length() == 0) { + // it's not set, lets check if the user passed + // proxy host and port from command line + port = System.getProperty("http.proxyPort"); + if (port != null) { + this.setProxyPort(port); + } + } + } + } + + /* + * This method uses Apache soap util to create the proper DOM elements. + * + * @return Element + */ + private org.w3c.dom.Element createDocument() throws SAXException, IOException { + Document doc = null; + String next = this.getRandomFileName();//get filename or "" + + /* Note that the filename is also used as a key to the pool (if used) + ** Documents provided in the testplan are not currently pooled, as they may change + * between samples. + */ + + if (next.length() > 0 && getMemoryCache()) { + doc = DOMPool.getDocument(next); + if (doc == null){ + doc = openDocument(next); + if (doc != null) {// we created the document + DOMPool.putDocument(next, doc); + } + } + } else { // Must be local content - or not using pool + doc = openDocument(next); + } + + if (doc == null) { + return null; + } + return doc.getDocumentElement(); + } + + /** + * Open the file and create a Document. + * + * @param file - input filename or empty if using data from tesplan + * @return Document + * @throws IOException + * @throws SAXException + */ + private Document openDocument(String file) throws SAXException, IOException { + /* + * Consider using Apache commons pool to create a pool of document + * builders or make sure XMLParserUtils creates builders efficiently. + */ + DocumentBuilder XDB = XMLParserUtils.getXMLDocBuilder(); + XDB.setErrorHandler(null);//Suppress messages to stdout + + Document doc = null; + // if either a file or path location is given, + // get the file object. + if (file.length() > 0) {// we have a file + if (this.getReadResponse()) { + TextFile tfile = new TextFile(file); + fileContents = tfile.getText(); + } + InputStream fileInputStream = null; + try { + fileInputStream = new BufferedInputStream(new FileInputStream(file)); + doc = XDB.parse(fileInputStream); + } finally { + JOrphanUtils.closeQuietly(fileInputStream); + } + } else {// must be a "here" document + fileContents = getXmlData(); + if (fileContents != null && fileContents.length() > 0) { + doc = XDB.parse(new InputSource(new StringReader(fileContents))); + } else { + log.warn("No post data provided!"); + } + } + return doc; + } + + /* + * Required to satisfy HTTPSamplerBase Should not be called, as we override + * sample() + */ + + @Override + protected HTTPSampleResult sample(URL u, String s, boolean b, int i) { + throw new RuntimeException("Not implemented - should not be called"); + } + + /** + * Sample the URL using Apache SOAP driver. Implementation note for myself + * and those that are curious. Current logic marks the end after the + * response has been read. If read response is set to false, the buffered + * reader will read, but do nothing with it. Essentially, the stream from + * the server goes into the ether. + */ + @Override + public SampleResult sample() { + SampleResult result = new SampleResult(); + result.setSuccessful(false); // Assume it will fail + result.setResponseCode("000"); // ditto $NON-NLS-1$ + result.setSampleLabel(getName()); + try { + result.setURL(this.getUrl()); + org.w3c.dom.Element rdoc = createDocument(); + if (rdoc == null) { + throw new SOAPException("Could not create document", null); + } + // set the response defaults + result.setDataEncoding(ENCODING); + result.setContentType("text/xml"); // $NON-NLS-1$ + result.setDataType(SampleResult.TEXT); + result.setSamplerData(fileContents);// WARNING - could be large + + Envelope msgEnv = Envelope.unmarshall(rdoc); + result.sampleStart(); + SOAPHTTPConnection spconn = null; + // if a blank HeaderManager exists, try to + // get the SOAPHTTPConnection. After the first + // request, there should be a connection object + // stored with the cookie header info. + if (this.getHeaderManager() != null && this.getHeaderManager().getSOAPHeader() != null) { + spconn = (SOAPHTTPConnection) this.getHeaderManager().getSOAPHeader(); + } else { + spconn = new SOAPHTTPConnection(); + } + spconn.setTimeout(getTimeoutAsInt()); + + // set the auth. thanks to KiYun Roe for contributing the patch + // I cleaned up the patch slightly. 5-26-05 + if (getAuthManager() != null) { + if (getAuthManager().getAuthForURL(getUrl()) != null) { + AuthManager authmanager = getAuthManager(); + Authorization auth = authmanager.getAuthForURL(getUrl()); + spconn.setUserName(auth.getUser()); + spconn.setPassword(auth.getPass()); + } else { + log.warn("the URL for the auth was null." + " Username and password not set"); + } + } + // check the proxy + String phost = ""; + int pport = 0; + // if use proxy is set, we try to pick up the + // proxy host and port from either the text + // fields or from JMeterUtil if they were passed + // from command line + if (this.getUseProxy()) { + if (this.getProxyHost().length() > 0 && this.getProxyPort() > 0) { + phost = this.getProxyHost(); + pport = this.getProxyPort(); + } else { + if (System.getProperty("http.proxyHost") != null || System.getProperty("http.proxyPort") != null) { + phost = System.getProperty("http.proxyHost"); + pport = Integer.parseInt(System.getProperty("http.proxyPort")); + } + } + // if for some reason the host is blank and the port is + // zero, the sampler will fail silently + if (phost.length() > 0 && pport > 0) { + spconn.setProxyHost(phost); + spconn.setProxyPort(pport); + if (PROXY_USER.length()>0 && PROXY_PASS.length()>0){ + spconn.setProxyUserName(PROXY_USER); + spconn.setProxyPassword(PROXY_PASS); + } + } + } + + HeaderManager headerManager = this.getHeaderManager(); + Hashtable reqHeaders = null; + if(headerManager != null) { + int size = headerManager.getHeaders().size(); + reqHeaders = new Hashtable(size); + for (int i = 0; i < size; i++) { + Header header = headerManager.get(i); + reqHeaders.put(header.getName(), header.getValue()); + } + } + spconn.setMaintainSession(getMaintainSession()); + spconn.send(this.getUrl(), this.getSoapAction(), reqHeaders, msgEnv, + null, new SOAPContext()); + + @SuppressWarnings("unchecked") // API uses raw types + final Map headers = spconn.getHeaders(); + result.setResponseHeaders(convertSoapHeaders(headers)); + + if (this.getHeaderManager() != null) { + this.getHeaderManager().setSOAPHeader(spconn); + } + + BufferedReader br = null; + if (spconn.receive() != null) { + br = spconn.receive(); + SOAPContext sc = spconn.getResponseSOAPContext(); + // Set details from the actual response + // Needs to be done before response can be stored + final String contentType = sc.getContentType(); + result.setContentType(contentType); + result.setEncodingAndType(contentType); + int length=0; + if (getReadResponse()) { + StringWriter sw = new StringWriter(); + length=IOUtils.copy(br, sw); + result.sampleEnd(); + result.setResponseData(sw.toString().getBytes(result.getDataEncodingWithDefault())); + } else { + // by not reading the response + // for real, it improves the + // performance on slow clients + length=br.read(); + result.sampleEnd(); + result.setResponseData(JMeterUtils.getResString("read_response_message"), null); //$NON-NLS-1$ + } + // It is not possible to access the actual HTTP response code, so we assume no data means failure + if (length > 0){ + result.setSuccessful(true); + result.setResponseCodeOK(); + result.setResponseMessageOK(); + } else { + result.setSuccessful(false); + result.setResponseCode("999"); + result.setResponseMessage("Empty response"); + } + } else { + result.sampleEnd(); + result.setSuccessful(false); + final String contentType = spconn.getResponseSOAPContext().getContentType(); + result.setContentType(contentType); + result.setEncodingAndType(contentType); + result.setResponseData(spconn.getResponseSOAPContext().toString().getBytes(result.getDataEncodingWithDefault())); + } + if (br != null) { + br.close(); + } + // reponse code doesn't really apply, since + // the soap driver doesn't provide a + // response code + } catch (IllegalArgumentException exception){ + String message = exception.getMessage(); + log.warn(message); + result.setResponseMessage(message); + } catch (SAXException exception) { + log.warn(exception.toString()); + result.setResponseMessage(exception.getMessage()); + } catch (SOAPException exception) { + log.warn(exception.toString()); + result.setResponseMessage(exception.getMessage()); + } catch (MalformedURLException exception) { + String message = exception.getMessage(); + log.warn(message); + result.setResponseMessage(message); + } catch (IOException exception) { + String message = exception.getMessage(); + log.warn(message); + result.setResponseMessage(message); + } catch (NoClassDefFoundError error){ + log.error("Missing class: ",error); + result.setResponseMessage(error.toString()); + } catch (Exception exception) { + if ("javax.mail.MessagingException".equals(exception.getClass().getName())){ + log.warn(exception.toString()); + result.setResponseMessage(exception.getMessage()); + } else { + log.error("Problem processing the SOAP request", exception); + result.setResponseMessage(exception.toString()); + } + } finally { + // Make sure the sample start time and sample end time are recorded + // in order not to confuse the statistics calculation methods: if + // an error occurs and an exception is thrown it is possible that + // the result.sampleStart() or result.sampleEnd() won't be called + if (result.getStartTime() == 0) + { + result.sampleStart(); + } + if (result.getEndTime() == 0) + { + result.sampleEnd(); + } + } + return result; + } + + /** + * We override this to prevent the wrong encoding and provide no + * implementation. We want to reuse the other parts of HTTPSampler, but not + * the connection. The connection is handled by the Apache SOAP driver. + */ + @Override + public void addEncodedArgument(String name, String value, String metaData) { + } + + public String convertSoapHeaders(Map ht) { + StringBuilder buf = new StringBuilder(); + for (Entry entry : ht.entrySet()) { + buf.append(entry.getKey()).append("=").append(entry.getValue()).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ + } + return buf.toString(); + } + +// /** +// * Process headerLines +// * @param en enumeration of Strings +// * @return String containing the lines +// */ +// private String convertSoapHeaders(Enumeration en) { +// StringBuilder buf = new StringBuilder(100); +// while (en.hasMoreElements()) { +// buf.append(en.nextElement()).append("\n"); //$NON-NLS-1$ +// } +// return buf.toString(); +// } + + public String getTimeout() { + return getPropertyAsString(TIMEOUT); + } + + public int getTimeoutAsInt() { + return getPropertyAsInt(TIMEOUT); + } + + public void setTimeout(String text) { + setProperty(TIMEOUT, text); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testEnded() + */ + @Override + public void testEnded() { + DOMPool.clear(); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testEnded(java.lang.String) + */ + @Override + public void testEnded(String host) { + testEnded(); + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/Base64Encoder.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/Base64Encoder.java new file mode 100644 index 00000000000..b7e9f2c5cde --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/Base64Encoder.java @@ -0,0 +1,83 @@ +/* + * 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.protocol.http.util; + +/** + * This class provides an implementation of Base64 encoding without relying on + * the the sun.* packages. + * + * @version $Revision$ + */ +public final class Base64Encoder { + private static final char[] pem_array = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47 }; + + private static final char eq = 61; + + /** + * Private constructor to prevent instantiation. + */ + private Base64Encoder() { + } + + public static String encode(String s) { + return encode(s.getBytes()); // TODO - charset? + } + + public static String encode(byte[] bs) { + StringBuilder out = new StringBuilder(); + int bl = bs.length; + for (int i = 0; i < bl; i += 3) { + out.append(encodeAtom(bs, i, (bl - i))); + } + return out.toString(); + } + + public static String encodeAtom(byte[] b, int strt, int left) { + StringBuilder out = new StringBuilder(); + if (left == 1) { + byte b1 = b[strt]; + int k = 0; + out.append(String.valueOf(pem_array[b1 >>> 2 & 63])); + out.append(String.valueOf(pem_array[(b1 << 4 & 48) + (k >>> 4 & 15)])); + out.append(String.valueOf(eq)); + out.append(String.valueOf(eq)); + return out.toString(); + } + if (left == 2) { + byte b2 = b[strt]; + byte b4 = b[strt + 1]; + int l = 0; + out.append(String.valueOf(pem_array[b2 >>> 2 & 63])); + out.append(String.valueOf(pem_array[(b2 << 4 & 48) + (b4 >>> 4 & 15)])); + out.append(String.valueOf(pem_array[(b4 << 2 & 60) + (l >>> 6 & 3)])); + out.append(String.valueOf(eq)); + return out.toString(); + } + byte b3 = b[strt]; + byte b5 = b[strt + 1]; + byte b6 = b[strt + 2]; + out.append(String.valueOf(pem_array[b3 >>> 2 & 63])); + out.append(String.valueOf(pem_array[(b3 << 4 & 48) + (b5 >>> 4 & 15)])); + out.append(String.valueOf(pem_array[(b5 << 2 & 60) + (b6 >>> 6 & 3)])); + out.append(String.valueOf(pem_array[b6 & 63])); + return out.toString(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/ConversionUtils.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/ConversionUtils.java new file mode 100644 index 00000000000..7bd2c7a11e2 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/ConversionUtils.java @@ -0,0 +1,273 @@ +/* + * 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.protocol.http.util; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; + +// @see TestHTTPUtils for unit tests + +/** + * General purpose conversion utilities related to HTTP/HTML + */ +public class ConversionUtils { + + private static final String CHARSET_EQ = "charset="; // $NON-NLS-1$ + private static final int CHARSET_EQ_LEN = CHARSET_EQ.length(); + + private static final String SLASHDOTDOT = "/.."; // $NON-NLS-1$ + private static final String DOTDOT = ".."; // $NON-NLS-1$ + private static final String SLASH = "/"; // $NON-NLS-1$ + private static final String COLONSLASHSLASH = "://"; // $NON-NLS-1$ + + /** + * Extract the encoding (charset) from the Content-Type, e.g. + * "text/html; charset=utf-8". + * + * @param contentType + * string from which the encoding should be extracted + * @return the charset encoding - or null, if none was found or + * the charset is not supported + * @throws IllegalCharsetNameException + * if the found charset is not supported + */ + public static String getEncodingFromContentType(String contentType){ + String charSet = null; + if (contentType != null) { + int charSetStartPos = contentType.toLowerCase(java.util.Locale.ENGLISH).indexOf(CHARSET_EQ); + if (charSetStartPos >= 0) { + charSet = contentType.substring(charSetStartPos + CHARSET_EQ_LEN); + if (charSet != null) { + // Remove quotes from charset name, see bug 55852 + charSet = StringUtils.replaceChars(charSet, "\'\"", null); + charSet = charSet.trim(); + if (charSet.length() > 0) { + // See Bug 44784 + int semi = charSet.indexOf(';'); + if (semi == 0){ + return null; + } + if (semi != -1) { + charSet = charSet.substring(0,semi); + } + if (!Charset.isSupported(charSet)){ + return null; + } + return charSet; + } + return null; + } + } + } + return charSet; + } + + /** + * Generate an absolute URL from a possibly relative location, + * allowing for extraneous leading "../" segments. + * The Java {@link URL#URL(URL, String)} constructor does not remove these. + * + * @param baseURL the base URL which is used to resolve missing protocol/host in the location + * @param location the location, possibly with extraneous leading "../" + * @return URL with extraneous ../ removed + * @throws MalformedURLException when the given URL is malformed + * @see Bug 46690 - handling of 302 redirects with invalid relative paths + */ + public static URL makeRelativeURL(URL baseURL, String location) throws MalformedURLException{ + URL initial = new URL(baseURL,location); + + // skip expensive processing if it cannot apply + if (!location.startsWith("../")){// $NON-NLS-1$ + return initial; + } + String path = initial.getPath(); + // Match /../[../] etc. + Pattern p = Pattern.compile("^/((?:\\.\\./)+)"); // $NON-NLS-1$ + Matcher m = p.matcher(path); + if (m.lookingAt()){ + String prefix = m.group(1); // get ../ or ../../ etc. + if (location.startsWith(prefix)){ + return new URL(baseURL, location.substring(prefix.length())); + } + } + return initial; + } + + /** + * @param url String Url to escape + * @return String cleaned up url + * @throws Exception when given url leads to a malformed URL or URI + */ + public static String escapeIllegalURLCharacters(String url) throws Exception{ + String decodeUrl = URLDecoder.decode(url,"UTF-8"); + URL urlString = new URL(decodeUrl); + URI uri = new URI(urlString.getProtocol(), urlString.getUserInfo(), urlString.getHost(), urlString.getPort(), urlString.getPath(), urlString.getQuery(), urlString.getRef()); + return uri.toString(); + } + + /** + * Checks a URL and encodes it if necessary, + * i.e. if it is not currently correctly encoded. + * Warning: it may not work on all unencoded URLs. + * @param url non-encoded URL + * @return URI which has been encoded as necessary + * @throws URISyntaxException if parts of the url form a non valid URI + */ + public static final URI sanitizeUrl(URL url) throws URISyntaxException { + try { + return url.toURI(); // Assume the URL is already encoded + } catch (URISyntaxException e) { // it's not, so encode it + return new URI( + url.getProtocol(), + url.getUserInfo(), + url.getHost(), + url.getPort(), + url.getPath(), + url.getQuery(), + url.getRef()); // anchor or fragment + } + } + + /** + * collapses absolute or relative URLs containing '/..' converting + * http://host/path1/../path2 to http://host/path2 + * or /one/two/../three to + * /one/three + * + * @param url in which the '/..'s should be removed + * @return collapsed URL + * @see Bug 49083 - collapse /.. in redirect URLs + */ + public static String removeSlashDotDot(String url) + { + if (url == null || (url = url.trim()).length() < 4 || !url.contains(SLASHDOTDOT)) + { + return url; + } + + /** + * http://auth@host:port/path1/path2/path3/?query#anchor + */ + + // get to 'path' part of the URL, preserving schema, auth, host if + // present + + // find index of path start + + int dotSlashSlashIndex = url.indexOf(COLONSLASHSLASH); + final int pathStartIndex; + if (dotSlashSlashIndex >= 0) + { + // absolute URL + pathStartIndex = url.indexOf(SLASH, dotSlashSlashIndex + COLONSLASHSLASH.length()); + } else + { + // document or context-relative URL like: + // '/path/to' + // OR '../path/to' + // OR '/path/to/../path/' + pathStartIndex = 0; + } + + // find path endIndex + int pathEndIndex = url.length(); + + int questionMarkIdx = url.indexOf('?'); + if (questionMarkIdx > 0) + { + pathEndIndex = questionMarkIdx; + } else { + int anchorIdx = url.indexOf('#'); + if (anchorIdx > 0) + { + pathEndIndex = anchorIdx; + } + } + + // path is between idx='pathStartIndex' (inclusive) and + // idx='pathEndIndex' (exclusive) + String currentPath = url.substring(pathStartIndex, pathEndIndex); + + final boolean startsWithSlash = currentPath.startsWith(SLASH); + final boolean endsWithSlash = currentPath.endsWith(SLASH); + + StringTokenizer st = new StringTokenizer(currentPath, SLASH); + List tokens = new ArrayList(); + while (st.hasMoreTokens()) + { + tokens.add(st.nextToken()); + } + + for (int i = 0; i < tokens.size(); i++) + { + if (i < tokens.size() - 1) + { + final String thisToken = tokens.get(i); + + // Verify for a ".." component at next iteration + if (thisToken.length() > 0 && !thisToken.equals(DOTDOT) && tokens.get(i + 1).equals(DOTDOT)) + { + tokens.remove(i); + tokens.remove(i); + i = i - 2; + if (i < -1) + { + i = -1; + } + } + } + + } + + StringBuilder newPath = new StringBuilder(); + if (startsWithSlash) { + newPath.append(SLASH); + } + for (int i = 0; i < tokens.size(); i++) + { + newPath.append(tokens.get(i)); + + // append '/' if this isn't the last token or it is but the original + // path terminated w/ a '/' + boolean appendSlash = i < (tokens.size() - 1) ? true : endsWithSlash; + if (appendSlash) + { + newPath.append(SLASH); + } + } + + // install new path + StringBuilder s = new StringBuilder(url); + s.replace(pathStartIndex, pathEndIndex, newPath.toString()); + return s.toString(); + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/DOMPool.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/DOMPool.java new file mode 100644 index 00000000000..abb41dd1145 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/DOMPool.java @@ -0,0 +1,86 @@ +/* + * 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.protocol.http.util; + +import java.util.Collections; +import java.util.Map; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.jmeter.util.JMeterUtils; +import org.w3c.dom.Document; + +/** + * The purpose of this class is to cache the DOM Documents in memory and by-pass + * parsing. For old systems or laptops, it's not practical to parse the XML + * documents every time. Therefore using a memory cache can reduce the CPU + * usage. + *

+ * For now this is a simple version to test the feasibility of caching. If it + * works, this class will be replaced with an Apache commons or something + * equivalent. If I was familiar with Apache Commons Pool, I would probably use + * it, but since I don't know the API, it is quicker for Proof of Concept to + * just write a dumb one. If the number documents in the pool exceed several + * hundred, it will take a long time for the lookup. + *

+ * Created on: Jun 17, 2003
+ * + */ +public final class DOMPool { + /** + * The cache is created with an initial size of 50. Running a webservice + * test on an old system will likely run into memory or CPU problems long + * before the HashMap is an issue. + */ + @SuppressWarnings("unchecked") // LRUMap does not support generics currently + private static final Map MEMCACHE = Collections.synchronizedMap( + new LRUMap(JMeterUtils.getPropDefault("soap.document_cache", 50))); + + /** + * Return a document. + * + * @param key key of the document + * @return Document + */ + public static Document getDocument(Object key) { + return MEMCACHE.get(key); + } + + /** + * Add an object to the cache. + * + * @param key key of the document + * @param data Document to store + */ + public static void putDocument(Object key, Document data) { + MEMCACHE.put(key, data); + } + + /** + * Private constructor to prevent instantiation. + */ + private DOMPool() { + } + + /** + * Clear cache + */ + public static void clear() { + MEMCACHE.clear(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/EncoderCache.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/EncoderCache.java new file mode 100644 index 00000000000..d9440c00304 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/EncoderCache.java @@ -0,0 +1,76 @@ +/* + * 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.protocol.http.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import org.apache.oro.util.Cache; +import org.apache.oro.util.CacheLRU; + +public class EncoderCache { + + /** The encoding which should be usd for URLs, according to HTTP specification */ + public static final String URL_ARGUMENT_ENCODING = "UTF-8"; + + private Cache cache; + + public EncoderCache(int cacheSize) { + cache = new CacheLRU(cacheSize); + } + + + /** + * Get the specified value URL encoded using UTF-8 encoding + * + * @param k the value to encode + * @return the value URL encoded using UTF-8 + */ + public String getEncoded(String k) { + try { + return getEncoded(k, URL_ARGUMENT_ENCODING); + } catch (UnsupportedEncodingException e) { + // This can't happen (how should utf8 not be supported!?!), + // so just throw an Error: + throw new Error("Should not happen: " + e.toString()); + } + } + + /** + * Get the specified value URL encoded using the specified encoding + * + * @param k the value to encode + * @param contentEncoding the encoding to use when URL encoding + * @return the value URL encoded using the specified encoding + * @throws UnsupportedEncodingException if the specified encoding is not supported + */ + public String getEncoded(String k, String contentEncoding) throws UnsupportedEncodingException { + String cacheKey = k + contentEncoding; + // Check if we have it in the cache + Object encodedValue = cache.getElement(cacheKey); + if (encodedValue != null) { + return (String) encodedValue; + } + // Perform the encoding + encodedValue = URLEncoder.encode(k, contentEncoding); + // Add to cache + cache.addElement(cacheKey, encodedValue); + return (String) encodedValue; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/HC4TrustAllSSLSocketFactory.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/HC4TrustAllSSLSocketFactory.java new file mode 100644 index 00000000000..80b3aec54a2 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/HC4TrustAllSSLSocketFactory.java @@ -0,0 +1,100 @@ +/* + * 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.protocol.http.util; + +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLSocket; + +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.params.HttpParams; +import org.apache.jmeter.util.HttpSSLProtocolSocketFactory; +import org.apache.jmeter.util.JsseSSLManager; + +/** + * Apache HttpClient protocol factory to generate SSL sockets + */ + +public class HC4TrustAllSSLSocketFactory extends SSLSocketFactory { + + private static final TrustStrategy TRUSTALL = new TrustStrategy(){ + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) { + return true; + } + }; + private javax.net.ssl.SSLSocketFactory factory; + + /** + * Create an SSL factory which trusts all certificates and hosts. + * {@link SSLSocketFactory#SSLSocketFactory(TrustStrategy, org.apache.http.conn.ssl.X509HostnameVerifier)} + * @throws GeneralSecurityException if there's a problem setting up the security + */ + public HC4TrustAllSSLSocketFactory() throws GeneralSecurityException { + this(new HttpSSLProtocolSocketFactory((JsseSSLManager)JsseSSLManager.getInstance(), JsseSSLManager.CPS)); + } + + /** + * Create an SSL factory which trusts all certificates and hosts. + * {@link SSLSocketFactory#SSLSocketFactory(TrustStrategy, org.apache.http.conn.ssl.X509HostnameVerifier)} + * @param factory javax.net.ssl.SSLSocketFactory + * @throws GeneralSecurityException if there's a problem setting up the security + */ + protected HC4TrustAllSSLSocketFactory(javax.net.ssl.SSLSocketFactory factory) throws GeneralSecurityException { + super(TRUSTALL, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + this.factory = new HttpSSLProtocolSocketFactory((JsseSSLManager)JsseSSLManager.getInstance(), JsseSSLManager.CPS); + } + + /* (non-Javadoc) + * @see org.apache.http.conn.ssl.SSLSocketFactory#createSocket(org.apache.http.params.HttpParams) + */ + @Override + public Socket createSocket(HttpParams params) throws IOException { + return factory.createSocket(); + } + + /* (non-Javadoc) + * @see org.apache.http.conn.ssl.SSLSocketFactory#createSocket() + */ + @Override + public Socket createSocket() throws IOException { + return factory.createSocket(); + } + + /* (non-Javadoc) + * @see org.apache.http.conn.ssl.SSLSocketFactory#createLayeredSocket(java.net.Socket, java.lang.String, int, boolean) + */ + @Override + public Socket createLayeredSocket(Socket socket, String host, int port, + boolean autoClose) throws IOException, UnknownHostException { + SSLSocket sslSocket = (SSLSocket) this.factory.createSocket( + socket, + host, + port, + autoClose + ); + ALLOW_ALL_HOSTNAME_VERIFIER.verify(host, sslSocket); + return sslSocket; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPArgument.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPArgument.java new file mode 100644 index 00000000000..556fd03d914 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPArgument.java @@ -0,0 +1,268 @@ +/* + * 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.protocol.http.util; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +//For unit tests, @see TestHTTPArgument + +/* + * + * Represents an Argument for HTTP requests. + */ +public class HTTPArgument extends Argument implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final String ALWAYS_ENCODE = "HTTPArgument.always_encode"; + + private static final String USE_EQUALS = "HTTPArgument.use_equals"; + + private static final EncoderCache cache = new EncoderCache(1000); + + /** + * Constructor for the Argument object. + *

+ * The value is assumed to be not encoded. + * + * @param name + * name of the paramter + * @param value + * value of the parameter + * @param metadata + * the separator to use between name and value + */ + public HTTPArgument(String name, String value, String metadata) { + this(name, value, false); + this.setMetaData(metadata); + } + + public void setUseEquals(boolean ue) { + if (ue) { + setMetaData("="); + } else { + setMetaData(""); + } + setProperty(new BooleanProperty(USE_EQUALS, ue)); + } + + public boolean isUseEquals() { + boolean eq = getPropertyAsBoolean(USE_EQUALS); + if (getMetaData().equals("=") || (getValue() != null && getValue().length() > 0)) { + setUseEquals(true); + return true; + } + return eq; + + } + + public void setAlwaysEncoded(boolean ae) { + setProperty(new BooleanProperty(ALWAYS_ENCODE, ae)); + } + + public boolean isAlwaysEncoded() { + return getPropertyAsBoolean(ALWAYS_ENCODE); + } + + /** + * Constructor for the Argument object. + *

+ * The value is assumed to be not encoded. + * + * @param name + * name of the parameter + * @param value + * value of the parameter + */ + public HTTPArgument(String name, String value) { + this(name, value, false); + } + + /** + * @param name + * name of the parameter + * @param value + * value of the parameter + * @param alreadyEncoded + * true if the value is already encoded, in which + * case they are decoded before storage + */ + public HTTPArgument(String name, String value, boolean alreadyEncoded) { + // We assume the argument value is encoded according to the HTTP spec, i.e. UTF-8 + this(name, value, alreadyEncoded, EncoderCache.URL_ARGUMENT_ENCODING); + } + + /** + * Construct a new HTTPArgument instance; alwaysEncoded is set to true. + * + * @param name the name of the parameter + * @param value the value of the parameter + * @param alreadyEncoded true if the name and value is already encoded, in which case they are decoded before storage. + * @param contentEncoding the encoding used for the parameter value + */ + public HTTPArgument(String name, String value, boolean alreadyEncoded, String contentEncoding) { + setAlwaysEncoded(true); + if (alreadyEncoded) { + try { + // We assume the name is always encoded according to spec + if(log.isDebugEnabled()) { + log.debug("Decoding name, calling URLDecoder.decode with '"+name+"' and contentEncoding:"+EncoderCache.URL_ARGUMENT_ENCODING); + } + name = URLDecoder.decode(name, EncoderCache.URL_ARGUMENT_ENCODING); + // The value is encoded in the specified encoding + if(log.isDebugEnabled()) { + log.debug("Decoding value, calling URLDecoder.decode with '"+value+"' and contentEncoding:"+contentEncoding); + } + value = URLDecoder.decode(value, contentEncoding); + } catch (UnsupportedEncodingException e) { + log.error(contentEncoding + " encoding not supported!"); + throw new Error(e.toString(), e); + } + } + setName(name); + setValue(value); + setMetaData("="); + } + + /** + * Construct a new HTTPArgument instance + * + * @param name + * the name of the parameter + * @param value + * the value of the parameter + * @param metaData + * the separator to use between name and value + * @param alreadyEncoded + * true if the name and value is already encoded + */ + public HTTPArgument(String name, String value, String metaData, boolean alreadyEncoded) { + // We assume the argument value is encoded according to the HTTP spec, i.e. UTF-8 + this(name, value, metaData, alreadyEncoded, EncoderCache.URL_ARGUMENT_ENCODING); + } + + /** + * Construct a new HTTPArgument instance + * + * @param name the name of the parameter + * @param value the value of the parameter + * @param metaData the separator to use between name and value + * @param alreadyEncoded true if the name and value is already encoded + * @param contentEncoding the encoding used for the parameter value + */ + public HTTPArgument(String name, String value, String metaData, boolean alreadyEncoded, String contentEncoding) { + this(name, value, alreadyEncoded, contentEncoding); + setMetaData(metaData); + } + + public HTTPArgument(Argument arg) { + this(arg.getName(), arg.getValue(), arg.getMetaData()); + } + + /** + * Constructor for the Argument object + */ + public HTTPArgument() { + } + + /** + * Sets the Name attribute of the Argument object. + * + * @param newName + * the new Name value + */ + @Override + public void setName(String newName) { + if (newName == null || !newName.equals(getName())) { + super.setName(newName); + } + } + + /** + * Get the argument value encoded using UTF-8 + * + * @return the argument value encoded in UTF-8 + */ + public String getEncodedValue() { + // Encode according to the HTTP spec, i.e. UTF-8 + try { + return getEncodedValue(EncoderCache.URL_ARGUMENT_ENCODING); + } catch (UnsupportedEncodingException e) { + // This can't happen (how should utf8 not be supported!?!), + // so just throw an Error: + throw new Error("Should not happen: " + e.toString()); + } + } + + /** + * Get the argument value encoded in the specified encoding + * + * @param contentEncoding the encoding to use when encoding the argument value + * @return the argument value encoded in the specified encoding + * @throws UnsupportedEncodingException of the encoding is not supported + */ + public String getEncodedValue(String contentEncoding) throws UnsupportedEncodingException { + if (isAlwaysEncoded()) { + return cache.getEncoded(getValue(), contentEncoding); + } else { + return getValue(); + } + } + + public String getEncodedName() { + if (isAlwaysEncoded()) { + return cache.getEncoded(getName()); + } else { + return getName(); + } + + } + + /** + * Converts all {@link Argument} entries in the collection to {@link HTTPArgument} entries. + * + * @param args collection of {@link Argument} and/or {@link HTTPArgument} entries + */ + public static void convertArgumentsToHTTP(Arguments args) { + List newArguments = new LinkedList(); + PropertyIterator iter = args.getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + if (!(arg instanceof HTTPArgument)) { + newArguments.add(new HTTPArgument(arg)); + } else { + newArguments.add(arg); + } + } + args.removeAllArguments(); + args.setArguments(newArguments); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPConstants.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPConstants.java new file mode 100644 index 00000000000..9df80f9917a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPConstants.java @@ -0,0 +1,29 @@ +/* + * 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.protocol.http.util; + +/** + * Constants used in HTTP, mainly header names. + * This is a class suitable for extending + * or for accessing individual constants without needing to import them all. + */ + +public class HTTPConstants implements HTTPConstantsInterface { + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPConstantsInterface.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPConstantsInterface.java new file mode 100644 index 00000000000..586c890f3a5 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPConstantsInterface.java @@ -0,0 +1,80 @@ +/* + * 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.protocol.http.util; + + +/** + * Constants used in HTTP, mainly header names. + */ + +public interface HTTPConstantsInterface { + + int DEFAULT_HTTPS_PORT = 443; + String DEFAULT_HTTPS_PORT_STRING = "443"; // $NON-NLS-1$ + int DEFAULT_HTTP_PORT = 80; + String DEFAULT_HTTP_PORT_STRING = "80"; // $NON-NLS-1$ + String PROTOCOL_HTTP = "http"; // $NON-NLS-1$ + String PROTOCOL_HTTPS = "https"; // $NON-NLS-1$ + String HEAD = "HEAD"; // $NON-NLS-1$ + String POST = "POST"; // $NON-NLS-1$ + String PUT = "PUT"; // $NON-NLS-1$ + String GET = "GET"; // $NON-NLS-1$ + String OPTIONS = "OPTIONS"; // $NON-NLS-1$ + String TRACE = "TRACE"; // $NON-NLS-1$ + String DELETE = "DELETE"; // $NON-NLS-1$ + String PATCH = "PATCH"; // $NON-NLS-1$ + String PROPFIND = "PROPFIND"; // $NON-NLS-1$ + String PROPPATCH = "PROPPATCH"; // $NON-NLS-1$ + String MKCOL = "MKCOL"; // $NON-NLS-1$ + String COPY = "COPY"; // $NON-NLS-1$ + String MOVE = "MOVE"; // $NON-NLS-1$ + String LOCK = "LOCK"; // $NON-NLS-1$ + String UNLOCK = "UNLOCK"; // $NON-NLS-1$ + String CONNECT = "CONNECT"; // $NON-NLS-1$ + String REPORT = "REPORT"; // $NON-NLS-1$ + String MKCALENDAR = "MKCALENDAR"; // $NON-NLS-1$ + String HEADER_AUTHORIZATION = "Authorization"; // $NON-NLS-1$ + String HEADER_COOKIE = "Cookie"; // $NON-NLS-1$ + String HEADER_CONNECTION = "Connection"; // $NON-NLS-1$ + String CONNECTION_CLOSE = "close"; // $NON-NLS-1$ + String KEEP_ALIVE = "keep-alive"; // $NON-NLS-1$ + // e.g. "Transfer-Encoding: chunked", which is processed automatically by the underlying protocol + String TRANSFER_ENCODING = "transfer-encoding"; // $NON-NLS-1$ + String HEADER_CONTENT_ENCODING = "content-encoding"; // $NON-NLS-1$ + String HTTP_1_1 = "HTTP/1.1"; // $NON-NLS-1$ + String HEADER_SET_COOKIE = "set-cookie"; // $NON-NLS-1$ + String ENCODING_GZIP = "gzip"; // $NON-NLS-1$ + String HEADER_CONTENT_DISPOSITION = "Content-Disposition"; // $NON-NLS-1$ + String HEADER_CONTENT_TYPE = "Content-Type"; // $NON-NLS-1$ + String HEADER_CONTENT_LENGTH = "Content-Length"; // $NON-NLS-1$ + String HEADER_HOST = "Host"; // $NON-NLS-1$ + String HEADER_LOCAL_ADDRESS = "X-LocalAddress"; // $NON-NLS-1$ pseudo-header for reporting Local Address + String HEADER_LOCATION = "Location"; // $NON-NLS-1$ + String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; // $NON-NLS-1$ + String MULTIPART_FORM_DATA = "multipart/form-data"; // $NON-NLS-1$ + // For handling caching + String IF_NONE_MATCH = "If-None-Match"; // $NON-NLS-1$ + String IF_MODIFIED_SINCE = "If-Modified-Since"; // $NON-NLS-1$ + String ETAG = "Etag"; // $NON-NLS-1$ + String LAST_MODIFIED = "Last-Modified"; // $NON-NLS-1$ + String EXPIRES = "Expires"; // $NON-NLS-1$ + String CACHE_CONTROL = "Cache-Control"; //e.g. public, max-age=259200 + String DATE = "Date"; //e.g. Date Header of response + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPFileArg.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPFileArg.java new file mode 100644 index 00000000000..9a4af3ff08b --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPFileArg.java @@ -0,0 +1,234 @@ +/* + * 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.protocol.http.util; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; + +/** + * Class representing a file parameter for http upload. + * Consists of a http parameter name/file path pair with (optional) mimetype. + * + * Also provides temporary storage for the headers which are sent with files. + * + */ +public class HTTPFileArg extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + /** Name used to store the file's path. */ + private static final String FILEPATH = "File.path"; + + /** Name used to store the file's paramname. */ + private static final String PARAMNAME = "File.paramname"; + + /** Name used to store the file's mimetype. */ + private static final String MIMETYPE = "File.mimetype"; + + /** temporary storage area for the body header. */ + private String header; + + /** + * Constructor for an empty HTTPFileArg object + */ + public HTTPFileArg() { + } + + /** + * Constructor for the HTTPFileArg object with given path. + * + * @param path + * path to the file to use + * @throws IllegalArgumentException + * if path is null + */ + public HTTPFileArg(String path) { + this(path, "", ""); + } + + /** + * Constructor for the HTTPFileArg object with full information. + * + * @param path + * path of the file to use + * @param paramname + * name of the http parameter to use for the file + * @param mimetype + * mimetype of the file + * @throws IllegalArgumentException + * if any parameter is null + */ + public HTTPFileArg(String path, String paramname, String mimetype) { + if (path == null || paramname == null || mimetype == null){ + throw new IllegalArgumentException("Parameters must not be null"); + } + setPath(path); + setParamName(paramname); + setMimeType(mimetype); + } + + /** + * Constructor for the HTTPFileArg object with full information, + * using existing properties + * + * @param path + * path of the file to use + * @param paramname + * name of the http parameter to use for the file + * @param mimetype + * mimetype of the file + * @throws IllegalArgumentException + * if any parameter is null + */ + public HTTPFileArg(JMeterProperty path, JMeterProperty paramname, JMeterProperty mimetype) { + if (path == null || paramname == null || mimetype == null){ + throw new IllegalArgumentException("Parameters must not be null"); + } + setProperty(FILEPATH, path); + setProperty(MIMETYPE, mimetype); + setProperty(PARAMNAME, paramname); + } + + private void setProperty(String name, JMeterProperty prop) { + JMeterProperty jmp = prop.clone(); + jmp.setName(name); + setProperty(jmp); + } + + /** + * Copy Constructor. + * + * @param file + * {@link HTTPFileArg} to get information about the path, http + * parameter name and mimetype of the file + * @throws IllegalArgumentException + * if any of those retrieved information is null + */ + public HTTPFileArg(HTTPFileArg file) { + this(file.getPath(), file.getParamName(), file.getMimeType()); + } + + /** + * Set the http parameter name of the File. + * + * @param newParamName + * the new http parameter name + */ + public void setParamName(String newParamName) { + setProperty(new StringProperty(PARAMNAME, newParamName)); + } + + /** + * Get the http parameter name of the File. + * + * @return the http parameter name + */ + public String getParamName() { + return getPropertyAsString(PARAMNAME); + } + + /** + * Set the mimetype of the File. + * + * @param newMimeType + * the new mimetype + */ + public void setMimeType(String newMimeType) { + setProperty(new StringProperty(MIMETYPE, newMimeType)); + } + + /** + * Get the mimetype of the File. + * + * @return the http parameter mimetype + */ + public String getMimeType() { + return getPropertyAsString(MIMETYPE); + } + + /** + * Set the path of the File. + * + * @param newPath + * the new path + */ + public void setPath(String newPath) { + setProperty(new StringProperty(FILEPATH, newPath)); + } + + /** + * Get the path of the File. + * + * @return the file's path + */ + public String getPath() { + return getPropertyAsString(FILEPATH); + } + + /** + * Sets the body header for the HTTPFileArg object. Header + * contains path, parameter name and mime type information. + * This is only intended for use by methods which need to store information + * temporarily whilst creating the HTTP body. + * + * @param newHeader + * the new Header value + */ + public void setHeader(String newHeader) { + header = newHeader; + } + + /** + * Gets the saved body header for the HTTPFileArg object. + * + * @return saved body header + */ + public String getHeader() { + return header; + } + + /** + * returns path, param name, mime type information of + * HTTPFileArg object. + * + * @return the string demonstration of HTTPFileArg object in this + * format: + * "path:'<PATH>'|param:'<PARAM NAME>'|mimetype:'<MIME TYPE>'" + */ + @Override + public String toString() { + return "path:'" + getPath() + + "'|param:'" + getParamName() + + "'|mimetype:'" + getMimeType() + "'"; + } + + /** + * Check if the entry is not empty. + * @return true if Path, name or mimetype fields are not the empty string + */ + public boolean isNotEmpty() { + return getPath().length() > 0 + || getParamName().length() > 0 + || getMimeType().length() > 0; // TODO should we allow mimetype only? + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPFileArgs.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPFileArgs.java new file mode 100644 index 00000000000..371eb41077c --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/HTTPFileArgs.java @@ -0,0 +1,236 @@ +/* + * 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.protocol.http.util; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * A set of HTTPFileArg objects. + * + */ +public class HTTPFileArgs extends ConfigTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + /** The name of the property used to store the files. */ + private static final String HTTP_FILE_ARGS = "HTTPFileArgs.files"; //$NON-NLS-1$ + + /** + * Create a new HTTPFileArgs object with no files. + */ + public HTTPFileArgs() { + setProperty(new CollectionProperty(HTTP_FILE_ARGS, new ArrayList())); + } + + /** + * Get the files. + * + * @return the files + */ + public CollectionProperty getHTTPFileArgsCollection() { + return (CollectionProperty) getProperty(HTTP_FILE_ARGS); + } + + /** + * Clear the files. + */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(HTTP_FILE_ARGS, new ArrayList())); + } + + /** + * Set the list of files. Any existing files will be lost. + * + * @param files the new files + */ + public void setHTTPFileArgs(List files) { + setProperty(new CollectionProperty(HTTP_FILE_ARGS, files)); + } + + /** + * Add a new file with the given path. + * + * @param path + * the path of the file + */ + public void addHTTPFileArg(String path) { + addHTTPFileArg(new HTTPFileArg(path)); + } + + /** + * Add a new file. + * + * @param file + * the new file + */ + public void addHTTPFileArg(HTTPFileArg file) { + TestElementProperty newHTTPFileArg = new TestElementProperty(file.getPath(), file); + if (isRunningVersion()) { + this.setTemporary(newHTTPFileArg); + } + getHTTPFileArgsCollection().addItem(newHTTPFileArg); + } + + /** + * adds a new File to the HTTPFileArgs list to be uploaded with http + * request. + * + * @param path file full path. + * @param param http parameter name. + * @param mime mime type of file. + */ + public void addHTTPFileArg(String path, String param, String mime) { + addHTTPFileArg(new HTTPFileArg(path, param, mime)); + } + + /** + * Get a PropertyIterator of the files. + * + * @return an iteration of the files + */ + public PropertyIterator iterator() { + return getHTTPFileArgsCollection().iterator(); + } + + /** + * Get the current arguments as an array. + * + * @return an array of file arguments + */ + public HTTPFileArg[] asArray(){ + CollectionProperty props = getHTTPFileArgsCollection(); + final int size = props.size(); + HTTPFileArg[] args = new HTTPFileArg[size]; + for(int i=0; i0 + && res.getResponseData().length == 0) { + readFile(resultFileName,res); + } + return res; + } + + private void retrieveHTTPItem(HierarchicalStreamReader reader, + HTTPSampleResult res, Object subItem) { + if (subItem instanceof URL) { + res.setURL((URL) subItem); + } else { + String nodeName = reader.getNodeName(); + if (nodeName.equals(TAG_COOKIES)) { + res.setCookies((String) subItem); + } else if (nodeName.equals(TAG_METHOD)) { + res.setHTTPMethod((String) subItem); + } else if (nodeName.equals(TAG_QUERY_STRING)) { + res.setQueryString((String) subItem); + } else if (nodeName.equals(TAG_REDIRECT_LOCATION)) { + res.setRedirectLocation((String) subItem); + } + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/LoopbackHTTPSocket.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/LoopbackHTTPSocket.java new file mode 100644 index 00000000000..5f93a4238cf --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/LoopbackHTTPSocket.java @@ -0,0 +1,97 @@ +/* + * 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.protocol.http.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.apache.jmeter.samplers.SampleResult; + +/* + * Socket that reads back from the output + */ +public class LoopbackHTTPSocket extends Socket { + + // get access to buffer + static class LoopbackOutputStream extends ByteArrayOutputStream{ + byte [] getBuffer() { + return buf; + } + } + + // wrap read() methods to track output buffer + static class LoopBackInputStream extends ByteArrayInputStream{ + private LoopbackOutputStream os; + @Override + public synchronized int read() { + buf=os.getBuffer(); // make sure buffer details + count=buf.length; // track the output + return super.read(); + } + @Override + public synchronized int read(byte[] b, int off, int len) { + buf=os.getBuffer(); + count=buf.length; + return super.read(b, off, len); + } + + public LoopBackInputStream(LoopbackOutputStream _os) { + super(_os.getBuffer()); + os=_os; + } + } + + private final LoopbackOutputStream os; + + private LoopbackHTTPSocket() throws IOException{ + os=new LoopbackOutputStream(); + // Preload the output so that can be read back as HTTP + os.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n".getBytes(SampleResult.DEFAULT_HTTP_ENCODING)); + } + + public LoopbackHTTPSocket(String host, int port, InetAddress localAddress, int localPort, int timeout) throws IOException { + this(); + } + + public LoopbackHTTPSocket(String host, int port, InetAddress localAddr, int localPort) throws IOException { + this(); + } + + public LoopbackHTTPSocket(String host, int port) throws UnknownHostException, IOException { + this(); + } + + // Override so we can intercept the stream + @Override + public OutputStream getOutputStream() throws IOException { + return os; + } + + // Override so we can intercept the stream + @Override + public InputStream getInputStream() throws IOException { + return new LoopBackInputStream(os); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/LoopbackHttpClientSocketFactory.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/LoopbackHttpClientSocketFactory.java new file mode 100644 index 00000000000..c3adc5cd921 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/LoopbackHttpClientSocketFactory.java @@ -0,0 +1,99 @@ +/* + * 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.protocol.http.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.net.UnknownHostException; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; + +/** + * Commons HttpClient protocol factory to generate Loopback HTTP sockets + */ + +public class LoopbackHttpClientSocketFactory implements ProtocolSocketFactory { + + public LoopbackHttpClientSocketFactory() { + super(); + } + + @Override + public Socket createSocket(String host, int port, InetAddress clientHost, + int clientPort) throws IOException, UnknownHostException { + return new LoopbackHTTPSocket(host,port,clientHost,clientPort); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + return new LoopbackHTTPSocket(host,port); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, + HttpConnectionParams params) + throws IOException, UnknownHostException, ConnectTimeoutException { + int timeout = params.getConnectionTimeout(); + if (timeout == 0) { + return new LoopbackHTTPSocket(host,port,localAddress,localPort); + } else { + return new LoopbackHTTPSocket(host,port,localAddress,localPort, timeout); + } + } + + /** + * Convenience method to set up the necessary HttpClient protocol and URL handler. + * + * Only works for HttpClient, because it's not possible (or at least very difficult) + * to provide a different socket factory for the HttpURLConnection class. + */ + public static void setup(){ + final String LOOPBACK = "loopback"; // $NON-NLS-1$ + + // This ensures tha HttpClient knows about the protocol + Protocol.registerProtocol(LOOPBACK, new Protocol(LOOPBACK,new LoopbackHttpClientSocketFactory(),1)); + + // Now allow the URL handling to work. + URLStreamHandlerFactory ushf = new URLStreamHandlerFactory(){ + @Override + public URLStreamHandler createURLStreamHandler(String protocol) { + if (protocol.equalsIgnoreCase(LOOPBACK)){ + return new URLStreamHandler(){ + @Override + protected URLConnection openConnection(URL u) throws IOException { + return null;// not needed for HttpClient + } + }; + } + return null; + } + }; + + java.net.URL.setURLStreamHandlerFactory(ushf); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/SlowHC4SSLSocketFactory.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/SlowHC4SSLSocketFactory.java new file mode 100644 index 00000000000..393be90d78f --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/SlowHC4SSLSocketFactory.java @@ -0,0 +1,41 @@ +/* + * 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.protocol.http.util; + +import java.security.GeneralSecurityException; + +import org.apache.jmeter.util.HttpSSLProtocolSocketFactory; +import org.apache.jmeter.util.JsseSSLManager; + +/** + * Apache HttpClient protocol factory to generate "slow" SSL sockets for emulating dial-up modems + */ + +public class SlowHC4SSLSocketFactory extends HC4TrustAllSSLSocketFactory { + + /** + * Create a factory + * @param cps - characters per second, must be > 0 + * @throws GeneralSecurityException if there's a problem setting up the security + * @throws IllegalArgumentException if cps ≤ 0 + */ + public SlowHC4SSLSocketFactory(final int cps) throws GeneralSecurityException { + super(new HttpSSLProtocolSocketFactory((JsseSSLManager)JsseSSLManager.getInstance(), cps)); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/SlowHC4SocketFactory.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/SlowHC4SocketFactory.java new file mode 100644 index 00000000000..e7b989dd4c3 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/SlowHC4SocketFactory.java @@ -0,0 +1,56 @@ +/* + * 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.protocol.http.util; + +import java.net.Socket; + +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.params.HttpParams; +import org.apache.jmeter.util.SlowSocket; + +/** + * Apache HttpClient protocol factory to generate "slow" sockets for emulating dial-up modems + */ + +public class SlowHC4SocketFactory extends PlainSocketFactory { + + private final int CPS; // Characters per second to emulate + + /** + * Create a factory + * @param cps - characters per second + */ + public SlowHC4SocketFactory(final int cps) { + super(); + CPS = cps; + } + + // Override all the super-class Socket methods. + + @Override + public Socket createSocket(final HttpParams params) { + return new SlowSocket(CPS); + } + + @Override + public Socket createSocket() { + return new SlowSocket(CPS); + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/SlowHttpClientSocketFactory.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/SlowHttpClientSocketFactory.java new file mode 100644 index 00000000000..8581687e427 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/SlowHttpClientSocketFactory.java @@ -0,0 +1,71 @@ +/* + * 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.protocol.http.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.jmeter.util.SlowSocket; + +/** + * Commons HttpClient protocol factory to generate "slow" sockets for emulating dial-up modems + */ + +public class SlowHttpClientSocketFactory implements ProtocolSocketFactory { + + private final int CPS; // Characters per second to emulate + + /** + * + * @param cps - characters per second + */ + public SlowHttpClientSocketFactory(final int cps) { + super(); + CPS = cps; + } + + @Override + public Socket createSocket(String host, int port, InetAddress clientHost, + int clientPort) throws IOException, UnknownHostException { + return new SlowSocket(CPS,host,port,clientHost,clientPort); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + return new SlowSocket(CPS,host,port); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, + HttpConnectionParams params) + throws IOException, UnknownHostException, ConnectTimeoutException { + int timeout = params.getConnectionTimeout(); + if (timeout == 0) { + return new SlowSocket(CPS,host,port,localAddress,localPort); + } else { + return new SlowSocket(CPS,host,port,localAddress,localPort, timeout); + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/WSDLException.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/WSDLException.java new file mode 100644 index 00000000000..5c3548be1a1 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/WSDLException.java @@ -0,0 +1,46 @@ +/* + * 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.protocol.http.util; + +/** + * Created on: Jun 3, 2003
+ * + */ +public class WSDLException extends Exception { + + private static final long serialVersionUID = 240L; + + public WSDLException() { + super(); + } + + /** + * @param message detailed error message to include in exception + */ + public WSDLException(String message) { + super(message); + } + + /** + * @param exception exception to extract the detailed message from and include that message as detailed message in this exception + */ + public WSDLException(Exception exception) { + super(exception.getMessage()); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/WSDLHelper.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/WSDLHelper.java new file mode 100644 index 00000000000..70fa49136a1 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/WSDLHelper.java @@ -0,0 +1,425 @@ +/* + * 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.protocol.http.util; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * For now I use DOM for WSDLHelper, but it would be more efficient to use JAXB + * to generate an object model for WSDL and use it to perform serialization and + * deserialization. It also makes it easier to traverse the WSDL to get + * necessary information. + *

+ * Created on: Jun 3, 2003
+ * + */ +public class WSDLHelper { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String WSDL_NAMESPACE = "http://schemas.xmlsoap.org/wsdl/"; //$NON-NLS-1$ + + private static final String SOAP11_BINDING_NAMESPACE = "http://schemas.xmlsoap.org/wsdl/soap/"; //$NON-NLS-1$ + + private static final String SOAP12_BINDING_NAMESPACE = "http://schemas.xmlsoap.org/wsdl/soap12/"; //$NON-NLS-1$ + + private static int GET_WDSL_TIMEOUT = 5000; // timeout to retrieve wsdl when server not response + + /** + * -------------------------------------------- The members used by the + * class to do its work -------------------------------------------- + */ + + private URL WSDLURL = null; + + private URLConnection CONN = null; + + private Document WSDLDOC = null; + + private String SOAPBINDING = null; + + private URL bindingURL = null; + + private Object[] SOAPOPS = null; + + private final Map ACTIONS = new HashMap(); + + private final AuthManager AUTH; + + /** + * Default constructor takes a string URL + * + * @param url + * url to the wsdl + * @throws MalformedURLException + * if url is malformed + */ + public WSDLHelper(String url) throws MalformedURLException { + this(url, null); + } + + /** + * @param url + * url to the wsdl + * @param auth + * {@link AuthManager} to use + * @throws MalformedURLException + * if url is malformed + */ + public WSDLHelper(String url, AuthManager auth) throws MalformedURLException { + WSDLURL = new URL(url); + this.AUTH = auth; + } + + /** + * Returns the URL + * + * @return the URL + */ + public URL getURL() { + return this.WSDLURL; + } + + /** + * Return the protocol from the URL. this is needed, so that HTTPS works + * as expected. + * + * @return protocol extracted from url + */ + public String getProtocol() { + return this.bindingURL.getProtocol(); + } + + /** + * Return the host in the WSDL binding address + * + * @return host extracted from url + */ + public String getBindingHost() { + return this.bindingURL.getHost(); + } + + /** + * Return the path in the WSDL for the binding address + * + * @return path extracted from url + */ + public String getBindingPath() { + return this.bindingURL.getPath(); + } + + /** + * Return the port for the binding address + * + * @return port extracted from url + */ + public int getBindingPort() { + return this.bindingURL.getPort(); + } + + /** + * Returns the binding point for the webservice. Right now it naively + * assumes there's only one binding point with numerous soap operations. + * + * @return String + */ + public String getBinding() { + try { + NodeList services = this.WSDLDOC.getElementsByTagNameNS(WSDL_NAMESPACE, "service"); + // the document should only have one service node + // if it doesn't it may not work! + Element node = (Element) services.item(0); + NodeList ports = node.getElementsByTagNameNS(WSDL_NAMESPACE, "port"); + if(ports.getLength()>0) { + Element pnode = (Element) ports.item(0); + // NOTUSED String portname = pnode.getAttribute("name"); + // used to check binding, but now it doesn't. it was + // failing when wsdl did not using binding as expected + NodeList servlist = pnode.getElementsByTagNameNS(SOAP11_BINDING_NAMESPACE, "address"); + // check soap12 + if (servlist.getLength() == 0) { + servlist = pnode.getElementsByTagNameNS(SOAP12_BINDING_NAMESPACE, "address"); + } + Element addr = (Element) servlist.item(0); + this.SOAPBINDING = addr.getAttribute("location"); + this.bindingURL = new URL(this.SOAPBINDING); + return this.SOAPBINDING; + } else { + return null; + } + } catch (Exception exception) { + log.warn("Exception calling getBinding:"+exception.getMessage(),exception); + return null; + } + } + + /** + * Method is used internally to connect to the URL. It's protected; + * therefore external classes should use parse to get the resource at the + * given location. + * + * @throws IOException when I/O error occurs + */ + protected void connect() throws IOException { + CONN = WSDLURL.openConnection(); + CONN.setConnectTimeout(GET_WDSL_TIMEOUT); + CONN.setReadTimeout(GET_WDSL_TIMEOUT); + // in the rare case the WSDL is protected and requires + // authentication, use the AuthManager to set the + // authorization. Basic and Digest authorization are + // pretty weak and don't provide real security. + if (CONN instanceof HttpURLConnection && this.AUTH != null && this.AUTH.getAuthHeaderForURL(this.WSDLURL) != null) { + CONN.setRequestProperty("Authorization", this.AUTH.getAuthHeaderForURL(this.WSDLURL)); + } + } + + /** + * We try to close the connection to make sure it doesn't hang around. + */ + protected void close() { + try { + if (CONN != null) { + CONN.getInputStream().close(); + } + } catch (IOException ignored) { + // do nothing + } + } + + /** + * Method is used internally to parse the InputStream and build the document + * using javax.xml.parser API. + * + * @throws ParserConfigurationException When building {@link DocumentBuilder} fails + * @throws IOException when reading the document fails + * @throws SAXException when parsing the document fails + */ + protected void buildDocument() throws ParserConfigurationException, IOException, SAXException { + DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); + dbfactory.setNamespaceAware(true); + DocumentBuilder docbuild = dbfactory.newDocumentBuilder(); + WSDLDOC = docbuild.parse(CONN.getInputStream()); + } + + /** + * Call this method to retrieve the WSDL. This method must be called, + * otherwise a connection to the URL won't be made and the stream won't be + * parsed. + * + * @throws WSDLException when parsing fails + */ + public void parse() throws WSDLException { + try { + this.connect(); + this.buildDocument(); + SOAPOPS = this.getOperations(); + } catch (IOException exception) { + throw (new WSDLException(exception)); + } catch (SAXException exception) { + throw (new WSDLException(exception)); + } catch (ParserConfigurationException exception) { + throw (new WSDLException(exception)); + } finally { + this.close(); + } + } + + /** + * Get a list of the web methods as a string array. + * + * @return list of web methods + */ + public String[] getWebMethods() { + for (int idx = 0; idx < SOAPOPS.length; idx++) { + // get the node + Node act = (Node) SOAPOPS[idx]; + // get the soap:operation + NodeList opers = ((Element) act).getElementsByTagNameNS(SOAP11_BINDING_NAMESPACE, "operation"); + if (opers.getLength() == 0) { + opers = ((Element) act).getElementsByTagNameNS(SOAP12_BINDING_NAMESPACE, "operation"); + } + + // there should only be one soap:operation node per operation + Element op = (Element) opers.item(0); + String value; + if (op != null) { + value = op.getAttribute("soapAction"); + } else { + value = ""; + } + String key = ((Element) act).getAttribute("name"); + this.ACTIONS.put(key, value); + } + Set keys = this.ACTIONS.keySet(); + String[] stringmeth = new String[keys.size()]; + Object[] stringKeys = keys.toArray(); + System.arraycopy(stringKeys, 0, stringmeth, 0, keys.size()); + return stringmeth; + } + + /** + * Return the soap action matching the operation name. + * + * @param key + * name of the operation + * @return associated action + */ + public String getSoapAction(String key) { + return this.ACTIONS.get(key); + } + + /** + * Get the wsdl document. + * + * @return wsdl document + */ + public Document getWSDLDocument() { + return WSDLDOC; + } + + /** + * Method will look at the binding nodes and see if the first child is a + * soap:binding. If it is, it adds it to an array. + * + * @return Node[] + */ + public Object[] getSOAPBindings() { + ArrayList list = new ArrayList(); + NodeList bindings = WSDLDOC.getElementsByTagNameNS(WSDL_NAMESPACE,"binding"); + for (int idx = 0; idx < bindings.getLength(); idx++) { + Element nd = (Element) bindings.item(idx); + NodeList slist = nd.getElementsByTagNameNS(SOAP11_BINDING_NAMESPACE,"binding"); + if(slist.getLength()==0) { + slist = nd.getElementsByTagNameNS(SOAP12_BINDING_NAMESPACE,"binding"); + } + if (slist.getLength() > 0) { + nd.getAttribute("name"); + list.add(nd); + } + } + if (list.size() > 0) { + return list.toArray(); + } + return new Object[0]; + } + + /** + * Look at the bindings with soap operations and get the soap operations. + * Since WSDL may describe multiple bindings and each binding may have + * multiple soap operations, we iterate through the binding nodes with a + * first child that is a soap binding. If a WSDL doesn't use the same + * formatting convention, it is possible we may not get a list of all the + * soap operations. If that is the case, getSOAPBindings() will need to be + * changed. I should double check the WSDL spec to see what the official + * requirement is. Another option is to get all operation nodes and check to + * see if the first child is a soap:operation. The benefit of not getting + * all operation nodes is WSDL could contain duplicate operations that are + * not SOAP methods. If there are a large number of methods and half of them + * are HTTP operations, getting all operations could slow things down. + * + * @return Node[] + */ + public Object[] getOperations() { + Object[] res = this.getSOAPBindings(); + ArrayList ops = new ArrayList(); + // first we iterate through the bindings + for (int idx = 0; idx < res.length; idx++) { + Element one = (Element) res[idx]; + NodeList opnodes = one.getElementsByTagNameNS(WSDL_NAMESPACE, "operation"); + // now we iterate through the operations + for (int idz = 0; idz < opnodes.getLength(); idz++) { + // if the first child is soap:operation + // we add it to the array + Element child = (Element) opnodes.item(idz); + int numberOfSoapOperationNodes = child.getElementsByTagNameNS(SOAP11_BINDING_NAMESPACE, "operation").getLength() + + child.getElementsByTagNameNS(SOAP12_BINDING_NAMESPACE, "operation").getLength(); + if (numberOfSoapOperationNodes>0) { + ops.add(child); + } + } + } + return ops.toArray(); + } + + /** + * return the "wsdl method name" from a soap action + * @param soapAction the soap action + * @return the associated "wsdl method name" or null if not found + */ + public String getSoapActionName(String soapAction) { + for (Map.Entry entry : ACTIONS.entrySet()) { + if (entry.getValue().equals(soapAction)) { + return entry.getKey(); + } + } + return null; + } + + /** + * Simple test for the class uses bidbuy.wsdl from Apache's soap driver + * examples. + * + * @param args standard arguments for a main class (not used here) + */ + public static void main(String[] args) { + try { + WSDLHelper help = + // new WSDLHelper("http://localhost/WSTest/WSTest.asmx?WSDL"); + // new WSDLHelper("http://localhost/AxisWSDL.xml"); + //new WSDLHelper("http://localhost:8080/WSMyUpper.wsdl"); + //new WSDLHelper("http://localhost:8080/test.wsdl"); + new WSDLHelper("http://localhost:8080/ServiceGateway.wsdl"); + // new WSDLHelper("http://services.bio.ifi.lmu.de:1046/prothesaurus/services/BiologicalNameService?wsdl"); + long start = System.currentTimeMillis(); + help.parse(); + String[] methods = help.getWebMethods(); + System.out.println("el: " + (System.currentTimeMillis() - start)); + for (int idx = 0; idx < methods.length; idx++) { + System.out.println("method name: " + methods[idx]); + } + System.out.println("service url: " + help.getBinding()); + System.out.println("protocol: " + help.getProtocol()); + System.out.println("port=" + help.getURL().getPort()); + } catch (Exception exception) { + System.out.println("main method catch:"); + exception.printStackTrace(); + } + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/Filter.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/Filter.java new file mode 100644 index 00000000000..291be2ef5fc --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/Filter.java @@ -0,0 +1,111 @@ +/* + * 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.protocol.http.util.accesslog; + +import org.apache.jmeter.testelement.TestElement; + +/** + * Description:
+ *
+ * Filter interface is designed to make it easier to use Access Logs for JMeter + * test plans. Normally, a person would have to clean a log file manually and + * create the JMeter requests. The access log parse utility uses the filter to + * include/exclude files by either file name or regular expression pattern. + *

+ * It will also be used by HttpSamplers that use access logs. Using access logs + * is intended as a way to simulate production traffic. For functional testing, + * it is better to use the standard functional testing tools in JMeter. Using + * access logs can also reduce the amount of memory needed to run large test + * plans.
+ * + * @version $Revision$ + */ + +public interface Filter { + + /** + * @param oldextension old extension + * @param newextension new extension + */ + void setReplaceExtension(String oldextension, String newextension); + + /** + * Include all files in the array. + * + * @param filenames names of files to include + */ + void includeFiles(String[] filenames); + + /** + * Exclude all files in the array + * + * @param filenames names of files to exclude + */ + void excludeFiles(String[] filenames); + + /** + * Include any log entry that contains the following regular expression + * pattern. + * + * @param regexp list of regexp that match entries that should be included + */ + void includePattern(String[] regexp); + + /** + * Exclude any log entry that contains the following regular expression + * pattern. + * + * @param regexp list of regexp that match entries that should be excluded + */ + void excludePattern(String[] regexp); + + /** + * Log parser will call this method to see if a particular entry should be + * filtered or not. + * + * @param path + * log line that should be checked if it should to be filtered + * out + * @param sampler + * {@link TestElement} in which the line would be added + * @return boolean true if line should be filtered out, + * false otherwise + */ + boolean isFiltered(String path,TestElement sampler); + + /** + * In case the user wants to replace the file extension, log parsers should + * call this method. This is useful for regression test plans. If a website + * is migrating from one platform to another and the file extension changes, + * the filter provides an easy way to do it without spending a lot of time. + * + * @param text log line to be filtered + * @return String + */ + String filter(String text); + + /** + * Tell the filter when the parsing has reached the end of the log file and + * is about to begin again. Gives the filter a chance to adjust it's values, + * if needed. + * + */ + void reset(); + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/Generator.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/Generator.java new file mode 100644 index 00000000000..1dc4eaa52ac --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/Generator.java @@ -0,0 +1,138 @@ +/* + * 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.protocol.http.util.accesslog; + +/** + * Description:
+ *
+ * Generator is a base interface that defines the minimum methods needed to + * implement a concrete generator. The reason for creating this interface is + * eventually JMeter could use the logs directly rather than pre- process the + * logs into a JMeter .jmx file. In situations where a test plan simulates load + * from production logs, it is more efficient for JMeter to use the logs + * directly. + *

+ * From first hand experience, loading a test plan with 10K or more Requests + * requires a lot of memory. It's important to keep in mind this type of testing + * is closer to functional and regression testing than the typical stress tests. + * Typically, this kind of testing is most useful for search sites that get a + * large number of requests per day, but the request parameters vary + * dramatically. E-commerce sites typically have limited inventory, therefore it + * is better to design test plans that use data from the database. + *

+ * + * @version $Revision$ + */ + +public interface Generator { + + /** + * close the generator + */ + void close(); + + /** + * The host is the name of the server. + * + * @param host name of the server + */ + void setHost(String host); + + /** + * This is the label for the request, which is used in the logs and results. + * + * @param label label of the request + */ + void setLabel(String label); + + /** + * The method is the HTTP request method. It's normally POST or GET. + * + * @param post_get method of the HTTP request + */ + void setMethod(String post_get); + + /** + * Set the request parameters + * + * @param params request parameter + */ + void setParams(NVPair[] params); + + /** + * The path is the web page you want to test. + * + * @param path path of the web page + */ + void setPath(String path); + + /** + * The default port for HTTP is 80, but not all servers run on that port. + * + * @param port - + * port number + */ + void setPort(int port); + + /** + * Set the querystring for the request if the method is GET. + * + * @param querystring query string of the request + */ + void setQueryString(String querystring); + + /** + * The source logs is the location where the access log resides. + * + * @param sourcefile path to the access log file + */ + void setSourceLogs(String sourcefile); + + /** + * The target can be either a java.io.File or a Sampler. We make it generic, + * so that later on we can use these classes directly from a HTTPSampler. + * + * @param target target to generate into + */ + void setTarget(Object target); + + /** + * The method is responsible for calling the necessary methods to generate a + * valid request. If the generator is used to pre-process access logs, the + * method wouldn't return anything. If the generator is used by a control + * element, it should return the correct Sampler class with the required + * fields set. + * + * @return prefilled sampler + */ + Object generateRequest(); + + /** + * If the generator is converting the logs to a .jmx file, save should be + * called. + */ + void save(); + + /** + * The purpose of the reset is so Samplers can explicitly call reset to + * create a new instance of HTTPSampler. + * + */ + void reset(); +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java new file mode 100644 index 00000000000..2886de86e2a --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java @@ -0,0 +1,435 @@ +/* + * 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.protocol.http.util.accesslog; + +import java.io.Serializable; +import java.util.ArrayList; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; + +// For JUnit tests, @see TestLogFilter + +/** + * Description:
+ *
+ * LogFilter is a basic implementation of Filter interface. This implementation + * will keep a record of the filtered strings to avoid repeating the process + * unnecessarily. + *

+ * The current implementation supports replacing the file extension. The reason + * for supporting this is from first hand experience porting an existing website + * to Tomcat + JSP. Later on we may want to provide the ability to replace the + * whole filename. If the need materializes, we can add it later. + *

+ * Example of how to use it is provided in the main method. An example is + * provided below. + *

+ * testf = new LogFilter();
+ * String[] incl = { "hello.html", "index.html", "/index.jsp" };
+ * String[] thefiles = { "/test/hello.jsp", "/test/one/hello.html", "hello.jsp", "hello.htm", "/test/open.jsp",
+ *      "/test/open.html", "/index.jsp", "/index.jhtml", "newindex.jsp", "oldindex.jsp", "oldindex1.jsp",
+ *      "oldindex2.jsp", "oldindex3.jsp", "oldindex4.jsp", "oldindex5.jsp", "oldindex6.jsp", "/test/index.htm" };
+ * testf.excludeFiles(incl);
+ * System.out.println(" ------------ exclude test -------------");
+ * for (int idx = 0; idx < thefiles.length; idx++) {
+ *  boolean fl = testf.isFiltered(thefiles[idx]);
+ *  String line = testf.filter(thefiles[idx]);
+ *  if (line != null) {
+ *     System.out.println("the file: " + line);
+ *  }
+ * }
+ * 
+ * + * As a general note. Both isFiltered and filter() have to be called. Calling + * either one will not produce the desired result. isFiltered(string) will tell + * you if a string should be filtered. The second step is to filter the string, + * which will return null if it is filtered and replace any part of the string + * that should be replaced. + */ + +public class LogFilter implements Filter, Serializable { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // protected members used by class to filter + + protected boolean CHANGEEXT = false; + + protected String OLDEXT = null; + + protected String NEWEXT = null; + + protected String[] INCFILE = null; + + protected String[] EXCFILE = null; + + protected boolean FILEFILTER = false; + + protected boolean USEFILE = true; + + protected String[] INCPTRN = null; + + protected String[] EXCPTRN = null; + + protected boolean PTRNFILTER = false; + + protected ArrayList EXCPATTERNS = new ArrayList(); + + protected ArrayList INCPATTERNS = new ArrayList(); + + protected String NEWFILE = null; + + /** + * The default constructor is empty + */ + public LogFilter() { + super(); + } + + /** + * The method will replace the file extension with the new one. You can + * either provide the extension without the period ".", or with. The method + * will check for period and add it if it isn't present. + * + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#setReplaceExtension(java.lang.String, + * java.lang.String) + */ + @Override + public void setReplaceExtension(String oldext, String newext) { + if (oldext != null && newext != null) { + this.CHANGEEXT = true; + if (oldext.indexOf('.') < 0 && newext.indexOf('.') < 0) { + this.OLDEXT = "." + oldext; + this.NEWEXT = "." + newext; + } else { + this.OLDEXT = oldext; + this.NEWEXT = newext; + } + } + } + + /** + * Give the filter a list of files to include + * + * @param filenames + * list of files to include + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#includeFiles(java.lang.String[]) + */ + @Override + public void includeFiles(String[] filenames) { + if (filenames != null && filenames.length > 0) { + INCFILE = filenames; + this.FILEFILTER = true; + } + } + + /** + * Give the filter a list of files to exclude + * + * @param filenames + * list of files to exclude + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#excludeFiles(java.lang.String[]) + */ + @Override + public void excludeFiles(String[] filenames) { + if (filenames != null && filenames.length > 0) { + EXCFILE = filenames; + this.FILEFILTER = true; + } + } + + /** + * Give the filter a set of regular expressions to filter with for + * inclusion. This method hasn't been fully implemented and test yet. The + * implementation is not complete. + * + * @param regexp + * list of regular expressions + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#includePattern(String[]) + */ + @Override + public void includePattern(String[] regexp) { + if (regexp != null && regexp.length > 0) { + INCPTRN = regexp; + this.PTRNFILTER = true; + // now we create the compiled pattern and + // add it to the arraylist + for (int idx = 0; idx < INCPTRN.length; idx++) { + this.INCPATTERNS.add(this.createPattern(INCPTRN[idx])); + } + } + } + + /** + * Give the filter a set of regular expressions to filter with for + * exclusion. This method hasn't been fully implemented and test yet. The + * implementation is not complete. + * + * @param regexp + * list of regular expressions + * + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#excludePattern(String[]) + */ + @Override + public void excludePattern(String[] regexp) { + if (regexp != null && regexp.length > 0) { + EXCPTRN = regexp; + this.PTRNFILTER = true; + // now we create the compiled pattern and + // add it to the arraylist + for (int idx = 0; idx < EXCPTRN.length; idx++) { + this.EXCPATTERNS.add(this.createPattern(EXCPTRN[idx])); + } + } + } + + /** + * In the case of log filtering the important thing is whether the log entry + * should be used. Therefore, the method will only return true if the entry + * should be used. Since the interface defines both inclusion and exclusion, + * that means by default inclusion filtering assumes all entries are + * excluded unless it matches. In the case of exclusion filtering, it assumes + * all entries are included unless it matches, which means it should be + * excluded. + * + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#isFiltered(String, TestElement) + * @param path path to be tested + * @return true if entry should be excluded + */ + @Override + public boolean isFiltered(String path,TestElement el) { + if (this.FILEFILTER) { + return filterFile(path); + } + if (this.PTRNFILTER) { + return filterPattern(path); + } + return false; + } + + /** + * Filter the file. The implementation performs the exclusion first before + * the inclusion. This means if a file name is in both string arrays, the + * exclusion will take priority. Depending on how users expect this to work, + * we may want to change the priority so that inclusion is performed first + * and exclusion second. Another possible alternative is to perform both + * inclusion and exclusion. Doing so would make the most sense if the method + * throws an exception and tells the user the same filename is in both the + * include and exclude array. + * + * @param file the file to filter + * @return boolean + */ + protected boolean filterFile(String file) { + // double check this logic make sure it + // makes sense + if (this.EXCFILE != null) { + return excFile(file); + } else if (this.INCFILE != null) { + return !incFile(file); + } + return false; + } + + /** + * Method implements the logic for filtering file name inclusion. The method + * iterates through the array and uses indexOf. Once it finds a match, it + * won't bother with the rest of the filenames in the array. + * + * @param text + * name of the file to tested (must not be null) + * @return boolean include + */ + public boolean incFile(String text) { + // inclusion filter assumes most of + // the files are not wanted, therefore + // usefile is set to false unless it + // matches. + this.USEFILE = false; + for (int idx = 0; idx < this.INCFILE.length; idx++) { + if (text.indexOf(this.INCFILE[idx]) > -1) { + this.USEFILE = true; + break; + } + } + return this.USEFILE; + } + + /** + * Method implements the logic for filtering file name exclusion. The method + * iterates through the array and uses indexOf. Once it finds a match, it + * won't bother with the rest of the filenames in the array. + * + * @param text + * name of the file to be tested (must not be null) + * @return boolean exclude + */ + public boolean excFile(String text) { + // exclusion filter assumes most of + // the files are used, therefore + // usefile is set to true, unless + // it matches. + this.USEFILE = true; + boolean exc = false; + for (int idx = 0; idx < this.EXCFILE.length; idx++) { + if (text.indexOf(this.EXCFILE[idx]) > -1) { + exc = true; + this.USEFILE = false; + break; + } + } + return exc; + } + + /** + * The current implementation assumes the user has checked the regular + * expressions so that they don't cancel each other. The basic assumption is + * the method will return true if the text should be filtered. If not, it + * will return false, which means it should not be filtered. + * + * @param text text to be checked + * @return boolean + */ + protected boolean filterPattern(String text) { + if (this.INCPTRN != null) { + return !incPattern(text); + } else if (this.EXCPTRN != null) { + return excPattern(text); + } + return false; + } + + /** + * By default, the method assumes the entry is not included, unless it + * matches. In that case, it will return true. + * + * @param text text to be checked + * @return true if text is included + */ + protected boolean incPattern(String text) { + this.USEFILE = false; + for (int idx = 0; idx < this.INCPATTERNS.size(); idx++) { + if (JMeterUtils.getMatcher().contains(text, this.INCPATTERNS.get(idx))) { + this.USEFILE = true; + break; + } + } + return this.USEFILE; + } + + /** + * The method assumes by default the text is not excluded. If the text + * matches the pattern, it will then return true. + * + * @param text text to be checked + * @return true if text is excluded + */ + protected boolean excPattern(String text) { + this.USEFILE = true; + boolean exc = false; + for (int idx = 0; idx < this.EXCPATTERNS.size(); idx++) { + if (JMeterUtils.getMatcher().contains(text, this.EXCPATTERNS.get(idx))) { + exc = true; + this.USEFILE = false; + break; + } + } + return exc; + } + + /** + * Method uses indexOf to replace the old extension with the new extension. + * It might be good to use regular expression, but for now this is a simple + * method. The method isn't designed to replace multiple instances of the + * text, since that isn't how file extensions work. If the string contains + * more than one instance of the old extension, only the first instance will + * be replaced. + * + * @param text + * name of the file in which the extension should be replaced + * (must not be null) + * @return true if the extension could be replaced, + * false otherwise + */ + public boolean replaceExtension(String text) { + int pt = text.indexOf(this.OLDEXT); + if (pt > -1) { + int extsize = this.OLDEXT.length(); + this.NEWFILE = text.substring(0, pt) + this.NEWEXT + text.substring(pt + extsize); + return true; + } else { + return false; + } + } + + /** + * The current implementation checks the boolean if the text should be used + * or not. isFilter( string) has to be called first. + * + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#filter(java.lang.String) + */ + @Override + public String filter(String text) { + if (this.CHANGEEXT) { + if (replaceExtension(text)) { + return this.NEWFILE; + } else { + return text; + } + } else if (this.USEFILE) { + return text; + } else { + return null; + } + } + + /** + * create a new pattern object from the string. + * + * @param pattern + * string representation of the perl5 compatible regex pattern + * @return compiled Pattern, or null if no pattern could be + * compiled + */ + public Pattern createPattern(String pattern) { + try { + return JMeterUtils.getPatternCache().getPattern(pattern, + Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); + } catch (MalformedCachePatternException exception) { + log.error("Problem with pattern: "+pattern,exception); + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void reset() { + + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java new file mode 100644 index 00000000000..7800c2792c1 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java @@ -0,0 +1,77 @@ +/* + * 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.protocol.http.util.accesslog; + +import org.apache.jmeter.testelement.TestElement; + +/** + * Description:
+ *
+ * LogParser is the base interface for classes implementing concrete parse + * logic. For an example of how to use the interface, look at the Tomcat access + * log parser. + *

+ * The original log parser was written in 2 hours to parse access logs. Since + * then, the design and implementation has been rewritten from scratch several + * times to make it more generic and extensible. The first version was hard + * coded and written over the weekend. + *

+ * + * @version $Revision$ + */ + +public interface LogParser { + + /** + * close the any streams or readers. + */ + void close(); + + /** + * the method will parse the given number of lines. Pass "-1" to parse the + * entire file. If the end of the file is reached without parsing a line, a + * 0 is returned. If the method is subsequently called again, it will + * restart parsing at the beginning. + * + * @param count max lines to parse, or -1 for the entire file + * @param el {@link TestElement} to read lines into + * @return number of lines parsed + */ + int parseAndConfigure(int count, TestElement el); + + /** + * We allow for filters, so that users can simply point to an Access log + * without having to clean it up. This makes it significantly easier and + * reduces the amount of work. Plus I'm lazy, so going through a log file to + * clean it up is a bit tedious. One example of this is using the filter to + * exclude any log entry that has a 505 response code. + * + * @param filter {@link Filter} to use + */ + void setFilter(Filter filter); + + /** + * The method is provided to make it easy to dynamically create new classes + * using Class.newInstance(). Then the access log file is set using this + * method. + * + * @param source name of the access log file + */ + void setSourceFile(String source); +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/NVPair.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/NVPair.java new file mode 100644 index 00000000000..c38d656a986 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/NVPair.java @@ -0,0 +1,84 @@ +/* + * 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.protocol.http.util.accesslog; + +/** + * Description:
+ *
+ * + * @version $Revision$ + */ + +public class NVPair { + + protected String NAME = ""; + + protected String VALUE = ""; + + public NVPair() { + } + + /** + * The constructor takes a name and value which represent HTTP request + * parameters. + * + * @param name name of the request parameter + * @param value value of the request parameter + */ + public NVPair(String name, String value) { + this.NAME = name; + this.VALUE = value; + } + + /** + * Set the name + * + * @param name name of the request parameter + */ + public void setName(String name) { + this.NAME = name; + } + + /** + * Set the value + * + * @param value value of the request parameter + */ + public void setValue(String value) { + this.VALUE = value; + } + + /** + * Return the name + * + * @return name + */ + public String getName() { + return this.NAME; + } + + /** + * Return the value + * + * @return value + */ + public String getValue() { + return this.VALUE; + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java new file mode 100644 index 00000000000..10ec624d20c --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java @@ -0,0 +1,46 @@ +/* + * 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.protocol.http.util.accesslog; + +import org.apache.jmeter.testelement.TestElement; + +public class OrderPreservingLogParser extends SharedTCLogParser { + + public OrderPreservingLogParser() { + super(); + } + + public OrderPreservingLogParser(String source) { + super(source); + } + + /** + * parse a set number of lines from the access log. Keep in mind the number + * of lines parsed will depend the filter and number of lines in the log. + * The method returns the actual lines parsed. + * + * @param count number of max lines to read + * @return lines parsed + */ + @Override + public synchronized int parseAndConfigure(int count, TestElement el) { + return this.parse(el, count); + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java new file mode 100644 index 00000000000..0e325b8887c --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java @@ -0,0 +1,226 @@ +/* + * 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. + * + */ + +/* + * Created on May 21, 2004 + */ +package org.apache.jmeter.protocol.http.util.accesslog; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.sampler.HTTPSampler; +import org.apache.jmeter.testelement.TestCloneable; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * Provides Session Filtering for the AccessLog Sampler. + * + */ +public class SessionFilter implements Filter, Serializable, TestCloneable,ThreadListener { + private static final long serialVersionUID = 232L; + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * These objects are static across multiple threads in a test, via clone() + * method. + */ + protected Map cookieManagers; + protected Set managersInUse; + + protected CookieManager lastUsed; + + /* + * (non-Javadoc) + * + * @see org.apache.jmeter.protocol.http.util.accesslog.LogFilter#excPattern(java.lang.String) + */ + protected boolean hasExcPattern(String text) { + return false; + } + + protected String getIpAddress(String logLine) { + Pattern incIp = JMeterUtils.getPatternCache().getPattern("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}", + Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); + Perl5Matcher matcher = JMeterUtils.getMatcher(); + matcher.contains(logLine, incIp); + return matcher.getMatch().group(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void reset() { + cookieManagers.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object clone() { + if(cookieManagers == null) + { + cookieManagers = new ConcurrentHashMap(); + } + if(managersInUse == null) + { + managersInUse = Collections.synchronizedSet(new HashSet()); + } + SessionFilter f = new SessionFilter(); + f.cookieManagers = cookieManagers; + f.managersInUse = managersInUse; + return f; + } + + /** + * + */ + public SessionFilter() { + } + + /** + * {@inheritDoc} + */ + @Override + public void excludeFiles(String[] filenames) { + } + + /** + * {@inheritDoc} + */ + @Override + public void excludePattern(String[] regexp) { + } + + /** + * {@inheritDoc} + */ + @Override + public String filter(String text) { + return text; + } + + /** + * {@inheritDoc} + */ + @Override + public void includeFiles(String[] filenames) { + } + + /** + * {@inheritDoc} + */ + @Override + public void includePattern(String[] regexp) { + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isFiltered(String path,TestElement sampler) { + String ipAddr = getIpAddress(path); + CookieManager cm = getCookieManager(ipAddr); + ((HTTPSampler)sampler).setCookieManager(cm); + return false; + } + + protected CookieManager getCookieManager(String ipAddr) + { + CookieManager cm = null; + // First have to release the cookie we were using so other + // threads stuck in wait can move on + synchronized(managersInUse) + { + if(lastUsed != null) + { + managersInUse.remove(lastUsed); + managersInUse.notify(); + } + } + // let notified threads move on and get lock on managersInUse + if(lastUsed != null) + { + Thread.yield(); + } + // here is the core routine to find appropriate cookie manager and + // check it's not being used. If used, wait until whoever's using it gives + // it up + synchronized(managersInUse) + { + cm = cookieManagers.get(ipAddr); + if(cm == null) + { + cm = new CookieManager(); + cm.testStarted(); + cookieManagers.put(ipAddr,cm); + } + while(managersInUse.contains(cm)) + { + try { + managersInUse.wait(); + } catch (InterruptedException e) { + log.info("SessionFilter wait interrupted"); + } + } + managersInUse.add(cm); + lastUsed = cm; + } + return cm; + } + + /** + * {@inheritDoc} + */ + @Override + public void setReplaceExtension(String oldextension, String newextension) { + } + + /** + * {@inheritDoc} + */ + @Override + public void threadFinished() { + synchronized(managersInUse) + { + managersInUse.remove(lastUsed); + managersInUse.notify(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void threadStarted() { + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java new file mode 100644 index 00000000000..6e573dcdbc0 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java @@ -0,0 +1,120 @@ +/* + * 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.protocol.http.util.accesslog; + +import java.io.IOException; + +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestCloneable; +import org.apache.jmeter.testelement.TestElement; + +public class SharedTCLogParser extends TCLogParser implements TestCloneable { + + public SharedTCLogParser() { + super(); + } + + public SharedTCLogParser(String source) { + super(source); + } + + /** + * {@inheritDoc} + */ + @Override + public Object clone() { + SharedTCLogParser parser = new SharedTCLogParser(); + parser.FILENAME = FILENAME; + parser.FILTER = FILTER; + return parser; + } + + /** + * {@inheritDoc} + */ + @Override + public int parse(TestElement el, int parseCount) { + FileServer fileServer = FileServer.getFileServer(); + fileServer.reserveFile(FILENAME); + try { + return parse(fileServer, el, parseCount); + } catch (Exception exception) { + log.error("Problem creating samples", exception); + } + return -1;// indicate that an error occured + } + + /** + * The method is responsible for reading each line, and breaking out of the + * while loop if a set number of lines is given. + * + * @param breader + * reader to read lines from + * @param el + * {@link TestElement} in which to add the parsed lines + * @param parseCount + * max number of lines to parse + * @return number of read lines + */ + protected int parse(FileServer breader, TestElement el, int parseCount) { + int actualCount = 0; + String line = null; + try { + // read one line at a time using + // BufferedReader + line = breader.readLine(FILENAME); + while (line != null) { + if (line.length() > 0) { + actualCount += this.parseLine(line, el); + } + // we check the count to see if we have exceeded + // the number of lines to parse. There's no way + // to know where to stop in the file. Therefore + // we use break to escape the while loop when + // we've reached the count. + if (parseCount != -1 && actualCount >= parseCount) { + break; + } + line = breader.readLine(FILENAME); + } + if (line == null) { + breader.closeFile(FILENAME); + // this.READER = new BufferedReader(new + // FileReader(this.SOURCE)); + // parse(this.READER,el); + } + } catch (IOException ioe) { + log.error("Error reading log file", ioe); + } + return actualCount; + } + + /** + * {@inheritDoc} + */ + @Override + public void close() { + try { + FileServer.getFileServer().closeFile(FILENAME); + } catch (IOException e) { + // do nothing + } + } + +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/StandardGenerator.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/StandardGenerator.java new file mode 100644 index 00000000000..50287356d83 --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/StandardGenerator.java @@ -0,0 +1,230 @@ +/* + * 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.protocol.http.util.accesslog; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Description:
+ *
+ * StandardGenerator will be the default generator used to pre-process logs. It + * uses JMeter classes to generate the .jmx file. The first version of the + * utility only generated the HTTP requests as XML, but it required users to + * copy and paste it into a blank jmx file. Doing that way isn't flexible and + * would require changes to keep the format in sync. + *

+ * This version is a completely new class with a totally different + * implementation, since generating the XML is no longer handled by the + * generator. The generator is only responsible for handling the parsed results + * and passing it to the appropriate JMeter class. + *

+ * Notes:
+ * the class needs to first create a thread group and add it to the HashTree. + * Then the samplers should be added to the thread group. Listeners shouldn't be + * added and should be left up to the user. One option is to provide parameters, + * so the user can pass the desired listener to the tool. + */ + +public class StandardGenerator implements Generator, Serializable { + + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected HTTPSamplerBase SAMPLE = null; + + protected transient FileWriter WRITER = null; + + protected transient OutputStream OUTPUT = null; + + protected String FILENAME = null; + + protected File FILE = null; + + // NOT USED transient protected ThreadGroup THREADGROUP = null; + // Anyway, was this supposed to be the class from java.lang, or + // jmeter.threads? + + /** + * The constructor is used by GUI and samplers to generate request objects. + */ + public StandardGenerator() { + super(); + init(); + } + + /** + * + * @param file name of a file (TODO seems not to be used anywhere) + */ + public StandardGenerator(String file) { + FILENAME = file; + init(); + } + + /** + * initialize the generator. It should create the following objects. + *

+ *

    + *
  1. ListedHashTree
  2. + *
  3. ThreadGroup
  4. + *
  5. File object
  6. + *
  7. Writer
  8. + *
+ */ + private void init() {// called from ctor, so must not be overridable + generateRequest(); + } + + /** + * Create the FileWriter to save the JMX file. + */ + protected void initStream() { + try { + this.OUTPUT = new FileOutputStream(FILE); + } catch (IOException exception) { + log.error(exception.getMessage()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void close() { + JOrphanUtils.closeQuietly(OUTPUT); + JOrphanUtils.closeQuietly(WRITER); + } + + /** + * {@inheritDoc} + */ + @Override + public void setHost(String host) { + SAMPLE.setDomain(host); + } + + /** + * {@inheritDoc} + */ + @Override + public void setLabel(String label) { + + } + + /** + * {@inheritDoc} + */ + @Override + public void setMethod(String post_get) { + SAMPLE.setMethod(post_get); + } + + /** + * {@inheritDoc} + */ + @Override + public void setParams(NVPair[] params) { + for (int idx = 0; idx < params.length; idx++) { + SAMPLE.addArgument(params[idx].getName(), params[idx].getValue()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setPath(String path) { + SAMPLE.setPath(path); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPort(int port) { + SAMPLE.setPort(port); + } + + /** + * {@inheritDoc} + */ + @Override + public void setQueryString(String querystring) { + SAMPLE.parseArguments(querystring); + } + + /** + * {@inheritDoc} + */ + @Override + public void setSourceLogs(String sourcefile) { + } + + /** + * {@inheritDoc} + */ + @Override + public void setTarget(Object target) { + } + + /** + * {@inheritDoc} + */ + @Override + public Object generateRequest() { + SAMPLE = HTTPSamplerFactory.newInstance(); + return SAMPLE; + } + + /** + * save must be called to write the jmx file, otherwise it will not be + * saved. + */ + @Override + public void save() { + // no implementation at this time, since + // we bypass the idea of having a console + // tool to generate test plans. Instead + // I decided to have a sampler that uses + // the generator and parser directly + } + + /** + * Reset the HTTPSampler to make sure it is a new instance. + *

+ * {@inheritDoc} + */ + @Override + public void reset() { + SAMPLE = null; + generateRequest(); + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java new file mode 100644 index 00000000000..30712fb99ab --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java @@ -0,0 +1,585 @@ +/* + * 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.protocol.http.util.accesslog; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.zip.GZIPInputStream; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// For JUnit tests, @see TestTCLogParser + +/** + * Description:
+ *
+ * Currently the parser only handles GET/POST requests. It's easy enough to add + * support for other request methods by changing checkMethod. The is a complete + * rewrite of a tool I wrote for myself earlier. The older algorithm was basic + * and did not provide the same level of flexibility I want, so I wrote a new + * one using a totally new algorithm. This implementation reads one line at a + * time using BufferedReader. When it gets to the end of the file and the + * sampler needs to get more requests, the parser will re-initialize the + * BufferedReader. The implementation uses StringTokenizer to create tokens. + *

+ * The parse algorithm is the following: + *

    + *
  1. cleans the entry by looking for backslash "\" + *
  2. looks to see if GET or POST is in the line + *
  3. tokenizes using quotes " + *
  4. finds the token with the request method + *
  5. gets the string of the token and tokenizes it using space + *
  6. finds the first token beginning with slash character + *
  7. tokenizes the string using question mark "?" + *
  8. get the path from the first token + *
  9. returns the second token and checks it for parameters + *
  10. tokenizes the string using ampersand "&" + *
  11. parses each token to name/value pairs + *
+ *

+ * Extending this class is fairly simple. Most access logs use the same format + * starting from the request method. Therefore, changing the implementation of + * cleanURL(string) method should be sufficient to support new log formats. + * Tomcat uses common log format, so any webserver that uses the format should + * work with this parser. Servers that are known to use non standard formats are + * IIS and Netscape. + */ + +public class TCLogParser implements LogParser { + protected static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * TODO should these fields be public? + * They don't appear to be used externally. + * + * Also, are they any different from HTTPConstants.GET etc. ? + * In some cases they seem to be used as the method name from the Tomcat log. + * However the RMETHOD field is used as the value for HTTPSamplerBase.METHOD, + * for which HTTPConstants is most approriate. + */ + public static final String GET = "GET"; + + public static final String POST = "POST"; + + public static final String HEAD = "HEAD"; + + /** protected members * */ + protected String RMETHOD = null; + + /** + * The path to the access log file + */ + protected String URL_PATH = null; + + protected boolean useFILE = true; + + protected File SOURCE = null; + + protected String FILENAME = null; + + protected BufferedReader READER = null; + + /** + * Handles to supporting classes + */ + protected Filter FILTER = null; + + /** + * by default, we probably should decode the parameter values + */ + protected boolean decode = true; + + // TODO downcase UPPER case non-final variables + + /** + * + */ + public TCLogParser() { + super(); + } + + /** + * @param source name of the source file + */ + public TCLogParser(String source) { + setSourceFile(source); + } + + /** + * by default decode is set to true. if the parameters shouldn't be + * decoded, call the method with false + * @param decodeparams flag whether parameters should be decoded + */ + public void setDecodeParameterValues(boolean decodeparams) { + this.decode = decodeparams; + } + + /** + * decode the parameter values is to true by default + * @return true if parameter values should be decoded, false otherwise + */ + public boolean decodeParameterValue() { + return this.decode; + } + + /** + * Calls this method to set whether or not to use the path in the log. We + * may want to provide the ability to filter the log file later on. By + * default, the parser uses the file in the log. + * + * @param file + * flag whether to use the path from the log + */ + public void setUseParsedFile(boolean file) { + this.useFILE = file; + } + + /** + * Use the filter to include/exclude files in the access logs. This is + * provided as a convenience and reduce the need to spend hours cleaning up + * log files. + * + * @param filter {@link Filter} to be used while reading the log lines + */ + @Override + public void setFilter(Filter filter) { + FILTER = filter; + } + + /** + * Sets the source file. + * + * @param source name of the source file + */ + @Override + public void setSourceFile(String source) { + this.FILENAME = source; + } + + /** + * parse the entire file. + * + * @param el TestElement to read the lines into + * @param parseCount number of max lines to read + * @return number of read lines, or -1 if an error occurred while reading + */ + public int parse(TestElement el, int parseCount) { + if (this.SOURCE == null) { + this.SOURCE = new File(this.FILENAME); + } + try { + if (this.READER == null) { + this.READER = getReader(this.SOURCE); + } + return parse(this.READER, el, parseCount); + } catch (Exception exception) { + log.error("Problem creating samples", exception); + } + return -1;// indicate that an error occured + } + + private static BufferedReader getReader(File file) throws IOException { + if (! isGZIP(file)) { + return new BufferedReader(new FileReader(file)); + } + GZIPInputStream in = new GZIPInputStream(new FileInputStream(file)); + return new BufferedReader(new InputStreamReader(in)); + } + + private static boolean isGZIP(File file) throws IOException { + FileInputStream in = new FileInputStream(file); + try { + return in.read() == (GZIPInputStream.GZIP_MAGIC & 0xFF) + && in.read() == (GZIPInputStream.GZIP_MAGIC >> 8); + } finally { + in.close(); + } + } + + /** + * parse a set number of lines from the access log. Keep in mind the number + * of lines parsed will depend on the filter and number of lines in the log. + * The method returns the actual number of lines parsed. + * + * @param count number of lines to read + * @param el {@link TestElement} to read lines into + * @return lines parsed + */ + @Override + public int parseAndConfigure(int count, TestElement el) { + return this.parse(el, count); + } + + /** + * The method is responsible for reading each line, and breaking out of the + * while loop if a set number of lines is given.
+ * Note: empty lines will not be counted + * + * @param breader {@link BufferedReader} to read lines from + * @param el {@link TestElement} to read lines into + * @param parseCount number of lines to read + * @return number of lines parsed + */ + protected int parse(BufferedReader breader, TestElement el, int parseCount) { + int actualCount = 0; + String line = null; + try { + // read one line at a time using + // BufferedReader + line = breader.readLine(); + while (line != null) { + if (line.length() > 0) { + actualCount += this.parseLine(line, el); + } + // we check the count to see if we have exceeded + // the number of lines to parse. There's no way + // to know where to stop in the file. Therefore + // we use break to escape the while loop when + // we've reached the count. + if (parseCount != -1 && actualCount >= parseCount) { + break; + } + line = breader.readLine(); + } + if (line == null) { + breader.close(); + this.READER = null; + // this.READER = new BufferedReader(new + // FileReader(this.SOURCE)); + // parse(this.READER,el); + } + } catch (IOException ioe) { + log.error("Error reading log file", ioe); + } + return actualCount; + } + + /** + * parseLine calls the other parse methods to parse the given text. + * + * @param line single line to be parsed + * @param el {@link TestElement} in which the line will be added + * @return number of lines parsed (zero or one, actually) + */ + protected int parseLine(String line, TestElement el) { + int count = 0; + // we clean the line to get + // rid of extra stuff + String cleanedLine = this.cleanURL(line); + log.debug("parsing line: " + line); + // now we set request method + el.setProperty(HTTPSamplerBase.METHOD, RMETHOD); + if (FILTER != null) { + log.debug("filter is not null"); + if (!FILTER.isFiltered(line,el)) { + log.debug("line was not filtered"); + // increment the current count + count++; + // we filter the line first, before we try + // to separate the URL into file and + // parameters. + line = FILTER.filter(cleanedLine); + if (line != null) { + createUrl(line, el); + } + } else { + log.debug("Line was filtered"); + } + } else { + log.debug("filter was null"); + // increment the current count + count++; + // in the case when the filter is not set, we + // parse all the lines + createUrl(cleanedLine, el); + } + return count; + } + + /** + * @param line single line of which the url should be extracted + * @param el {@link TestElement} into which the url will be added + */ + private void createUrl(String line, TestElement el) { + String paramString = null; + // check the URL for "?" symbol + paramString = this.stripFile(line, el); + if (paramString != null) { + this.checkParamFormat(line); + // now that we have stripped the file, we can parse the parameters + this.convertStringToJMRequest(paramString, el); + } + } + + /** + * The method cleans the URL using the following algorithm. + *

    + *
  1. check for double quotes + *
  2. check the request method + *
  3. tokenize using double quotes + *
  4. find first token containing request method + *
  5. tokenize string using space + *
  6. find first token that begins with "/" + *
+ * Example Tomcat log entry: + *

+ * 127.0.0.1 - - [08/Jan/2003:07:03:54 -0500] "GET /addrbook/ HTTP/1.1" 200 + * 1981 + *

+ * would result in the extracted url /addrbook/ + * + * @param entry line from which the url is to be extracted + * @return cleaned url + */ + public String cleanURL(String entry) { + String url = entry; + // if the string contains atleast one double + // quote and checkMethod is true, go ahead + // and tokenize the string. + if (entry.indexOf('"') > -1 && checkMethod(entry)) { + StringTokenizer tokens = null; + // we tokenize using double quotes. this means + // for tomcat we should have 3 tokens if there + // isn't any additional information in the logs + tokens = this.tokenize(entry, "\""); + while (tokens.hasMoreTokens()) { + String toke = tokens.nextToken(); + // if checkMethod on the token is true + // we tokenzie it using space and escape + // the while loop. Only the first matching + // token will be used + if (checkMethod(toke)) { + StringTokenizer token2 = this.tokenize(toke, " "); + while (token2.hasMoreTokens()) { + String t = (String) token2.nextElement(); + if (t.equalsIgnoreCase(GET)) { + RMETHOD = GET; + } else if (t.equalsIgnoreCase(POST)) { + RMETHOD = POST; + } else if (t.equalsIgnoreCase(HEAD)) { + RMETHOD = HEAD; + } + // there should only be one token + // that starts with slash character + if (t.startsWith("/")) { + url = t; + break; + } + } + break; + } + } + return url; + } + // we return the original string + return url; + } + + /** + * The method checks for POST, GET and HEAD methods currently. + * The other methods aren't supported yet. + * + * @param text text to be checked for HTTP method + * @return true if method is supported, false otherwise + */ + public boolean checkMethod(String text) { + if (text.indexOf("GET") > -1) { + this.RMETHOD = GET; + return true; + } else if (text.indexOf("POST") > -1) { + this.RMETHOD = POST; + return true; + } else if (text.indexOf("HEAD") > -1) { + this.RMETHOD = HEAD; + return true; + } else { + return false; + } + } + + /** + * Tokenize the URL into two tokens. If the URL has more than one "?", the + * parse may fail. Only the first two tokens are used. The first token is + * automatically parsed and set at {@link TCLogParser#URL_PATH URL_PATH}. + * + * @param url url which should be stripped from parameters + * @param el {@link TestElement} to parse url into + * @return String presenting the parameters, or null when none where found + */ + public String stripFile(String url, TestElement el) { + if (url.indexOf('?') > -1) { + StringTokenizer tokens = this.tokenize(url, "?"); + this.URL_PATH = tokens.nextToken(); + el.setProperty(HTTPSamplerBase.PATH, URL_PATH); + return tokens.hasMoreTokens() ? tokens.nextToken() : null; + } + el.setProperty(HTTPSamplerBase.PATH, url); + return null; + } + + /** + * Checks the string to make sure it has /path/file?name=value format. If + * the string doesn't contains a "?", it will return false. + * + * @param url url to check for parameters + * @return true if url contains a ?, + * false otherwise + */ + public boolean checkURL(String url) { + if (url.indexOf('?') > -1) { + return true; + } + return false; + } + + /** + * Checks the string to see if it contains "&" and "=". If it does, return + * true, so that it can be parsed. + * + * @param text text to be checked for & and = + * @return true if text contains both & + * and =, false otherwise + */ + public boolean checkParamFormat(String text) { + if (text.indexOf('&') > -1 && text.indexOf('=') > -1) { + return true; + } + return false; + } + + /** + * Convert a single line into XML + * + * @param text to be be converted + * @param el {@link HTTPSamplerBase} which consumes the text + */ + public void convertStringToJMRequest(String text, TestElement el) { + ((HTTPSamplerBase) el).parseArguments(text); + } + + /** + * Parse the string parameters into NVPair[] array. Once they are parsed, it + * is returned. The method uses parseOneParameter(string) to convert each + * pair. + * + * @param stringparams String with parameters to be parsed + * @return array of {@link NVPair}s + */ + public NVPair[] convertStringtoNVPair(String stringparams) { + List vparams = this.parseParameters(stringparams); + NVPair[] nvparams = new NVPair[vparams.size()]; + // convert the Parameters + for (int idx = 0; idx < nvparams.length; idx++) { + nvparams[idx] = this.parseOneParameter(vparams.get(idx)); + } + return nvparams; + } + + /** + * Method expects name and value to be separated by an equal sign "=". The + * method uses StringTokenizer to make a NVPair object. If there happens to + * be more than one "=" sign, the others are ignored. The chance of a string + * containing more than one is unlikely and would not conform to HTTP spec. + * I should double check the protocol spec to make sure this is accurate. + * + * @param parameter + * to be parsed + * @return {@link NVPair} with the parsed name and value of the parameter + */ + protected NVPair parseOneParameter(String parameter) { + String name = ""; // avoid possible NPE when trimming the name + String value = null; + try { + StringTokenizer param = this.tokenize(parameter, "="); + name = param.nextToken(); + value = param.nextToken(); + } catch (Exception e) { + // do nothing. it's naive, but since + // the utility is meant to parse access + // logs the formatting should be correct + } + if (value == null) { + value = ""; + } else { + if (decode) { + try { + value = URLDecoder.decode(value,"UTF-8"); + } catch (UnsupportedEncodingException e) { + log.warn(e.getMessage()); + } + } + } + return new NVPair(name.trim(), value.trim()); + } + + /** + * Method uses StringTokenizer to convert the string into single pairs. The + * string should conform to HTTP protocol spec, which means the name/value + * pairs are separated by the ampersand symbol "&". Someone could write the + * querystrings by hand, but that would be round about and go against the + * purpose of this utility. + * + * @param parameters string to be parsed + * @return List of name/value pairs + */ + protected List parseParameters(String parameters) { + List parsedParams = new ArrayList(); + StringTokenizer paramtokens = this.tokenize(parameters, "&"); + while (paramtokens.hasMoreElements()) { + parsedParams.add(paramtokens.nextToken()); + } + return parsedParams; + } + + /** + * Parses the line using java.util.StringTokenizer. + * + * @param line + * line to be parsed + * @param delim + * delimiter + * @return StringTokenizer constructed with line and delim + */ + public StringTokenizer tokenize(String line, String delim) { + return new StringTokenizer(line, delim); + } + + @Override + public void close() { + try { + this.READER.close(); + this.READER = null; + this.SOURCE = null; + } catch (IOException e) { + // do nothing + } + } +} diff --git a/src/protocol/http/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java b/src/protocol/http/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java new file mode 100644 index 00000000000..43fcb032bad --- /dev/null +++ b/src/protocol/http/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java @@ -0,0 +1,354 @@ +/* +o * 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.protocol.http.visualizers; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.TextBoxDialoger.TextBoxDoubleClick; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.RequestView; +import org.apache.jmeter.visualizers.SamplerResultTab.RowResult; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.log.Logger; + +/** + * Specializer panel to view a HTTP request parsed + * + */ +public class RequestViewHTTP implements RequestView { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY_LABEL = "view_results_table_request_tab_http"; //$NON-NLS-1$ + + private static final String CHARSET_DECODE = "ISO-8859-1"; //$NON-NLS-1$ + + private static final String PARAM_CONCATENATE = "&"; //$NON-NLS-1$ + + private JPanel paneParsed; + + private ObjectTableModel requestModel = null; + + private ObjectTableModel paramsModel = null; + + private ObjectTableModel headersModel = null; + + private static final String[] COLUMNS_REQUEST = new String[] { + " ", // one space for blank header // $NON-NLS-1$ + " " }; // one space for blank header // $NON-NLS-1$ + + private static final String[] COLUMNS_PARAMS = new String[] { + "view_results_table_request_params_key", // $NON-NLS-1$ + "view_results_table_request_params_value" }; // $NON-NLS-1$ + + private static final String[] COLUMNS_HEADERS = new String[] { + "view_results_table_request_headers_key", // $NON-NLS-1$ + "view_results_table_request_headers_value" }; // $NON-NLS-1$ + + private JTable tableRequest = null; + + private JTable tableParams = null; + + private JTable tableHeaders = null; + + // Request headers column renderers + private static final TableCellRenderer[] RENDERERS_REQUEST = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + // Request headers column renderers + private static final TableCellRenderer[] RENDERERS_PARAMS = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + // Request headers column renderers + private static final TableCellRenderer[] RENDERERS_HEADERS = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + /** + * Pane to view HTTP request sample in view results tree + */ + public RequestViewHTTP() { + requestModel = new ObjectTableModel(COLUMNS_REQUEST, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + paramsModel = new ObjectTableModel(COLUMNS_PARAMS, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + headersModel = new ObjectTableModel(COLUMNS_HEADERS, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#init() + */ + @Override + public void init() { + paneParsed = new JPanel(new BorderLayout(0, 5)); + paneParsed.add(createRequestPane()); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#clearData() + */ + @Override + public void clearData() { + requestModel.clearData(); + paramsModel.clearData(); + headersModel.clearData(); // clear results table before filling + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#setSamplerResult(java.lang.Object) + */ + @Override + public void setSamplerResult(Object objectResult) { + + if (objectResult instanceof HTTPSampleResult) { + HTTPSampleResult sampleResult = (HTTPSampleResult) objectResult; + + // Display with same order HTTP protocol + requestModel.addRow(new RowResult( + JMeterUtils.getResString("view_results_table_request_http_method"), //$NON-NLS-1$ + sampleResult.getHTTPMethod())); + + URL hUrl = sampleResult.getURL(); + if (hUrl != null){ // can be null - e.g. if URL was invalid + requestModel.addRow(new RowResult(JMeterUtils + .getResString("view_results_table_request_http_protocol"), //$NON-NLS-1$ + hUrl.getProtocol())); + requestModel.addRow(new RowResult( + JMeterUtils.getResString("view_results_table_request_http_host"), //$NON-NLS-1$ + hUrl.getHost())); + int port = hUrl.getPort() == -1 ? hUrl.getDefaultPort() : hUrl.getPort(); + requestModel.addRow(new RowResult( + JMeterUtils.getResString("view_results_table_request_http_port"), //$NON-NLS-1$ + Integer.valueOf(port))); + requestModel.addRow(new RowResult( + JMeterUtils.getResString("view_results_table_request_http_path"), //$NON-NLS-1$ + hUrl.getPath())); + + String queryGet = hUrl.getQuery() == null ? "" : hUrl.getQuery(); //$NON-NLS-1$ + // Concatenate query post if exists + String queryPost = sampleResult.getQueryString(); + if (queryPost != null && queryPost.length() > 0) { + if (queryGet.length() > 0) { + queryGet += PARAM_CONCATENATE; + } + queryGet += queryPost; + } + queryGet = RequestViewHTTP.decodeQuery(queryGet); + if (queryGet != null) { + Set> keys = RequestViewHTTP.getQueryMap(queryGet).entrySet(); + for (Entry entry : keys) { + paramsModel.addRow(new RowResult(entry.getKey(),entry.getValue())); + } + } + } + // Display cookie in headers table (same location on http protocol) + String cookie = sampleResult.getCookies(); + if (cookie != null && cookie.length() > 0) { + headersModel.addRow(new RowResult( + JMeterUtils.getParsedLabel("view_results_table_request_http_cookie"), //$NON-NLS-1$ + sampleResult.getCookies())); + } + // Parsed request headers + LinkedHashMap lhm = JMeterUtils.parseHeaders(sampleResult.getRequestHeaders()); + for (Iterator> iterator = lhm.entrySet().iterator(); iterator.hasNext();) { + Map.Entry entry = iterator.next(); + headersModel.addRow(new RowResult(entry.getKey(), entry.getValue())); + } + + } else { + // add a message when no http sample + requestModel.addRow(new RowResult("", //$NON-NLS-1$ + JMeterUtils.getResString("view_results_table_request_http_nohttp"))); //$NON-NLS-1$ + } + } + + /** + * @param query + * query to parse for param and value pairs + * @return Map params and Svalue + */ + //TODO: move to utils class (JMeterUtils?) + public static Map getQueryMap(String query) { + + Map map = new HashMap(); + if (query.trim().startsWith(" 2 ) {// detected invalid syntax (Bug 52491) + // Return as for SOAP above + map.clear(); + map.put(" ", query); //blank name // $NON-NLS-1$ + return map; + } + String name = null; + if (paramSplit.length > 0) { + name = paramSplit[0]; + } + String value = ""; // empty init // $NON-NLS-1$ + if (paramSplit.length > 1) { + // We use substring to keep = sign (Bug 54055), we are sure = is present + value = param.substring(param.indexOf("=")+1); // $NON-NLS-1$ + } + map.put(name, value); + } + return map; + } + + /** + * Decode a query string + * + * @param query + * to decode + * @return a decode query string + */ + public static String decodeQuery(String query) { + if (query != null && query.length() > 0) { + try { + query = URLDecoder.decode(query, CHARSET_DECODE); // better ISO-8859-1 than UTF-8 + } catch(IllegalArgumentException e) { + log.warn("Error decoding query, maybe your request parameters should be encoded:" + query, e); + return null; + } catch (UnsupportedEncodingException uee) { + log.warn("Error decoding query, maybe your request parameters should be encoded:" + query, uee); + return null; + } + return query; + } + return null; + } + + @Override + public JPanel getPanel() { + return paneParsed; + } + + /** + * Create a pane with three tables (request, params, headers) + * + * @return Pane to display request data + */ + private Component createRequestPane() { + // Set up the 1st table Result with empty headers + tableRequest = new JTable(requestModel); + tableRequest.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableRequest.addMouseListener(new TextBoxDoubleClick(tableRequest)); + + setFirstColumnPreferredSize(tableRequest); + RendererUtils.applyRenderers(tableRequest, RENDERERS_REQUEST); + + // Set up the 2nd table + tableParams = new JTable(paramsModel); + tableParams.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableParams.addMouseListener(new TextBoxDoubleClick(tableParams)); + setFirstColumnPreferredSize(tableParams); + tableParams.getTableHeader().setDefaultRenderer( + new HeaderAsPropertyRenderer()); + RendererUtils.applyRenderers(tableParams, RENDERERS_PARAMS); + + // Set up the 3rd table + tableHeaders = new JTable(headersModel); + tableHeaders.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableHeaders.addMouseListener(new TextBoxDoubleClick(tableHeaders)); + setFirstColumnPreferredSize(tableHeaders); + tableHeaders.getTableHeader().setDefaultRenderer( + new HeaderAsPropertyRenderer()); + RendererUtils.applyRenderers(tableHeaders, RENDERERS_HEADERS); + + // Create the split pane + JSplitPane topSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + GuiUtils.makeScrollPane(tableParams), + GuiUtils.makeScrollPane(tableHeaders)); + topSplit.setOneTouchExpandable(true); + topSplit.setResizeWeight(0.50); // set split ratio + topSplit.setBorder(null); // see bug jdk 4131528 + + JSplitPane paneParsed = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + GuiUtils.makeScrollPane(tableRequest), topSplit); + paneParsed.setOneTouchExpandable(true); + paneParsed.setResizeWeight(0.25); // set split ratio (only 5 lines to display) + paneParsed.setBorder(null); // see bug jdk 4131528 + + // Hint to background color on bottom tabs (grey, not blue) + JPanel panel = new JPanel(new BorderLayout()); + panel.add(paneParsed); + return panel; + } + + private void setFirstColumnPreferredSize(JTable table) { + TableColumn column = table.getColumnModel().getColumn(0); + column.setMaxWidth(300); + column.setPreferredWidth(160); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#getLabel() + */ + @Override + public String getLabel() { + return JMeterUtils.getResString(KEY_LABEL); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/config/JavaConfig.java b/src/protocol/java/org/apache/jmeter/protocol/java/config/JavaConfig.java new file mode 100644 index 00000000000..4a7286d69f6 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/config/JavaConfig.java @@ -0,0 +1,120 @@ +/* + * 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.protocol.java.config; + +import java.io.Serializable; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.java.sampler.JavaSampler; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * The JavaConfig class contains the configuration data necessary + * for the Java protocol. This data is used to configure a + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} instance to + * perform performance test samples. + * + * @version $Revision$ + */ +public class JavaConfig extends ConfigTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + /** + * Constructor for the JavaConfig object + */ + public JavaConfig() { + setArguments(new Arguments()); + } + + /** + * Sets the class name attribute of the JavaConfig object. This is the class + * name of the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation which will be used to execute the test. + * + * @param classname + * the new classname value + */ + public void setClassname(String classname) { + setProperty(JavaSampler.CLASSNAME, classname); + } + + /** + * Gets the class name attribute of the JavaConfig object. This is the class + * name of the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation which will be used to execute the test. + * + * @return the classname value + */ + public String getClassname() { + return getPropertyAsString(JavaSampler.CLASSNAME); + } + + /** + * Adds an argument to the list of arguments for this JavaConfig object. The + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation can access these arguments through the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerContext}. + * + * @param name + * the name of the argument to be added + * @param value + * the value of the argument to be added + */ + public void addArgument(String name, String value) { + Arguments args = this.getArguments(); + args.addArgument(name, value); + } + + /** + * Removes all of the arguments associated with this JavaConfig object. + */ + public void removeArguments() { + setProperty(new TestElementProperty(JavaSampler.ARGUMENTS, new Arguments())); + } + + /** + * Set all of the arguments for this JavaConfig object. This will replace + * any previously added arguments. The + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation can access these arguments through the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerContext}. + * + * @param args + * the new arguments + */ + public void setArguments(Arguments args) { + setProperty(new TestElementProperty(JavaSampler.ARGUMENTS, args)); + } + + /** + * Gets the arguments for this JavaConfig object. The + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation can access these arguments through the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerContext}. + * + * @return the arguments + */ + public Arguments getArguments() { + return (Arguments) getProperty(JavaSampler.ARGUMENTS).getObjectValue(); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java b/src/protocol/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java new file mode 100644 index 00000000000..865873e7587 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java @@ -0,0 +1,280 @@ +/* + * 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.protocol.java.config.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.ComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.protocol.java.config.JavaConfig; +import org.apache.jmeter.protocol.java.sampler.JavaSampler; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerClient; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The JavaConfigGui class provides the user interface for the + * {@link JavaConfig} object. + * + */ +public class JavaConfigGui extends AbstractConfigGui implements ActionListener { + private static final long serialVersionUID = 240L; + + /** Logging */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** A combo box allowing the user to choose a test class. */ + private JComboBox classnameCombo; + + /** + * Indicates whether or not the name of this component should be displayed + * as part of the GUI. If true, this is a standalone component. If false, it + * is embedded in some other component. + */ + private boolean displayName = true; + + /** A panel allowing the user to set arguments for this test. */ + private ArgumentsPanel argsPanel; + + /** + * Create a new JavaConfigGui as a standalone component. + */ + public JavaConfigGui() { + this(true); + } + + /** + * Create a new JavaConfigGui as either a standalone or an embedded + * component. + * + * @param displayNameField + * tells whether the component name should be displayed with the + * GUI. If true, this is a standalone component. If false, this + * component is embedded in some other component. + */ + public JavaConfigGui(boolean displayNameField) { + this.displayName = displayNameField; + init(); + } + + /** {@inheritDoc} */ + @Override + public String getLabelResource() { + return "java_request_defaults"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout. + */ + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(0, 5)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + JPanel classnameRequestPanel = new JPanel(new BorderLayout(0, 5)); + classnameRequestPanel.add(createClassnamePanel(), BorderLayout.NORTH); + classnameRequestPanel.add(createParameterPanel(), BorderLayout.CENTER); + + add(classnameRequestPanel, BorderLayout.CENTER); + } + + /** + * Create a panel with GUI components allowing the user to select a test + * class. + * + * @return a panel containing the relevant components + */ + private JPanel createClassnamePanel() { + List possibleClasses = new ArrayList(); + + try { + // Find all the classes which implement the JavaSamplerClient + // interface. + possibleClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), + new Class[] { JavaSamplerClient.class }); + + // Remove the JavaConfig class from the list since it only + // implements the interface for error conditions. + + possibleClasses.remove(JavaSampler.class.getName() + "$ErrorSamplerClient"); + } catch (Exception e) { + log.debug("Exception getting interfaces.", e); + } + + JLabel label = new JLabel(JMeterUtils.getResString("protocol_java_classname")); // $NON-NLS-1$ + + classnameCombo = new JComboBox(possibleClasses.toArray()); + classnameCombo.addActionListener(this); + classnameCombo.setEditable(false); + label.setLabelFor(classnameCombo); + + HorizontalPanel panel = new HorizontalPanel(); + panel.add(label); + panel.add(classnameCombo); + + return panel; + } + + /** + * Handle action events for this component. This method currently handles + * events for the classname combo box. + * + * @param evt + * the ActionEvent to be handled + */ + @Override + public void actionPerformed(ActionEvent evt) { + if (evt.getSource() == classnameCombo) { + String className = ((String) classnameCombo.getSelectedItem()).trim(); + try { + JavaSamplerClient client = (JavaSamplerClient) Class.forName(className, true, + Thread.currentThread().getContextClassLoader()).newInstance(); + + Arguments currArgs = new Arguments(); + argsPanel.modifyTestElement(currArgs); + Map currArgsMap = currArgs.getArgumentsAsMap(); + + Arguments newArgs = new Arguments(); + Arguments testParams = null; + try { + testParams = client.getDefaultParameters(); + } catch (AbstractMethodError e) { + log.warn("JavaSamplerClient doesn't implement " + + "getDefaultParameters. Default parameters won't " + + "be shown. Please update your client class: " + className); + } + + if (testParams != null) { + PropertyIterator i = testParams.getArguments().iterator(); + while (i.hasNext()) { + Argument arg = (Argument) i.next().getObjectValue(); + String name = arg.getName(); + String value = arg.getValue(); + + // If a user has set parameters in one test, and then + // selects a different test which supports the same + // parameters, those parameters should have the same + // values that they did in the original test. + if (currArgsMap.containsKey(name)) { + String newVal = currArgsMap.get(name); + if (newVal != null && newVal.length() > 0) { + value = newVal; + } + } + newArgs.addArgument(name, value); + } + } + + argsPanel.configure(newArgs); + } catch (Exception e) { + log.error("Error getting argument list for " + className, e); + } + } + } + + /** + * Create a panel containing components allowing the user to provide + * arguments to be passed to the test class instance. + * + * @return a panel containing the relevant components + */ + private JPanel createParameterPanel() { + argsPanel = new ArgumentsPanel(JMeterUtils.getResString("paramtable")); // $NON-NLS-1$ + return argsPanel; + } + + /** {@inheritDoc} */ + @Override + public void configure(TestElement config) { + super.configure(config); + + argsPanel.configure((Arguments) config.getProperty(JavaSampler.ARGUMENTS).getObjectValue()); + + String className = config.getPropertyAsString(JavaSampler.CLASSNAME); + if(checkContainsClassName(classnameCombo.getModel(), className)) { + classnameCombo.setSelectedItem(className); + } else { + log.error("Error setting class:'"+className+"' in JavaSampler "+getName()+", check for a missing jar in your jmeter 'search_paths' and 'plugin_dependency_paths' properties"); + } + } + + /** + * Check combo contains className + * @param model ComboBoxModel + * @param className String class name + * @return boolean + */ + private static final boolean checkContainsClassName(ComboBoxModel model, String className) { + int size = model.getSize(); + Set set = new HashSet(size); + for (int i = 0; i < size; i++) { + set.add((String)model.getElementAt(i)); + } + return set.contains(className); + } + + /** {@inheritDoc} */ + @Override + public TestElement createTestElement() { + JavaConfig config = new JavaConfig(); + modifyTestElement(config); + return config; + } + + /** {@inheritDoc} */ + @Override + public void modifyTestElement(TestElement config) { + configureTestElement(config); + ((JavaConfig) config).setArguments((Arguments) argsPanel.createTestElement()); + ((JavaConfig) config).setClassname(String.valueOf(classnameCombo.getSelectedItem())); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#clearGui() + */ + @Override + public void clearGui() { + super.clearGui(); + this.displayName = true; + argsPanel.clearGui(); + classnameCombo.setSelectedIndex(0); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java b/src/protocol/java/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java new file mode 100644 index 00000000000..8eda8a69ae5 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java @@ -0,0 +1,175 @@ +/* + * 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.protocol.java.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import org.apache.jmeter.protocol.java.sampler.BeanShellSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; + +public class BeanShellSamplerGui extends AbstractSamplerGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox resetInterpreter;// reset the bsh.Interpreter before each execution + + private JTextField filename;// script file name (if present) + + private JTextField parameters;// parameters to pass to script file (or script) + + private JSyntaxTextArea scriptField;// script area + + public BeanShellSamplerGui() { + init(); + } + + @Override + public void configure(TestElement element) { + scriptField.setInitialText(element.getPropertyAsString(BeanShellSampler.SCRIPT)); + scriptField.setCaretPosition(0); + filename.setText(element.getPropertyAsString(BeanShellSampler.FILENAME)); + parameters.setText(element.getPropertyAsString(BeanShellSampler.PARAMETERS)); + resetInterpreter.setSelected(element.getPropertyAsBoolean(BeanShellSampler.RESET_INTERPRETER)); + super.configure(element); + } + + @Override + public TestElement createTestElement() { + BeanShellSampler sampler = new BeanShellSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement te) { + te.clear(); + this.configureTestElement(te); + te.setProperty(BeanShellSampler.SCRIPT, scriptField.getText()); + te.setProperty(BeanShellSampler.FILENAME, filename.getText()); + te.setProperty(BeanShellSampler.PARAMETERS, parameters.getText()); + te.setProperty(new BooleanProperty(BeanShellSampler.RESET_INTERPRETER, resetInterpreter.isSelected())); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + filename.setText(""); //$NON-NLS-1$ + parameters.setText(""); //$NON-NLS-1$ + scriptField.setInitialText(""); //$NON-NLS-1$ + resetInterpreter.setSelected(false); + } + + @Override + public String getLabelResource() { + return "bsh_sampler_title"; // $NON-NLS-1$ + } + + private JPanel createFilenamePanel()// TODO ought to be a FileChooser ... + { + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script_file")); // $NON-NLS-1$ + + filename = new JTextField(10); + filename.setName(BeanShellSampler.FILENAME); + label.setLabelFor(filename); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(filename, BorderLayout.CENTER); + return filenamePanel; + } + + private JPanel createParameterPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script_parameters")); // $NON-NLS-1$ + + parameters = new JTextField(10); + parameters.setName(BeanShellSampler.PARAMETERS); + label.setLabelFor(parameters); + + JPanel parameterPanel = new JPanel(new BorderLayout(5, 0)); + parameterPanel.add(label, BorderLayout.WEST); + parameterPanel.add(parameters, BorderLayout.CENTER); + return parameterPanel; + } + + private JPanel createResetPanel() { + resetInterpreter = new JCheckBox(JMeterUtils.getResString("bsh_script_reset_interpreter")); // $NON-NLS-1$ + resetInterpreter.setName(BeanShellSampler.PARAMETERS); + + JPanel resetInterpreterPanel = new JPanel(new BorderLayout()); + resetInterpreterPanel.add(resetInterpreter, BorderLayout.WEST); + return resetInterpreterPanel; + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createResetPanel()); + box.add(createParameterPanel()); + box.add(createFilenamePanel()); + add(box, BorderLayout.NORTH); + + JPanel panel = createScriptPanel(); + add(panel, BorderLayout.CENTER); + // Don't let the input field shrink too much + add(Box.createVerticalStrut(panel.getPreferredSize().height), BorderLayout.WEST); + } + + private JPanel createScriptPanel() { + scriptField = new JSyntaxTextArea(20, 20); + + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script")); // $NON-NLS-1$ + label.setLabelFor(scriptField); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(label, BorderLayout.NORTH); + panel.add(new JTextScrollPane(scriptField), BorderLayout.CENTER); + + JTextArea explain = new JTextArea(JMeterUtils.getResString("bsh_script_variables")); //$NON-NLS-1$ + explain.setLineWrap(true); + explain.setEditable(false); + explain.setBackground(this.getBackground()); + panel.add(explain, BorderLayout.SOUTH); + + return panel; + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/control/gui/JavaTestSamplerGui.java b/src/protocol/java/org/apache/jmeter/protocol/java/control/gui/JavaTestSamplerGui.java new file mode 100644 index 00000000000..b6e4389e44a --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/control/gui/JavaTestSamplerGui.java @@ -0,0 +1,99 @@ +/* + * 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.protocol.java.control.gui; + +import java.awt.BorderLayout; + +import org.apache.jmeter.protocol.java.config.JavaConfig; +import org.apache.jmeter.protocol.java.config.gui.JavaConfigGui; +import org.apache.jmeter.protocol.java.sampler.JavaSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; + +/** + * The JavaTestSamplerGui class provides the user interface for + * the {@link JavaSampler}. + * + */ +public class JavaTestSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + /** Panel containing the configuration options. */ + private JavaConfigGui javaPanel = null; + + /** + * Constructor for JavaTestSamplerGui + */ + public JavaTestSamplerGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "java_request"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + javaPanel = new JavaConfigGui(false); + + add(javaPanel, BorderLayout.CENTER); + } + + /* Implements JMeterGuiComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + JavaSampler sampler = new JavaSampler(); + modifyTestElement(sampler); + return sampler; + } + + /* Implements JMeterGuiComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + JavaConfig config = (JavaConfig) javaPanel.createTestElement(); + configureTestElement(sampler); + sampler.addTestElement(config); + } + + /* Overrides AbstractJMeterGuiComponent.configure(TestElement) */ + @Override + public void configure(TestElement el) { + super.configure(el); + javaPanel.configure(el); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#clearGui() + */ + @Override + public void clearGui() { + super.clearGui(); + javaPanel.clearGui(); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/AbstractJavaSamplerClient.java b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/AbstractJavaSamplerClient.java new file mode 100644 index 00000000000..eab05005691 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/AbstractJavaSamplerClient.java @@ -0,0 +1,85 @@ +/* + * 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.protocol.java.sampler; + +import org.apache.jmeter.config.Arguments; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * An abstract implementation of the JavaSamplerClient interface. This + * implementation provides default implementations of most of the methods in the + * interface, as well as some convenience methods, in order to simplify + * development of JavaSamplerClient implementations. + *

+ * See {@link org.apache.jmeter.protocol.java.test.SleepTest} for an example of + * how to extend this class. + *

+ * While it may be necessary to make changes to the JavaSamplerClient interface + * from time to time (therefore requiring changes to any implementations of this + * interface), we intend to make this abstract class provide reasonable + * implementations of any new methods so that subclasses do not necessarily need + * to be updated for new versions. Therefore, when creating a new + * JavaSamplerClient implementation, developers are encouraged to subclass this + * abstract class rather than implementing the JavaSamplerClient interface + * directly. Implementing JavaSamplerClient directly will continue to be + * supported for cases where extending this class is not possible (for example, + * when the client class is already a subclass of some other class). + *

+ * The runTest() method of JavaSamplerClient does not have a default + * implementation here, so subclasses must define at least this method. It may + * be useful to override other methods as well. + * + * @see JavaSamplerClient#runTest(JavaSamplerContext) + * + * @version $Revision$ + */ +public abstract class AbstractJavaSamplerClient implements JavaSamplerClient { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* Implements JavaSamplerClient.setupTest(JavaSamplerContext) */ + @Override + public void setupTest(JavaSamplerContext context) { + log.debug(getClass().getName() + ": setupTest"); + } + + /* Implements JavaSamplerClient.teardownTest(JavaSamplerContext) */ + @Override + public void teardownTest(JavaSamplerContext context) { + log.debug(getClass().getName() + ": teardownTest"); + } + + /* Implements JavaSamplerClient.getDefaultParameters() */ + @Override + public Arguments getDefaultParameters() { + return null; + } + + /** + * Get a Logger instance which can be used by subclasses to log information. + * This is the same Logger which is used by the base JavaSampler classes + * (jmeter.protocol.java). + * + * @return a Logger instance which can be used for logging + */ + protected Logger getLogger() { + return log; + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSampler.java b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSampler.java new file mode 100644 index 00000000000..f5e46f8222f --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSampler.java @@ -0,0 +1,141 @@ +/* + * 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.protocol.java.sampler; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.bsf.BSFEngine; +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler which understands BSF + * + */ +public class BSFSampler extends BSFTestElement implements Sampler, TestBean, ConfigMergabilityIndicator { + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public BSFSampler() { + super(); + } + + @Override + public SampleResult sample(Entry e)// Entry tends to be ignored ... + { + final String label = getName(); + final String request = getScript(); + final String fileName = getFilename(); + log.debug(label + " " + fileName); + SampleResult res = new SampleResult(); + res.setSampleLabel(label); + InputStream is = null; + BSFEngine bsfEngine = null; + // There's little point saving the manager between invocations + // as we need to reset most of the beans anyway + BSFManager mgr = new BSFManager(); + + // TODO: find out how to retrieve these from the script + // At present the script has to use SampleResult methods to set them. + res.setResponseCode("200"); // $NON-NLS-1$ + res.setResponseMessage("OK"); // $NON-NLS-1$ + res.setSuccessful(true); + res.setDataType(SampleResult.TEXT); // Default (can be overridden by the script) + + res.sampleStart(); + try { + initManager(mgr); + mgr.declareBean("SampleResult", res, res.getClass()); // $NON-NLS-1$ + + // These are not useful yet, as have not found how to get updated values back + //mgr.declareBean("ResponseCode", "200", String.class); // $NON-NLS-1$ + //mgr.declareBean("ResponseMessage", "OK", String.class); // $NON-NLS-1$ + //mgr.declareBean("IsSuccess", Boolean.TRUE, Boolean.class); // $NON-NLS-1$ + + // N.B. some engines (e.g. Javascript) cannot handle certain declareBean() calls + // after the engine has been initialised, so create the engine last + bsfEngine = mgr.loadScriptingEngine(getScriptLanguage()); + + Object bsfOut = null; + if (fileName.length()>0) { + res.setSamplerData("File: "+fileName); + is = new BufferedInputStream(new FileInputStream(fileName)); + bsfOut = bsfEngine.eval(fileName, 0, 0, IOUtils.toString(is)); + } else { + res.setSamplerData(request); + bsfOut = bsfEngine.eval("script", 0, 0, request); + } + + if (bsfOut != null) { + res.setResponseData(bsfOut.toString(), null); + } + } catch (BSFException ex) { + log.warn("BSF error", ex); + res.setSuccessful(false); + res.setResponseCode("500"); // $NON-NLS-1$ + res.setResponseMessage(ex.toString()); + } catch (Exception ex) {// Catch evaluation errors + log.warn("Problem evaluating the script", ex); + res.setSuccessful(false); + res.setResponseCode("500"); // $NON-NLS-1$ + res.setResponseMessage(ex.toString()); + } finally { + res.sampleEnd(); + IOUtils.closeQuietly(is); +// Will be done by mgr.terminate() anyway +// if (bsfEngine != null) { +// bsfEngine.terminate(); +// } + mgr.terminate(); + } + + return res; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerBeanInfo.java b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerBeanInfo.java new file mode 100644 index 00000000000..ddfa2cc059f --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerBeanInfo.java @@ -0,0 +1,31 @@ +/* + * 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.protocol.java.sampler; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +/** + * BSF Sampler Bean Info + */ +public class BSFSamplerBeanInfo extends BSFBeanInfoSupport { + + public BSFSamplerBeanInfo() { + super(BSFSampler.class); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources.properties b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources.properties new file mode 100644 index 00000000000..c80a452b5b2 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources.properties @@ -0,0 +1,29 @@ +# 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. + + +displayName=BSF Sampler +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) +filenameGroup.displayName=Script file (overrides script) +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources_fr.properties b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources_fr.properties new file mode 100644 index 00000000000..5b1c5d4fd3a --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources_fr.properties @@ -0,0 +1,29 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Echantillon BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BeanShellSampler.java b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BeanShellSampler.java new file mode 100644 index 00000000000..e390f2cc870 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BeanShellSampler.java @@ -0,0 +1,188 @@ +/* + * 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.protocol.java.sampler; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +/** + * A sampler which understands BeanShell + * + */ +public class BeanShellSampler extends BeanShellTestElement implements Sampler, Interruptible, ConfigMergabilityIndicator +{ + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 3; + + public static final String FILENAME = "BeanShellSampler.filename"; //$NON-NLS-1$ + + public static final String SCRIPT = "BeanShellSampler.query"; //$NON-NLS-1$ + + public static final String PARAMETERS = "BeanShellSampler.parameters"; //$NON-NLS-1$ + + public static final String INIT_FILE = "beanshell.sampler.init"; //$NON-NLS-1$ + + public static final String RESET_INTERPRETER = "BeanShellSampler.resetInterpreter"; //$NON-NLS-1$ + + private transient volatile BeanShellInterpreter savedBsh = null; + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + @Override + public String getScript() { + return this.getPropertyAsString(SCRIPT); + } + + @Override + public String getFilename() { + return getPropertyAsString(FILENAME); + } + + @Override + public String getParameters() { + return getPropertyAsString(PARAMETERS); + } + + @Override + public boolean isResetInterpreter() { + return getPropertyAsBoolean(RESET_INTERPRETER); + } + + @Override + public SampleResult sample(Entry e)// Entry tends to be ignored ... + { + // log.info(getLabel()+" "+getFilename()); + SampleResult res = new SampleResult(); + boolean isSuccessful = false; + res.setSampleLabel(getName()); + res.sampleStart(); + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + res.sampleEnd(); + res.setResponseCode("503");//$NON-NLS-1$ + res.setResponseMessage("BeanShell Interpreter not found"); + res.setSuccessful(false); + return res; + } + try { + String request = getScript(); + String fileName = getFilename(); + if (fileName.length() == 0) { + res.setSamplerData(request); + } else { + res.setSamplerData(fileName); + } + + bshInterpreter.set("SampleResult", res); //$NON-NLS-1$ + + // Set default values + bshInterpreter.set("ResponseCode", "200"); //$NON-NLS-1$ + bshInterpreter.set("ResponseMessage", "OK");//$NON-NLS-1$ + bshInterpreter.set("IsSuccess", true);//$NON-NLS-1$ + + res.setDataType(SampleResult.TEXT); // assume text output - script can override if necessary + + savedBsh = bshInterpreter; + Object bshOut = processFileOrScript(bshInterpreter); + savedBsh = null; + + if (bshOut != null) {// Set response data + String out = bshOut.toString(); + res.setResponseData(out, null); + } + // script can also use setResponseData() so long as it returns null + + res.setResponseCode(bshInterpreter.get("ResponseCode").toString());//$NON-NLS-1$ + res.setResponseMessage(bshInterpreter.get("ResponseMessage").toString());//$NON-NLS-1$ + isSuccessful = Boolean.valueOf(bshInterpreter.get("IsSuccess") //$NON-NLS-1$ + .toString()).booleanValue(); + } + /* + * To avoid class loading problems when bsh,jar is missing, we don't try + * to catch this error separately catch (bsh.EvalError ex) { + * log.debug("",ex); res.setResponseCode("500");//$NON-NLS-1$ + * res.setResponseMessage(ex.toString()); } + */ + // but we do trap this error to make tests work better + catch (NoClassDefFoundError ex) { + log.error("BeanShell Jar missing? " + ex.toString()); + res.setResponseCode("501");//$NON-NLS-1$ + res.setResponseMessage(ex.toString()); + res.setStopThread(true); // No point continuing + } catch (Exception ex) // Mainly for bsh.EvalError + { + log.warn(ex.toString()); + res.setResponseCode("500");//$NON-NLS-1$ + res.setResponseMessage(ex.toString()); + } finally { + savedBsh = null; + } + + res.sampleEnd(); + + // Set if we were successful or not + res.setSuccessful(isSuccessful); + + return res; + } + + @Override + public boolean interrupt() { + if (savedBsh != null) { + try { + savedBsh.evalNoLog("interrupt()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + return true; + } + return false; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java new file mode 100644 index 00000000000..b5743951801 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java @@ -0,0 +1,97 @@ +/* + * 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.protocol.java.sampler; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.script.Bindings; +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223Sampler extends JSR223TestElement implements Cloneable, Sampler, TestBean, ConfigMergabilityIndicator { + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final long serialVersionUID = 234L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + @Override + public SampleResult sample(Entry entry) { + SampleResult result = new SampleResult(); + result.setSampleLabel(getName()); + result.setSuccessful(true); + result.setResponseCodeOK(); + result.setResponseMessageOK(); + + final String filename = getFilename(); + if (filename.length() > 0){ + result.setSamplerData("File: "+filename); + } else { + result.setSamplerData(getScript()); + } + result.setDataType(SampleResult.TEXT); + result.sampleStart(); + try { + ScriptEngine scriptEngine = getScriptEngine(); + Bindings bindings = scriptEngine.createBindings(); + bindings.put("SampleResult",result); + Object ret = processFileOrScript(scriptEngine, bindings); + if (ret != null && (result.getResponseData() == null || result.getResponseData()==SampleResult.EMPTY_BA)){ + result.setResponseData(ret.toString(), null); + } + } catch (IOException e) { + log.error("Problem in JSR223 script "+getName()+", message:"+e, e); + result.setSuccessful(false); + result.setResponseCode("500"); // $NON-NLS-1$ + result.setResponseMessage(e.toString()); + } catch (ScriptException e) { + log.error("Problem in JSR223 script "+getName()+", message:"+e, e); + result.setSuccessful(false); + result.setResponseCode("500"); // $NON-NLS-1$ + result.setResponseMessage(e.toString()); + } + result.sampleEnd(); + return result; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerBeanInfo.java b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerBeanInfo.java new file mode 100644 index 00000000000..14cd382c868 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerBeanInfo.java @@ -0,0 +1,28 @@ +/* + * 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.protocol.java.sampler; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223SamplerBeanInfo extends JSR223BeanInfoSupport { + + public JSR223SamplerBeanInfo() { + super(JSR223Sampler.class); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources.properties b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources.properties new file mode 100644 index 00000000000..8358184305c --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources.properties @@ -0,0 +1,31 @@ +# 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. + +displayName=JSR223 Sampler +cacheKey.displayName=Compilation cache key +cacheKey.shortDescription=If Cache key is not empty, script will be compiled if JSR223 underlying script language supports it and CompiledScript will be cached, ensure script does not use any variable before making it cacheable +cacheKey_group.displayName=Script compilation caching +scriptingLanguage.displayName=Script language (e.g. Groovy, beanshell, javascript, jexl ...) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR 223 language, e.g. Groovy (Best performances), beanshell, javascript, jexl, scala.. +scripting.displayName=Script (variables: ctx vars props SampleResult sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR 223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources_fr.properties b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources_fr.properties new file mode 100644 index 00000000000..fb73806df9a --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources_fr.properties @@ -0,0 +1,32 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Echantillon JSR223 +cacheKey.displayName=Clef de caching +cacheKey.shortDescription=Si la clef de caching n'est pas vide, le script sera compil\u00E9 si le language sous-jacent JSR223 fournit cette fonctionnalit\u00E9 et l'objet CompiledScript sera mis en cache, assurez vous avant d'utiliser ce caching que le script n'utilise pas de variables JMeter +cacheKey_group.displayName=Param\u00E8tres de caching du Script compil\u00E9 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props SampleResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JavaSampler.java b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JavaSampler.java new file mode 100644 index 00000000000..18c55248185 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JavaSampler.java @@ -0,0 +1,333 @@ +/* + * 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.protocol.java.sampler; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler for executing custom Java code in each sample. See + * {@link JavaSamplerClient} and {@link AbstractJavaSamplerClient} for + * information on writing Java code to be executed by this sampler. + * + */ +public class JavaSampler extends AbstractSampler implements TestStateListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; // Remember to change this when the class changes ... + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.protocol.java.config.gui.JavaConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + /** + * Set used to register instances which implement tearDownTest. + * This is used so that the JavaSamplerClient can be notified when the test ends. + */ + private static final Set TEAR_DOWN_SET = new HashSet(); + + /** + * Property key representing the classname of the JavaSamplerClient to user. + */ + public static final String CLASSNAME = "classname"; + + /** + * Property key representing the arguments for the JavaSamplerClient. + */ + public static final String ARGUMENTS = "arguments"; + + /** + * The JavaSamplerClient class used by this sampler. + * Created by testStarted; copied to cloned instances. + */ + private Class javaClass; + + /** + * If true, the JavaSamplerClient class implements tearDownTest. + * Created by testStarted; copied to cloned instances. + */ + private boolean isToBeRegistered; + + /** + * The JavaSamplerClient instance used by this sampler to actually perform + * the sample. + */ + private transient JavaSamplerClient javaClient = null; + + /** + * The JavaSamplerContext instance used by this sampler to hold information + * related to the test run, such as the parameters specified for the sampler + * client. + */ + private transient JavaSamplerContext context = null; + + /** + * Create a JavaSampler. + */ + public JavaSampler() { + setArguments(new Arguments()); + } + + /* + * Ensure that the required class variables are cloned, + * as this is not currently done by the super-implementation. + */ + @Override + public Object clone() { + JavaSampler clone = (JavaSampler) super.clone(); + clone.javaClass = this.javaClass; + clone.isToBeRegistered = this.isToBeRegistered; + return clone; + } + + private void initClass() { + String name = getClassname().trim(); + try { + javaClass = Class.forName(name, false, Thread.currentThread().getContextClassLoader()); + Method method = javaClass.getMethod("teardownTest", new Class[]{JavaSamplerContext.class}); + isToBeRegistered = !method.getDeclaringClass().equals(AbstractJavaSamplerClient.class); + log.info("Created class: " + name + ". Uses tearDownTest: " + isToBeRegistered); + } catch (Exception e) { + log.error(whoAmI() + "\tException initialising: " + name, e); + } + + } + + /** + * Set the arguments (parameters) for the JavaSamplerClient to be executed + * with. + * + * @param args + * the new arguments. These replace any existing arguments. + */ + public void setArguments(Arguments args) { + setProperty(new TestElementProperty(ARGUMENTS, args)); + } + + /** + * Get the arguments (parameters) for the JavaSamplerClient to be executed + * with. + * + * @return the arguments + */ + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /** + * Sets the Classname attribute of the JavaConfig object + * + * @param classname + * the new Classname value + */ + public void setClassname(String classname) { + setProperty(CLASSNAME, classname); + } + + /** + * Gets the Classname attribute of the JavaConfig object + * + * @return the Classname value + */ + public String getClassname() { + return getPropertyAsString(CLASSNAME); + } + + /** + * Performs a test sample. + * + * The sample() method retrieves the reference to the Java + * client and calls its runTest() method. + * + * @see JavaSamplerClient#runTest(JavaSamplerContext) + * + * @param entry + * the Entry for this sample + * @return test SampleResult + */ + @Override + public SampleResult sample(Entry entry) { + Arguments args = getArguments(); + args.addArgument(TestElement.NAME, getName()); // Allow Sampler access + // to test element name + context = new JavaSamplerContext(args); + if (javaClient == null) { + log.debug(whoAmI() + "\tCreating Java Client"); + javaClient = createJavaClient(); + javaClient.setupTest(context); + } + + SampleResult result = javaClient.runTest(context); + + // Only set the default label if it has not been set + if (result != null && result.getSampleLabel().length() == 0) { + result.setSampleLabel(getName()); + } + + return result; + } + + /** + * Returns reference to JavaSamplerClient. + * + * The createJavaClient() method uses reflection to create an + * instance of the specified Java protocol client. If the class can not be + * found, the method returns a reference to this object. + * + * @return JavaSamplerClient reference. + */ + private JavaSamplerClient createJavaClient() { + if (javaClass == null) { // failed to initialise the class + return new ErrorSamplerClient(); + } + JavaSamplerClient client; + try { + client = (JavaSamplerClient) javaClass.newInstance(); + + if (log.isDebugEnabled()) { + log.debug(whoAmI() + "\tCreated:\t" + getClassname() + "@" + + Integer.toHexString(client.hashCode())); + } + + if(isToBeRegistered) { + TEAR_DOWN_SET.add(this); + } + } catch (Exception e) { + log.error(whoAmI() + "\tException creating: " + getClassname(), e); + client = new ErrorSamplerClient(); + } + return client; + } + + /** + * Retrieves reference to JavaSamplerClient. + * + * Convience method used to check for null reference without actually + * creating a JavaSamplerClient + * + * @return reference to JavaSamplerClient NOTUSED private JavaSamplerClient + * retrieveJavaClient() { return javaClient; } + */ + + /** + * Generate a String identifier of this instance for debugging purposes. + * + * @return a String identifier for this sampler instance + */ + private String whoAmI() { + StringBuilder sb = new StringBuilder(); + sb.append(Thread.currentThread().getName()); + sb.append("@"); + sb.append(Integer.toHexString(hashCode())); + sb.append("-"); + sb.append(getName()); + return sb.toString(); + } + + // TestStateListener implementation + /* Implements TestStateListener.testStarted() */ + @Override + public void testStarted() { + log.debug(whoAmI() + "\ttestStarted"); + initClass(); + } + + /* Implements TestStateListener.testStarted(String) */ + @Override + public void testStarted(String host) { + log.debug(whoAmI() + "\ttestStarted(" + host + ")"); + initClass(); + } + + /** + * Method called at the end of the test. This is called only on one instance + * of JavaSampler. This method will loop through all of the other + * JavaSamplers which have been registered (automatically in the + * constructor) and notify them that the test has ended, allowing the + * JavaSamplerClients to cleanup. + */ + @Override + public void testEnded() { + log.debug(whoAmI() + "\ttestEnded"); + synchronized (TEAR_DOWN_SET) { + for (JavaSampler javaSampler : TEAR_DOWN_SET) { + JavaSamplerClient client = javaSampler.javaClient; + if (client != null) { + client.teardownTest(javaSampler.context); + } + } + TEAR_DOWN_SET.clear(); + } + } + + /* Implements TestStateListener.testEnded(String) */ + @Override + public void testEnded(String host) { + testEnded(); + } + + /** + * A {@link JavaSamplerClient} implementation used for error handling. If an + * error occurs while creating the real JavaSamplerClient object, it is + * replaced with an instance of this class. Each time a sample occurs with + * this class, the result is marked as a failure so the user can see that + * the test failed. + */ + class ErrorSamplerClient extends AbstractJavaSamplerClient { + /** + * Return SampleResult with data on error. + * + * @see JavaSamplerClient#runTest(JavaSamplerContext) + */ + @Override + public SampleResult runTest(JavaSamplerContext p_context) { + log.debug(whoAmI() + "\trunTest"); + Thread.yield(); + SampleResult results = new SampleResult(); + results.setSuccessful(false); + results.setResponseData(("Class not found: " + getClassname()), null); + results.setSampleLabel("ERROR: " + getClassname()); + return results; + } + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java new file mode 100644 index 00000000000..f2f0931cbf0 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java @@ -0,0 +1,121 @@ +/* + * 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.protocol.java.sampler; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.samplers.SampleResult; + +/** + * This interface defines the interactions between the JavaSampler and external + * Java programs which can be executed by JMeter. Any Java class which wants to + * be executed as a JMeter test must implement this interface (either directly + * or indirectly through AbstractJavaSamplerClient). + *

+ * JMeter will create one instance of a JavaSamplerClient implementation for + * each user/thread in the test. Additional instances may be created for + * internal use by JMeter (for example, to find out what parameters are + * supported by the client). + *

+ * When the test is started, setupTest() will be called on each thread's + * JavaSamplerClient instance to initialize the client. Then runTest() will be + * called for each iteration of the test. Finally, teardownTest() will be called + * to allow the client to do any necessary clean-up. + *

+ * The JMeter JavaSampler GUI allows a list of parameters to be defined for the + * test. These are passed to the various test methods through the + * {@link JavaSamplerContext}. A list of default parameters can be defined + * through the getDefaultParameters() method. These parameters and any default + * values associated with them will be shown in the GUI. Users can add other + * parameters as well. + *

+ * When possible, Java tests should extend {@link AbstractJavaSamplerClient + * AbstractJavaSamplerClient} rather than implementing JavaSamplerClient + * directly. This should protect your tests from future changes to the + * interface. While it may be necessary to make changes to the JavaSamplerClient + * interface from time to time (therefore requiring changes to any + * implementations of this interface), we intend to make this abstract class + * provide reasonable default implementations of any new methods so that + * subclasses do not necessarily need to be updated for new versions. + * Implementing JavaSamplerClient directly will continue to be supported for + * cases where extending this class is not possible (for example, when the + * client class is already a subclass of some other class). + *

+ * See {@link org.apache.jmeter.protocol.java.test.SleepTest} for an example of + * how to implement this interface. + * + * @version $Revision$ + */ +public interface JavaSamplerClient { + /** + * Do any initialization required by this client. It is generally + * recommended to do any initialization such as getting parameter values in + * the setupTest method rather than the runTest method in order to add as + * little overhead as possible to the test. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + void setupTest(JavaSamplerContext context); + + /** + * Perform a single sample for each iteration. This method returns a + * SampleResult object. SampleResult has many + * fields which can be used. At a minimum, the test should use + * SampleResult.sampleStart and + * SampleResult.sampleEndto set the time that the test + * required to execute. It is also a good idea to set the sampleLabel and + * the successful flag. + * + * @see org.apache.jmeter.samplers.SampleResult#sampleStart() + * @see org.apache.jmeter.samplers.SampleResult#sampleEnd() + * @see org.apache.jmeter.samplers.SampleResult#setSuccessful(boolean) + * @see org.apache.jmeter.samplers.SampleResult#setSampleLabel(String) + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + * + * @return a SampleResult giving the results of this sample. + */ + SampleResult runTest(JavaSamplerContext context); + + /** + * Do any clean-up required by this test at the end of a test run. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + void teardownTest(JavaSamplerContext context); + + /** + * Provide a list of parameters which this test supports. Any parameter + * names and associated values returned by this method will appear in the + * GUI by default so the user doesn't have to remember the exact names. The + * user can add other parameters which are not listed here. If this method + * returns null then no parameters will be listed. If the value for some + * parameter is null then that parameter will be listed in the GUI with an + * empty value. + * + * @return a specification of the parameters used by this test which should + * be listed in the GUI, or null if no parameters should be listed. + */ + Arguments getDefaultParameters(); +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerContext.java b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerContext.java new file mode 100644 index 00000000000..e6f3e1e7d16 --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerContext.java @@ -0,0 +1,226 @@ +/* + * 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.protocol.java.sampler; + +import java.util.Iterator; +import java.util.Map; + +import org.apache.jmeter.config.Arguments; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * JavaSamplerContext is used to provide context information to a + * JavaSamplerClient implementation. This currently consists of the + * initialization parameters which were specified in the GUI. Additional data + * may be accessible through JavaSamplerContext in the future. + * + * @version $Revision$ + */ +public class JavaSamplerContext { + /* + * Implementation notes: + * + * All of the methods in this class are currently read-only. If update + * methods are included in the future, they should be defined so that a + * single instance of JavaSamplerContext can be associated with each thread. + * Therefore, no synchronization should be needed. The same instance should + * be used for the call to setupTest, all calls to runTest, and the call to + * teardownTest. + */ + + /** Logging */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Map containing the initialization parameters for the JavaSamplerClient. + */ + private final Map params; + + /** + * Create a new JavaSampler with the specified initialization parameters. + * + * @param args + * the initialization parameters. + */ + public JavaSamplerContext(Arguments args) { + this.params = args.getArgumentsAsMap(); + } + + /** + * Determine whether or not a value has been specified for the parameter + * with this name. + * + * @param name + * the name of the parameter to test + * @return true if the parameter value has been specified, false otherwise. + */ + public boolean containsParameter(String name) { + return params.containsKey(name); + } + + /** + * Get an iterator of the parameter names. Each entry in the Iterator is a + * String. + * + * @return an Iterator of Strings listing the names of the parameters which + * have been specified for this test. + */ + public Iterator getParameterNamesIterator() { + return params.keySet().iterator(); + } + + /** + * Get the value of a specific parameter as a String, or null if the value + * was not specified. + * + * @param name + * the name of the parameter whose value should be retrieved + * @return the value of the parameter, or null if the value was not + * specified + */ + public String getParameter(String name) { + return getParameter(name, null); + } + + /** + * Get the value of a specified parameter as a String, or return the + * specified default value if the value was not specified. + * + * @param name + * the name of the parameter whose value should be retrieved + * @param defaultValue + * the default value to return if the value of this parameter was + * not specified + * @return the value of the parameter, or the default value if the parameter + * was not specified + */ + public String getParameter(String name, String defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + return params.get(name); + } + + /** + * Get the value of a specified parameter as an integer. An exception will + * be thrown if the parameter is not specified or if it is not an integer. + * The value may be specified in decimal, hexadecimal, or octal, as defined + * by Integer.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @return the value of the parameter + * + * @throws NumberFormatException + * if the parameter is not specified or is not an integer + * + * @see java.lang.Integer#decode(java.lang.String) + */ + public int getIntParameter(String name) throws NumberFormatException { + if (params == null || !params.containsKey(name)) { + throw new NumberFormatException("No value for parameter named '" + name + "'."); + } + + return Integer.decode(params.get(name)).intValue(); + } + + /** + * Get the value of a specified parameter as an integer, or return the + * specified default value if the value was not specified or is not an + * integer. A warning will be logged if the value is not an integer. The + * value may be specified in decimal, hexadecimal, or octal, as defined by + * Integer.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @param defaultValue + * the default value to return if the value of this parameter was + * not specified + * @return the value of the parameter, or the default value if the parameter + * was not specified + * + * @see java.lang.Integer#decode(java.lang.String) + */ + public int getIntParameter(String name, int defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + + try { + return Integer.decode(params.get(name)).intValue(); + } catch (NumberFormatException e) { + log.warn("Value for parameter '" + name + "' not an integer: '" + params.get(name) + "'. Using default: '" + + defaultValue + "'.", e); + return defaultValue; + } + } + + /** + * Get the value of a specified parameter as a long. An exception will be + * thrown if the parameter is not specified or if it is not a long. The + * value may be specified in decimal, hexadecimal, or octal, as defined by + * Long.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @return the value of the parameter + * + * @throws NumberFormatException + * if the parameter is not specified or is not a long + * + * @see Long#decode(String) + */ + public long getLongParameter(String name) throws NumberFormatException { + if (params == null || !params.containsKey(name)) { + throw new NumberFormatException("No value for parameter named '" + name + "'."); + } + + return Long.decode(params.get(name)).longValue(); + } + + /** + * Get the value of a specified parameter as along, or return the specified + * default value if the value was not specified or is not a long. A warning + * will be logged if the value is not a long. The value may be specified in + * decimal, hexadecimal, or octal, as defined by Long.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @param defaultValue + * the default value to return if the value of this parameter was + * not specified + * @return the value of the parameter, or the default value if the parameter + * was not specified + * + * @see Long#decode(String) + */ + public long getLongParameter(String name, long defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + try { + return Long.decode(params.get(name)).longValue(); + } catch (NumberFormatException e) { + log.warn("Value for parameter '" + name + "' not a long: '" + params.get(name) + "'. Using default: '" + + defaultValue + "'.", e); + return defaultValue; + } + } +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/test/JavaTest.java b/src/protocol/java/org/apache/jmeter/protocol/java/test/JavaTest.java new file mode 100644 index 00000000000..2eab53f4a0b --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/test/JavaTest.java @@ -0,0 +1,353 @@ +/* + * 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.protocol.java.test; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The JavaTest class is a simple sampler which is intended for + * use when developing test plans. The sampler generates results internally, so + * does not need access to any external resources such as web, ftp or LDAP + * servers. In addition, because the exact values of most of the SampleResult + * can be directly set, it is possible to easily test most Assertions that use + * the sample results. + * + *

+ * During each sample, this client will sleep for some amount of time. The + * amount of time to sleep is determined from the two parameters Sleep_Time and + * Sleep_Mask using the formula: + * + *

+ * totalSleepTime = Sleep_Time + (System.currentTimeMillis() % Sleep_Mask)
+ * 
+ * + * Thus, the Sleep_Mask provides a way to add a random component to the sleep + * time. + *

+ * The sampler is able to define the precise values of: + * + *

+ *
+ *  - responseCode
+ *  - responseMessage
+ *  - Label
+ *  - success/fail status
+ *
+ * 
+ * + * The elapsed time and end-time cannot be directly controlled. + *

+ * Note: this class was derived from {@link SleepTest}. + * + */ + +public class JavaTest extends AbstractJavaSamplerClient implements Serializable { + + private static final Logger LOG = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + /** The base number of milliseconds to sleep during each sample. */ + private long sleepTime; + + /** The default value of the SleepTime parameter, in milliseconds. */ + public static final long DEFAULT_SLEEP_TIME = 100; + + /** The name used to store the SleepTime parameter. */ + private static final String SLEEP_NAME = "Sleep_Time"; + + /** + * A mask to be applied to the current time in order to add a semi-random + * component to the sleep time. + */ + private long sleepMask; + + /** The default value of the SleepMask parameter. */ + public static final long DEFAULT_SLEEP_MASK = 0xff; + + /** Formatted string representation of the default SleepMask. */ + private static final String DEFAULT_MASK_STRING = "0x" + (Long.toHexString(DEFAULT_SLEEP_MASK)).toUpperCase(java.util.Locale.ENGLISH); + + /** The name used to store the SleepMask parameter. */ + private static final String MASK_NAME = "Sleep_Mask"; + + /** The label to store in the sample result. */ + private String label; + + /** The default value of the Label parameter. */ + // private static final String LABEL_DEFAULT = "JavaTest"; + /** The name used to store the Label parameter. */ + private static final String LABEL_NAME = "Label"; + + /** The response message to store in the sample result. */ + private String responseMessage; + + /** The default value of the ResponseMessage parameter. */ + private static final String RESPONSE_MESSAGE_DEFAULT = ""; + + /** The name used to store the ResponseMessage parameter. */ + private static final String RESPONSE_MESSAGE_NAME = "ResponseMessage"; + + /** The response code to be stored in the sample result. */ + private String responseCode; + + /** The default value of the ResponseCode parameter. */ + private static final String RESPONSE_CODE_DEFAULT = ""; + + /** The name used to store the ResponseCode parameter. */ + private static final String RESPONSE_CODE_NAME = "ResponseCode"; + + /** The sampler data (shown as Request Data in the Tree display). */ + private String samplerData; + + /** The default value of the SamplerData parameter. */ + private static final String SAMPLER_DATA_DEFAULT = ""; + + /** The name used to store the SamplerData parameter. */ + private static final String SAMPLER_DATA_NAME = "SamplerData"; + + /** Holds the result data (shown as Response Data in the Tree display). */ + private String resultData; + + /** The default value of the ResultData parameter. */ + private static final String RESULT_DATA_DEFAULT = ""; + + /** The name used to store the ResultData parameter. */ + private static final String RESULT_DATA_NAME = "ResultData"; + + /** The success status to be stored in the sample result. */ + private boolean success; + + /** The default value of the Success Status parameter. */ + private static final String SUCCESS_DEFAULT = "OK"; + + /** The name used to store the Success Status parameter. */ + private static final String SUCCESS_NAME = "Status"; + + /** + * Default constructor for JavaTest. + * + * The Java Sampler uses the default constructor to instantiate an instance + * of the client class. + */ + public JavaTest() { + LOG.debug(whoAmI() + "\tConstruct"); + } + + /* + * Utility method to set up all the values + */ + private void setupValues(JavaSamplerContext context) { + + sleepTime = context.getLongParameter(SLEEP_NAME, DEFAULT_SLEEP_TIME); + sleepMask = context.getLongParameter(MASK_NAME, DEFAULT_SLEEP_MASK); + + responseMessage = context.getParameter(RESPONSE_MESSAGE_NAME, RESPONSE_MESSAGE_DEFAULT); + + responseCode = context.getParameter(RESPONSE_CODE_NAME, RESPONSE_CODE_DEFAULT); + + success = context.getParameter(SUCCESS_NAME, SUCCESS_DEFAULT).equalsIgnoreCase("OK"); + + label = context.getParameter(LABEL_NAME, ""); + if (label.length() == 0) { + label = context.getParameter(TestElement.NAME); // default to name of element + } + + samplerData = context.getParameter(SAMPLER_DATA_NAME, SAMPLER_DATA_DEFAULT); + + resultData = context.getParameter(RESULT_DATA_NAME, RESULT_DATA_DEFAULT); + } + + /** + * Do any initialization required by this client. + * + * There is none, as it is done in runTest() in order to be able to vary the + * data for each sample. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + @Override + public void setupTest(JavaSamplerContext context) { + if (LOG.isDebugEnabled()) { + LOG.debug(whoAmI() + "\tsetupTest()"); + listParameters(context); + } + } + + /** + * Provide a list of parameters which this test supports. Any parameter + * names and associated values returned by this method will appear in the + * GUI by default so the user doesn't have to remember the exact names. The + * user can add other parameters which are not listed here. If this method + * returns null then no parameters will be listed. If the value for some + * parameter is null then that parameter will be listed in the GUI with an + * empty value. + * + * @return a specification of the parameters used by this test which should + * be listed in the GUI, or null if no parameters should be listed. + */ + @Override + public Arguments getDefaultParameters() { + Arguments params = new Arguments(); + params.addArgument(SLEEP_NAME, String.valueOf(DEFAULT_SLEEP_TIME)); + params.addArgument(MASK_NAME, DEFAULT_MASK_STRING); + params.addArgument(LABEL_NAME, ""); + params.addArgument(RESPONSE_CODE_NAME, RESPONSE_CODE_DEFAULT); + params.addArgument(RESPONSE_MESSAGE_NAME, RESPONSE_MESSAGE_DEFAULT); + params.addArgument(SUCCESS_NAME, SUCCESS_DEFAULT); + params.addArgument(SAMPLER_DATA_NAME, SAMPLER_DATA_DEFAULT); + params.addArgument(RESULT_DATA_NAME, SAMPLER_DATA_DEFAULT); + return params; + } + + /** + * Perform a single sample.
+ * In this case, this method will simply sleep for some amount of time. + * + * This method returns a SampleResult object. + * + *

+     *
+     *  The following fields are always set:
+     *  - responseCode (default "")
+     *  - responseMessage (default "")
+     *  - label (set from LABEL_NAME parameter if it exists, else element name)
+     *  - success (default true)
+     *
+     * 
+ * + * The following fields are set from the user-defined parameters, if + * supplied: + * + *
+     * -samplerData - responseData
+     * 
+ * + * @see org.apache.jmeter.samplers.SampleResult#sampleStart() + * @see org.apache.jmeter.samplers.SampleResult#sampleEnd() + * @see org.apache.jmeter.samplers.SampleResult#setSuccessful(boolean) + * @see org.apache.jmeter.samplers.SampleResult#setSampleLabel(String) + * @see org.apache.jmeter.samplers.SampleResult#setResponseCode(String) + * @see org.apache.jmeter.samplers.SampleResult#setResponseMessage(String) + * @see org.apache.jmeter.samplers.SampleResult#setResponseData(byte []) + * @see org.apache.jmeter.samplers.SampleResult#setDataType(String) + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + * + * @return a SampleResult giving the results of this sample. + */ + @Override + public SampleResult runTest(JavaSamplerContext context) { + setupValues(context); + + SampleResult results = new SampleResult(); + + results.setResponseCode(responseCode); + results.setResponseMessage(responseMessage); + results.setSampleLabel(label); + + if (samplerData != null && samplerData.length() > 0) { + results.setSamplerData(samplerData); + } + + if (resultData != null && resultData.length() > 0) { + results.setResponseData(resultData, null); + results.setDataType(SampleResult.TEXT); + } + + // Record sample start time. + results.sampleStart(); + + long sleep = sleepTime; + if (sleepTime > 0 && sleepMask > 0) { // / Only do the calculation if + // it is needed + long start = System.currentTimeMillis(); + // Generate a random-ish offset value using the current time. + sleep = sleepTime + (start % sleepMask); + } + + try { + // Execute the sample. In this case sleep for the + // specified time, if any + if (sleep > 0) { + TimeUnit.MILLISECONDS.sleep(sleep); + } + results.setSuccessful(success); + } catch (InterruptedException e) { + LOG.warn("JavaTest: interrupted."); + results.setSuccessful(true); + } catch (Exception e) { + LOG.error("JavaTest: error during sample", e); + results.setSuccessful(false); + } finally { + // Record end time and populate the results. + results.sampleEnd(); + } + + if (LOG.isDebugEnabled()) { + LOG.debug(whoAmI() + "\trunTest()" + "\tTime:\t" + results.getTime()); + listParameters(context); + } + + return results; + } + + /** + * Dump a list of the parameters in this context to the debug log. + * Should only be called if debug is enabled. + * + * @param context + * the context which contains the initialization parameters. + */ + private void listParameters(JavaSamplerContext context) { + Iterator argsIt = context.getParameterNamesIterator(); + while (argsIt.hasNext()) { + String name = argsIt.next(); + LOG.debug(name + "=" + context.getParameter(name)); + } + } + + /** + * Generate a String identifier of this test for debugging purposes. + * + * @return a String identifier for this test instance + */ + private String whoAmI() { + StringBuilder sb = new StringBuilder(); + sb.append(Thread.currentThread().toString()); + sb.append("@"); + sb.append(Integer.toHexString(hashCode())); + return sb.toString(); + } + +} diff --git a/src/protocol/java/org/apache/jmeter/protocol/java/test/SleepTest.java b/src/protocol/java/org/apache/jmeter/protocol/java/test/SleepTest.java new file mode 100644 index 00000000000..9e609562bad --- /dev/null +++ b/src/protocol/java/org/apache/jmeter/protocol/java/test/SleepTest.java @@ -0,0 +1,222 @@ +/* + * 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.protocol.java.test; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The SleepTest class is a simple example class for a JMeter + * Java protocol client. The class implements the JavaSamplerClient + * interface. + *

+ * During each sample, this client will sleep for some amount of time. The + * amount of time to sleep is determined from the two parameters SleepTime and + * SleepMask using the formula: + * + *

+ * totalSleepTime = SleepTime + (System.currentTimeMillis() % SleepMask)
+ * 
+ * + * Thus, the SleepMask provides a way to add a random component to the sleep + * time. + * + * @version $Revision$ + */ +public class SleepTest extends AbstractJavaSamplerClient implements Serializable { + + private static final Logger LOG = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + /** + * The default value of the SleepTime parameter, in milliseconds. + */ + public static final long DEFAULT_SLEEP_TIME = 1000; + + /** + * The default value of the SleepMask parameter. + */ + public static final long DEFAULT_SLEEP_MASK = 0x3ff; + + /** + * The base number of milliseconds to sleep during each sample. + */ + private long sleepTime; + + /** + * A mask to be applied to the current time in order to add a random + * component to the sleep time. + */ + private long sleepMask; + + // The name of the sampler + private String name; + + /** + * Default constructor for SleepTest. + * + * The Java Sampler uses the default constructor to instantiate an instance + * of the client class. + */ + public SleepTest() { + LOG.debug(whoAmI() + "\tConstruct"); + } + + /** + * Do any initialization required by this client. In this case, + * initialization consists of getting the values of the SleepTime and + * SleepMask parameters. It is generally recommended to do any + * initialization such as getting parameter values in the setupTest method + * rather than the runTest method in order to add as little overhead as + * possible to the test. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + @Override + public void setupTest(JavaSamplerContext context) { + if (LOG.isDebugEnabled()) { + LOG.debug(whoAmI() + "\tsetupTest()"); + listParameters(context); + } + sleepTime = context.getLongParameter("SleepTime", DEFAULT_SLEEP_TIME); + sleepMask = context.getLongParameter("SleepMask", DEFAULT_SLEEP_MASK); + name = context.getParameter(TestElement.NAME); + } + + /** + * Perform a single sample. In this case, this method will simply sleep for + * some amount of time. Perform a single sample for each iteration. This + * method returns a SampleResult object. + * SampleResult has many fields which can be used. At a + * minimum, the test should use SampleResult.sampleStart and + * SampleResult.sampleEndto set the time that the test + * required to execute. It is also a good idea to set the sampleLabel and + * the successful flag. + * + * @see org.apache.jmeter.samplers.SampleResult#sampleStart() + * @see org.apache.jmeter.samplers.SampleResult#sampleEnd() + * @see org.apache.jmeter.samplers.SampleResult#setSuccessful(boolean) + * @see org.apache.jmeter.samplers.SampleResult#setSampleLabel(String) + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + * + * @return a SampleResult giving the results of this sample. + */ + @Override + public SampleResult runTest(JavaSamplerContext context) { + SampleResult results = new SampleResult(); + results.setSampleLabel(name); + long sleep = sleepTime; + // Only do the calculation if it is needed + if (sleepTime > 0 && sleepMask > 0) { + long start = System.currentTimeMillis(); + // Generate a random-ish offset value using the current time. + sleep = sleepTime + (start % sleepMask); + } + results.setSamplerData("Sleep Test: time = " + sleep); + + try { + // Record sample start time. + results.sampleStart(); + + // Execute the sample. In this case sleep for the + // specified time. + TimeUnit.MILLISECONDS.sleep(sleep); + + results.setSuccessful(true); + } catch (InterruptedException e) { + LOG.warn("SleepTest: interrupted."); + results.setSuccessful(true); + results.setResponseMessage(e.toString()); + } catch (Exception e) { + LOG.error("SleepTest: error during sample", e); + results.setSuccessful(false); + results.setResponseMessage(e.toString()); + } finally { + results.sampleEnd(); + } + + if (LOG.isDebugEnabled()) { + LOG.debug(whoAmI() + "\trunTest()" + "\tTime:\t" + results.getTime()); + listParameters(context); + } + + return results; + } + + /** + * Provide a list of parameters which this test supports. Any parameter + * names and associated values returned by this method will appear in the + * GUI by default so the user doesn't have to remember the exact names. The + * user can add other parameters which are not listed here. If this method + * returns null then no parameters will be listed. If the value for some + * parameter is null then that parameter will be listed in the GUI with an + * empty value. + * + * @return a specification of the parameters used by this test which should + * be listed in the GUI, or null if no parameters should be listed. + */ + @Override + public Arguments getDefaultParameters() { + Arguments params = new Arguments(); + params.addArgument("SleepTime", String.valueOf(DEFAULT_SLEEP_TIME)); + params.addArgument("SleepMask", "0x" + (Long.toHexString(DEFAULT_SLEEP_MASK)).toUpperCase(java.util.Locale.ENGLISH)); + return params; + } + + /** + * Dump a list of the parameters in this context to the debug log. + * + * @param context + * the context which contains the initialization parameters. + */ + private void listParameters(JavaSamplerContext context) { + Iterator argsIt = context.getParameterNamesIterator(); + while (argsIt.hasNext()) { + String name = argsIt.next(); + LOG.debug(name + "=" + context.getParameter(name)); + } + } + + /** + * Generate a String identifier of this test for debugging purposes. + * + * @return a String identifier for this test instance + */ + private String whoAmI() { + StringBuilder sb = new StringBuilder(); + sb.append(Thread.currentThread().toString()); + sb.append("@"); + sb.append(Integer.toHexString(hashCode())); + return sb.toString(); + } +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElement.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElement.java new file mode 100644 index 00000000000..ea0d63a171f --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElement.java @@ -0,0 +1,708 @@ +/* + * 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.protocol.jdbc; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A base class for all JDBC test elements handling the basics of a SQL request. + * + */ +public abstract class AbstractJDBCTestElement extends AbstractTestElement implements TestStateListener{ + private static final long serialVersionUID = 235L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String COMMA = ","; // $NON-NLS-1$ + private static final char COMMA_CHAR = ','; + + private static final String UNDERSCORE = "_"; // $NON-NLS-1$ + + // String used to indicate a null value + private static final String NULL_MARKER = + JMeterUtils.getPropDefault("jdbcsampler.nullmarker","]NULL["); // $NON-NLS-1$ + + private static final int MAX_OPEN_PREPARED_STATEMENTS = + JMeterUtils.getPropDefault("jdbcsampler.maxopenpreparedstatements", 100); + + private static final String INOUT = "INOUT"; // $NON-NLS-1$ + + private static final String OUT = "OUT"; // $NON-NLS-1$ + + // TODO - should the encoding be configurable? + protected static final String ENCODING = "UTF-8"; // $NON-NLS-1$ + + // key: name (lowercase) from java.sql.Types; entry: corresponding int value + private static final Map mapJdbcNameToInt; + // read-only after class init + + static { + // based on e291. Getting the Name of a JDBC Type from javaalmanac.com + // http://javaalmanac.com/egs/java.sql/JdbcInt2Str.html + mapJdbcNameToInt = new HashMap(); + + //Get all fields in java.sql.Types and store the corresponding int values + Field[] fields = java.sql.Types.class.getFields(); + for (int i=0; i> perConnCache = + new ConcurrentHashMap>(); + + /** + * Creates a JDBCSampler. + */ + protected AbstractJDBCTestElement() { + } + + /** + * Execute the test element. + * + * @param conn a {@link SampleResult} in case the test should sample; null if only execution is requested + * @return the result of the execute command + * @throws SQLException if a database error occurs + * @throws UnsupportedEncodingException when the result can not be converted to the required charset + * @throws IOException when I/O error occurs + * @throws UnsupportedOperationException if the user provided incorrect query type + */ + protected byte[] execute(Connection conn) throws SQLException, UnsupportedEncodingException, IOException, UnsupportedOperationException { + log.debug("executing jdbc"); + Statement stmt = null; + + try { + // Based on query return value, get results + String _queryType = getQueryType(); + if (SELECT.equals(_queryType)) { + stmt = conn.createStatement(); + stmt.setQueryTimeout(getIntegerQueryTimeout()); + ResultSet rs = null; + try { + rs = stmt.executeQuery(getQuery()); + return getStringFromResultSet(rs).getBytes(ENCODING); + } finally { + close(rs); + } + } else if (CALLABLE.equals(_queryType)) { + CallableStatement cstmt = getCallableStatement(conn); + int out[]=setArguments(cstmt); + // A CallableStatement can return more than 1 ResultSets + // plus a number of update counts. + boolean hasResultSet = cstmt.execute(); + String sb = resultSetsToString(cstmt,hasResultSet, out); + return sb.getBytes(ENCODING); + } else if (UPDATE.equals(_queryType)) { + stmt = conn.createStatement(); + stmt.setQueryTimeout(getIntegerQueryTimeout()); + stmt.executeUpdate(getQuery()); + int updateCount = stmt.getUpdateCount(); + String results = updateCount + " updates"; + return results.getBytes(ENCODING); + } else if (PREPARED_SELECT.equals(_queryType)) { + PreparedStatement pstmt = getPreparedStatement(conn); + setArguments(pstmt); + ResultSet rs = null; + try { + rs = pstmt.executeQuery(); + return getStringFromResultSet(rs).getBytes(ENCODING); + } finally { + close(rs); + } + } else if (PREPARED_UPDATE.equals(_queryType)) { + PreparedStatement pstmt = getPreparedStatement(conn); + setArguments(pstmt); + pstmt.executeUpdate(); + String sb = resultSetsToString(pstmt,false,null); + return sb.getBytes(ENCODING); + } else if (ROLLBACK.equals(_queryType)){ + conn.rollback(); + return ROLLBACK.getBytes(ENCODING); + } else if (COMMIT.equals(_queryType)){ + conn.commit(); + return COMMIT.getBytes(ENCODING); + } else if (AUTOCOMMIT_FALSE.equals(_queryType)){ + conn.setAutoCommit(false); + return AUTOCOMMIT_FALSE.getBytes(ENCODING); + } else if (AUTOCOMMIT_TRUE.equals(_queryType)){ + conn.setAutoCommit(true); + return AUTOCOMMIT_TRUE.getBytes(ENCODING); + } else { // User provided incorrect query type + throw new UnsupportedOperationException("Unexpected query type: "+_queryType); + } + } finally { + close(stmt); + } + } + + private String resultSetsToString(PreparedStatement pstmt, boolean result, int[] out) throws SQLException, UnsupportedEncodingException { + StringBuilder sb = new StringBuilder(); + int updateCount = 0; + if (!result) { + updateCount = pstmt.getUpdateCount(); + } + do { + if (result) { + ResultSet rs = null; + try { + rs = pstmt.getResultSet(); + sb.append(getStringFromResultSet(rs)).append("\n"); // $NON-NLS-1$ + } finally { + close(rs); + } + } else { + sb.append(updateCount).append(" updates.\n"); + } + result = pstmt.getMoreResults(); + if (!result) { + updateCount = pstmt.getUpdateCount(); + } + } while (result || (updateCount != -1)); + if (out!=null && pstmt instanceof CallableStatement){ + ArrayList outputValues = new ArrayList(); + CallableStatement cs = (CallableStatement) pstmt; + sb.append("Output variables by position:\n"); + for(int i=0; i < out.length; i++){ + if (out[i]!=java.sql.Types.NULL){ + Object o = cs.getObject(i+1); + outputValues.add(o); + sb.append("["); + sb.append(i+1); + sb.append("] "); + sb.append(o); + if( o instanceof java.sql.ResultSet && RS_COUNT_RECORDS.equals(resultSetHandler)) { + sb.append(" ").append(countRows((ResultSet) o)).append(" rows"); + } + sb.append("\n"); + } + } + String varnames[] = getVariableNames().split(COMMA); + if(varnames.length > 0) { + JMeterVariables jmvars = getThreadContext().getVariables(); + for(int i = 0; i < varnames.length && i < outputValues.size(); i++) { + String name = varnames[i].trim(); + if (name.length()>0){ // Save the value in the variable if present + Object o = outputValues.get(i); + if( o instanceof java.sql.ResultSet ) { + ResultSet resultSet = (ResultSet) o; + if(RS_STORE_AS_OBJECT.equals(resultSetHandler)) { + jmvars.putObject(name, o); + } + else if( RS_COUNT_RECORDS.equals(resultSetHandler)) { + jmvars.put(name,o.toString()+" "+countRows(resultSet)+" rows"); + } + else { + jmvars.put(name, o.toString()); + } + } + else { + jmvars.put(name, o == null ? null : o.toString()); + } + } + } + } + } + return sb.toString(); + } + + /** + * Count rows in result set + * @param resultSet {@link ResultSet} + * @return number of rows in resultSet + * @throws SQLException + */ + private static final int countRows(ResultSet resultSet) throws SQLException { + return resultSet.last() ? resultSet.getRow() : 0; + } + + private int[] setArguments(PreparedStatement pstmt) throws SQLException, IOException { + if (getQueryArguments().trim().length()==0) { + return new int[]{}; + } + String[] arguments = CSVSaveService.csvSplitString(getQueryArguments(), COMMA_CHAR); + String[] argumentsTypes = getQueryArgumentsTypes().split(COMMA); + if (arguments.length != argumentsTypes.length) { + throw new SQLException("number of arguments ("+arguments.length+") and number of types ("+argumentsTypes.length+") are not equal"); + } + int[] outputs= new int[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + String argument = arguments[i]; + String argumentType = argumentsTypes[i]; + String[] arg = argumentType.split(" "); + String inputOutput=""; + if (arg.length > 1) { + argumentType = arg[1]; + inputOutput=arg[0]; + } + int targetSqlType = getJdbcType(argumentType); + try { + if (!OUT.equalsIgnoreCase(inputOutput)){ + if (argument.equals(NULL_MARKER)){ + pstmt.setNull(i+1, targetSqlType); + } else { + pstmt.setObject(i+1, argument, targetSqlType); + } + } + if (OUT.equalsIgnoreCase(inputOutput)||INOUT.equalsIgnoreCase(inputOutput)) { + CallableStatement cs = (CallableStatement) pstmt; + cs.registerOutParameter(i+1, targetSqlType); + outputs[i]=targetSqlType; + } else { + outputs[i]=java.sql.Types.NULL; // can't have an output parameter type null + } + } catch (NullPointerException e) { // thrown by Derby JDBC (at least) if there are no "?" markers in statement + throw new SQLException("Could not set argument no: "+(i+1)+" - missing parameter marker?"); + } + } + return outputs; + } + + + private static int getJdbcType(String jdbcType) throws SQLException { + Integer entry = mapJdbcNameToInt.get(jdbcType.toLowerCase(java.util.Locale.ENGLISH)); + if (entry == null) { + try { + entry = Integer.decode(jdbcType); + } catch (NumberFormatException e) { + throw new SQLException("Invalid data type: "+jdbcType); + } + } + return (entry).intValue(); + } + + + private CallableStatement getCallableStatement(Connection conn) throws SQLException { + return (CallableStatement) getPreparedStatement(conn,true); + + } + private PreparedStatement getPreparedStatement(Connection conn) throws SQLException { + return getPreparedStatement(conn,false); + } + + private PreparedStatement getPreparedStatement(Connection conn, boolean callable) throws SQLException { + Map preparedStatementMap = perConnCache.get(conn); + if (null == preparedStatementMap ) { + @SuppressWarnings("unchecked") // LRUMap is not generic + Map lruMap = new LRUMap(MAX_OPEN_PREPARED_STATEMENTS) { + private static final long serialVersionUID = 1L; + @Override + protected boolean removeLRU(LinkEntry entry) { + PreparedStatement preparedStatement = (PreparedStatement)entry.getValue(); + close(preparedStatement); + return true; + } + }; + preparedStatementMap = Collections.synchronizedMap(lruMap); + // As a connection is held by only one thread, we cannot already have a + // preparedStatementMap put by another thread + perConnCache.put(conn, preparedStatementMap); + } + PreparedStatement pstmt = preparedStatementMap.get(getQuery()); + if (null == pstmt) { + if (callable) { + pstmt = conn.prepareCall(getQuery()); + } else { + pstmt = conn.prepareStatement(getQuery()); + } + pstmt.setQueryTimeout(getIntegerQueryTimeout()); + // PreparedStatementMap is associated to one connection so + // 2 threads cannot use the same PreparedStatement map at the same time + preparedStatementMap.put(getQuery(), pstmt); + } else { + int timeoutInS = getIntegerQueryTimeout(); + if(pstmt.getQueryTimeout() != timeoutInS) { + pstmt.setQueryTimeout(getIntegerQueryTimeout()); + } + } + pstmt.clearParameters(); + return pstmt; + } + + private static void closeAllStatements(Collection collection) { + for (PreparedStatement pstmt : collection) { + close(pstmt); + } + } + + /** + * Gets a Data object from a ResultSet. + * + * @param rs + * ResultSet passed in from a database query + * @return a Data object + * @throws java.sql.SQLException + * @throws UnsupportedEncodingException + */ + private String getStringFromResultSet(ResultSet rs) throws SQLException, UnsupportedEncodingException { + ResultSetMetaData meta = rs.getMetaData(); + + StringBuilder sb = new StringBuilder(); + + int numColumns = meta.getColumnCount(); + for (int i = 1; i <= numColumns; i++) { + sb.append(meta.getColumnLabel(i)); + if (i==numColumns){ + sb.append('\n'); + } else { + sb.append('\t'); + } + } + + + JMeterVariables jmvars = getThreadContext().getVariables(); + String varnames[] = getVariableNames().split(COMMA); + String resultVariable = getResultVariable().trim(); + List > results = null; + if(resultVariable.length() > 0) { + results = new ArrayList >(); + jmvars.putObject(resultVariable, results); + } + int j = 0; + while (rs.next()) { + Map row = null; + j++; + for (int i = 1; i <= numColumns; i++) { + Object o = rs.getObject(i); + if(results != null) { + if(row == null) { + row = new HashMap(numColumns); + results.add(row); + } + row.put(meta.getColumnLabel(i), o); + } + if (o instanceof byte[]) { + o = new String((byte[]) o, ENCODING); + } + sb.append(o); + if (i==numColumns){ + sb.append('\n'); + } else { + sb.append('\t'); + } + if (i <= varnames.length) { // i starts at 1 + String name = varnames[i - 1].trim(); + if (name.length()>0){ // Save the value in the variable if present + jmvars.put(name+UNDERSCORE+j, o == null ? null : o.toString()); + } + } + } + } + // Remove any additional values from previous sample + for(int i=0; i < varnames.length; i++){ + String name = varnames[i].trim(); + if (name.length()>0 && jmvars != null){ + final String varCount = name+"_#"; // $NON-NLS-1$ + // Get the previous count + String prevCount = jmvars.get(varCount); + if (prevCount != null){ + int prev = Integer.parseInt(prevCount); + for (int n=j+1; n <= prev; n++ ){ + jmvars.remove(name+UNDERSCORE+n); + } + } + jmvars.put(varCount, Integer.toString(j)); // save the current count + } + } + + return sb.toString(); + } + + public static void close(Connection c) { + try { + if (c != null) { + c.close(); + } + } catch (SQLException e) { + log.warn("Error closing Connection", e); + } + } + + public static void close(Statement s) { + try { + if (s != null) { + s.close(); + } + } catch (SQLException e) { + log.warn("Error closing Statement " + s.toString(), e); + } + } + + public static void close(ResultSet rs) { + try { + if (rs != null) { + rs.close(); + } + } catch (SQLException e) { + log.warn("Error closing ResultSet", e); + } + } + + /** + * @return the integer representation queryTimeout + */ + public int getIntegerQueryTimeout() { + int timeout = 0; + try { + timeout = Integer.parseInt(queryTimeout); + } catch (NumberFormatException nfe) { + timeout = 0; + } + return timeout; + } + + /** + * @return the queryTimeout + */ + public String getQueryTimeout() { + return queryTimeout ; + } + + /** + * @param queryTimeout query timeout in seconds + */ + public void setQueryTimeout(String queryTimeout) { + this.queryTimeout = queryTimeout; + } + + public String getQuery() { + return query; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(80); + sb.append("["); // $NON-NLS-1$ + sb.append(getQueryType()); + sb.append("] "); // $NON-NLS-1$ + sb.append(getQuery()); + sb.append("\n"); + sb.append(getQueryArguments()); + sb.append("\n"); + sb.append(getQueryArgumentsTypes()); + return sb.toString(); + } + + /** + * @param query + * The query to set. + */ + public void setQuery(String query) { + this.query = query; + } + + /** + * @return Returns the dataSource. + */ + public String getDataSource() { + return dataSource; + } + + /** + * @param dataSource + * The dataSource to set. + */ + public void setDataSource(String dataSource) { + this.dataSource = dataSource; + } + + /** + * @return Returns the queryType. + */ + public String getQueryType() { + return queryType; + } + + /** + * @param queryType The queryType to set. + */ + public void setQueryType(String queryType) { + this.queryType = queryType; + } + + public String getQueryArguments() { + return queryArguments; + } + + public void setQueryArguments(String queryArguments) { + this.queryArguments = queryArguments; + } + + public String getQueryArgumentsTypes() { + return queryArgumentsTypes; + } + + public void setQueryArgumentsTypes(String queryArgumentsType) { + this.queryArgumentsTypes = queryArgumentsType; + } + + /** + * @return the variableNames + */ + public String getVariableNames() { + return variableNames; + } + + /** + * @param variableNames the variableNames to set + */ + public void setVariableNames(String variableNames) { + this.variableNames = variableNames; + } + + /** + * @return the resultSetHandler + */ + public String getResultSetHandler() { + return resultSetHandler; + } + + /** + * @param resultSetHandler the resultSetHandler to set + */ + public void setResultSetHandler(String resultSetHandler) { + this.resultSetHandler = resultSetHandler; + } + + /** + * @return the resultVariable + */ + public String getResultVariable() { + return resultVariable ; + } + + /** + * @param resultVariable the variable name in which results will be stored + */ + public void setResultVariable(String resultVariable) { + this.resultVariable = resultVariable; + } + + + /** + * {@inheritDoc} + * @see org.apache.jmeter.testelement.TestStateListener#testStarted() + */ + @Override + public void testStarted() { + testStarted(""); + } + + /** + * {@inheritDoc} + * @see org.apache.jmeter.testelement.TestStateListener#testStarted(java.lang.String) + */ + @Override + public void testStarted(String host) { + cleanCache(); + } + + /** + * {@inheritDoc} + * @see org.apache.jmeter.testelement.TestStateListener#testEnded() + */ + @Override + public void testEnded() { + testEnded(""); + } + + /** + * {@inheritDoc} + * @see org.apache.jmeter.testelement.TestStateListener#testEnded(java.lang.String) + */ + @Override + public void testEnded(String host) { + cleanCache(); + } + + /** + * Clean cache of PreparedStatements + */ + private static final void cleanCache() { + for (Map element : perConnCache.values()) { + closeAllStatements(element.values()); + } + perConnCache.clear(); + } + +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/JDBCTestElementBeanInfoSupport.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/JDBCTestElementBeanInfoSupport.java new file mode 100644 index 00000000000..e53566431e8 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/JDBCTestElementBeanInfoSupport.java @@ -0,0 +1,106 @@ +/* + * 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.protocol.jdbc; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TypeEditor; + +public abstract class JDBCTestElementBeanInfoSupport extends BeanInfoSupport { + + /** + * @param beanClass class to create bean info for + */ + public JDBCTestElementBeanInfoSupport(Class beanClass) { + super(beanClass); + + createPropertyGroup("varName", // $NON-NLS-1$ + new String[]{"dataSource" }); // $NON-NLS-1$ + + createPropertyGroup("sql", // $NON-NLS-1$ + new String[] { + "queryType", // $NON-NLS-1$ + "query", // $NON-NLS-1$ + "queryArguments", // $NON-NLS-1$ + "queryArgumentsTypes", // $NON-NLS-1$ + "variableNames", // $NON-NLS-1$ + "resultVariable", // $NON-NLS-1$ + "queryTimeout", // $NON-NLS-1$ + "resultSetHandler" // $NON-NLS-1$ + }); + + PropertyDescriptor p = property("dataSource"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property("queryArguments"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property("queryArgumentsTypes"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property("variableNames"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property("resultSetHandler"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, AbstractJDBCTestElement.RS_STORE_AS_STRING); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(TAGS,new String[]{ + AbstractJDBCTestElement.RS_STORE_AS_STRING, + AbstractJDBCTestElement.RS_STORE_AS_OBJECT, + AbstractJDBCTestElement.RS_COUNT_RECORDS + }); + + p = property("resultVariable"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property("queryTimeout"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("queryType"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, AbstractJDBCTestElement.SELECT); + p.setValue(NOT_OTHER,Boolean.TRUE); + p.setValue(TAGS,new String[]{ + AbstractJDBCTestElement.SELECT, + AbstractJDBCTestElement.UPDATE, + AbstractJDBCTestElement.CALLABLE, + AbstractJDBCTestElement.PREPARED_SELECT, + AbstractJDBCTestElement.PREPARED_UPDATE, + AbstractJDBCTestElement.COMMIT, + AbstractJDBCTestElement.ROLLBACK, + AbstractJDBCTestElement.AUTOCOMMIT_FALSE, + AbstractJDBCTestElement.AUTOCOMMIT_TRUE, + }); + + p = property("query", TypeEditor.TextAreaEditor); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + p.setValue(TEXT_LANGUAGE, "sql"); // $NON-NLS-1$ + + } +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElement.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElement.java new file mode 100644 index 00000000000..dfeb7f6464b --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElement.java @@ -0,0 +1,512 @@ +/* + * 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.protocol.jdbc.config; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.avalon.excalibur.datasource.DataSourceComponent; +import org.apache.avalon.excalibur.datasource.ResourceLimitingJdbcDataSource; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.apache.avalon.framework.logger.LogKitLogger; +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class DataSourceElement extends AbstractTestElement + implements ConfigElement, TestStateListener, TestBean + { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + private transient String dataSource, driver, dbUrl, username, password, checkQuery, poolMax, connectionAge, timeout, + trimInterval,transactionIsolation; + + private transient boolean keepAlive, autocommit; + + /* + * The datasource is set up by testStarted and cleared by testEnded. + * These are called from different threads, so access must be synchronized. + * The same instance is called in each case. + */ + private transient ResourceLimitingJdbcDataSource excaliburSource; + + // Keep a record of the pre-thread pools so that they can be disposed of at the end of a test + private transient Set perThreadPoolSet; + + public DataSourceElement() { + } + + @Override + public void testEnded() { + synchronized (this) { + if (excaliburSource != null) { + excaliburSource.dispose(); + } + excaliburSource = null; + } + if (perThreadPoolSet != null) {// in case + for(ResourceLimitingJdbcDataSource dsc : perThreadPoolSet){ + log.debug("Disposing pool: "+dsc.getInstrumentableName()+" @"+System.identityHashCode(dsc)); + dsc.dispose(); + } + perThreadPoolSet=null; + } + } + + @Override + public void testEnded(String host) { + testEnded(); + } + + @Override + @SuppressWarnings("deprecation") // call to TestBeanHelper.prepare() is intentional + public void testStarted() { + this.setRunningVersion(true); + TestBeanHelper.prepare(this); + JMeterVariables variables = getThreadContext().getVariables(); + String poolName = getDataSource(); + if(JOrphanUtils.isBlank(poolName)) { + throw new IllegalArgumentException("Variable Name must not be empty for element:"+getName()); + } else if (variables.getObject(poolName) != null) { + log.error("JDBC data source already defined for: "+poolName); + } else { + String maxPool = getPoolMax(); + perThreadPoolSet = Collections.synchronizedSet(new HashSet()); + if (maxPool.equals("0")){ // i.e. if we want per thread pooling + variables.putObject(poolName, new DataSourceComponentImpl()); // pool will be created later + } else { + ResourceLimitingJdbcDataSource src=initPool(maxPool); + synchronized(this){ + excaliburSource = src; + variables.putObject(poolName, new DataSourceComponentImpl(excaliburSource)); + } + } + } + } + + @Override + public void testStarted(String host) { + testStarted(); + } + + @Override + public Object clone() { + DataSourceElement el = (DataSourceElement) super.clone(); + synchronized (this) { + el.excaliburSource = excaliburSource; + el.perThreadPoolSet = perThreadPoolSet; + } + return el; + } + + /* + * Utility routine to get the connection from the pool. + * Purpose: + * - allows JDBCSampler to be entirely independent of the pooling classes + * - allows the pool storage mechanism to be changed if necessary + */ + public static Connection getConnection(String poolName) throws SQLException{ + Object poolObject = + JMeterContextService.getContext().getVariables().getObject(poolName); + if (poolObject == null) { + throw new SQLException("No pool found named: '" + poolName + "', ensure Variable Name matches Variable Name of JDBC Connection Configuration"); + } else { + if(poolObject instanceof DataSourceComponent) { + DataSourceComponent pool = (DataSourceComponent) poolObject; + return pool.getConnection(); + } else { + String errorMsg = "Found object stored under variable:'"+poolName + +"' with class:"+poolObject.getClass().getName()+" and value: '"+poolObject+" but it's not a DataSourceComponent, check you're not already using this name as another variable"; + log.error(errorMsg); + throw new SQLException(errorMsg); + } + } + } + + /* + * Set up the DataSource - maxPool is a parameter, so the same code can + * also be used for setting up the per-thread pools. + */ + private ResourceLimitingJdbcDataSource initPool(String maxPool) { + ResourceLimitingJdbcDataSource source = null; + source = new ResourceLimitingJdbcDataSource(); + DefaultConfiguration config = new DefaultConfiguration("rl-jdbc"); // $NON-NLS-1$ + + if (log.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(40); + sb.append("MaxPool: "); + sb.append(maxPool); + sb.append(" Timeout: "); + sb.append(getTimeout()); + sb.append(" TrimInt: "); + sb.append(getTrimInterval()); + sb.append(" Auto-Commit: "); + sb.append(isAutocommit()); + log.debug(sb.toString()); + } + DefaultConfiguration poolController = new DefaultConfiguration("pool-controller"); // $NON-NLS-1$ + poolController.setAttribute("max", maxPool); // $NON-NLS-1$ + poolController.setAttribute("max-strict", "true"); // $NON-NLS-1$ $NON-NLS-2$ + poolController.setAttribute("blocking", "true"); // $NON-NLS-1$ $NON-NLS-2$ + poolController.setAttribute("timeout", getTimeout()); // $NON-NLS-1$ + poolController.setAttribute("trim-interval", getTrimInterval()); // $NON-NLS-1$ + config.addChild(poolController); + + DefaultConfiguration autoCommit = new DefaultConfiguration("auto-commit"); // $NON-NLS-1$ + autoCommit.setValue(String.valueOf(isAutocommit())); + config.addChild(autoCommit); + + if (log.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(40); + sb.append("KeepAlive: "); + sb.append(isKeepAlive()); + sb.append(" Age: "); + sb.append(getConnectionAge()); + sb.append(" CheckQuery: "); + sb.append(getCheckQuery()); + log.debug(sb.toString()); + } + DefaultConfiguration cfgKeepAlive = new DefaultConfiguration("keep-alive"); // $NON-NLS-1$ + cfgKeepAlive.setAttribute("disable", String.valueOf(!isKeepAlive())); // $NON-NLS-1$ + cfgKeepAlive.setAttribute("age", getConnectionAge()); // $NON-NLS-1$ + cfgKeepAlive.setValue(getCheckQuery()); + poolController.addChild(cfgKeepAlive); + + String _username = getUsername(); + if (log.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(40); + sb.append("Driver: "); + sb.append(getDriver()); + sb.append(" DbUrl: "); + sb.append(getDbUrl()); + sb.append(" User: "); + sb.append(_username); + log.debug(sb.toString()); + } + DefaultConfiguration cfgDriver = new DefaultConfiguration("driver"); // $NON-NLS-1$ + cfgDriver.setValue(getDriver()); + config.addChild(cfgDriver); + DefaultConfiguration cfgDbUrl = new DefaultConfiguration("dburl"); // $NON-NLS-1$ + cfgDbUrl.setValue(getDbUrl()); + config.addChild(cfgDbUrl); + + if (_username.length() > 0){ + DefaultConfiguration cfgUsername = new DefaultConfiguration("user"); // $NON-NLS-1$ + cfgUsername.setValue(_username); + config.addChild(cfgUsername); + DefaultConfiguration cfgPassword = new DefaultConfiguration("password"); // $NON-NLS-1$ + cfgPassword.setValue(getPassword()); + config.addChild(cfgPassword); + } + + // log is required to ensure errors are available + source.enableLogging(new LogKitLogger(log)); + try { + source.configure(config); + source.setInstrumentableName(getDataSource()); + } catch (ConfigurationException e) { + log.error("Could not configure datasource for pool: "+getDataSource(),e); + } + return source; + } + + // used to hold per-thread singleton connection pools + private static final ThreadLocal> perThreadPoolMap = + new ThreadLocal>(){ + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + /* + * Wrapper class to allow getConnection() to be implemented for both shared + * and per-thread pools. + * + */ + private class DataSourceComponentImpl implements DataSourceComponent{ + + private final ResourceLimitingJdbcDataSource sharedDSC; + + DataSourceComponentImpl(){ + sharedDSC=null; + } + + DataSourceComponentImpl(ResourceLimitingJdbcDataSource p_dsc){ + sharedDSC=p_dsc; + } + + @Override + public Connection getConnection() throws SQLException { + Connection conn = null; + ResourceLimitingJdbcDataSource dsc = null; + if (sharedDSC != null){ // i.e. shared pool + dsc = sharedDSC; + } else { + Map poolMap = perThreadPoolMap.get(); + dsc = poolMap.get(getDataSource()); + if (dsc == null){ + dsc = initPool("1"); + poolMap.put(getDataSource(),dsc); + log.debug("Storing pool: "+dsc.getInstrumentableName()+" @"+System.identityHashCode(dsc)); + perThreadPoolSet.add(dsc); + } + } + if (dsc != null) { + conn=dsc.getConnection(); + int transactionIsolation = DataSourceElementBeanInfo.getTransactionIsolationMode(getTransactionIsolation()); + if (transactionIsolation >= 0 && conn.getTransactionIsolation() != transactionIsolation) { + try { + // make sure setting the new isolation mode is done in an auto committed transaction + conn.setTransactionIsolation(transactionIsolation); + log.debug("Setting transaction isolation: " + transactionIsolation + " @" + + System.identityHashCode(dsc)); + } catch (SQLException ex) { + log.error("Could not set transaction isolation: " + transactionIsolation + " @" + + System.identityHashCode(dsc)); + } + } + } + return conn; + } + + @Override + public void configure(Configuration arg0) throws ConfigurationException { + } + + } + + @Override + public void addConfigElement(ConfigElement config) { + } + + @Override + public boolean expectsModification() { + return false; + } + + /** + * @return Returns the checkQuery. + */ + public String getCheckQuery() { + return checkQuery; + } + + /** + * @param checkQuery + * The checkQuery to set. + */ + public void setCheckQuery(String checkQuery) { + this.checkQuery = checkQuery; + } + + /** + * @return Returns the connectionAge. + */ + public String getConnectionAge() { + return connectionAge; + } + + /** + * @param connectionAge + * The connectionAge to set. + */ + public void setConnectionAge(String connectionAge) { + this.connectionAge = connectionAge; + } + + /** + * @return Returns the poolname. + */ + public String getDataSource() { + return dataSource; + } + + /** + * @param dataSource + * The poolname to set. + */ + public void setDataSource(String dataSource) { + this.dataSource = dataSource; + } + + /** + * @return Returns the dbUrl. + */ + public String getDbUrl() { + return dbUrl; + } + + /** + * @param dbUrl + * The dbUrl to set. + */ + public void setDbUrl(String dbUrl) { + this.dbUrl = dbUrl; + } + + /** + * @return Returns the driver. + */ + public String getDriver() { + return driver; + } + + /** + * @param driver + * The driver to set. + */ + public void setDriver(String driver) { + this.driver = driver; + } + + /** + * @return Returns the password. + */ + public String getPassword() { + return password; + } + + /** + * @param password + * The password to set. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * @return Returns the poolMax. + */ + public String getPoolMax() { + return poolMax; + } + + /** + * @param poolMax + * The poolMax to set. + */ + public void setPoolMax(String poolMax) { + this.poolMax = poolMax; + } + + /** + * @return Returns the timeout. + */ + public String getTimeout() { + return timeout; + } + + /** + * @param timeout + * The timeout to set. + */ + public void setTimeout(String timeout) { + this.timeout = timeout; + } + + /** + * @return Returns the trimInterval. + */ + public String getTrimInterval() { + return trimInterval; + } + + /** + * @param trimInterval + * The trimInterval to set. + */ + public void setTrimInterval(String trimInterval) { + this.trimInterval = trimInterval; + } + + /** + * @return Returns the username. + */ + public String getUsername() { + return username; + } + + /** + * @param username + * The username to set. + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * @return Returns the autocommit. + */ + public boolean isAutocommit() { + return autocommit; + } + + /** + * @param autocommit + * The autocommit to set. + */ + public void setAutocommit(boolean autocommit) { + this.autocommit = autocommit; + } + + /** + * @return Returns the keepAlive. + */ + public boolean isKeepAlive() { + return keepAlive; + } + + /** + * @param keepAlive + * The keepAlive to set. + */ + public void setKeepAlive(boolean keepAlive) { + this.keepAlive = keepAlive; + } + + /** + * @return the transaction isolation level + */ + public String getTransactionIsolation() { + return transactionIsolation; + } + + /** + * @param transactionIsolation The transaction isolation level to set. NULL to + * use the default of the driver. + */ + public void setTransactionIsolation(String transactionIsolation) { + this.transactionIsolation = transactionIsolation; + } +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementBeanInfo.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementBeanInfo.java new file mode 100644 index 00000000000..4b343b4095e --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementBeanInfo.java @@ -0,0 +1,133 @@ +/* + * 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. + * + */ + +/* + * Created on May 15, 2004 + */ +package org.apache.jmeter.protocol.jdbc.config; + +import java.beans.PropertyDescriptor; +import java.sql.Connection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TypeEditor; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class DataSourceElementBeanInfo extends BeanInfoSupport { + private static final Logger log = LoggingManager.getLoggerForClass(); + private static Map TRANSACTION_ISOLATION_MAP = new HashMap(5); + static { + // Will use default isolation + TRANSACTION_ISOLATION_MAP.put("DEFAULT", Integer.valueOf(-1)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_NONE", Integer.valueOf(Connection.TRANSACTION_NONE)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_READ_COMMITTED", Integer.valueOf(Connection.TRANSACTION_READ_COMMITTED)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_READ_UNCOMMITTED", Integer.valueOf(Connection.TRANSACTION_READ_UNCOMMITTED)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_REPEATABLE_READ", Integer.valueOf(Connection.TRANSACTION_REPEATABLE_READ)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_SERIALIZABLE", Integer.valueOf(Connection.TRANSACTION_SERIALIZABLE)); + } + + public DataSourceElementBeanInfo() { + super(DataSourceElement.class); + + createPropertyGroup("varName", new String[] { "dataSource" }); + + createPropertyGroup("pool", new String[] { "poolMax", "timeout", + "trimInterval", "autocommit", "transactionIsolation" }); + + createPropertyGroup("keep-alive", new String[] { "keepAlive", "connectionAge", "checkQuery" }); + + createPropertyGroup("database", new String[] { "dbUrl", "driver", "username", "password" }); + + PropertyDescriptor p = property("dataSource"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("poolMax"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "10"); + p = property("timeout"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "10000"); + p = property("trimInterval"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "60000"); + p = property("autocommit"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + p = property("transactionIsolation"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "DEFAULT"); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + Set modesSet = TRANSACTION_ISOLATION_MAP.keySet(); + String[] modes = modesSet.toArray(new String[modesSet.size()]); + p.setValue(TAGS, modes); + p = property("keepAlive"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + p = property("connectionAge"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "5000"); + p = property("checkQuery"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "Select 1"); + p = property("dbUrl"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("driver"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("username"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("password", TypeEditor.PasswordEditor); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + } + + /** + * Converts a string description of a valid transaction isolation mode to the respective integer value. + * Currently supported tags and their values are: + *
+ *
DEFAULT
-1
+ *
TRANSACTION_NONE
{@value java.sql.Connection#TRANSACTION_NONE}
+ *
TRANSACTION_REAd_COMMITTED
{@value java.sql.Connection#TRANSACTION_READ_COMMITTED}
+ *
TRANSACTION_READ_UNCOMMITTED
{@value java.sql.Connection#TRANSACTION_READ_UNCOMMITTED}
+ *
TRANSACTION_REPEATABLE_READ
{@value java.sql.Connection#TRANSACTION_REPEATABLE_READ}
+ *
TRANSACTION_SERIALIZABLE
{@value java.sql.Connection#TRANSACTION_SERIALIZABLE}
+ *
+ * @param tag name of the transaction isolation mode + * @return integer value of the given transaction isolation mode + */ + public static int getTransactionIsolationMode(String tag) { + if (!StringUtils.isEmpty(tag)) { + Integer isolationMode = TRANSACTION_ISOLATION_MAP.get(tag); + if (isolationMode == null) { + try { + return Integer.parseInt(tag); + } catch (NumberFormatException e) { + log.warn("Illegal transaction isolation configuration '" + tag + "'"); + } + } + } + return -1; + } +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources.properties new file mode 100644 index 00000000000..d16c32be54c --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources.properties @@ -0,0 +1,46 @@ +# 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. + +displayName=JDBC Connection Configuration +pool.displayName=Connection Pool Configuration +varName.displayName=Variable Name Bound to Pool +keep-alive.displayName=Connection Validation by Pool +database.displayName=Database Connection Configuration +autocommit.displayName=Auto Commit +autocommit.shortDescription=Whether queries should be auto committed. +poolMax.displayName=Max Number of Connections +poolMax.shortDescription=Maximum number of connections the pool will open at one time +connectionAge.displayName=Max Connection age (ms) +connectionAge.shortDescription=Maximum number of milliseconds an idle connection is kept before discarding +driver.displayName=JDBC Driver class +driver.shortDescription=Full package and class name of the JDBC driver to be used (Must be in JMeter's classpath) +dbUrl.displayName=Database URL +dbUrl.shortDescription=Full URL for the database, including jdbc protocol parts +username.displayName=Username +username.shortDescription=Username to use in connecting to database +password.displayName=Password +password.shortDescription=Password used to connect to database +checkQuery.displayName=Validation Query +checkQuery.shortDescription=A query used to validate a connection still works. Only relevant if Keep Alive is true. +dataSource.displayName=Variable Name +dataSource.shortDescription=Name of the JMeter variable that the pool will be bound to. +timeout.displayName=Pool Timeout +timeout.shortDescription=The pool blocks requests for connection until a connection is available. This is the maximum blocking time before an exception is returned. +trimInterval.displayName=Idle Cleanup Interval (ms) +trimInterval.shortDescription=The pool removes extra idle connections at regular intervals +keepAlive.displayName=Keep-Alive +keepAlive.shortDescription=Whether the pool should validate connections. If no, Connection Age and Validation Query are ignored. +transactionIsolation.displayName=Transaction Isolation +transactionIsolation.shortDescription=Transaction Isolation Level \ No newline at end of file diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_es.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_es.properties new file mode 100644 index 00000000000..e31209cbb51 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_es.properties @@ -0,0 +1,45 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=Auto Commit +autocommit.shortDescription=Queries con auto-commit +checkQuery.displayName=Query de Validaci\u00F3n +checkQuery.shortDescription=Una query utilizada para validar que una conexi\u00F3n funciona. S\u00F3lo es relevante si Keep Alive est\u00E1 a true. +connectionAge.displayName=Edad m\u00E1xima de las Conexiones (ms) +connectionAge.shortDescription=N\u00FAmero m\u00E1ximo de milisegundos que una conexi\u00F3n se mantiene inactiva antes de descartarla. +dataSource.displayName=Nombre de Variable +dataSource.shortDescription=Nombre de la variable JMeter a la que se enlazar\u00E1 el pool. +database.displayName=Configuraci\u00F3n de la Conexi\u00F3n a Base de Datos +dbUrl.displayName=URL de la Base de Datos +dbUrl.shortDescription=URL completa de la Base de Datos, incluyendo las partes del protocolo jdbc +displayName=Configuraci\u00F3n de la Conexi\u00F3n JDBC +driver.displayName=Clase del Driver JDBC +driver.shortDescription=Nombre completo (con paquete) de la clase del driver JDBC a utilizar (Debe estar en el classpath de JMeter) +keep-alive.displayName=Validaci\u00F3n de Conexi\u00F3n por Pool +keepAlive.displayName=Keep-Alive +keepAlive.shortDescription=Validaci\u00F3n de conexiones. Si se indica que no, la Edad de Conexi\u00F3n y la Valicadi\u00F3n de Query son ignorados. +password.displayName=Password +password.shortDescription=Password utilizados para conectar a la base de datos +pool.displayName=Configuraci\u00F3n del Pool de Conexiones +poolMax.displayName=N\u00FAmero M\u00E1ximo de Conexiones +poolMax.shortDescription=N\u00FAmero m\u00E1ximo de conexiones del pool que se abrir\u00E1n a la vez +timeout.displayName=Timeout del Pool +timeout.shortDescription=El pool bloquea peticiones de conexi\u00F3n hasta que una conexi\u00F3n est\u00E1 disponible. Este es el tiempo m\u00E1ximo de bloqueo antes de que una excepci\u00F3n es devuelta. +trimInterval.displayName=Intervalo de Limpieza por Inactividad (ms) +trimInterval.shortDescription=El pool elimina conexiones inactivas a intervalos regulares +username.displayName=Nombre de Usuario +username.shortDescription=Nombre de Usuario a utilizar al conectar a la base de datos +varName.displayName=Nombre Variable Enlazado al Pool diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_fr.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_fr.properties new file mode 100644 index 00000000000..e5a92eb72dc --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_fr.properties @@ -0,0 +1,47 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=Validation automatique (auto commit) +autocommit.shortDescription=D\u00E9fini si les requ\u00EAtes doivent \u00EAtre valid\u00E9es automatiquement. +checkQuery.displayName=Requ\u00EAte de validation +checkQuery.shortDescription=Une requ\u00EAte \u00E0 utiliser pour valider que la connexion fonctionne. Utilis\u00E9 seulement si le param\u00E9tre connexion persistante (keep-alive) est activ\u00E9 (true). +connectionAge.displayName=Dur\u00E9e de vie maximum d'une connexion (ms) +connectionAge.shortDescription=Nombre maximum en millisecondes pendant lequel une connexion disponible est gard\u00E9e avant d'\u00EAtre ferm\u00E9e. +dataSource.displayName=Nom de liaison +dataSource.shortDescription=Nom de la variable JMeter sur laquelle le pool sera li\u00E9. +database.displayName=Configuration de connexion \u00E0 la base de donn\u00E9es +dbUrl.displayName=URL de la base de donn\u00E9es +dbUrl.shortDescription=URL compl\u00E8te pour acc\u00E9der \u00E0 la base de donn\u00E9es, incluant la partie jdbc du protocole +displayName=Configuration de connexion JDBC +driver.displayName=Classe du pilote JDBC +driver.shortDescription=Nom (paquet+classe) complet du pilote JDBC \u00E0 utiliser. (Doit \u00EAtre dans le classpath de JMeter) +keep-alive.displayName=Validation des connexions par le pool +keepAlive.displayName=Connexions persistantes (keep-alive) +keepAlive.shortDescription=Est-ce que le pool doit valider les connexions. Sinon, la dur\u00E9e de vie des connexions et des validations de requ\u00EAtes sont ignor\u00E9es. +password.displayName=Mot de passe +password.shortDescription=Mot de passe \u00E0 utiliser pour se connecter \u00E0 la base de donn\u00E9es +pool.displayName=Configuration du pool de connexions +poolMax.displayName=Nombre maximum de connexions +poolMax.shortDescription=Nombre maximum de connexions que le pool peut ouvrir en m\u00EAme temps +timeout.displayName=Expiration du pool (ms) +timeout.shortDescription=D\u00E9lai d'attente maximum pour obtenir une connexion du pool si ce dernier n'a plus de connexion disponible. A l'expiration, une exception est retourn\u00E9e. +trimInterval.displayName=Intervalle de nettoyage des connexions disponibles (ms) +trimInterval.shortDescription=Le pool supprime les connexions disponibles suppl\u00E9mentaires \u00E0 intervalle r\u00E9gulier +username.displayName=Identifiant +username.shortDescription=L'identifiant \u00E0 utiliser pour la connexion \u00E0 la base de donn\u00E9es +varName.displayName=Nom de liaison du pool +transactionIsolation.displayName=Isolation de la Transaction +transactionIsolation.shortDescription=Niveau d'isolation de la transaction \ No newline at end of file diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_pt_BR.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_pt_BR.properties new file mode 100644 index 00000000000..778b3693146 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_pt_BR.properties @@ -0,0 +1,45 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=Commit autom\u00E1tico +autocommit.shortDescription=Se a consulta deve realizar commit autom\u00E1tico +checkQuery.displayName=Consulta de Valida\u00E7\u00E3o +checkQuery.shortDescription=Consulta utilizada para verificar se a conex\u00E3o ainda est\u00E1 ativa. Relevante apenas se Manter Ativa est\u00E1 configurado. +connectionAge.displayName=Tempo m\u00E1ximo de conex\u00E3o (ms) +connectionAge.shortDescription=Tempo m\u00E1ximo (em milissegundos) que uma conex\u00E3o n\u00E3o utilizada \u00E9 mantida antes de ser descartada. +dataSource.displayName=Nome da Vari\u00E1vel +dataSource.shortDescription=Nome da vari\u00E1vel do JMeter que referencia o grupo de conex\u00F5es. +database.displayName=Configura\u00E7\u00E3o da Conex\u00E3o com o Banco de Dados +dbUrl.displayName=URL do banco de dados +dbUrl.shortDescription=URL completa do banco de dados, incluindo partes do protocolo jdbc. +displayName=Configura\u00E7\u00E3o da Conex\u00E3o JDBC +driver.displayName=Classe do Driver JDBC +driver.shortDescription=Pacote completo e nome da classe do driver JDBC a ser utilizado (Necessita estar no classpath) +keep-alive.displayName=Valida\u00E7\u00E3o da Conex\u00E3o pelo Grupo de Conex\u00F5es +keepAlive.displayName=Manter Ativa +keepAlive.shortDescription=Se o grupo de conex\u00E3o deve validar conex\u00F5es. Se n\u00E3o, Tempo da Conex\u00E3o e Consulta de Valida\u00E7\u00E3o s\u00E3o ignorados. +password.displayName=Senha +password.shortDescription=Senha usada para conex\u00E3o com o banco de dados +pool.displayName=Configura\u00E7\u00E3o do Grupo de Conex\u00F5es +poolMax.displayName=Limite de Conex\u00F5es +poolMax.shortDescription=N\u00FAmero m\u00E1ximo de conex\u00F5es que o grupo de conex\u00F5es ir\u00E1 criar ao mesmo tempo. +timeout.displayName=Timeout do grupo de conex\u00F5es +timeout.shortDescription=O grupo de conex\u00F5es bloqueia requisi\u00E7\u00F5es de conex\u00F5es at\u00E9 que uma conex\u00E3o esteja dispon\u00EDvel. Este \u00E9 o tempo de bloqueio m\u00E1ximo antes que uma exce\u00E7\u00E3o seja retornada. +trimInterval.displayName=Intervalo de limpeza de conex\u00F5es ociosas (ms) +trimInterval.shortDescription=O grupo de conex\u00F5es remove conex\u00F5es ociosas em intervalos regulares. +username.displayName=Nome do usu\u00E1rio +username.shortDescription=Nome do usu\u00E1rio usado na conex\u00E3o com o banco de dados. +varName.displayName=Nome da Vari\u00E1vel que Referencia o Grupo de Conex\u00F5es diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_tr.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_tr.properties new file mode 100644 index 00000000000..e8f44c6728b --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_tr.properties @@ -0,0 +1,45 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=An\u0131nda \u0130\u015Flem +autocommit.shortDescription=Sorgular\u0131n an\u0131nda olarak i\u015Flenip i\u015Flenmeyece\u011Fi. +checkQuery.displayName=Do\u011Frulama Sorgusu +checkQuery.shortDescription=Ba\u011Flant\u0131n\u0131n hala \u00E7al\u0131\u015F\u0131p \u00E7al\u0131\u015Fmad\u0131\u011F\u0131n\u0131 kontrol eden sorgu. Sadece "Canl\u0131Tut" se\u00E7ili ise ge\u00E7erlidir. +connectionAge.displayName=Maksimum Ba\u011Flant\u0131 Ya\u015F\u0131 (ms) +connectionAge.shortDescription=Bo\u015Ftaki ba\u011Flant\u0131n\u0131n kopar\u0131lmadan korunaca\u011F\u0131 maksimum milisaniye say\u0131s\u0131 +dataSource.displayName=De\u011Fi\u015Fken \u0130smi +dataSource.shortDescription=Havuzun ba\u011Flanaca\u011F\u0131 JMeter de\u011Fi\u015Fkeninin ismi. +database.displayName=Veritaban\u0131 Ba\u011Flant\u0131s\u0131 Ayar\u0131 +dbUrl.displayName=Veritaban\u0131 Adresi (URL) +dbUrl.shortDescription=Veritaban\u0131 i\u00E7in tam adres (URL), jdbc protokolu k\u0131s\u0131mlar\u0131 dahil +displayName=JDBC Ba\u011Flant\u0131 Ayarlar\u0131 +driver.displayName=JDBC S\u00FCr\u00FCc\u00FC s\u0131n\u0131f\u0131 +driver.shortDescription=Kullan\u0131lacak JDBC s\u00FCr\u00FCc\u00FCs\u00FCn\u00FCn tam paket ve s\u0131n\u0131f ismi\n(JMeter s\u0131n\u0131f yolunda(classpath) yer almal\u0131) +keep-alive.displayName=Havuzla ba\u011Flant\u0131 Do\u011Frulamas\u0131 +keepAlive.displayName=Canl\u0131-Tut +keepAlive.shortDescription=Havuzun ba\u011Flant\u0131 do\u011Frulamas\u0131 yap\u0131p yapmayaca\u011F\u0131. "no" ise, Ba\u011Flant\u0131 Ya\u015F\u0131 ve Ba\u011Flant\u0131 Do\u011Frulamas\u0131 yoksay\u0131lacak. +password.displayName=\u015Eifre +password.shortDescription=Veritaban\u0131na ba\u011Flan\u0131l\u0131rken kullan\u0131lacak \u015Fifre +pool.displayName=Ba\u011Flant\u0131 Havuzu Ayar\u0131 +poolMax.displayName=Maksimum Ba\u011Flant\u0131 Say\u0131s\u0131 +poolMax.shortDescription=Havuzun ayn\u0131 zamanda a\u00E7aca\u011F\u0131 maksimum ba\u011Flant\u0131 say\u0131s\u0131 +timeout.displayName=Havuz Zaman A\u015F\u0131m\u0131 +timeout.shortDescription=Havuz bir ba\u011Flant\u0131 uygun hale gelinceye kadar t\u00FCm istekleri engeller. Bu hata d\u00F6nmeden \u00F6nceki maksimum engelleme zaman\u0131d\u0131r. +trimInterval.displayName=Bo\u015Ftaki Toparlama Ara\u015F\u0131\u011F\u0131 (ms) +trimInterval.shortDescription=Havuz d\u00FCzenli aral\u0131klarla bo\u015Ftaki fazladan ba\u011Flant\u0131lar\u0131 kald\u0131r\u0131r. +username.displayName=Kullan\u0131c\u0131 ismi +username.shortDescription=Veritaban\u0131na ba\u011Flan\u0131l\u0131rken kullan\u0131lacak kullan\u0131c\u0131 ismi +varName.displayName=Havuzla \u0130li\u015Fkilendirilecek De\u011Fi\u015Fkenin \u0130smi diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_zh_TW.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_zh_TW.properties new file mode 100644 index 00000000000..5edd8a603ca --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_zh_TW.properties @@ -0,0 +1,45 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=\u81EA\u52D5 Commit +autocommit.shortDescription=\u662F\u5426\u8981\u81EA\u52D5Commit SQL +checkQuery.displayName=\u9A57\u8B49\u67E5\u8A62 +checkQuery.shortDescription=\u7576KeepAlive\u70BA\u771F\u6642,\u7528\u4F86\u6AA2\u67E5\u9023\u7DDA\u662F\u5426\u4ECD\u6B63\u5E38 +connectionAge.displayName=\u6700\u9577\u9023\u7DDA\u6642\u9593(\u5FAE\u79D2) +connectionAge.shortDescription=\u8A2D\u5B9A\u9023\u7DDA\u6700\u9577\u53EF\u9592\u7F6E\u591A\u5C11\u5FAE\u79D2 +dataSource.displayName=\u8B8A\u6578\u540D\u7A31 +dataSource.shortDescription=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8B8A\u6578\u540D\u7A31 +database.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u8A2D\u5B9A +dbUrl.displayName=\u8CC7\u6599\u5EAB URL +dbUrl.shortDescription=\u8CC7\u6599\u5EAB\u5B8C\u6574 URL, \u542B JDBC \u654D\u8FF0\u90E8\u4EFD +displayName=JDBC \u9023\u7DDA\u8A2D\u5B9A +driver.displayName=JDBC \u9A45\u52D5\u7A0B\u5F0F +driver.shortDescription=JDBC \u5B8C\u6574 class \u540D\u7A31(\u9808\u5728 JMeter \u7684 CLASSPATH \u4E2D) +keep-alive.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u9032\u884C\u9023\u7DDA\u9A57\u8B49 +keepAlive.displayName=\u4FDD\u6301\u9023\u7DDA +keepAlive.shortDescription=\u9023\u7DDA\u6C60\u662F\u5426\u8981\u9A57\u8B49\u9023\u7DDA, \u5982\u679C\u4E0D\u8981, \u6700\u9577\u9023\u7DDA\u6642\u9593(\u5FAE\u79D2)\u548C\u9023\u7DDA\u9A57\u8B49\u5169\u500B\u8A2D\u5B9A\u90FD\u6703\u88AB\u5FFD\u7565 +password.displayName=\u5BC6\u78BC +password.shortDescription=\u8CC7\u6599\u5EAB\u9023\u7DDA\u5BC6\u78BC +pool.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8A2D\u5B9A +poolMax.displayName=\u6700\u5927\u9023\u7DDA\u6578 +poolMax.shortDescription=\u9023\u7DDA\u6C60\u4E00\u6B21\u6700\u591A\u958B\u5E7E\u500B\u9023\u7DDA +timeout.displayName=\u9023\u7DDA\u6C60 TimeOut +timeout.shortDescription=\u7B49\u5F85\u53D6\u5F97\u9023\u7DDA\u7684\u6700\u9577\u6642\u9593, \u8D85\u904E\u5C31 exception +trimInterval.displayName=\u9592\u7F6E\u6E05\u9664\u6642\u9694(\u5FAE\u79D2) +trimInterval.shortDescription=\u8D85\u904E\u9592\u7F6E\u6E05\u9664\u6642\u9694(\u5FAE\u79D2),\u9023\u7DDA\u6C60\u81EA\u52D5\u79FB\u9664\u8A72\u9023\u7DDA +username.displayName=\u4F7F\u7528\u8005 +username.shortDescription=\u9023\u7DDA\u4F7F\u7528\u8005 +varName.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8B8A\u6578\u540D\u7A31 diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/AbstractJDBCProcessor.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/AbstractJDBCProcessor.java new file mode 100644 index 00000000000..d0470f2700d --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/AbstractJDBCProcessor.java @@ -0,0 +1,62 @@ +/* + * 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.protocol.jdbc.processor; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; + +import org.apache.jmeter.protocol.jdbc.AbstractJDBCTestElement; +import org.apache.jmeter.protocol.jdbc.config.DataSourceElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * As pre- and post-processors essentially do the same this class provides the implementation. + */ +public abstract class AbstractJDBCProcessor extends AbstractJDBCTestElement { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + /** + * Calls the JDBC code to be executed. + */ + protected void process() { + Connection conn = null; + if(JOrphanUtils.isBlank(getDataSource())) { + throw new IllegalArgumentException("Variable Name must not be null in "+getName()); + } + try { + conn = DataSourceElement.getConnection(getDataSource()); + execute(conn); + } catch (SQLException ex) { + log.warn("SQL Problem in "+ getName() + ": " + ex.toString()); + } catch (IOException ex) { + log.warn("IO Problem in "+ getName() + ": " + ex.toString()); + } catch (UnsupportedOperationException ex) { + log.warn("Execution Problem in "+ getName() + ": " + ex.toString()); + } finally { + close(conn); + } + } + +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessor.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessor.java new file mode 100644 index 00000000000..41024a8c2ae --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessor.java @@ -0,0 +1,36 @@ +/* + * 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.protocol.jdbc.processor; + +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.testbeans.TestBean; + +/** + * Post processor handling JDBC Requests + */ +public class JDBCPostProcessor extends AbstractJDBCProcessor implements TestBean, PostProcessor { + + private static final long serialVersionUID = 1L; + + @Override + public void process() { + super.process(); + } + +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorBeanInfo.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorBeanInfo.java new file mode 100644 index 00000000000..721dd3c9d7e --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorBeanInfo.java @@ -0,0 +1,36 @@ +/* + * 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. + * + */ + +/* + * Created on May 16, 2004 + * + */ +package org.apache.jmeter.protocol.jdbc.processor; + +import org.apache.jmeter.protocol.jdbc.JDBCTestElementBeanInfoSupport; + + +public class JDBCPostProcessorBeanInfo extends JDBCTestElementBeanInfoSupport { + + /** + * + */ + public JDBCPostProcessorBeanInfo() { + super(JDBCPostProcessor.class); + } +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources.properties new file mode 100644 index 00000000000..8807c57a9f5 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources.properties @@ -0,0 +1,36 @@ +# 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. + +displayName=JDBC PostProcessor +varName.displayName=Variable Name Bound to Pool +sql.displayName=SQL Query +query.displayName=Query +query.shortDescription=SQL Query to send to database +queryType.displayName=Query Type +queryType.shortDescription=Determines if the SQL statement should be run as a select statement or an update statement. +dataSource.displayName=Variable Name +dataSource.shortDescription=Name of the JMeter variable that the connection pool is bound to. +queryArguments.displayName=Parameter values +queryArguments.shortDescription=SQL parameter values (comma separated) +queryArgumentsTypes.displayName=Parameter types +queryArgumentsTypes.shortDescription=JDBC Type names from java.sql.Types. VARCHAR, INTEGER, etc. (comma separated) +variableNames.displayName=Variable names +variableNames.shortDescription=Output variable names for each column (comma separated) +resultSetHandler.displayName=Handle ResultSet +resultSetHandler.shortDescription=How should return values of type ResultSet be handled +resultVariable.displayName=Result variable name +resultVariable.shortDescription=Name of the JMeter variable that stores the result set objects in a list of maps for looking up results by column name. +queryTimeout.displayName=Query timeout +queryTimeout.shortDescription=The timeout of statement measured in seconds diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources_fr.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources_fr.properties new file mode 100644 index 00000000000..6b046e0c767 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources_fr.properties @@ -0,0 +1,37 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=Nom de liaison +dataSource.shortDescription=Nom de la variable JMeter qui sera li\u00E9e au pool de connexion +displayName=Post-Processeur JDBC +query.displayName=Requ\u00EAte +query.shortDescription=Requ\u00EAte SQL \u00E0 envoyer \u00E0 la base de donn\u00E9es +queryArguments.displayName=Valeurs des param\u00E8tres +queryArguments.shortDescription=Valeurs des param\u00E8tres SQL +queryArgumentsTypes.displayName=Types des param\u00E8tres +queryArgumentsTypes.shortDescription=Noms des types JDBC depuis java.sql.Types. Ex. VARCHAR, INTEGER, etc. (s\u00E9par\u00E9s par des virgules) +queryType.displayName=Type de requ\u00EAte +queryType.shortDescription=D\u00E9termine si l'instruction SQL doit \u00EAtre ex\u00E9cut\u00E9e comme une commande SELECT ou une commande UPDATE. +resultSetHandler.displayName=Gestion ResultSet +resultSetHandler.shortDescription=Comment les valeurs de type ResultSet sont renvoy\u00E9es +resultVariable.displayName=Nom de la variable des R\u00E9sultats +resultVariable.shortDescription=Nom de la variable JMeter qui stocke les r\u00E9sultats sous forme d'objets dans une liste de type 'maps' permettant la recherche des r\u00E9sultats par nom de colonne. +sql.displayName=Requ\u00EAte SQL +varName.displayName=Nom de liaison avec le pool +variableNames.displayName=Noms des variables +variableNames.shortDescription=Noms des variables en sortie pour chaque colonne (s\u00E9par\u00E9s par des virgules) +queryTimeout.displayName=D\u00E9lai d'expiration de la requ\u00EAte +queryTimeout.shortDescription=D\u00E9lai d'expiration de le requ\u00EAte en secondes \ No newline at end of file diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessor.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessor.java new file mode 100644 index 00000000000..60c0131dd61 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessor.java @@ -0,0 +1,36 @@ +/* + * 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.protocol.jdbc.processor; + +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.testbeans.TestBean; + +/** + * Preprocessor handling JDBC Requests + */ +public class JDBCPreProcessor extends AbstractJDBCProcessor implements TestBean, PreProcessor { + + private static final long serialVersionUID = 1L; + + @Override + public void process() { + super.process(); + } + +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorBeanInfo.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorBeanInfo.java new file mode 100644 index 00000000000..2a5ff8fc5fb --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorBeanInfo.java @@ -0,0 +1,36 @@ +/* + * 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. + * + */ + +/* + * Created on May 16, 2004 + * + */ +package org.apache.jmeter.protocol.jdbc.processor; + +import org.apache.jmeter.protocol.jdbc.JDBCTestElementBeanInfoSupport; + + +public class JDBCPreProcessorBeanInfo extends JDBCTestElementBeanInfoSupport { + + /** + * + */ + public JDBCPreProcessorBeanInfo() { + super(JDBCPreProcessor.class); + } +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources.properties new file mode 100644 index 00000000000..b9833e9315f --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources.properties @@ -0,0 +1,36 @@ +# 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. + +displayName=JDBC PreProcessor +varName.displayName=Variable Name Bound to Pool +sql.displayName=SQL Query +query.displayName=Query +query.shortDescription=SQL Query to send to database +queryType.displayName=Query Type +queryType.shortDescription=Determines if the SQL statement should be run as a select statement or an update statement. +dataSource.displayName=Variable Name +dataSource.shortDescription=Name of the JMeter variable that the connection pool is bound to. +queryArguments.displayName=Parameter values +queryArguments.shortDescription=SQL parameter values (comma separated) +queryArgumentsTypes.displayName=Parameter types +queryArgumentsTypes.shortDescription=JDBC Type names from java.sql.Types. VARCHAR, INTEGER, etc. (comma separated) +variableNames.displayName=Variable names +variableNames.shortDescription=Output variable names for each column (comma separated) +resultSetHandler.displayName=Handle ResultSet +resultSetHandler.shortDescription=How should return values of type ResultSet be handled +resultVariable.displayName=Result variable name +resultVariable.shortDescription=Name of the JMeter variable that stores the result set objects in a list of maps for looking up results by column name. +queryTimeout.displayName=Query timeout +queryTimeout.shortDescription=The timeout of statement measured in seconds \ No newline at end of file diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources_fr.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources_fr.properties new file mode 100644 index 00000000000..693169b43ea --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources_fr.properties @@ -0,0 +1,37 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=Nom de liaison +dataSource.shortDescription=Nom de la variable JMeter qui sera li\u00E9e au pool de connexion +displayName=Pr\u00E9-Processeur JDBC +query.displayName=Requ\u00EAte +query.shortDescription=Requ\u00EAte SQL \u00E0 envoyer \u00E0 la base de donn\u00E9es +queryArguments.displayName=Valeurs des param\u00E8tres +queryArguments.shortDescription=Valeurs des param\u00E8tres SQL +queryArgumentsTypes.displayName=Types des param\u00E8tres +queryArgumentsTypes.shortDescription=Noms des types JDBC depuis java.sql.Types. Ex. VARCHAR, INTEGER, etc. (s\u00E9par\u00E9s par des virgules) +queryType.displayName=Type de requ\u00EAte +queryType.shortDescription=D\u00E9termine si l'instruction SQL doit \u00EAtre ex\u00E9cut\u00E9e comme une commande SELECT ou une commande UPDATE. +resultSetHandler.displayName=Gestion ResultSet +resultSetHandler.shortDescription=Comment les valeurs de type ResultSet sont renvoy\u00E9es +resultVariable.displayName=Nom de la variable des R\u00E9sultats +resultVariable.shortDescription=Nom de la variable JMeter qui stocke les r\u00E9sultats sous forme d'objets dans une liste de type 'maps' permettant la recherche des r\u00E9sultats par nom de colonne. +sql.displayName=Requ\u00EAte SQL +varName.displayName=Nom de liaison avec le pool +variableNames.displayName=Noms des variables +variableNames.shortDescription=Noms des variables en sortie pour chaque colonne (s\u00E9par\u00E9s par des virgules) +queryTimeout.displayName=D\u00E9lai d'expiration de la requ\u00EAte +queryTimeout.shortDescription=D\u00E9lai d'expiration de le requ\u00EAte en secondes \ No newline at end of file diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSampler.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSampler.java new file mode 100644 index 00000000000..55217df3f08 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSampler.java @@ -0,0 +1,120 @@ +/* + * 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.protocol.jdbc.sampler; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.protocol.jdbc.AbstractJDBCTestElement; +import org.apache.jmeter.protocol.jdbc.config.DataSourceElement; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * A sampler which understands JDBC database requests. + * + */ +public class JDBCSampler extends AbstractJDBCTestElement implements Sampler, TestBean, ConfigMergabilityIndicator { + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final long serialVersionUID = 234L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Creates a JDBCSampler. + */ + public JDBCSampler() { + } + + @Override + public SampleResult sample(Entry e) { + log.debug("sampling jdbc"); + + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.setSamplerData(toString()); + res.setDataType(SampleResult.TEXT); + res.setContentType("text/plain"); // $NON-NLS-1$ + res.setDataEncoding(ENCODING); + + // Assume we will be successful + res.setSuccessful(true); + res.setResponseMessageOK(); + res.setResponseCodeOK(); + + + res.sampleStart(); + Connection conn = null; + + try { + if(JOrphanUtils.isBlank(getDataSource())) { + throw new IllegalArgumentException("Variable Name must not be null in "+getName()); + } + + try { + conn = DataSourceElement.getConnection(getDataSource()); + } finally { + // FIXME: there is separate connect time field now + res.latencyEnd(); // use latency to measure connection time + } + res.setResponseHeaders(conn.toString()); + res.setResponseData(execute(conn)); + } catch (SQLException ex) { + final String errCode = Integer.toString(ex.getErrorCode()); + res.setResponseMessage(ex.toString()); + res.setResponseCode(ex.getSQLState()+ " " +errCode); + res.setResponseData(ex.getMessage().getBytes()); + res.setSuccessful(false); + } catch (Exception ex) { + res.setResponseMessage(ex.toString()); + res.setResponseCode("000"); + res.setResponseData(ex.getMessage().getBytes()); + res.setSuccessful(false); + } finally { + close(conn); + } + + // TODO: process warnings? Set Code and Message to success? + res.sampleEnd(); + return res; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerBeanInfo.java b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerBeanInfo.java new file mode 100644 index 00000000000..783b3df7c7b --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerBeanInfo.java @@ -0,0 +1,36 @@ +/* + * 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. + * + */ + +/* + * Created on May 16, 2004 + * + */ +package org.apache.jmeter.protocol.jdbc.sampler; + +import org.apache.jmeter.protocol.jdbc.JDBCTestElementBeanInfoSupport; + + +public class JDBCSamplerBeanInfo extends JDBCTestElementBeanInfoSupport { + + /** + * + */ + public JDBCSamplerBeanInfo() { + super(JDBCSampler.class); + } +} diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources.properties new file mode 100644 index 00000000000..181cc730307 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources.properties @@ -0,0 +1,36 @@ +# 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. + +displayName=JDBC Request +varName.displayName=Variable Name Bound to Pool +sql.displayName=SQL Query +query.displayName=Query +query.shortDescription=SQL Query to send to database +queryType.displayName=Query Type +queryType.shortDescription=Determines if the SQL statement should be run as a select statement or an update statement. +dataSource.displayName=Variable Name +dataSource.shortDescription=Name of the JMeter variable that the connection pool is bound to. +queryArguments.displayName=Parameter values +queryArguments.shortDescription=SQL parameter values (comma separated) +queryArgumentsTypes.displayName=Parameter types +queryArgumentsTypes.shortDescription=JDBC Type names from java.sql.Types. VARCHAR, INTEGER, etc. (comma separated) +variableNames.displayName=Variable names +variableNames.shortDescription=Output variable names for each column (comma separated) +resultSetHandler.displayName=Handle ResultSet +resultSetHandler.shortDescription=How should return values of type ResultSet be handled +resultVariable.displayName=Result variable name +resultVariable.shortDescription=Name of the JMeter variable that stores the result set objects in a list of maps for looking up results by column name. +queryTimeout.displayName=Query timeout (s) +queryTimeout.shortDescription=The timeout of statement measured in seconds \ No newline at end of file diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_es.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_es.properties new file mode 100644 index 00000000000..4bcb59852f6 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_es.properties @@ -0,0 +1,30 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=Nombre de Variable +dataSource.shortDescription=Nombre de la variable JMeter a la cual est\u00E1 ligado el pool de conexiones +displayName=Petici\u00F3n JDBC +query.displayName=Query +query.shortDescription=Query SQL a enviar a la base de datos +queryType.displayName=Solo Query +queryType.shortDescription=is true, se lanzar\u00E1 como una query y no como un update/inser. Si no, se lanza como update. +sql.displayName=Query SQL +varName.displayName=Nombre de Variable Ligada al Pool +queryArguments.displayName=Argumentos +queryArguments.shortDescription=los valores de los argumentos separados por comas +queryArgumentsTypes.displayName=Tipos de los argumentos +queryArgumentsTypes.shortDescription=los valores de los argumentos separados por comas + diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_fr.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_fr.properties new file mode 100644 index 00000000000..af3d7f00979 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_fr.properties @@ -0,0 +1,37 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=Nom de liaison +dataSource.shortDescription=Nom de la variable JMeter qui sera li\u00E9e au pool de connexion +displayName=Requ\u00EAte JDBC +query.displayName=Requ\u00EAte +query.shortDescription=Requ\u00EAte SQL \u00E0 envoyer \u00E0 la base de donn\u00E9es +queryArguments.displayName=Valeurs des param\u00E8tres +queryArguments.shortDescription=Valeurs des param\u00E8tres SQL +queryArgumentsTypes.displayName=Types des param\u00E8tres +queryArgumentsTypes.shortDescription=Noms des types JDBC depuis java.sql.Types. Ex. VARCHAR, INTEGER, etc. (s\u00E9par\u00E9s par des virgules) +queryType.displayName=Type de requ\u00EAte +queryType.shortDescription=D\u00E9termine si l'instruction SQL doit \u00EAtre ex\u00E9cut\u00E9e comme une commande SELECT ou une commande UPDATE. +resultSetHandler.displayName=Gestion ResultSet +resultSetHandler.shortDescription=Comment les valeurs de type ResultSet sont renvoy\u00E9es +resultVariable.displayName=Nom de la variable des R\u00E9sultats +resultVariable.shortDescription=Nom de la variable JMeter qui stocke les r\u00E9sultats sous forme d'objets dans une liste de type 'maps' permettant la recherche des r\u00E9sultats par nom de colonne. +sql.displayName=Requ\u00EAte SQL +varName.displayName=Nom de liaison avec le pool +variableNames.displayName=Noms des variables +variableNames.shortDescription=Noms des variables en sortie pour chaque colonne (s\u00E9par\u00E9s par des virgules) +queryTimeout.displayName=D\u00E9lai d'expiration de la requ\u00EAte (s) +queryTimeout.shortDescription=D\u00E9lai d'expiration de le requ\u00EAte en secondes \ No newline at end of file diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_pt_BR.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_pt_BR.properties new file mode 100644 index 00000000000..69419cbe937 --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_pt_BR.properties @@ -0,0 +1,30 @@ +# 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. + +dataSource.displayName=Nome da Vari\u00E1vel +dataSource.shortDescription=Nome da vari\u00E1vel do JMeter que referencia o grupo de conex\u00F5es. +displayName=Requisi\u00E7\u00E3o JDBC +query.displayName=Consulta +query.shortDescription=Consulta SQL que ser\u00E1 enviada ao banco de dados +queryArguments.displayName=Valores dos par\u00E2metros +queryArguments.shortDescription=Valores dos par\u00E2metros do SQL (separados por v\u00EDrgula) +queryArgumentsTypes.displayName=Tipos dos par\u00E2metros +queryArgumentsTypes.shortDescription=Nomes dos tipos do JDBC de java.sql.Types. VARCHAR, INTEGER, etc. (separados por v\u00EDrgula) +queryType.displayName=Tipo da Consulta +queryType.shortDescription=Determina se a instru\u00E7\u00E3o SQL dever\u00E1 ser executada como uma instru\u00E7\u00E3o de consulta ou de atualiza\u00E7\u00E3o. +sql.displayName=Consulta SQL +varName.displayName=Nome da vari\u00E1vel que referencia o grupo de conex\u00F5es. +variableNames.displayName=Nomes das vari\u00E1veis +variableNames.shortDescription=Nomes das vari\u00E1veis de sa\u00EDda para cada coluna (separado por v\u00EDrgula) diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_tr.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_tr.properties new file mode 100644 index 00000000000..8a51ec46e6c --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_tr.properties @@ -0,0 +1,29 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=De\u011Fi\u015Fken \u0130smi +dataSource.shortDescription=Ba\u011Flant\u0131 havuzunun ili\u015Fkilendirilece\u011Fi JMeter de\u011Fi\u015Fkeninin ismi. +displayName=JDBC \u0130ste\u011Fi +query.displayName=Sorgu +query.shortDescription=Veritaban\u0131na g\u00F6nderilecek SQL sorgusu +queryArguments.displayName=Parametre de\u011Ferleri +queryArguments.shortDescription=SQL parametresi de\u011Ferleri +queryArgumentsTypes.displayName=Parametre tipleri +queryArgumentsTypes.shortDescription=java.sql.Types'tan JDBC Tip isimleri. VARCHAR, INTEGER, gibi. +queryType.displayName=Sorgu Tipi +queryType.shortDescription=SQL ifadesinin select veya update ifadesi olarak \u00E7al\u0131\u015Ft\u0131r\u0131laca\u011F\u0131n\u0131 belirler. +sql.displayName=SQL Sorgusu +varName.displayName=Havuzla ili\u015Fkilendirilecek De\u011Fi\u015Fkenin \u0130smi diff --git a/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_zh_TW.properties b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_zh_TW.properties new file mode 100644 index 00000000000..41226d3c8bd --- /dev/null +++ b/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_zh_TW.properties @@ -0,0 +1,23 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=\u8B8A\u6578\u540D\u7A31 +dataSource.shortDescription=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8B8A\u6578\u540D\u7A31 +displayName=JDBC \u8981\u6C42 +query.displayName=\u67E5\u8A62 +query.shortDescription=\u50B3\u9001\u7D66\u8CC7\u6599\u5EAB\u7684 SQL \u654D\u8FF0 +sql.displayName=SQL \u654D\u8FF0 +varName.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8B8A\u6578\u540D\u7A31 diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/Utils.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/Utils.java new file mode 100644 index 00000000000..362809cdc1a --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/Utils.java @@ -0,0 +1,253 @@ +/* + * 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.protocol.jms; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Map; + +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.naming.Context; +import javax.naming.NamingException; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.jms.sampler.JMSProperties; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Utility methods for JMS protocol. + * WARNING - the API for this class is likely to change! + */ +public final class Utils { + // By default priority is 4 + // http://docs.oracle.com/javaee/6/tutorial/doc/bncfu.html + public static final String DEFAULT_PRIORITY_4 = "4"; // $NON-NLS-1$ + + // By default a message never expires + // http://docs.oracle.com/javaee/6/tutorial/doc/bncfu.html + public static final String DEFAULT_NO_EXPIRY = "0"; // $NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static void close(MessageConsumer closeable, Logger log){ + if (closeable != null){ + try { + closeable.close(); + } catch (JMSException e) { + log.error("Error during close: ", e); + } + } + } + + public static void close(Session closeable, Logger log) { + if (closeable != null){ + try { + closeable.close(); + } catch (JMSException e) { + log.error("Error during close: ", e); + } + } + } + + public static void close(Connection closeable, Logger log) { + if (closeable != null){ + try { + closeable.close(); + } catch (JMSException e) { + log.error("Error during close: ", e); + } + } + } + + public static void close(MessageProducer closeable, Logger log) { + if (closeable != null){ + try { + closeable.close(); + } catch (JMSException e) { + log.error("Error during close: ", e); + } + } + } + + public static String messageProperties(Message msg){ + return messageProperties(new StringBuilder(), msg).toString(); + } + + public static StringBuilder messageProperties(StringBuilder sb, Message msg){ + requestHeaders(sb, msg); + sb.append("Properties:\n"); + Enumeration rme; + try { + rme = msg.getPropertyNames(); + while(rme.hasMoreElements()){ + String name=(String) rme.nextElement(); + sb.append(name).append('\t'); + String value=msg.getStringProperty(name); + sb.append(value).append('\n'); + } + } catch (JMSException e) { + sb.append("\nError: "+e.toString()); + } + return sb; + } + + public static StringBuilder requestHeaders(StringBuilder sb, Message msg){ + try { + sb.append("JMSCorrelationId ").append(msg.getJMSCorrelationID()).append('\n'); + sb.append("JMSMessageId ").append(msg.getJMSMessageID()).append('\n'); + sb.append("JMSTimestamp ").append(msg.getJMSTimestamp()).append('\n'); + sb.append("JMSType ").append(msg.getJMSType()).append('\n'); + sb.append("JMSExpiration ").append(msg.getJMSExpiration()).append('\n'); + sb.append("JMSPriority ").append(msg.getJMSPriority()).append('\n'); + sb.append("JMSDestination ").append(msg.getJMSDestination()).append('\n'); + } catch (JMSException e) { + sb.append("\nError: "+e.toString()); + } + return sb; + } + + /** + * Method will lookup a given destination (topic/queue) using JNDI. + * + * @param context + * context to use for lookup + * @param name + * the destination name + * @return the destination, never null + * @throws NamingException + * if the name cannot be found as a Destination + */ + public static Destination lookupDestination(Context context, String name) throws NamingException { + Object o = context.lookup(name); + if (o instanceof Destination){ + return (Destination) o; + } + throw new NamingException("Found: "+name+"; expected Destination, but was: "+(o!=null ? o.getClass().getName() : "null")); + } + + /** + * Get value from Context environment taking into account non fully + * compliant JNDI implementations + * + * @param context + * context to use + * @param key + * key to lookup in contexts environment + * @return String or null if context.getEnvironment() is not compliant + * @throws NamingException + * if a naming problem occurs while getting the environment + */ + public static final String getFromEnvironment(Context context, String key) throws NamingException { + try { + Hashtable env = context.getEnvironment(); + if(env != null) { + return (String) env.get(key); + } else { + log.warn("context.getEnvironment() returned null (should not happen according to javadoc but non compliant implementation can return this)"); + return null; + } + } catch (javax.naming.OperationNotSupportedException ex) { + // Some JNDI implementation can return this + log.warn("context.getEnvironment() not supported by implementation "); + return null; + } + } + + /** + * Obtain the queue connection from the context and factory name. + * + * @param ctx + * context to use + * @param factoryName + * name of the object factory to look up in context + * @return the queue connection + * @throws JMSException + * when creation of the connection fails + * @throws NamingException + * when lookup in context fails + */ + public static Connection getConnection(Context ctx, String factoryName) throws JMSException, NamingException { + Object objfac = null; + try { + objfac = ctx.lookup(factoryName); + } catch (NoClassDefFoundError e) { + throw new NamingException("Lookup failed: "+e.toString()); + } + if (objfac instanceof javax.jms.ConnectionFactory) { + String username = getFromEnvironment(ctx, Context.SECURITY_PRINCIPAL); + if(username != null) { + String password = getFromEnvironment(ctx, Context.SECURITY_CREDENTIALS); + return ((javax.jms.ConnectionFactory) objfac).createConnection(username, password); + } + else { + return ((javax.jms.ConnectionFactory) objfac).createConnection(); + } + } + throw new NamingException("Expected javax.jms.ConnectionFactory, found "+(objfac != null ? objfac.getClass().getName(): "null")); + } + + /** + * Set JMS Properties to msg + * @param msg Message to operate on + * @param map Map of Properties to be set on the message + * @throws JMSException when msg throws a {@link JMSException} while the properties get set + */ + public static void addJMSProperties(Message msg, Map map) throws JMSException { + if(map == null) { + return; + } + for (Map.Entry me : map.entrySet()) { + String name = me.getKey(); + Object value = me.getValue(); + if (log.isDebugEnabled()) { + log.debug("Adding property [" + name + "=" + value + "]"); + } + + // WebsphereMQ does not allow corr. id. to be set using setStringProperty() + if("JMSCorrelationID".equalsIgnoreCase(name)) { // $NON-NLS-1$ + msg.setJMSCorrelationID((String)value); + } else { + msg.setObjectProperty(name, value); + } + } + } + + + /** + * Converts {@link Arguments} to {@link JMSProperties} defaulting to String type + * Used to convert version <= 2.10 test plans + * @param args {@link Arguments} to be converted + * @return jmsProperties The converted {@link JMSProperties} + */ + public static final JMSProperties convertArgumentsToJmsProperties(Arguments args) { + JMSProperties jmsProperties = new JMSProperties(); + Map map = args.getArgumentsAsMap(); + for (Map.Entry entry : map.entrySet()) { + jmsProperties.addJmsProperty(entry.getKey(), entry.getValue()); + } + return jmsProperties; + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/client/ClientPool.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/client/ClientPool.java new file mode 100644 index 00000000000..d425f31d0a0 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/client/ClientPool.java @@ -0,0 +1,83 @@ +/* + * 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.protocol.jms.client; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + * ClientPool holds the client instances in an ArrayList. The main purpose of + * this is to make it easier to clean up all the instances at the end of a test. + * If we didn't do this, threads might become zombie. + * + * N.B. This class needs to be fully synchronized as it is called from sample threads + * and the thread that runs testEnded() methods. + */ +public class ClientPool { + + //GuardedBy("this") + private static final ArrayList clients = new ArrayList(); + + //GuardedBy("this") + private static final Map client_map = new ConcurrentHashMap(); + + /** + * Add a ReceiveClient to the ClientPool. This is so that we can make sure + * to close all clients and make sure all threads are destroyed. + * + * @param client the ReceiveClient to add + */ + public static synchronized void addClient(Closeable client) { + clients.add(client); + } + + /** + * Clear all the clients created by either Publish or Subscribe sampler. We + * need to do this to make sure all the threads creatd during the test are + * destroyed and cleaned up. In some cases, the client provided by the + * manufacturer of the JMS server may have bugs and some threads may become + * zombie. In those cases, it is not the responsibility of JMeter for those + * bugs. + */ + public static synchronized void clearClient() { + for (Closeable client : clients) { + try { + client.close(); + } catch (IOException e) { + // Ignored + } + client = null; + } + clients.clear(); + client_map.clear(); + } + + // TODO Method with 0 reference, really useful ? + public static void put(Object key, Object client) { + client_map.put(key, client); + } + + // TODO Method with 0 reference, really useful ? + public static Object get(Object key) { + return client_map.get(key); + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/client/InitialContextFactory.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/client/InitialContextFactory.java new file mode 100644 index 00000000000..4ea1f6774e8 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/client/InitialContextFactory.java @@ -0,0 +1,178 @@ +/* + * 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.protocol.jms.client; + +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * InitialContextFactory is responsible for getting an instance of the initial context. + */ +public class InitialContextFactory { + + private static final ConcurrentHashMap MAP = new ConcurrentHashMap(); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Look up the context from the local cache, creating it if necessary. + * + * @param initialContextFactory used to set the property {@link Context#INITIAL_CONTEXT_FACTORY} + * @param providerUrl used to set the property {@link Context#PROVIDER_URL} + * @param useAuth set true if security is to be used. + * @param securityPrincipal used to set the property {@link Context#SECURITY_PRINCIPAL} + * @param securityCredentials used to set the property {@link Context#SECURITY_CREDENTIALS} + * @return the context, never null + * @throws NamingException when creation of the context fails + */ + public static Context lookupContext(String initialContextFactory, + String providerUrl, boolean useAuth, String securityPrincipal, String securityCredentials) throws NamingException { + String cacheKey = createKey(Thread.currentThread().getId(),initialContextFactory ,providerUrl, securityPrincipal, securityCredentials); + Context ctx = MAP.get(cacheKey); + if (ctx == null) { + Properties props = new Properties(); + props.setProperty(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); + props.setProperty(Context.PROVIDER_URL, providerUrl); + if (useAuth && securityPrincipal != null && securityCredentials != null + && securityPrincipal.length() > 0 && securityCredentials.length() > 0) { + props.setProperty(Context.SECURITY_PRINCIPAL, securityPrincipal); + props.setProperty(Context.SECURITY_CREDENTIALS, securityCredentials); + log.info("authentication properties set"); + } + try { + ctx = new InitialContext(props); + } catch (NoClassDefFoundError e){ + throw new NamingException(e.toString()); + } catch (Exception e) { + throw new NamingException(e.toString()); + } + // we want to return the context that is actually in the map + // if it's the first put we will have a null result + Context oldCtx = MAP.putIfAbsent(cacheKey, ctx); + if(oldCtx != null) { + // There was an object in map, destroy the temporary and return one in map (oldCtx) + try { + ctx.close(); + } catch (Exception e) { + // NOOP + } + ctx = oldCtx; + } + // else No object in Map, ctx is the one + } + return ctx; + } + + /** + * Create cache key + * @param threadId Thread Id + * @param initialContextFactory + * @param providerUrl + * @param securityPrincipal + * @param securityCredentials + * @return the cache key + */ + private static String createKey( + long threadId, + String initialContextFactory, + String providerUrl, String securityPrincipal, + String securityCredentials) { + StringBuilder builder = new StringBuilder(); + builder.append(threadId); + builder.append("#"); + builder.append(initialContextFactory); + builder.append("#"); + builder.append(providerUrl); + builder.append("#"); + if(!StringUtils.isEmpty(securityPrincipal)) { + builder.append(securityPrincipal); + builder.append("#"); + } + if(!StringUtils.isEmpty(securityCredentials)) { + builder.append(securityCredentials); + } + return builder.toString(); + } + + /** + * Initialize the JNDI initial context + * + * @param useProps + * if true, create a new InitialContext; otherwise use the other + * parameters to call + * {@link #lookupContext(String, String, boolean, String, String)} + * @param initialContextFactory + * name of the initial context factory (ignored if + * useProps is true) + * @param providerUrl + * url of the provider to use (ignored if useProps + * is true) + * @param useAuth + * true if auth should be used, false + * otherwise (ignored if useProps is + * true) + * @param securityPrincipal + * name of the principal to (ignored if useProps is + * true) + * @param securityCredentials + * credentials for the principal (ignored if + * useProps is true) + * @return the context, never null + * @throws NamingException + * when creation of the context fails + */ + public static Context getContext(boolean useProps, + String initialContextFactory, String providerUrl, + boolean useAuth, String securityPrincipal, String securityCredentials) throws NamingException { + if (useProps) { + try { + return new InitialContext(); + } catch (NoClassDefFoundError e){ + throw new NamingException(e.toString()); + } catch (Exception e) { + throw new NamingException(e.toString()); + } + } else { + return lookupContext(initialContextFactory, providerUrl, useAuth, securityPrincipal, securityCredentials); + } + } + + /** + * clear all the InitialContext objects. + */ + public static void close() { + for (Context ctx : MAP.values()) { + try { + ctx.close(); + } catch (NamingException e) { + log.error(e.getMessage()); + } + } + MAP.clear(); + log.info("InitialContextFactory.close() called and Context instances cleaned up"); + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/client/Publisher.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/client/Publisher.java new file mode 100644 index 00000000000..f83e5c13881 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/client/Publisher.java @@ -0,0 +1,210 @@ +/* + * 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.protocol.jms.client; + +import java.io.Closeable; +import java.io.Serializable; +import java.util.Map; +import java.util.Map.Entry; + +import javax.jms.BytesMessage; +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.MapMessage; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.ObjectMessage; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.naming.Context; +import javax.naming.NamingException; + +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class Publisher implements Closeable { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Connection connection; + + private final Session session; + + private final MessageProducer producer; + + private final Context ctx; + + private final boolean staticDest; + + /** + * Create a publisher using either the jndi.properties file or the provided + * parameters. Uses a static destination and persistent messages(for + * backward compatibility) + * + * @param useProps + * true if a jndi.properties file is to be used + * @param initialContextFactory + * the (ignored if useProps is true) + * @param providerUrl + * (ignored if useProps is true) + * @param connfactory + * name of the object factory to look up in context + * @param destinationName + * name of the destination to use + * @param useAuth + * (ignored if useProps is true) + * @param securityPrincipal + * (ignored if useProps is true) + * @param securityCredentials + * (ignored if useProps is true) + * @throws JMSException + * if the context could not be initialised, or there was some + * other error + * @throws NamingException + * when creation of the publisher fails + */ + public Publisher(boolean useProps, String initialContextFactory, String providerUrl, + String connfactory, String destinationName, boolean useAuth, + String securityPrincipal, String securityCredentials) throws JMSException, NamingException { + this(useProps, initialContextFactory, providerUrl, connfactory, + destinationName, useAuth, securityPrincipal, + securityCredentials, true); + } + + + /** + * Create a publisher using either the jndi.properties file or the provided + * parameters + * + * @param useProps + * true if a jndi.properties file is to be used + * @param initialContextFactory + * the (ignored if useProps is true) + * @param providerUrl + * (ignored if useProps is true) + * @param connfactory + * name of the object factory to lookup in context + * @param destinationName + * name of the destination to use + * @param useAuth + * (ignored if useProps is true) + * @param securityPrincipal + * (ignored if useProps is true) + * @param securityCredentials + * (ignored if useProps is true) + * @param staticDestination + * true if the destination is not to change between loops + * @throws JMSException + * if the context could not be initialised, or there was some + * other error + * @throws NamingException + * when creation of the publisher fails + */ + public Publisher(boolean useProps, String initialContextFactory, String providerUrl, + String connfactory, String destinationName, boolean useAuth, + String securityPrincipal, String securityCredentials, + boolean staticDestination) throws JMSException, NamingException { + super(); + boolean initSuccess = false; + try{ + ctx = InitialContextFactory.getContext(useProps, initialContextFactory, + providerUrl, useAuth, securityPrincipal, securityCredentials); + connection = Utils.getConnection(ctx, connfactory); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + staticDest = staticDestination; + if (staticDest) { + Destination dest = Utils.lookupDestination(ctx, destinationName); + producer = session.createProducer(dest); + } else { + producer = session.createProducer(null); + } + initSuccess = true; + } finally { + if(!initSuccess) { + close(); + } + } + } + + public Message publish(String text, String destinationName, Map properties, int deliveryMode, int priority, long expiration) + throws JMSException, NamingException { + TextMessage msg = session.createTextMessage(text); + return setPropertiesAndSend(destinationName, properties, msg, deliveryMode, priority, expiration); + } + + public Message publish(Serializable contents, String destinationName, Map properties, int deliveryMode, int priority, long expiration) + throws JMSException, NamingException { + ObjectMessage msg = session.createObjectMessage(contents); + return setPropertiesAndSend(destinationName, properties, msg, deliveryMode, priority, expiration); + } + + public Message publish(byte[] bytes, String destinationName, Map properties, int deliveryMode, int priority, long expiration) + throws JMSException, NamingException { + BytesMessage msg = session.createBytesMessage(); + msg.writeBytes(bytes); + return setPropertiesAndSend(destinationName, properties, msg, deliveryMode, priority, expiration); + } + + public MapMessage publish(Map map, String destinationName, Map properties, + int deliveryMode, int priority, long expiration) + throws JMSException, NamingException { + MapMessage msg = session.createMapMessage(); + for (Entry me : map.entrySet()) { + msg.setObject(me.getKey(), me.getValue()); + } + return (MapMessage)setPropertiesAndSend(destinationName, properties, msg, deliveryMode, priority, expiration); + } + + /** + * @param destinationName + * @param properties Map + * @param msg Message + * @param deliveryMode + * @param priority + * @param expiration + * @return Message + * @throws JMSException + * @throws NamingException + */ + private Message setPropertiesAndSend(String destinationName, + Map properties, Message msg, + int deliveryMode, int priority, long expiration) + throws JMSException, NamingException { + Utils.addJMSProperties(msg, properties); + if (staticDest || destinationName == null) { + producer.send(msg, deliveryMode, priority, expiration); + } else { + Destination dest = Utils.lookupDestination(ctx, destinationName); + producer.send(dest, msg, deliveryMode, priority, expiration); + } + return msg; + } + + /** + * Close will close the session + */ + @Override + public void close() { + Utils.close(producer, log); + Utils.close(session, log); + Utils.close(connection, log); + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/client/ReceiveSubscriber.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/client/ReceiveSubscriber.java new file mode 100644 index 00000000000..aa06460dfef --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/client/ReceiveSubscriber.java @@ -0,0 +1,383 @@ +/* + * 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.protocol.jms.client; + +import java.io.Closeable; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.Session; +import javax.jms.Topic; +import javax.naming.Context; +import javax.naming.NamingException; + +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Generic MessageConsumer class, which has two possible strategies. + *
    + *
  • Use MessageConsumer.receive(timeout) to fetch messages.
  • + *
  • Use MessageListener.onMessage() to cache messages in a local queue.
  • + *
+ * In both cases, the {@link #getMessage(long)} method is used to return the next message, + * either directly using receive(timeout) or from the queue using poll(timeout). + */ +public class ReceiveSubscriber implements Closeable, MessageListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Connection connection; + + private final Session session; + + private final MessageConsumer subscriber; + + /* + * We use a LinkedBlockingQueue (rather than a ConcurrentLinkedQueue) because it has a + * poll-with-wait method that avoids the need to use a polling loop. + */ + private final LinkedBlockingQueue queue; + + /** + * No need for volatile as this variable is only accessed by a single thread + */ + private boolean connectionStarted; + + /** + * Constructor takes the necessary JNDI related parameters to create a + * connection and prepare to begin receiving messages.
+ * The caller must then invoke {@link #start()} to enable message reception. + * + * @param useProps + * if true, use jndi.properties instead of + * initialContextFactory, providerUrl, + * securityPrincipal, + * securityCredentials + * @param initialContextFactory + * name of the initial context factory (will be ignored if + * useProps is true) + * @param providerUrl + * url of the provider (will be ignored if useProps + * is true) + * @param connfactory + * name of the object factory to look up in context + * @param destinationName + * name of the destination + * @param durableSubscriptionId + * id for a durable subscription (if empty or null + * no durable subscription will be done) + * @param clientId + * client id to use (may be empty or null) + * @param jmsSelector + * Message Selector + * @param useAuth + * flag whether auth should be used (will be ignored if + * useProps is true) + * @param securityPrincipal + * name of the principal to use for auth (will be ignored if + * useProps is true) + * @param securityCredentials + * credentials for the principal (will be ignored if + * useProps is true) + * @throws JMSException + * if could not create context or other problem occurred. + * @throws NamingException + * when lookup of context or destination fails + */ + public ReceiveSubscriber(boolean useProps, + String initialContextFactory, String providerUrl, String connfactory, String destinationName, + String durableSubscriptionId, String clientId, String jmsSelector, boolean useAuth, + String securityPrincipal, String securityCredentials) throws NamingException, JMSException { + this(0, useProps, + initialContextFactory, providerUrl, connfactory, destinationName, + durableSubscriptionId, clientId, jmsSelector, useAuth, + securityPrincipal, securityCredentials, false); + } + + /** + * Constructor takes the necessary JNDI related parameters to create a + * connection and create an onMessageListener to prepare to begin receiving + * messages.
+ * The caller must then invoke {@link #start()} to enable message reception. + * + * @param queueSize + * maximum queue size, where a queueSize <=0 + * means no limit + * @param useProps + * if true, use jndi.properties instead of + * initialContextFactory, providerUrl, + * securityPrincipal, + * securityCredentials + * @param initialContextFactory + * name of the initial context factory (will be ignored if + * useProps is true) + * @param providerUrl + * url of the provider (will be ignored if useProps + * is true) + * @param connfactory + * name of the object factory to look up in context + * @param destinationName + * name of the destination + * @param durableSubscriptionId + * id for a durable subscription (if empty or null + * no durable subscription will be done) + * @param clientId + * client id to use (may be empty or null) + * @param jmsSelector + * Message Selector + * @param useAuth + * flag whether auth should be used (will be ignored if + * useProps is true) + * @param securityPrincipal + * name of the principal to use for auth (will be ignored if + * useProps is true) + * @param securityCredentials + * credentials for the principal (will be ignored if + * useProps is true) + * @throws JMSException + * if could not create context or other problem occurred. + * @throws NamingException + * when lookup of context or destination fails + */ + public ReceiveSubscriber(int queueSize, boolean useProps, + String initialContextFactory, String providerUrl, String connfactory, String destinationName, + String durableSubscriptionId, String clientId, String jmsSelector, boolean useAuth, + String securityPrincipal, String securityCredentials) throws NamingException, JMSException { + this(queueSize, useProps, + initialContextFactory, providerUrl, connfactory, destinationName, + durableSubscriptionId, clientId, jmsSelector, useAuth, + securityPrincipal, securityCredentials, true); + } + + + /** + * Constructor takes the necessary JNDI related parameters to create a + * connection and create an onMessageListener to prepare to begin receiving + * messages.
+ * The caller must then invoke {@link #start()} to enable message reception. + * + * @param queueSize + * maximum queue, where a queueSize <=0 means no limit + * @param useProps + * if true, use jndi.properties instead of + * initialContextFactory, providerUrl, + * securityPrincipal, + * securityCredentials + * @param initialContextFactory + * name of the initial context factory (will be ignored if + * useProps is true) + * @param providerUrl + * url of the provider (will be ignored if useProps + * is true) + * @param connfactory + * name of the object factory to look up in context + * @param destinationName + * name of the destination + * @param durableSubscriptionId + * id for a durable subscription (if empty or null + * no durable subscription will be done) + * @param clientId + * client id to use (may be empty or null) + * @param jmsSelector + * Message Selector + * @param useAuth + * flag whether auth should be used (will be ignored if + * useProps is true) + * @param securityPrincipal + * name of the principal to use for auth (will be ignored if + * useProps is true) + * @param securityCredentials + * credentials for the principal (will be ignored if + * useProps is true) + * @param useMessageListener + * if true create an onMessageListener to prepare to + * begin receiving messages, otherwise queue will be + * null + * @throws JMSException + * if could not create context or other problem occurred. + * @throws NamingException + * when lookup of context or destination fails + */ + private ReceiveSubscriber(int queueSize, boolean useProps, + String initialContextFactory, String providerUrl, String connfactory, String destinationName, + String durableSubscriptionId, String clientId, String jmsSelector, boolean useAuth, + String securityPrincipal, String securityCredentials, boolean useMessageListener) throws NamingException, JMSException { + boolean initSuccess = false; + try{ + Context ctx = InitialContextFactory.getContext(useProps, + initialContextFactory, providerUrl, useAuth, securityPrincipal, securityCredentials); + connection = Utils.getConnection(ctx, connfactory); + if(!isEmpty(clientId)) { + connection.setClientID(clientId); + } + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Destination dest = Utils.lookupDestination(ctx, destinationName); + subscriber = createSubscriber(session, dest, durableSubscriptionId, jmsSelector); + if(useMessageListener) { + if (queueSize <=0) { + queue = new LinkedBlockingQueue(); + } else { + queue = new LinkedBlockingQueue(queueSize); + } + subscriber.setMessageListener(this); + } else { + queue = null; + } + log.debug(" complete"); + initSuccess = true; + } + finally { + if(!initSuccess) { + close(); + } + } + } + + /** + * Return a simple MessageConsumer or a TopicSubscriber (as a durable subscription) + * @param session + * JMS session + * @param destination + * JMS destination, can be either topic or queue + * @param durableSubscriptionId + * If neither empty nor null, this means that a durable + * subscription will be used + * @param jmsSelector JMS Selector + * @return the message consumer + * @throws JMSException + */ + private MessageConsumer createSubscriber(Session session, + Destination destination, String durableSubscriptionId, + String jmsSelector) throws JMSException { + if (isEmpty(durableSubscriptionId)) { + if(isEmpty(jmsSelector)) { + return session.createConsumer(destination); + } else { + return session.createConsumer(destination, jmsSelector); + } + } else { + if(isEmpty(jmsSelector)) { + return session.createDurableSubscriber((Topic) destination, durableSubscriptionId); + } else { + return session.createDurableSubscriber((Topic) destination, durableSubscriptionId, jmsSelector, false); + } + } + } + + /** + * Calls Connection.start() to begin receiving inbound messages. + * @throws JMSException when starting the context fails + */ + public void start() throws JMSException { + log.debug("start()"); + connection.start(); + connectionStarted=true; + } + + /** + * Calls Connection.stop() to stop receiving inbound messages. + * @throws JMSException when stopping the context fails + */ + public void stop() throws JMSException { + log.debug("stop()"); + connection.stop(); + connectionStarted=false; + } + + /** + * Get the next message or null. + *

+ * Never blocks for longer than the specified timeout. + * + * @param timeout in milliseconds + * @return the next message or null + * + * @throws JMSException when receiving the message fails + */ + public Message getMessage(long timeout) throws JMSException { + Message message = null; + if (queue != null) { // Using onMessage Listener + try { + if (timeout < 10) { // Allow for short/negative times + message = queue.poll(); + } else { + message = queue.poll(timeout, TimeUnit.MILLISECONDS); + } + } catch (InterruptedException e) { + // Ignored + } + return message; + } + if (timeout < 10) { // Allow for short/negative times + message = subscriber.receiveNoWait(); + } else { + message = subscriber.receive(timeout); + } + return message; + } + /** + * close() will stop the connection first. + * Then it closes the subscriber, session and connection. + */ + @Override + public void close() { // called by SubscriberSampler#threadFinished() + log.debug("close()"); + try { + if(connection != null && connectionStarted) { + connection.stop(); + connectionStarted = false; + } + } catch (JMSException e) { + log.warn("Stopping connection throws exception, message:"+e.getMessage()); + } + Utils.close(subscriber, log); + Utils.close(session, log); + Utils.close(connection, log); + } + + + /** + * {@inheritDoc} + */ + @Override + public void onMessage(Message message) { + if (!queue.offer(message)){ + log.warn("Could not add message to queue"); + } + } + + + /** + * Checks whether string is empty + * + * @param s1 + * @return True if input is null, an empty string, or a white space-only string + */ + private boolean isEmpty(String s1) { + return (s1 == null || s1.trim().equals("")); + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSPropertiesPanel.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSPropertiesPanel.java new file mode 100644 index 00000000000..ef02655e460 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSPropertiesPanel.java @@ -0,0 +1,357 @@ +/* + * 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.protocol.jms.control.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.DefaultCellEditor; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableColumn; + +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.protocol.jms.sampler.JMSProperties; +import org.apache.jmeter.protocol.jms.sampler.JMSProperty; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Handles input for Jms Properties + * @since 2.11 + */ +public class JMSPropertiesPanel extends JPanel implements ActionListener { + + private static final long serialVersionUID = -2893899384410289131L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ADD_COMMAND = "Add"; //$NON-NLS-1$ + + private static final String DELETE_COMMAND = "Delete"; //$NON-NLS-1$ + + private static final int COL_NAME = 0; + private static final int COL_VALUE = 1; + private static final int COL_TYPE = 2; + + private InnerTableModel tableModel; + + private JTable jmsPropertiesTable; + + private JButton addButton; + + private JButton deleteButton; + + + /** + * Default Constructor. + */ + public JMSPropertiesPanel() { + tableModel = new InnerTableModel(); + init(); + } + + public TestElement createTestElement() { + JMSProperties jmsProperties = tableModel.jmsProperties; + return (TestElement) jmsProperties.clone(); + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @param el + * the test element to modify + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement el) { + GuiUtils.stopTableEditing(jmsPropertiesTable); + JMSProperties jmsProperties = (JMSProperties) el; + jmsProperties.clear(); + jmsProperties.addTestElement((TestElement) tableModel.jmsProperties.clone()); + } + + /** + * Clear GUI + */ + public void clearGui() { + tableModel.clearData(); + deleteButton.setEnabled(false); + } + + /** + * Configures GUI from el + * @param el {@link TestElement} + */ + public void configure(TestElement el) { + tableModel.jmsProperties.clear(); + tableModel.jmsProperties.addTestElement((JMSProperties) el.clone()); + if (tableModel.getRowCount() != 0) { + deleteButton.setEnabled(true); + } + } + + /** + * Shows the main properties panel for this object. + */ + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder(10, 10, 5, 10)); + add(createPropertiesPanel(), BorderLayout.CENTER); + } + + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + + if (action.equals(DELETE_COMMAND)) { + if (tableModel.getRowCount() > 0) { + // If a table cell is being edited, we must cancel the editing + // before deleting the row. + if (jmsPropertiesTable.isEditing()) { + TableCellEditor cellEditor = jmsPropertiesTable.getCellEditor(jmsPropertiesTable.getEditingRow(), jmsPropertiesTable + .getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = jmsPropertiesTable.getSelectedRow(); + + if (rowSelected != -1) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable the DELETE and SAVE buttons if no rows remaining + // after delete. + if (tableModel.getRowCount() == 0) { + deleteButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight + // (select) the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + jmsPropertiesTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } else if (action.equals(ADD_COMMAND)) { + // If a table cell is being edited, we should accept the current + // value and stop the editing before adding a new row. + GuiUtils.stopTableEditing(jmsPropertiesTable); + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable the DELETE and SAVE buttons if they are currently + // disabled. + if (!deleteButton.isEnabled()) { + deleteButton.setEnabled(true); + } + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + jmsPropertiesTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + + public JPanel createPropertiesPanel() { + // create the JTable that holds JMSProperty per row + jmsPropertiesTable = new JTable(tableModel); + jmsPropertiesTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + jmsPropertiesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + jmsPropertiesTable.setPreferredScrollableViewportSize(new Dimension(100, 70)); + + + TableColumn mechanismColumn = jmsPropertiesTable.getColumnModel().getColumn(COL_TYPE); + mechanismColumn.setCellEditor(new TypeCellEditor()); + + JPanel panel = new JPanel(new BorderLayout(0, 5)); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("jms_props"))); //$NON-NLS-1$ + panel.add(new JScrollPane(jmsPropertiesTable)); + panel.add(createButtonPanel(), BorderLayout.SOUTH); + return panel; + } + + private JButton createButton(String resName, char mnemonic, String command, boolean enabled) { + JButton button = new JButton(JMeterUtils.getResString(resName)); + button.setMnemonic(mnemonic); + button.setActionCommand(command); + button.setEnabled(enabled); + button.addActionListener(this); + return button; + } + + private JPanel createButtonPanel() { + boolean tableEmpty = (tableModel.getRowCount() == 0); + + addButton = createButton("add", 'A', ADD_COMMAND, true); //$NON-NLS-1$ + deleteButton = createButton("delete", 'D', DELETE_COMMAND, !tableEmpty); //$NON-NLS-1$ + + // Button Panel + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addButton); + buttonPanel.add(deleteButton); + return buttonPanel; + } + + private static class InnerTableModel extends AbstractTableModel { + private static final long serialVersionUID = 4638155137475747946L; + final JMSProperties jmsProperties; + + public InnerTableModel() { + jmsProperties = new JMSProperties(); + } + + public void addNewRow() { + jmsProperties.addJmsProperty(new JMSProperty("","",String.class.getName())); + } + + public void clearData() { + jmsProperties.clear(); + fireTableDataChanged(); + } + + public void removeRow(int row) { + jmsProperties.removeJmsProperty(row); + } + + @Override + public boolean isCellEditable(int row, int column) { + // all table cells are editable + return true; + } + + @Override + public Class getColumnClass(int column) { + return getValueAt(0, column).getClass(); + } + + /** + * Required by table model interface. + */ + @Override + public int getRowCount() { + return jmsProperties.getJmsPropertyCount(); + } + + /** + * Required by table model interface. + */ + @Override + public int getColumnCount() { + return 3; + } + + /** + * Required by table model interface. + */ + @Override + public String getColumnName(int column) { + switch(column) { + case COL_NAME: + return "name"; + case COL_VALUE: + return "value"; + case COL_TYPE: + return "jms_properties_type"; + default: + return null; + } + } + + /** + * Required by table model interface. + */ + @Override + public Object getValueAt(int row, int column) { + JMSProperty property = jmsProperties.getJmsProperty(row); + + switch (column){ + case COL_NAME: + return property.getName(); + case COL_VALUE: + return property.getValue(); + case COL_TYPE: + return property.getType(); + default: + return null; + } + } + + @Override + public void setValueAt(Object value, int row, int column) { + JMSProperty property = jmsProperties.getJmsProperty(row); + if(log.isDebugEnabled()) { + log.debug("Setting jms property value: " + value); + } + switch (column){ + case COL_NAME: + property.setName((String)value); + break; + case COL_VALUE: + property.setValue((String) value); + break; + case COL_TYPE: + property.setType((String) value); + break; + default: + break; + } + } + } + + private static class TypeCellEditor extends DefaultCellEditor { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public TypeCellEditor() { + super(new JComboBox(new Object[]{ + Boolean.class.getName(), + Byte.class.getName(), + Short.class.getName(), + Integer.class.getName(), + Long.class.getName(), + Float.class.getName(), + Double.class.getName(), + String.class.getName() + })); + } + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSPublisherGui.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSPublisherGui.java new file mode 100644 index 00000000000..a77bc750709 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSPublisherGui.java @@ -0,0 +1,390 @@ +/* + * 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.protocol.jms.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.JLabeledRadioI18N; +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.jms.sampler.JMSProperties; +import org.apache.jmeter.protocol.jms.sampler.PublisherSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledPasswordField; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * This is the GUI for JMS Publisher + * + */ +public class JMSPublisherGui extends AbstractSamplerGui implements ChangeListener { + + private static final long serialVersionUID = 240L; + + private static final String ALL_FILES = "*.*"; //$NON-NLS-1$ + + //++ These names are used in the JMX files, and must not be changed + /** Take source from the named file */ + public static final String USE_FILE_RSC = "jms_use_file"; //$NON-NLS-1$ + /** Take source from a random file */ + public static final String USE_RANDOM_RSC = "jms_use_random_file"; //$NON-NLS-1$ + /** Take source from the text area */ + private static final String USE_TEXT_RSC = "jms_use_text"; //$NON-NLS-1$ + + /** Create a TextMessage */ + public static final String TEXT_MSG_RSC = "jms_text_message"; //$NON-NLS-1$ + /** Create a MapMessage */ + public static final String MAP_MSG_RSC = "jms_map_message"; //$NON-NLS-1$ + /** Create an ObjectMessage */ + public static final String OBJECT_MSG_RSC = "jms_object_message"; //$NON-NLS-1$ + /** Create a BytesMessage */ + public static final String BYTES_MSG_RSC = "jms_bytes_message"; //$NON-NLS-1$ + //-- End of names used in JMX files + + // Button group resources when Bytes Message is selected + private static final String[] CONFIG_ITEMS_BYTES_MSG = { USE_FILE_RSC, USE_RANDOM_RSC}; + + // Button group resources + private static final String[] CONFIG_ITEMS = { USE_FILE_RSC, USE_RANDOM_RSC, USE_TEXT_RSC }; + + private static final String[] MSGTYPES_ITEMS = { TEXT_MSG_RSC, MAP_MSG_RSC, OBJECT_MSG_RSC, BYTES_MSG_RSC }; + + private final JCheckBox useProperties = new JCheckBox(JMeterUtils.getResString("jms_use_properties_file"), false); //$NON-NLS-1$ + + private final JLabeledRadioI18N configChoice = new JLabeledRadioI18N("jms_config", CONFIG_ITEMS, USE_TEXT_RSC); //$NON-NLS-1$ + + private final JLabeledTextField jndiICF = new JLabeledTextField(JMeterUtils.getResString("jms_initial_context_factory")); //$NON-NLS-1$ + + private final JLabeledTextField urlField = new JLabeledTextField(JMeterUtils.getResString("jms_provider_url")); //$NON-NLS-1$ + + private final JLabeledTextField jndiConnFac = new JLabeledTextField(JMeterUtils.getResString("jms_connection_factory")); //$NON-NLS-1$ + + private final JLabeledTextField jmsDestination = new JLabeledTextField(JMeterUtils.getResString("jms_topic")); //$NON-NLS-1$ + + private final JLabeledTextField expiration = new JLabeledTextField(JMeterUtils.getResString("jms_expiration"),10); //$NON-NLS-1$ + + private final JLabeledTextField priority = new JLabeledTextField(JMeterUtils.getResString("jms_priority"),1); //$NON-NLS-1$ + + private final JCheckBox useAuth = new JCheckBox(JMeterUtils.getResString("jms_use_auth"), false); //$NON-NLS-1$ + + private final JLabeledTextField jmsUser = new JLabeledTextField(JMeterUtils.getResString("jms_user")); //$NON-NLS-1$ + + private final JLabeledTextField jmsPwd = new JLabeledPasswordField(JMeterUtils.getResString("jms_pwd")); //$NON-NLS-1$ + + private final JLabeledTextField iterations = new JLabeledTextField(JMeterUtils.getResString("jms_itertions")); //$NON-NLS-1$ + + private final FilePanel messageFile = new FilePanel(JMeterUtils.getResString("jms_file"), ALL_FILES); //$NON-NLS-1$ + + private final FilePanel randomFile = new FilePanel(JMeterUtils.getResString("jms_random_file"), ALL_FILES); //$NON-NLS-1$ + + private final JSyntaxTextArea textMessage = new JSyntaxTextArea(10, 50); // $NON-NLS-1$ + + private final JLabeledRadioI18N msgChoice = new JLabeledRadioI18N("jms_message_type", MSGTYPES_ITEMS, TEXT_MSG_RSC); //$NON-NLS-1$ + + private final JCheckBox useNonPersistentDelivery = new JCheckBox(JMeterUtils.getResString("jms_use_non_persistent_delivery"),false); //$NON-NLS-1$ + + // These are the names of properties used to define the labels + private static final String DEST_SETUP_STATIC = "jms_dest_setup_static"; // $NON-NLS-1$ + + private static final String DEST_SETUP_DYNAMIC = "jms_dest_setup_dynamic"; // $NON-NLS-1$ + // Button group resources + private static final String[] DEST_SETUP_ITEMS = { DEST_SETUP_STATIC, DEST_SETUP_DYNAMIC }; + + private final JLabeledRadioI18N destSetup = + new JLabeledRadioI18N("jms_dest_setup", DEST_SETUP_ITEMS, DEST_SETUP_STATIC); // $NON-NLS-1$ + + private JMSPropertiesPanel jmsPropertiesPanel; + + public JMSPublisherGui() { + init(); + } + + /** + * the name of the property for the JMSPublisherGui is jms_publisher. + */ + @Override + public String getLabelResource() { + return "jms_publisher"; //$NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + PublisherSampler sampler = new PublisherSampler(); + setupSamplerProperties(sampler); + + return sampler; + } + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement s) { + PublisherSampler sampler = (PublisherSampler) s; + setupSamplerProperties(sampler); + sampler.setDestinationStatic(destSetup.getText().equals(DEST_SETUP_STATIC)); + } + + /** + * Initialize the provided {@link PublisherSampler} with all the values as configured in the GUI. + * + * @param sampler {@link PublisherSampler} instance + */ + private void setupSamplerProperties(final PublisherSampler sampler) { + this.configureTestElement(sampler); + sampler.setUseJNDIProperties(String.valueOf(useProperties.isSelected())); + sampler.setJNDIIntialContextFactory(jndiICF.getText()); + sampler.setProviderUrl(urlField.getText()); + sampler.setConnectionFactory(jndiConnFac.getText()); + sampler.setDestination(jmsDestination.getText()); + sampler.setExpiration(expiration.getText()); + sampler.setPriority(priority.getText()); + sampler.setUsername(jmsUser.getText()); + sampler.setPassword(jmsPwd.getText()); + sampler.setTextMessage(textMessage.getText()); + sampler.setInputFile(messageFile.getFilename()); + sampler.setRandomPath(randomFile.getFilename()); + sampler.setConfigChoice(configChoice.getText()); + sampler.setMessageChoice(msgChoice.getText()); + sampler.setIterations(iterations.getText()); + sampler.setUseAuth(useAuth.isSelected()); + sampler.setUseNonPersistentDelivery(useNonPersistentDelivery.isSelected()); + + JMSProperties args = (JMSProperties) jmsPropertiesPanel.createTestElement(); + sampler.setJMSProperties(args); + } + + /** + * init() adds jndiICF to the mainPanel. The class reuses logic from + * SOAPSampler, since it is common. + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new VerticalPanel(); + add(mainPanel, BorderLayout.CENTER); + + mainPanel.add(useProperties); + mainPanel.add(jndiICF); + mainPanel.add(urlField); + mainPanel.add(jndiConnFac); + mainPanel.add(createDestinationPane()); + mainPanel.add(createAuthPane()); + mainPanel.add(createPriorityAndExpiration()); + mainPanel.add(iterations); + + jmsPropertiesPanel = new JMSPropertiesPanel(); //$NON-NLS-1$ + mainPanel.add(jmsPropertiesPanel); + + configChoice.setLayout(new BoxLayout(configChoice, BoxLayout.X_AXIS)); + mainPanel.add(configChoice); + msgChoice.setLayout(new BoxLayout(msgChoice, BoxLayout.X_AXIS)); + mainPanel.add(msgChoice); + mainPanel.add(messageFile); + mainPanel.add(randomFile); + + JPanel messageContentPanel = new JPanel(new BorderLayout()); + messageContentPanel.add(new JLabel(JMeterUtils.getResString("jms_text_area")), BorderLayout.NORTH); + messageContentPanel.add(new JTextScrollPane(textMessage), BorderLayout.CENTER); + + mainPanel.add(messageContentPanel); + useProperties.addChangeListener(this); + useAuth.addChangeListener(this); + configChoice.addChangeListener(this); + msgChoice.addChangeListener(this); + } + + @Override + public void clearGui(){ + super.clearGui(); + useProperties.setSelected(false); + jndiICF.setText(""); // $NON-NLS-1$ + urlField.setText(""); // $NON-NLS-1$ + jndiConnFac.setText(""); // $NON-NLS-1$ + jmsDestination.setText(""); // $NON-NLS-1$ + expiration.setText(""); // $NON-NLS-1$ + priority.setText(""); // $NON-NLS-1$ + jmsUser.setText(""); // $NON-NLS-1$ + jmsPwd.setText(""); // $NON-NLS-1$ + textMessage.setInitialText(""); // $NON-NLS-1$ + messageFile.setFilename(""); // $NON-NLS-1$ + randomFile.setFilename(""); // $NON-NLS-1$ + msgChoice.setText(""); // $NON-NLS-1$ + configChoice.setText(USE_TEXT_RSC); + updateConfig(USE_TEXT_RSC); + msgChoice.setText(TEXT_MSG_RSC); + iterations.setText("1"); // $NON-NLS-1$ + useAuth.setSelected(false); + jmsUser.setEnabled(false); + jmsPwd.setEnabled(false); + destSetup.setText(DEST_SETUP_STATIC); + useNonPersistentDelivery.setSelected(false); + jmsPropertiesPanel.clearGui(); + } + + /** + * the implementation loads the URL and the soap action for the request. + */ + @Override + public void configure(TestElement el) { + super.configure(el); + PublisherSampler sampler = (PublisherSampler) el; + useProperties.setSelected(sampler.getUseJNDIPropertiesAsBoolean()); + jndiICF.setText(sampler.getJNDIInitialContextFactory()); + urlField.setText(sampler.getProviderUrl()); + jndiConnFac.setText(sampler.getConnectionFactory()); + jmsDestination.setText(sampler.getDestination()); + jmsUser.setText(sampler.getUsername()); + jmsPwd.setText(sampler.getPassword()); + textMessage.setInitialText(sampler.getTextMessage()); + textMessage.setCaretPosition(0); + messageFile.setFilename(sampler.getInputFile()); + randomFile.setFilename(sampler.getRandomPath()); + configChoice.setText(sampler.getConfigChoice()); + msgChoice.setText(sampler.getMessageChoice()); + iterations.setText(sampler.getIterations()); + expiration.setText(sampler.getExpiration()); + priority.setText(sampler.getPriority()); + useAuth.setSelected(sampler.isUseAuth()); + jmsUser.setEnabled(useAuth.isSelected()); + jmsPwd.setEnabled(useAuth.isSelected()); + destSetup.setText(sampler.isDestinationStatic() ? DEST_SETUP_STATIC : DEST_SETUP_DYNAMIC); + useNonPersistentDelivery.setSelected(sampler.getUseNonPersistentDelivery()); + jmsPropertiesPanel.configure(sampler.getJMSProperties()); + updateChoice(msgChoice.getText()); + updateConfig(sampler.getConfigChoice()); + } + + /** + * When a widget state changes, it will notify this class so we can + * enable/disable the correct items. + */ + @Override + public void stateChanged(ChangeEvent event) { + if (event.getSource() == configChoice) { + updateConfig(configChoice.getText()); + } else if (event.getSource() == msgChoice) { + updateChoice(msgChoice.getText()); + } else if (event.getSource() == useProperties) { + final boolean isUseProperties = useProperties.isSelected(); + jndiICF.setEnabled(!isUseProperties); + urlField.setEnabled(!isUseProperties); + useAuth.setEnabled(!isUseProperties); + } else if (event.getSource() == useAuth) { + jmsUser.setEnabled(useAuth.isSelected() && useAuth.isEnabled()); + jmsPwd.setEnabled(useAuth.isSelected() && useAuth.isEnabled()); + } + } + /** + * Update choice contains the actual logic for hiding or showing Textarea if Bytes message + * is selected + * + * @param command + * @since 2.9 + */ + private void updateChoice(String command) { + String oldChoice = configChoice.getText(); + if (BYTES_MSG_RSC.equals(command)) { + String newChoice = USE_TEXT_RSC.equals(oldChoice) ? + USE_FILE_RSC : oldChoice; + configChoice.resetButtons(CONFIG_ITEMS_BYTES_MSG, newChoice); + textMessage.setEnabled(false); + } else { + configChoice.resetButtons(CONFIG_ITEMS, oldChoice); + textMessage.setEnabled(true); + } + validate(); + } + /** + * Update config contains the actual logic for enabling or disabling text + * message, file or random path. + * + * @param command + */ + private void updateConfig(String command) { + if (command.equals(USE_TEXT_RSC)) { + textMessage.setEnabled(true); + messageFile.enableFile(false); + randomFile.enableFile(false); + } else if (command.equals(USE_RANDOM_RSC)) { + textMessage.setEnabled(false); + messageFile.enableFile(false); + randomFile.enableFile(true); + } else { + textMessage.setEnabled(false); + messageFile.enableFile(true); + randomFile.enableFile(false); + } + } + + /** + * @return JPanel that contains destination infos + */ + private JPanel createDestinationPane() { + JPanel pane = new JPanel(new BorderLayout(3, 0)); + pane.add(jmsDestination, BorderLayout.WEST); + destSetup.setLayout(new BoxLayout(destSetup, BoxLayout.X_AXIS)); + pane.add(destSetup, BorderLayout.CENTER); + pane.add(useNonPersistentDelivery, BorderLayout.EAST); + return pane; + } + + /** + * @return JPanel Panel with checkbox to choose auth , user and password + */ + private JPanel createAuthPane() { + JPanel pane = new JPanel(); + pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS)); + pane.add(useAuth); + pane.add(Box.createHorizontalStrut(10)); + pane.add(jmsUser); + pane.add(Box.createHorizontalStrut(10)); + pane.add(jmsPwd); + return pane; + } + + /** + * @return JPanel Panel for priority and expiration + */ + private JPanel createPriorityAndExpiration() { + JPanel panel = new HorizontalPanel(); + panel.add(expiration); + panel.add(priority); + return panel; + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSSamplerGui.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSSamplerGui.java new file mode 100644 index 00000000000..b2c06eb7b31 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSSamplerGui.java @@ -0,0 +1,303 @@ +/* + * 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.protocol.jms.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; +import org.apache.jmeter.protocol.jms.sampler.JMSProperties; +import org.apache.jmeter.protocol.jms.sampler.JMSSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * Configuration screen for Java Messaging Point-to-Point requests.
+ * Created on: October 28, 2004 + * + */ +public class JMSSamplerGui extends AbstractSamplerGui { + + private static final long serialVersionUID = 240L; + + private JLabeledTextField queueConnectionFactory = new JLabeledTextField( + JMeterUtils.getResString("jms_queue_connection_factory")); //$NON-NLS-1$ + + private JLabeledTextField sendQueue = new JLabeledTextField(JMeterUtils.getResString("jms_send_queue")); //$NON-NLS-1$ + + private JLabeledTextField receiveQueue = new JLabeledTextField(JMeterUtils.getResString("jms_receive_queue")); //$NON-NLS-1$ + + private JLabeledTextField timeout = new JLabeledTextField(JMeterUtils.getResString("jms_timeout"),10); //$NON-NLS-1$ + + private JLabeledTextField expiration = new JLabeledTextField(JMeterUtils.getResString("jms_expiration"),10); //$NON-NLS-1$ + + private JLabeledTextField priority = new JLabeledTextField(JMeterUtils.getResString("jms_priority"),1); //$NON-NLS-1$ + + private JLabeledTextField jmsSelector = new JLabeledTextField(JMeterUtils.getResString("jms_selector")); //$NON-NLS-1$ + + private JSyntaxTextArea messageContent = new JSyntaxTextArea(10, 50); //$NON-NLS-1$ + + private JLabeledTextField initialContextFactory = new JLabeledTextField( + JMeterUtils.getResString("jms_initial_context_factory")); //$NON-NLS-1$ + + private JLabeledTextField providerUrl = new JLabeledTextField(JMeterUtils.getResString("jms_provider_url")); //$NON-NLS-1$ + + private String[] labels = new String[] { JMeterUtils.getResString("jms_request"), //$NON-NLS-1$ + JMeterUtils.getResString("jms_requestreply") }; //$NON-NLS-1$ + + private JLabeledChoice oneWay = new JLabeledChoice(JMeterUtils.getResString("jms_communication_style"), labels); //$NON-NLS-1$ + + private JMSPropertiesPanel jmsPropertiesPanel; + + private ArgumentsPanel jndiPropertiesPanel; + + private JCheckBox useNonPersistentDelivery; + + private JCheckBox useReqMsgIdAsCorrelId; + + private JCheckBox useResMsgIdAsCorrelId; + + public JMSSamplerGui() { + init(); + } + + /** + * Clears all fields. + */ + @Override + public void clearGui() {// renamed from clear + super.clearGui(); + queueConnectionFactory.setText(""); // $NON-NLS-1$ + sendQueue.setText(""); // $NON-NLS-1$ + receiveQueue.setText(""); // $NON-NLS-1$ + ((JComboBox) oneWay.getComponentList().get(1)).setSelectedItem(JMeterUtils.getResString("jms_request")); //$NON-NLS-1$ + timeout.setText(""); // $NON-NLS-1$ + expiration.setText(""); // $NON-NLS-1$ + priority.setText(""); // $NON-NLS-1$ + jmsSelector.setText(""); // $NON-NLS-1$ + messageContent.setInitialText(""); // $NON-NLS-1$ + initialContextFactory.setText(""); // $NON-NLS-1$ + providerUrl.setText(""); // $NON-NLS-1$ + jmsPropertiesPanel.clearGui(); + jndiPropertiesPanel.clear(); + } + + @Override + public TestElement createTestElement() { + JMSSampler sampler = new JMSSampler(); + this.configureTestElement(sampler); + transfer(sampler); + return sampler; + } + + private void transfer(JMSSampler element) { + element.setQueueConnectionFactory(queueConnectionFactory.getText()); + element.setSendQueue(sendQueue.getText()); + element.setReceiveQueue(receiveQueue.getText()); + + boolean isOneway = oneWay.getText().equals(JMeterUtils.getResString("jms_request")); //$NON-NLS-1$ + element.setIsOneway(isOneway); + + element.setNonPersistent(useNonPersistentDelivery.isSelected()); + element.setUseReqMsgIdAsCorrelId(useReqMsgIdAsCorrelId.isSelected()); + element.setUseResMsgIdAsCorrelId(useResMsgIdAsCorrelId.isSelected()); + element.setTimeout(timeout.getText()); + element.setExpiration(expiration.getText()); + element.setPriority(priority.getText()); + element.setJMSSelector(jmsSelector.getText()); + element.setContent(messageContent.getText()); + + element.setInitialContextFactory(initialContextFactory.getText()); + element.setContextProvider(providerUrl.getText()); + Arguments jndiArgs = (Arguments) jndiPropertiesPanel.createTestElement(); + element.setJNDIProperties(jndiArgs); + + JMSProperties args = (JMSProperties) jmsPropertiesPanel.createTestElement(); + element.setJMSProperties(args); + + } + + /** + * + * @param element the test element being created + */ + @Override + public void modifyTestElement(TestElement element) { + this.configureTestElement(element); + if (!(element instanceof JMSSampler)) return; + JMSSampler sampler = (JMSSampler) element; + transfer(sampler); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (!(el instanceof JMSSampler)) return; + JMSSampler sampler = (JMSSampler) el; + queueConnectionFactory.setText(sampler.getQueueConnectionFactory()); + sendQueue.setText(sampler.getSendQueue()); + receiveQueue.setText(sampler.getReceiveQueue()); + + JComboBox box = (JComboBox) oneWay.getComponentList().get(1); + String selected = null; + if (sampler.isOneway()) { + selected = JMeterUtils.getResString("jms_request"); //$NON-NLS-1$ + } else { + selected = JMeterUtils.getResString("jms_requestreply"); //$NON-NLS-1$ + } + box.setSelectedItem(selected); + + useNonPersistentDelivery.setSelected(sampler.isNonPersistent()); + useReqMsgIdAsCorrelId.setSelected(sampler.isUseReqMsgIdAsCorrelId()); + useResMsgIdAsCorrelId.setSelected(sampler.isUseResMsgIdAsCorrelId()); + + timeout.setText(sampler.getTimeout()); + expiration.setText(sampler.getExpiration()); + priority.setText(sampler.getPriority()); + jmsSelector.setText(sampler.getJMSSelector()); + messageContent.setInitialText(sampler.getContent()); + initialContextFactory.setText(sampler.getInitialContextFactory()); + providerUrl.setText(sampler.getContextProvider()); + + jmsPropertiesPanel.configure(sampler.getJMSProperties()); + // (TestElement) + // el.getProperty(JMSSampler.JMS_PROPERTIES).getObjectValue()); + + jndiPropertiesPanel.configure(sampler.getJNDIProperties()); + // (TestElement) + // el.getProperty(JMSSampler.JNDI_PROPERTIES).getObjectValue()); + } + + /** + * Initializes the configuration screen. + * + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel jmsQueueingPanel = new JPanel(new BorderLayout()); + jmsQueueingPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("jms_queueing"))); //$NON-NLS-1$ + + JPanel qcfPanel = new JPanel(new BorderLayout(5, 0)); + qcfPanel.add(queueConnectionFactory, BorderLayout.CENTER); + jmsQueueingPanel.add(qcfPanel, BorderLayout.NORTH); + + JPanel sendQueuePanel = new JPanel(new BorderLayout(5, 0)); + sendQueuePanel.add(sendQueue); + jmsQueueingPanel.add(sendQueuePanel, BorderLayout.CENTER); + + JPanel receiveQueuePanel = new JPanel(new BorderLayout(5, 0)); + receiveQueuePanel.add(jmsSelector,BorderLayout.SOUTH); + receiveQueuePanel.add(receiveQueue,BorderLayout.NORTH); + jmsQueueingPanel.add(receiveQueuePanel, BorderLayout.SOUTH); + + JPanel messagePanel = new JPanel(new BorderLayout()); + messagePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("jms_message_title"))); //$NON-NLS-1$ + + JPanel correlationPanel = new HorizontalPanel(); + correlationPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("jms_correlation_title"))); //$NON-NLS-1$ + + useReqMsgIdAsCorrelId = new JCheckBox(JMeterUtils.getResString("jms_use_req_msgid_as_correlid"),false); //$NON-NLS-1$ + + useResMsgIdAsCorrelId = new JCheckBox(JMeterUtils.getResString("jms_use_res_msgid_as_correlid"),false); //$NON-NLS-1$ + + correlationPanel.add(useReqMsgIdAsCorrelId); + correlationPanel.add(useResMsgIdAsCorrelId); + + JPanel messageNorthPanel = new JPanel(new BorderLayout()); + JPanel onewayPanel = new HorizontalPanel(); + onewayPanel.add(oneWay); + onewayPanel.add(correlationPanel); + messageNorthPanel.add(onewayPanel, BorderLayout.NORTH); + + useNonPersistentDelivery = new JCheckBox(JMeterUtils.getResString("jms_use_non_persistent_delivery"),false); //$NON-NLS-1$ + + JPanel timeoutPanel = new HorizontalPanel(); + timeoutPanel.add(timeout); + timeoutPanel.add(expiration); + timeoutPanel.add(priority); + timeoutPanel.add(useNonPersistentDelivery); + messageNorthPanel.add(timeoutPanel, BorderLayout.SOUTH); + + messagePanel.add(messageNorthPanel, BorderLayout.NORTH); + + JPanel messageContentPanel = new JPanel(new BorderLayout()); + messageContentPanel.add(new JLabel(JMeterUtils.getResString("jms_msg_content")), BorderLayout.NORTH); + messageContentPanel.add(new JTextScrollPane(messageContent), BorderLayout.CENTER); + messagePanel.add(messageContentPanel, BorderLayout.CENTER); + + jmsPropertiesPanel = new JMSPropertiesPanel(); //$NON-NLS-1$ + messagePanel.add(jmsPropertiesPanel, BorderLayout.SOUTH); + + Box mainPanel = Box.createVerticalBox(); + add(mainPanel, BorderLayout.CENTER); + mainPanel.add(jmsQueueingPanel, BorderLayout.NORTH); + mainPanel.add(messagePanel, BorderLayout.CENTER); + JPanel jndiPanel = createJNDIPanel(); + mainPanel.add(jndiPanel, BorderLayout.SOUTH); + + } + + /** + * Creates the panel for the JNDI configuration. + * + * @return the JNDI Panel + */ + private JPanel createJNDIPanel() { + JPanel jndiPanel = new JPanel(new BorderLayout()); + jndiPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("jms_jndi_props"))); //$NON-NLS-1$ + + JPanel contextPanel = new JPanel(new BorderLayout(10, 0)); + contextPanel.add(initialContextFactory); + jndiPanel.add(contextPanel, BorderLayout.NORTH); + + JPanel providerPanel = new JPanel(new BorderLayout(10, 0)); + providerPanel.add(providerUrl); + jndiPanel.add(providerPanel, BorderLayout.SOUTH); + + jndiPropertiesPanel = new ArgumentsPanel(JMeterUtils.getResString("jms_jndi_props")); //$NON-NLS-1$ + jndiPanel.add(jndiPropertiesPanel); + return jndiPanel; + } + + @Override + public String getLabelResource() { + return "jms_point_to_point"; //$NON-NLS-1$ // TODO - probably wrong + } + +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSSubscriberGui.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSSubscriberGui.java new file mode 100644 index 00000000000..3b47ff3a619 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/control/gui/JMSSubscriberGui.java @@ -0,0 +1,286 @@ +/* + * 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.protocol.jms.control.gui; + +import java.awt.BorderLayout; + +import javax.naming.Context; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.JLabeledRadioI18N; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.jms.sampler.SubscriberSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledPasswordField; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * This is the GUI for JMS Subscriber
+ * + */ +public class JMSSubscriberGui extends AbstractSamplerGui implements ChangeListener { + + private static final long serialVersionUID = 240L; + + private final JCheckBox useProperties = + new JCheckBox(JMeterUtils.getResString("jms_use_properties_file"), false); // $NON-NLS-1$ + + private final JLabeledTextField jndiICF = + new JLabeledTextField(JMeterUtils.getResString("jms_initial_context_factory")); // $NON-NLS-1$ + + private final JLabeledTextField urlField = + new JLabeledTextField(JMeterUtils.getResString("jms_provider_url")); // $NON-NLS-1$ + + private final JLabeledTextField jndiConnFac = + new JLabeledTextField(JMeterUtils.getResString("jms_connection_factory")); // $NON-NLS-1$ + + private final JLabeledTextField jmsDestination = + new JLabeledTextField(JMeterUtils.getResString("jms_topic")); // $NON-NLS-1$ + + private final JLabeledTextField jmsDurableSubscriptionId = + new JLabeledTextField(JMeterUtils.getResString("jms_durable_subscription_id")); // $NON-NLS-1$ + + private final JLabeledTextField jmsClientId = + new JLabeledTextField(JMeterUtils.getResString("jms_client_id")); // $NON-NLS-1$ + + private final JLabeledTextField jmsSelector = + new JLabeledTextField(JMeterUtils.getResString("jms_selector")); // $NON-NLS-1$ + + private final JLabeledTextField jmsUser = + new JLabeledTextField(JMeterUtils.getResString("jms_user")); // $NON-NLS-1$ + + private final JLabeledTextField jmsPwd = + new JLabeledPasswordField(JMeterUtils.getResString("jms_pwd")); // $NON-NLS-1$ + + private final JLabeledTextField iterations = + new JLabeledTextField(JMeterUtils.getResString("jms_itertions")); // $NON-NLS-1$ + + private final JCheckBox useAuth = + new JCheckBox(JMeterUtils.getResString("jms_use_auth"), false); //$NON-NLS-1$ + + private final JCheckBox readResponse = + new JCheckBox(JMeterUtils.getResString("jms_read_response"), true); // $NON-NLS-1$ + + private final JLabeledTextField timeout = + new JLabeledTextField(JMeterUtils.getResString("jms_timeout")); //$NON-NLS-1$ + + private final JLabeledTextField separator = + new JLabeledTextField(JMeterUtils.getResString("jms_separator")); //$NON-NLS-1$ + + //++ Do not change these strings; they are used in JMX files to record the button settings + public static final String RECEIVE_RSC = "jms_subscriber_receive"; // $NON-NLS-1$ + + public static final String ON_MESSAGE_RSC = "jms_subscriber_on_message"; // $NON-NLS-1$ + //-- + + // Button group resources + private static final String[] CLIENT_ITEMS = { RECEIVE_RSC, ON_MESSAGE_RSC }; + + private final JLabeledRadioI18N clientChoice = + new JLabeledRadioI18N("jms_client_type", CLIENT_ITEMS, RECEIVE_RSC); // $NON-NLS-1$ + + private final JCheckBox stopBetweenSamples = + new JCheckBox(JMeterUtils.getResString("jms_stop_between_samples"), true); // $NON-NLS-1$ + + // These are the names of properties used to define the labels + private static final String DEST_SETUP_STATIC = "jms_dest_setup_static"; // $NON-NLS-1$ + + private static final String DEST_SETUP_DYNAMIC = "jms_dest_setup_dynamic"; // $NON-NLS-1$ + // Button group resources + private static final String[] DEST_SETUP_ITEMS = { DEST_SETUP_STATIC, DEST_SETUP_DYNAMIC }; + + private final JLabeledRadioI18N destSetup = + new JLabeledRadioI18N("jms_dest_setup", DEST_SETUP_ITEMS, DEST_SETUP_STATIC); // $NON-NLS-1$ + + public JMSSubscriberGui() { + init(); + } + + @Override + public String getLabelResource() { + return "jms_subscriber_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + SubscriberSampler sampler = new SubscriberSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement s) { + SubscriberSampler sampler = (SubscriberSampler) s; + this.configureTestElement(sampler); + sampler.setUseJNDIProperties(String.valueOf(useProperties.isSelected())); + sampler.setJNDIIntialContextFactory(jndiICF.getText()); + sampler.setProviderUrl(urlField.getText()); + sampler.setConnectionFactory(jndiConnFac.getText()); + sampler.setDestination(jmsDestination.getText()); + sampler.setDurableSubscriptionId(jmsDurableSubscriptionId.getText()); + sampler.setClientID(jmsClientId.getText()); + sampler.setJmsSelector(jmsSelector.getText()); + sampler.setUsername(jmsUser.getText()); + sampler.setPassword(jmsPwd.getText()); + sampler.setUseAuth(useAuth.isSelected()); + sampler.setIterations(iterations.getText()); + sampler.setReadResponse(String.valueOf(readResponse.isSelected())); + sampler.setClientChoice(clientChoice.getText()); + sampler.setStopBetweenSamples(stopBetweenSamples.isSelected()); + sampler.setTimeout(timeout.getText()); + sampler.setDestinationStatic(destSetup.getText().equals(DEST_SETUP_STATIC)); + sampler.setSeparator(separator.getText()); + } + + /** + * init() adds jndiICF to the mainPanel. The class reuses logic from + * SOAPSampler, since it is common. + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new VerticalPanel(); + add(mainPanel, BorderLayout.CENTER); + + jndiICF.setToolTipText(Context.INITIAL_CONTEXT_FACTORY); + urlField.setToolTipText(Context.PROVIDER_URL); + jmsUser.setToolTipText(Context.SECURITY_PRINCIPAL); + jmsPwd.setToolTipText(Context.SECURITY_CREDENTIALS); + mainPanel.add(useProperties); + mainPanel.add(jndiICF); + mainPanel.add(urlField); + mainPanel.add(jndiConnFac); + mainPanel.add(createDestinationPane()); + mainPanel.add(jmsDurableSubscriptionId); + mainPanel.add(jmsClientId); + mainPanel.add(jmsSelector); + mainPanel.add(useAuth); + mainPanel.add(jmsUser); + mainPanel.add(jmsPwd); + mainPanel.add(iterations); + + mainPanel.add(readResponse); + mainPanel.add(timeout); + + JPanel choice = new HorizontalPanel(); + choice.add(clientChoice); + choice.add(stopBetweenSamples); + mainPanel.add(choice); + mainPanel.add(separator); + + useProperties.addChangeListener(this); + useAuth.addChangeListener(this); + } + + /** + * the implementation loads the URL and the soap action for the request. + */ + @Override + public void configure(TestElement el) { + super.configure(el); + SubscriberSampler sampler = (SubscriberSampler) el; + useProperties.setSelected(sampler.getUseJNDIPropertiesAsBoolean()); + jndiICF.setText(sampler.getJNDIInitialContextFactory()); + urlField.setText(sampler.getProviderUrl()); + jndiConnFac.setText(sampler.getConnectionFactory()); + jmsDestination.setText(sampler.getDestination()); + jmsDurableSubscriptionId.setText(sampler.getDurableSubscriptionId()); + jmsClientId.setText(sampler.getClientId()); + jmsSelector.setText(sampler.getJmsSelector()); + jmsUser.setText(sampler.getUsername()); + jmsPwd.setText(sampler.getPassword()); + iterations.setText(sampler.getIterations()); + useAuth.setSelected(sampler.isUseAuth()); + jmsUser.setEnabled(useAuth.isSelected()); + jmsPwd.setEnabled(useAuth.isSelected()); + readResponse.setSelected(sampler.getReadResponseAsBoolean()); + clientChoice.setText(sampler.getClientChoice()); + stopBetweenSamples.setSelected(sampler.isStopBetweenSamples()); + timeout.setText(sampler.getTimeout()); + separator.setText(sampler.getSeparator()); + destSetup.setText(sampler.isDestinationStatic() ? DEST_SETUP_STATIC : DEST_SETUP_DYNAMIC); + } + + @Override + public void clearGui(){ + super.clearGui(); + useProperties.setSelected(false); // $NON-NLS-1$ + jndiICF.setText(""); // $NON-NLS-1$ + urlField.setText(""); // $NON-NLS-1$ + jndiConnFac.setText(""); // $NON-NLS-1$ + jmsDestination.setText(""); // $NON-NLS-1$ + jmsDurableSubscriptionId.setText(""); // $NON-NLS-1$ + jmsClientId.setText(""); // $NON-NLS-1$ + jmsSelector.setText(""); // $NON-NLS-1$ + jmsUser.setText(""); // $NON-NLS-1$ + jmsPwd.setText(""); // $NON-NLS-1$ + iterations.setText("1"); // $NON-NLS-1$ + timeout.setText(""); // $NON-NLS-1$ + separator.setText(""); // $NON-NLS-1$ + useAuth.setSelected(false); + jmsUser.setEnabled(false); + jmsPwd.setEnabled(false); + readResponse.setSelected(true); + clientChoice.setText(RECEIVE_RSC); + stopBetweenSamples.setSelected(false); + destSetup.setText(DEST_SETUP_STATIC); + } + + /** + * When the state of a widget changes, it will notify the gui. the method + * then enables or disables certain parameters. + */ + @Override + public void stateChanged(ChangeEvent event) { + if (event.getSource() == useProperties) { + final boolean isUseProperties = useProperties.isSelected(); + jndiICF.setEnabled(!isUseProperties); + urlField.setEnabled(!isUseProperties); + useAuth.setEnabled(!isUseProperties); + } else if (event.getSource() == useAuth) { + jmsUser.setEnabled(useAuth.isSelected() && useAuth.isEnabled()); + jmsPwd.setEnabled(useAuth.isSelected() && useAuth.isEnabled()); + } + } + + private JPanel createDestinationPane() { + JPanel pane = new JPanel(new BorderLayout(3, 0)); + pane.add(jmsDestination, BorderLayout.CENTER); + destSetup.setLayout(new BoxLayout(destSetup, BoxLayout.X_AXIS)); + pane.add(destSetup, BorderLayout.EAST); + return pane; + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/BaseJMSSampler.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/BaseJMSSampler.java new file mode 100644 index 00000000000..67e7f2bc12a --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/BaseJMSSampler.java @@ -0,0 +1,372 @@ +/* + * 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.protocol.jms.sampler; + +import java.util.Date; + +import javax.jms.DeliveryMode; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +/** + * + * BaseJMSSampler is an abstract class which provides implementation for common + * properties. Rather than duplicate the code, it's contained in the base class. + */ +public abstract class BaseJMSSampler extends AbstractSampler { + + private static final long serialVersionUID = 240L; + + //++ These are JMX file attribute names and must not be changed + private static final String JNDI_INITIAL_CONTEXT_FAC = "jms.initial_context_factory"; // $NON-NLS-1$ + + private static final String PROVIDER_URL = "jms.provider_url"; // $NON-NLS-1$ + + private static final String CONN_FACTORY = "jms.connection_factory"; // $NON-NLS-1$ + + // N.B. Cannot change value, as that is used in JMX files + private static final String DEST = "jms.topic"; // $NON-NLS-1$ + + private static final String PRINCIPAL = "jms.security_principle"; // $NON-NLS-1$ + + private static final String CREDENTIALS = "jms.security_credentials"; // $NON-NLS-1$ + + private static final String ITERATIONS = "jms.iterations"; // $NON-NLS-1$ + + private static final String USE_AUTH = "jms.authenticate"; // $NON-NLS-1$ + + private static final String USE_PROPERTIES_FILE = "jms.jndi_properties"; // $NON-NLS-1$ + + private static final String READ_RESPONSE = "jms.read_response"; // $NON-NLS-1$ + + // Is Destination setup static? else dynamic + private static final String DESTINATION_STATIC = "jms.destination_static"; // $NON-NLS-1$ + private static final boolean DESTINATION_STATIC_DEFAULT = true; // default to maintain compatibility + + //-- End of JMX file attribute names + + // See BUG 45460. We need to keep the resource in order to interpret existing files + private static final String REQUIRED = JMeterUtils.getResString("jms_auth_required"); // $NON-NLS-1$ + + public BaseJMSSampler() { + } + + /** + * {@inheritDoc} + */ + @Override + public SampleResult sample(Entry e) { + return this.sample(); + } + + public abstract SampleResult sample(); + + // ------------- get/set properties ----------------------// + /** + * set the initial context factory + * + * @param icf the initial context factory + */ + public void setJNDIIntialContextFactory(String icf) { + setProperty(JNDI_INITIAL_CONTEXT_FAC, icf); + } + + /** + * method returns the initial context factory for jndi initial context + * lookup. + * + * @return the initial context factory + */ + public String getJNDIInitialContextFactory() { + return getPropertyAsString(JNDI_INITIAL_CONTEXT_FAC); + } + + /** + * set the provider user for jndi + * + * @param url the provider URL + */ + public void setProviderUrl(String url) { + setProperty(PROVIDER_URL, url); + } + + /** + * method returns the provider url for jndi to connect to + * + * @return the provider URL + */ + public String getProviderUrl() { + return getPropertyAsString(PROVIDER_URL); + } + + /** + * set the connection factory for + * + * @param factory the connection factory + */ + public void setConnectionFactory(String factory) { + setProperty(CONN_FACTORY, factory); + } + + /** + * return the connection factory parameter used to lookup the connection + * factory from the JMS server + * + * @return the connection factory + */ + public String getConnectionFactory() { + return getPropertyAsString(CONN_FACTORY); + } + + /** + * set the destination (topic or queue name) + * + * @param dest the destination + */ + public void setDestination(String dest) { + setProperty(DEST, dest); + } + + /** + * return the destination (topic or queue name) + * + * @return the destination + */ + public String getDestination() { + return getPropertyAsString(DEST); + } + + /** + * set the username to login into the jms server if needed + * + * @param user the name of the user + */ + public void setUsername(String user) { + setProperty(PRINCIPAL, user); + } + + /** + * return the username used to login to the jms server + * + * @return the username used to login to the jms server + */ + public String getUsername() { + return getPropertyAsString(PRINCIPAL); + } + + /** + * Set the password to login to the jms server + * + * @param pwd the password to use for login on the jms server + */ + public void setPassword(String pwd) { + setProperty(CREDENTIALS, pwd); + } + + /** + * return the password used to login to the jms server + * + * @return the password used to login to the jms server + */ + public String getPassword() { + return getPropertyAsString(CREDENTIALS); + } + + /** + * set the number of iterations the sampler should aggregate + * + * @param count the number of iterations + */ + public void setIterations(String count) { + setProperty(ITERATIONS, count); + } + + /** + * get the iterations as string + * + * @return the number of iterations + */ + public String getIterations() { + return getPropertyAsString(ITERATIONS); + } + + /** + * return the number of iterations as int instead of string + * + * @return the number of iterations as int instead of string + */ + public int getIterationCount() { + return getPropertyAsInt(ITERATIONS); + } + + /** + * Set whether authentication is required for JNDI + * + * @param useAuth flag whether to use authentication + */ + public void setUseAuth(boolean useAuth) { + setProperty(USE_AUTH, useAuth); + } + + /** + * return whether jndi requires authentication + * + * @return whether jndi requires authentication + */ + public boolean isUseAuth() { + final String useAuth = getPropertyAsString(USE_AUTH); + return useAuth.equalsIgnoreCase("true") || useAuth.equals(REQUIRED); // $NON-NLS-1$ + } + + /** + * set whether the sampler should read the response or not + * + * @param read whether the sampler should read the response or not + */ + public void setReadResponse(String read) { + setProperty(READ_RESPONSE, read); + } + + /** + * return whether the sampler should read the response + * + * @return whether the sampler should read the response + */ + public String getReadResponse() { + return getPropertyAsString(READ_RESPONSE); + } + + /** + * return whether the sampler should read the response as a boolean value + * + * @return whether the sampler should read the response as a boolean value + */ + public boolean getReadResponseAsBoolean() { + return getPropertyAsBoolean(READ_RESPONSE); + } + + /** + * if the sampler should use jndi.properties file, call the method with the string "true" + * + * @param properties flag whether to use jndi.properties file + */ + public void setUseJNDIProperties(String properties) { + setProperty(USE_PROPERTIES_FILE, properties); + } + + /** + * return whether the sampler should use properties file instead of UI + * parameters. + * + * @return the string "true" when the sampler should use properties file + * instead of UI parameters, the string "false" otherwise. + */ + public String getUseJNDIProperties() { + return getPropertyAsString(USE_PROPERTIES_FILE); + } + + /** + * return the properties as boolean true/false. + * + * @return whether the sampler should use properties file instead of UI parameters. + */ + public boolean getUseJNDIPropertiesAsBoolean() { + return getPropertyAsBoolean(USE_PROPERTIES_FILE); + } + + /** + * if the sampler should use a static destination, call the method with true + * + * @param isStatic flag whether the destination is a static destination + */ + public void setDestinationStatic(boolean isStatic) { + setProperty(DESTINATION_STATIC, isStatic, DESTINATION_STATIC_DEFAULT); + } + + /** + * return whether the sampler should use a static destination. + * + * @return whether the sampler should use a static destination. + */ + public boolean isDestinationStatic(){ + return getPropertyAsBoolean(DESTINATION_STATIC, DESTINATION_STATIC_DEFAULT); + } + + /** + * Returns a String with the JMS Message Header values. + * + * @param message JMS Message + * @return String with message header values. + */ + public static String getMessageHeaders(Message message) { + final StringBuilder response = new StringBuilder(256); + try { + response.append("JMS Message Header Attributes:"); + response.append("\n Correlation ID: "); + response.append(message.getJMSCorrelationID()); + + response.append("\n Delivery Mode: "); + if (message.getJMSDeliveryMode() == DeliveryMode.PERSISTENT) { + response.append("PERSISTANT"); + } else { + response.append("NON-PERSISTANT"); + } + + final Destination destination = message.getJMSDestination(); + + response.append("\n Destination: "); + response.append((destination == null ? null : destination + .toString())); + + response.append("\n Expiration: "); + response.append(new Date(message.getJMSExpiration())); + + response.append("\n Message ID: "); + response.append(message.getJMSMessageID()); + + response.append("\n Priority: "); + response.append(message.getJMSPriority()); + + response.append("\n Redelivered: "); + response.append(message.getJMSRedelivered()); + + final Destination replyTo = message.getJMSReplyTo(); + response.append("\n Reply to: "); + response.append((replyTo == null ? null : replyTo.toString())); + + response.append("\n Timestamp: "); + response.append(new Date(message.getJMSTimestamp())); + + response.append("\n Type: "); + response.append(message.getJMSType()); + + response.append("\n\n"); + + } catch (JMSException e) { + e.printStackTrace(); + } + + return new String(response); + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/FixedQueueExecutor.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/FixedQueueExecutor.java new file mode 100644 index 00000000000..30df78cf215 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/FixedQueueExecutor.java @@ -0,0 +1,113 @@ +/* + * 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.protocol.jms.sampler; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Request/reply executor with a fixed reply queue.
+ * + * Used by JMS Sampler (Point to Point) + * + */ +public class FixedQueueExecutor implements QueueExecutor { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** Sender. */ + private final MessageProducer producer; + + /** Timeout used for waiting on message. */ + private final int timeout; + + private final boolean useReqMsgIdAsCorrelId; + + /** + * Constructor. + * + * @param producer + * the queue to send the message on + * @param timeout + * timeout to use for the return message + * @param useReqMsgIdAsCorrelId + * whether to use the request message id as the correlation id + */ + public FixedQueueExecutor(MessageProducer producer, int timeout, boolean useReqMsgIdAsCorrelId) { + this.producer = producer; + this.timeout = timeout; + this.useReqMsgIdAsCorrelId = useReqMsgIdAsCorrelId; + } + + /** + * {@inheritDoc} + */ + @Override + public Message sendAndReceive(Message request, + int deliveryMode, + int priority, + long expiration) throws JMSException { + String id = request.getJMSCorrelationID(); + if(id == null && !useReqMsgIdAsCorrelId){ + throw new IllegalArgumentException("Correlation id is null. Set the JMSCorrelationID header."); + } + final CountDownLatch countDownLatch = new CountDownLatch(1); + final MessageAdmin admin = MessageAdmin.getAdmin(); + if(useReqMsgIdAsCorrelId) {// msgId not available until after send() is called + // Note: there is only one admin object which is shared between all threads + synchronized (admin) {// interlock with Receiver + producer.send(request, deliveryMode, priority, expiration); + id=request.getJMSMessageID(); + admin.putRequest(id, request, countDownLatch); + } + } else { + admin.putRequest(id, request, countDownLatch); + producer.send(request, deliveryMode, priority, expiration); + } + + try { + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName()+" will wait for reply " + id + " started on " + System.currentTimeMillis()); + } + // This used to be request.wait(timeout_ms), where 0 means forever + // However 0 means return immediately for the latch + if (timeout == 0){ + countDownLatch.await(); // + } else { + if(!countDownLatch.await(timeout, TimeUnit.MILLISECONDS)) { + log.debug("Timeout reached before getting a reply message"); + } + } + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName()+" done waiting for " + id + " on "+request+" ended on " + System.currentTimeMillis()); + } + + } catch (InterruptedException e) { + log.warn("Interrupt exception caught", e); + } + return admin.get(id); + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/JMSProperties.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/JMSProperties.java new file mode 100644 index 00000000000..11ebe354106 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/JMSProperties.java @@ -0,0 +1,248 @@ +/* + * 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.protocol.jms.sampler; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * A set of JMSProperty objects. + * @since 2.11 + */ +public class JMSProperties extends AbstractTestElement implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -2896138201054314563L; + /** The name of the property used to store the JmsProperties. */ + public static final String JMS_PROPERTIES = "JMSProperties.properties"; //$NON-NLS-1$ + + /** + * Create a new JmsPropertys object with no JmsProperties + */ + public JMSProperties() { + setProperty(new CollectionProperty(JMS_PROPERTIES, new ArrayList())); + } + + /** + * Get the JmsPropertiess. + * + * @return the JmsProperties + */ + public CollectionProperty getProperties() { + return (CollectionProperty) getProperty(JMS_PROPERTIES); + } + + /** + * Clear the JmsProperties. + */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(JMS_PROPERTIES, new ArrayList())); + } + + /** + * Set the list of JmsProperties. Any existing JmsProperties will be lost. + * + * @param jmsProperties + * the new JmsProperties + */ + public void setProperties(List jmsProperties) { + setProperty(new CollectionProperty(JMS_PROPERTIES, jmsProperties)); + } + + /** + * Get the JmsProperties as a Map. Each JMSProperty name is used as the key, and + * its value as the value. + * + * @return a new Map with String keys and values containing the JmsProperties + */ + public Map getJmsPropertysAsMap() { + PropertyIterator iter = getProperties().iterator(); + Map argMap = new LinkedHashMap(); + while (iter.hasNext()) { + JMSProperty arg = (JMSProperty) iter.next().getObjectValue(); + // Because CollectionProperty.mergeIn will not prevent adding two + // properties of the same name, we need to select the first value so + // that this element's values prevail over defaults provided by + // configuration + // elements: + if (!argMap.containsKey(arg.getName())) { + argMap.put(arg.getName(), arg.getValueAsObject()); + } + } + return argMap; + } + + /** + * Add a new JMSProperty with the given name and value. + * + * @param name + * the name of the JMSProperty + * @param value + * the value of the JMSProperty + */ + public void addJmsProperty(String name, String value) { + addJmsProperty(new JMSProperty(name, value)); + } + + /** + * Add a new argument. + * + * @param arg + * the new argument + */ + public void addJmsProperty(JMSProperty arg) { + TestElementProperty newArg = new TestElementProperty(arg.getName(), arg); + if (isRunningVersion()) { + this.setTemporary(newArg); + } + getProperties().addItem(newArg); + } + + /** + * Add a new argument with the given name, value, and metadata. + * + * @param name + * the name of the argument + * @param value + * the value of the argument + * @param type + * the type for the argument + */ + public void addJmsProperty(String name, String value, String type) { + addJmsProperty(new JMSProperty(name, value, type)); + } + + /** + * Get a PropertyIterator of the JmsProperties. + * + * @return an iteration of the JmsProperties + */ + public PropertyIterator iterator() { + return getProperties().iterator(); + } + + /** + * Create a string representation of the JmsProperties. + * + * @return the string representation of the JmsProperties + */ + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + PropertyIterator iter = getProperties().iterator(); + while (iter.hasNext()) { + JMSProperty arg = (JMSProperty) iter.next().getObjectValue(); + str.append(arg.toString()); + if (iter.hasNext()) { + str.append(","); //$NON-NLS-1$ + } + } + return str.toString(); + } + + /** + * Remove the specified argument from the list. + * + * @param row + * the index of the argument to remove + */ + public void removeJmsProperty(int row) { + if (row < getProperties().size()) { + getProperties().remove(row); + } + } + + /** + * Remove the specified argument from the list. + * + * @param arg + * the argument to remove + */ + public void removeJmsProperty(JMSProperty arg) { + PropertyIterator iter = getProperties().iterator(); + while (iter.hasNext()) { + JMSProperty item = (JMSProperty) iter.next().getObjectValue(); + if (arg.equals(item)) { + iter.remove(); + } + } + } + + /** + * Remove the argument with the specified name. + * + * @param argName + * the name of the argument to remove + */ + public void removeJmsProperty(String argName) { + PropertyIterator iter = getProperties().iterator(); + while (iter.hasNext()) { + JMSProperty arg = (JMSProperty) iter.next().getObjectValue(); + if (arg.getName().equals(argName)) { + iter.remove(); + } + } + } + + /** + * Remove all JmsProperties from the list. + */ + public void removeAllJmsPropertys() { + getProperties().clear(); + } + + /** + * Get the number of JmsProperties in the list. + * + * @return the number of JmsProperties + */ + public int getJmsPropertyCount() { + return getProperties().size(); + } + + /** + * Get a single JMSProperty. + * + * @param row + * the index of the JMSProperty to return. + * @return the JMSProperty at the specified index, or null if no JMSProperty + * exists at that index. + */ + public JMSProperty getJmsProperty(int row) { + JMSProperty argument = null; + + if (row < getProperties().size()) { + argument = (JMSProperty) getProperties().get(row).getObjectValue(); + } + + return argument; + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/JMSProperty.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/JMSProperty.java new file mode 100644 index 00000000000..76a33464750 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/JMSProperty.java @@ -0,0 +1,171 @@ +/* + * 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.protocol.jms.sampler; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.StringProperty; + +/** + * JMS Property with type + * @since 2.11 + */ +public class JMSProperty extends AbstractTestElement implements Serializable { + + + /** + * + */ + private static final long serialVersionUID = 6371090992800805753L; + + /** Name used to store the JmsProperty's name. */ + public static final String PROP_NAME = "JMSProperty.name"; // $NON-NLS-1$ + + /** Name used to store the JmsProperty's value. */ + public static final String PROP_VALUE = "JMSProperty.value"; // $NON-NLS-1$ + + /** Name used to store the JmsProperty's description. */ + public static final String PROP_TYPE = "JMSProperty.type"; // $NON-NLS-1$ + + private static final String DFLT_TYPE = String.class.getName(); + + /** + * Create a new JmsProperty without a name, value, or metadata. + */ + public JMSProperty() { + } + + /** + * Create a new JmsProperty with the specified name and value, and String type. + * + * @param name + * the prop name + * @param value + * the prop value + */ + public JMSProperty(String name, String value) { + this(name, value, DFLT_TYPE); + } + + /** + * Create a new JmsProperty with the specified name and value, and String type. + * + * @param name + * the prop name + * @param value + * the prop value + * @param type + * the type type + */ + public JMSProperty(String name, String value, String type) { + setProperty(new StringProperty(PROP_NAME, name)); + setProperty(new StringProperty(PROP_VALUE, value)); + setProperty(new StringProperty(PROP_TYPE, type)); + } + + /** + * Set the name of the JmsProperty. + * + * @param newName + * the new name + */ + @Override + public void setName(String newName) { + setProperty(new StringProperty(PROP_NAME, newName)); + } + + /** + * Get the name of the JmsProperty. + * + * @return the attribute's name + */ + @Override + public String getName() { + return getPropertyAsString(PROP_NAME); + } + + /** + * Sets the value of the JmsProperty. + * + * @param newValue + * the new value + */ + public void setValue(String newValue) { + setProperty(new StringProperty(PROP_VALUE, newValue)); + } + + /** + * Gets the value of the JmsProperty object. + * + * @return the attribute's value + */ + public String getValue() { + return getPropertyAsString(PROP_VALUE); + } + + /** + * Sets the Meta Data attribute of the JmsProperty. + * + * @param type + * the new type + */ + public void setType(String type) { + setProperty(new StringProperty(PROP_TYPE, type)); + } + + /** + * Gets the Meta Data attribute of the JmsProperty. + * + * @return the MetaData value + */ + public String getType() { + return getPropertyAsString(PROP_TYPE); + } + + @Override + public String toString() { + return getName() + "," + getValue()+","+getType(); + } + + public Object getValueAsObject() { + String type = getType(); + String value = getValue(); + + if(type.equals(Boolean.class.getName())) { + return Boolean.valueOf(value); + } else if(type.equals(Byte.class.getName())) { + return Byte.valueOf(value); + } else if(type.equals(Short.class.getName())) { + return Short.valueOf(value); + } else if(type.equals(Integer.class.getName())) { + return Integer.valueOf(value); + } else if(type.equals(Long.class.getName())) { + return Long.valueOf(value); + } else if(type.equals(Float.class.getName())) { + return Float.valueOf(value); + } else if(type.equals(Double.class.getName())) { + return Double.valueOf(value); + } else if(type.equals(String.class.getName())) { + return value; + } else { + return null; + } + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/JMSSampler.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/JMSSampler.java new file mode 100644 index 00000000000..f0a385973c8 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/JMSSampler.java @@ -0,0 +1,564 @@ +/* + * 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.protocol.jms.sampler; + +import java.util.Date; +import java.util.Hashtable; +import java.util.Map; + +import javax.jms.DeliveryMode; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Queue; +import javax.jms.QueueConnection; +import javax.jms.QueueConnectionFactory; +import javax.jms.QueueSender; +import javax.jms.QueueSession; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class implements the JMS Point-to-Point sampler + * + */ +public class JMSSampler extends AbstractSampler implements ThreadListener { + + private static final Logger LOGGER = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + private static final int DEFAULT_TIMEOUT = 2000; + private static final String DEFAULT_TIMEOUT_STRING = Integer.toString(DEFAULT_TIMEOUT); + + //++ These are JMX names, and must not be changed + private static final String JNDI_INITIAL_CONTEXT_FACTORY = "JMSSampler.initialContextFactory"; // $NON-NLS-1$ + + private static final String JNDI_CONTEXT_PROVIDER_URL = "JMSSampler.contextProviderUrl"; // $NON-NLS-1$ + + private static final String JNDI_PROPERTIES = "JMSSampler.jndiProperties"; // $NON-NLS-1$ + + private static final String TIMEOUT = "JMSSampler.timeout"; // $NON-NLS-1$ + + private static final String JMS_PRIORITY = "JMSSampler.priority"; // $NON-NLS-1$ + + private static final String JMS_EXPIRATION = "JMSSampler.expiration"; // $NON-NLS-1$ + + private static final String JMS_SELECTOR = "JMSSampler.jmsSelector"; // $NON-NLS-1$ + + private static final String JMS_SELECTOR_DEFAULT = ""; // $NON-NLS-1$ + + private static final String IS_ONE_WAY = "JMSSampler.isFireAndForget"; // $NON-NLS-1$ + + private static final String JMS_PROPERTIES = "arguments"; // $NON-NLS-1$ + + private static final String RECEIVE_QUEUE = "JMSSampler.ReceiveQueue"; // $NON-NLS-1$ + + private static final String XML_DATA = "HTTPSamper.xml_data"; // $NON-NLS-1$ + + private static final String SEND_QUEUE = "JMSSampler.SendQueue"; // $NON-NLS-1$ + + private static final String QUEUE_CONNECTION_FACTORY_JNDI = "JMSSampler.queueconnectionfactory"; // $NON-NLS-1$ + + private static final String IS_NON_PERSISTENT = "JMSSampler.isNonPersistent"; // $NON-NLS-1$ + + private static final String USE_REQ_MSGID_AS_CORRELID = "JMSSampler.useReqMsgIdAsCorrelId"; // $NON-NLS-1$ + + private static final String USE_RES_MSGID_AS_CORRELID = "JMSSampler.useResMsgIdAsCorrelId"; // $NON-NLS-1$ + + private static final boolean USE_RES_MSGID_AS_CORRELID_DEFAULT = false; // Default to be applied + + + //-- + + // Should we use java.naming.security.[principal|credentials] to create the QueueConnection? + private static final boolean USE_SECURITY_PROPERTIES = + JMeterUtils.getPropDefault("JMSSampler.useSecurity.properties", true); // $NON-NLS-1$ + + // + // Member variables + // + /** Queue for receiving messages (if applicable). */ + private transient Queue receiveQueue; + + /** The session with the queueing system. */ + private transient QueueSession session; + + /** Connection to the queueing system. */ + private transient QueueConnection connection; + + /** The executor for (pseudo) synchronous communication. */ + private transient QueueExecutor executor; + + /** Producer of the messages. */ + private transient QueueSender producer; + + private transient Receiver receiverThread = null; + + private transient Throwable thrown = null; + + private transient Context context = null; + + /** + * {@inheritDoc} + */ + @Override + public SampleResult sample(Entry entry) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.setSamplerData(getContent()); + res.setSuccessful(false); // Assume failure + res.setDataType(SampleResult.TEXT); + res.sampleStart(); + + try { + TextMessage msg = createMessage(); + if (isOneway()) { + int deliveryMode = isNonPersistent() ? + DeliveryMode.NON_PERSISTENT:DeliveryMode.PERSISTENT; + producer.send(msg, deliveryMode, Integer.parseInt(getPriority()), + Long.parseLong(getExpiration())); + res.setRequestHeaders(Utils.messageProperties(msg)); + res.setResponseOK(); + res.setResponseData("Oneway request has no response data", null); + } else { + if (!useTemporyQueue()) { + msg.setJMSReplyTo(receiveQueue); + } + Message replyMsg = executor.sendAndReceive(msg, + isNonPersistent() ? DeliveryMode.NON_PERSISTENT : DeliveryMode.PERSISTENT, + Integer.parseInt(getPriority()), + Long.parseLong(getExpiration())); + res.setRequestHeaders(Utils.messageProperties(msg)); + if (replyMsg == null) { + res.setResponseMessage("No reply message received"); + } else { + if (replyMsg instanceof TextMessage) { + res.setResponseData(((TextMessage) replyMsg).getText(), null); + } else { + res.setResponseData(replyMsg.toString(), null); + } + res.setResponseHeaders(Utils.messageProperties(replyMsg)); + res.setResponseOK(); + } + } + } catch (Exception e) { + LOGGER.warn(e.getLocalizedMessage(), e); + if (thrown != null){ + res.setResponseMessage(thrown.toString()); + } else { + res.setResponseMessage(e.getLocalizedMessage()); + } + } + res.sampleEnd(); + return res; + } + + private TextMessage createMessage() throws JMSException { + if (session == null) { + throw new IllegalStateException("Session may not be null while creating message"); + } + TextMessage msg = session.createTextMessage(); + msg.setText(getContent()); + addJMSProperties(msg); + return msg; + } + + private void addJMSProperties(TextMessage msg) throws JMSException { + Utils.addJMSProperties(msg, getJMSProperties().getJmsPropertysAsMap()); + } + + /** + * @return {@link JMSProperties} JMS Properties + */ + public JMSProperties getJMSProperties() { + Object o = getProperty(JMS_PROPERTIES).getObjectValue(); + JMSProperties jmsProperties = null; + // Backward compatibility with versions <= 2.10 + if(o instanceof Arguments) { + jmsProperties = Utils.convertArgumentsToJmsProperties((Arguments)o); + } else { + jmsProperties = (JMSProperties) o; + } + if(jmsProperties == null) { + jmsProperties = new JMSProperties(); + setJMSProperties(jmsProperties); + } + return jmsProperties; + } + + /** + * @param jmsProperties JMS Properties + */ + public void setJMSProperties(JMSProperties jmsProperties) { + setProperty(new TestElementProperty(JMS_PROPERTIES, jmsProperties)); + } + + public Arguments getJNDIProperties() { + return getArguments(JMSSampler.JNDI_PROPERTIES); + } + + public void setJNDIProperties(Arguments args) { + setProperty(new TestElementProperty(JMSSampler.JNDI_PROPERTIES, args)); + } + + public String getQueueConnectionFactory() { + return getPropertyAsString(QUEUE_CONNECTION_FACTORY_JNDI); + } + + public void setQueueConnectionFactory(String qcf) { + setProperty(QUEUE_CONNECTION_FACTORY_JNDI, qcf); + } + + public String getSendQueue() { + return getPropertyAsString(SEND_QUEUE); + } + + public void setSendQueue(String name) { + setProperty(SEND_QUEUE, name); + } + + public String getReceiveQueue() { + return getPropertyAsString(RECEIVE_QUEUE); + } + + public void setReceiveQueue(String name) { + setProperty(RECEIVE_QUEUE, name); + } + + public String getContent() { + return getPropertyAsString(XML_DATA); + } + + public void setContent(String content) { + setProperty(XML_DATA, content); + } + + public boolean isOneway() { + return getPropertyAsBoolean(IS_ONE_WAY); + } + + public boolean isNonPersistent() { + return getPropertyAsBoolean(IS_NON_PERSISTENT); + } + + /** + * Which request field to use for correlation? + * + * @return true if correlation should use the request JMSMessageID rather than JMSCorrelationID + */ + public boolean isUseReqMsgIdAsCorrelId() { + return getPropertyAsBoolean(USE_REQ_MSGID_AS_CORRELID); + } + + /** + * Which response field to use for correlation? + * + * @return true if correlation should use the response JMSMessageID rather than JMSCorrelationID + */ + public boolean isUseResMsgIdAsCorrelId() { + return getPropertyAsBoolean(USE_RES_MSGID_AS_CORRELID, USE_RES_MSGID_AS_CORRELID_DEFAULT); + } + + public String getInitialContextFactory() { + return getPropertyAsString(JMSSampler.JNDI_INITIAL_CONTEXT_FACTORY); + } + + public String getContextProvider() { + return getPropertyAsString(JMSSampler.JNDI_CONTEXT_PROVIDER_URL); + } + + public void setIsOneway(boolean isOneway) { + setProperty(new BooleanProperty(IS_ONE_WAY, isOneway)); + } + + public void setNonPersistent(boolean value) { + setProperty(new BooleanProperty(IS_NON_PERSISTENT, value)); + } + + public void setUseReqMsgIdAsCorrelId(boolean value) { + setProperty(new BooleanProperty(USE_REQ_MSGID_AS_CORRELID, value)); + } + + public void setUseResMsgIdAsCorrelId(boolean value) { + setProperty(USE_RES_MSGID_AS_CORRELID, value, USE_RES_MSGID_AS_CORRELID_DEFAULT); + } + + @Override + public String toString() { + return getQueueConnectionFactory() + ", queue: " + getSendQueue(); + } + + @Override + public void threadStarted() { + logThreadStart(); + + thrown = null; + try { + context = getInitialContext(); + Object obj = context.lookup(getQueueConnectionFactory()); + if (!(obj instanceof QueueConnectionFactory)) { + String msg = "QueueConnectionFactory expected, but got " + + (obj != null ? obj.getClass().getName() : "null"); + LOGGER.fatalError(msg); + throw new IllegalStateException(msg); + } + QueueConnectionFactory factory = (QueueConnectionFactory) obj; + Queue sendQueue = (Queue) context.lookup(getSendQueue()); + + if (!useTemporyQueue()) { + receiveQueue = (Queue) context.lookup(getReceiveQueue()); + receiverThread = Receiver.createReceiver(factory, receiveQueue, Utils.getFromEnvironment(context, Context.SECURITY_PRINCIPAL), + Utils.getFromEnvironment(context, Context.SECURITY_CREDENTIALS) + , isUseResMsgIdAsCorrelId(), getJMSSelector()); + } + + String principal = null; + String credentials = null; + if (USE_SECURITY_PROPERTIES){ + principal = Utils.getFromEnvironment(context, Context.SECURITY_PRINCIPAL); + credentials = Utils.getFromEnvironment(context, Context.SECURITY_CREDENTIALS); + } + if (principal != null && credentials != null) { + connection = factory.createQueueConnection(principal, credentials); + } else { + connection = factory.createQueueConnection(); + } + + session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Session created"); + } + + if (isOneway()) { + producer = session.createSender(sendQueue); + if (isNonPersistent()) { + producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); + } + producer.setPriority(Integer.parseInt(getPriority())); + producer.setTimeToLive(Long.parseLong(getExpiration())); + } else { + + if (useTemporyQueue()) { + executor = new TemporaryQueueExecutor(session, sendQueue); + } else { + producer = session.createSender(sendQueue); + executor = new FixedQueueExecutor(producer, getTimeoutAsInt(), isUseReqMsgIdAsCorrelId()); + } + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Starting connection"); + } + + connection.start(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Connection started"); + } + } catch (Exception e) { + thrown = e; + LOGGER.error(e.getLocalizedMessage(), e); + } catch (NoClassDefFoundError e) { + thrown = e; + LOGGER.error(e.getLocalizedMessage(), e); + } + } + + private Context getInitialContext() throws NamingException { + Hashtable table = new Hashtable(); + + if (getInitialContextFactory() != null && getInitialContextFactory().trim().length() > 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Using InitialContext [" + getInitialContextFactory() + "]"); + } + table.put(Context.INITIAL_CONTEXT_FACTORY, getInitialContextFactory()); + } + if (getContextProvider() != null && getContextProvider().trim().length() > 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Using Provider [" + getContextProvider() + "]"); + } + table.put(Context.PROVIDER_URL, getContextProvider()); + } + Map map = getArguments(JMSSampler.JNDI_PROPERTIES).getArgumentsAsMap(); + if (LOGGER.isDebugEnabled()) { + if (map.isEmpty()) { + LOGGER.debug("Empty JNDI properties"); + } else { + LOGGER.debug("Number of JNDI properties: " + map.size()); + } + } + for (Map.Entry me : map.entrySet()) { + table.put(me.getKey(), me.getValue()); + } + + Context context = new InitialContext(table); + if (LOGGER.isDebugEnabled()) { + printEnvironment(context); + } + return context; + } + + private void printEnvironment(Context context) throws NamingException { + try { + Hashtable env = context.getEnvironment(); + if(env != null) { + LOGGER.debug("Initial Context Properties"); + for (Map.Entry entry : env.entrySet()) { + LOGGER.debug(entry.getKey() + "=" + entry.getValue()); + } + } else { + LOGGER.warn("context.getEnvironment() returned null (should not happen according to javadoc but non compliant implementation can return this)"); + } + } catch (javax.naming.OperationNotSupportedException ex) { + // Some JNDI implementation can return this + LOGGER.warn("context.getEnvironment() not supported by implementation "); + } + } + + private void logThreadStart() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Thread started " + new Date()); + LOGGER.debug("JMSSampler: [" + Thread.currentThread().getName() + "], hashCode=[" + hashCode() + "]"); + LOGGER.debug("QCF: [" + getQueueConnectionFactory() + "], sendQueue=[" + getSendQueue() + "]"); + LOGGER.debug("Timeout = " + getTimeout() + "]"); + LOGGER.debug("Use temporary queue =" + useTemporyQueue() + "]"); + LOGGER.debug("Reply queue =" + getReceiveQueue() + "]"); + } + } + + private int getTimeoutAsInt() { + if (getPropertyAsInt(TIMEOUT) < 1) { + return DEFAULT_TIMEOUT; + } + return getPropertyAsInt(TIMEOUT); + } + + public String getTimeout() { + return getPropertyAsString(TIMEOUT, DEFAULT_TIMEOUT_STRING); + } + + public String getExpiration() { + String expiration = getPropertyAsString(JMS_EXPIRATION); + if (expiration.length() == 0) { + return Utils.DEFAULT_NO_EXPIRY; + } else { + return expiration; + } + } + + public String getPriority() { + String priority = getPropertyAsString(JMS_PRIORITY); + if (priority.length() == 0) { + return Utils.DEFAULT_PRIORITY_4; + } else { + return priority; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void threadFinished() { + LOGGER.debug("Thread ended " + new Date()); + + if (context != null) { + try { + context.close(); + } catch (NamingException ignored) { + // ignore + } + } + Utils.close(session, LOGGER); + Utils.close(connection, LOGGER); + if (receiverThread != null) { + receiverThread.deactivate(); + } + } + + private boolean useTemporyQueue() { + String recvQueue = getReceiveQueue(); + return recvQueue == null || recvQueue.trim().length() == 0; + } + + public void setArguments(Arguments args) { + setProperty(new TestElementProperty(JMSSampler.JMS_PROPERTIES, args)); + } + + public Arguments getArguments(String name) { + return (Arguments) getProperty(name).getObjectValue(); + } + + public void setTimeout(String s) { + setProperty(JMSSampler.TIMEOUT, s); + } + + public void setPriority(String s) { + setProperty(JMSSampler.JMS_PRIORITY, s, Utils.DEFAULT_PRIORITY_4); + } + + public void setExpiration(String s) { + setProperty(JMSSampler.JMS_EXPIRATION, s, Utils.DEFAULT_NO_EXPIRY); + } + + /** + * @return String JMS Selector + */ + public String getJMSSelector() { + return getPropertyAsString(JMSSampler.JMS_SELECTOR, JMS_SELECTOR_DEFAULT); + } + + /** + * @param selector String selector + */ + public void setJMSSelector(String selector) { + setProperty(JMSSampler.JMS_SELECTOR, selector, JMS_SELECTOR_DEFAULT); + } + + /** + * @param string name of the initial context factory to use + */ + public void setInitialContextFactory(String string) { + setProperty(JNDI_INITIAL_CONTEXT_FACTORY, string); + + } + + /** + * @param string url of the provider + */ + public void setContextProvider(String string) { + setProperty(JNDI_CONTEXT_PROVIDER_URL, string); + + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/MessageAdmin.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/MessageAdmin.java new file mode 100644 index 00000000000..a8dcf934bd8 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/MessageAdmin.java @@ -0,0 +1,162 @@ +/* + * 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.protocol.jms.sampler; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; + +import javax.jms.Message; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Administration of messages. + * + */ +public class MessageAdmin { + private static final MessageAdmin SINGLETON = new MessageAdmin(); + + private final Map table = new ConcurrentHashMap(); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private MessageAdmin() { + } + + /** + * Get the singleton MessageAdmin object + * + * @return singleton instance + */ + public static MessageAdmin getAdmin() { + return SINGLETON; + } + + /** + * Store a request under the given id, so that an arriving reply can be + * associated with this request and the waiting party can be signaled by + * means of a {@link CountDownLatch} + * + * @param id + * id of the request + * @param request + * request object to store under id + * @param latch + * communication latch to signal when a reply for this request + * was received + */ + public void putRequest(String id, Message request, CountDownLatch latch) { + if (log.isDebugEnabled()) { + log.debug("REQ_ID [" + id + "]"); + } + table.put(id, new PlaceHolder(request, latch)); + } + + /** + * Try to associate a reply to a previously stored request. If a matching + * request is found, the owner of the request will be notified with the + * registered {@link CountDownLatch} + * + * @param id + * id of the request + * @param reply + * object with the reply + */ + public void putReply(String id, Message reply) { + PlaceHolder holder = table.get(id); + if (log.isDebugEnabled()) { + log.debug("RPL_ID [" + id + "] for holder " + holder); + } + if (holder != null) { + holder.setReply(reply); + CountDownLatch latch = holder.getLatch(); + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName()+" releasing latch : " + latch); + } + latch.countDown(); + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName()+" released latch : " + latch); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Failed to match reply: " + reply); + } + } + } + + /** + * Get the reply message. + * + * @param id + * the id of the message + * @return the received message or null + */ + public Message get(String id) { + PlaceHolder holder = table.remove(id); + if (log.isDebugEnabled()) { + log.debug("GET_ID [" + id + "] for " + holder); + } + if (holder == null || !holder.hasReply()) { + log.debug("Message with " + id + " not found."); + } + return holder==null ? null : (Message) holder.getReply(); + } +} + +class PlaceHolder { + private final CountDownLatch latch; + private final Object request; + + private Object reply; + + PlaceHolder(Object original, CountDownLatch latch) { + this.request = original; + this.latch = latch; + } + + void setReply(Object reply) { + this.reply = reply; + } + + public Object getReply() { + return reply; + } + + public Object getRequest() { + return request; + } + + boolean hasReply() { + return reply != null; + } + + @Override + public String toString() { + return "request=" + request + ", reply=" + reply; + } + + /** + * @return the latch + */ + public CountDownLatch getLatch() { + return latch; + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/PublisherSampler.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/PublisherSampler.java new file mode 100644 index 00000000000..08b985aa43e --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/PublisherSampler.java @@ -0,0 +1,563 @@ +/* + * 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.protocol.jms.sampler; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.jms.DeliveryMode; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.naming.NamingException; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jmeter.protocol.jms.client.ClientPool; +import org.apache.jmeter.protocol.jms.client.InitialContextFactory; +import org.apache.jmeter.protocol.jms.client.Publisher; +import org.apache.jmeter.protocol.jms.control.gui.JMSPublisherGui; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.io.TextFile; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.XStream; + +/** + * This class implements the JMS Publisher sampler. + */ +public class PublisherSampler extends BaseJMSSampler implements TestStateListener { + + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //++ These are JMX file names and must not be changed + private static final String INPUT_FILE = "jms.input_file"; //$NON-NLS-1$ + + private static final String RANDOM_PATH = "jms.random_path"; //$NON-NLS-1$ + + private static final String TEXT_MSG = "jms.text_message"; //$NON-NLS-1$ + + private static final String CONFIG_CHOICE = "jms.config_choice"; //$NON-NLS-1$ + + private static final String MESSAGE_CHOICE = "jms.config_msg_type"; //$NON-NLS-1$ + + private static final String NON_PERSISTENT_DELIVERY = "jms.non_persistent"; //$NON-NLS-1$ + + private static final String JMS_PROPERTIES = "jms.jmsProperties"; // $NON-NLS-1$ + + private static final String JMS_PRIORITY = "jms.priority"; // $NON-NLS-1$ + + private static final String JMS_EXPIRATION = "jms.expiration"; // $NON-NLS-1$ + + //-- + + // Does not need to be synch. because it is only accessed from the sampler thread + // The ClientPool does access it in a different thread, but ClientPool is fully synch. + private transient Publisher publisher = null; + + private static final FileServer FSERVER = FileServer.getFileServer(); + + // Cache for file. Only used by sample() in a single thread + private String file_contents = null; + // Cache for object-message, only used when parsing from a file because in text-area + // property replacement might have been used + private Serializable object_msg_file_contents = null; + // Cache for bytes-message, only used when parsing from a file + private byte[] bytes_msg_file_contents = null; + + public PublisherSampler() { + } + + /** + * the implementation calls testStarted() without any parameters. + */ + @Override + public void testStarted(String test) { + testStarted(); + } + + /** + * the implementation calls testEnded() without any parameters. + */ + @Override + public void testEnded(String host) { + testEnded(); + } + + /** + * endTest cleans up the client + */ + @Override + public void testEnded() { + log.debug("PublisherSampler.testEnded called"); + ClientPool.clearClient(); + InitialContextFactory.close(); + } + + @Override + public void testStarted() { + } + + /** + * initialize the Publisher client. + * @throws JMSException + * @throws NamingException + * + */ + private void initClient() throws JMSException, NamingException { + publisher = new Publisher(getUseJNDIPropertiesAsBoolean(), getJNDIInitialContextFactory(), + getProviderUrl(), getConnectionFactory(), getDestination(), isUseAuth(), getUsername(), + getPassword(), isDestinationStatic()); + ClientPool.addClient(publisher); + log.debug("PublisherSampler.initClient called"); + } + + /** + * The implementation will publish n messages within a for loop. Once n + * messages are published, it sets the attributes of SampleResult. + * + * @return the populated sample result + */ + @Override + public SampleResult sample() { + SampleResult result = new SampleResult(); + result.setSampleLabel(getName()); + result.setSuccessful(false); // Assume it will fail + result.setResponseCode("000"); // ditto $NON-NLS-1$ + if (publisher == null) { + try { + initClient(); + } catch (JMSException e) { + result.setResponseMessage(e.toString()); + return result; + } catch (NamingException e) { + result.setResponseMessage(e.toString()); + return result; + } + } + StringBuilder buffer = new StringBuilder(); + StringBuilder propBuffer = new StringBuilder(); + int loop = getIterationCount(); + result.sampleStart(); + String type = getMessageChoice(); + + try { + Map msgProperties = getJMSProperties().getJmsPropertysAsMap(); + int deliveryMode = getUseNonPersistentDelivery() ? DeliveryMode.NON_PERSISTENT : DeliveryMode.PERSISTENT; + int priority = Integer.parseInt(getPriority()); + long expiration = Long.parseLong(getExpiration()); + + for (int idx = 0; idx < loop; idx++) { + if (JMSPublisherGui.TEXT_MSG_RSC.equals(type)){ + String tmsg = getMessageContent(); + Message msg = publisher.publish(tmsg, getDestination(), msgProperties, deliveryMode, priority, expiration); + buffer.append(tmsg); + Utils.messageProperties(propBuffer, msg); + } else if (JMSPublisherGui.MAP_MSG_RSC.equals(type)){ + Map m = getMapContent(); + Message msg = publisher.publish(m, getDestination(), msgProperties, deliveryMode, priority, expiration); + Utils.messageProperties(propBuffer, msg); + } else if (JMSPublisherGui.OBJECT_MSG_RSC.equals(type)){ + Serializable omsg = getObjectContent(); + Message msg = publisher.publish(omsg, getDestination(), msgProperties, deliveryMode, priority, expiration); + Utils.messageProperties(propBuffer, msg); + } else if (JMSPublisherGui.BYTES_MSG_RSC.equals(type)){ + byte[] bmsg = getBytesContent(); + Message msg = publisher.publish(bmsg, getDestination(), msgProperties, deliveryMode, priority, expiration); + Utils.messageProperties(propBuffer, msg); + } else { + throw new JMSException(type+ " is not recognised"); + } + } + result.setResponseCodeOK(); + result.setResponseMessage(loop + " messages published"); + result.setSuccessful(true); + result.setSamplerData(buffer.toString()); + result.setSampleCount(loop); + result.setRequestHeaders(propBuffer.toString()); + } catch (Exception e) { + result.setResponseMessage(e.toString()); + } finally { + result.sampleEnd(); + } + return result; + } + + private Map getMapContent() throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { + Map m = new HashMap(); + String text = getMessageContent(); + String[] lines = text.split("\n"); + for (String line : lines){ + String[] parts = line.split(",",3); + if (parts.length != 3) { + throw new IllegalArgumentException("line must have 3 parts: "+line); + } + String name = parts[0]; + String type = parts[1]; + if (!type.contains(".")){// Allow shorthand names + type = "java.lang."+type; + } + String value = parts[2]; + Object obj; + if (type.equals("java.lang.String")){ + obj = value; + } else { + Class clazz = Class.forName(type); + Method method = clazz.getMethod("valueOf", new Class[]{String.class}); + obj = method.invoke(clazz, value); + } + m.put(name, obj); + } + return m; + } + + /** + * Method will check the setting and get the contents for the message. + * + * @return the contents for the message + */ + private String getMessageContent() { + if (getConfigChoice().equals(JMSPublisherGui.USE_FILE_RSC)) { + // in the case the test uses a file, we set it locally and + // prevent loading the file repeatedly + if (file_contents == null) { + file_contents = getFileContent(getInputFile()); + } + return file_contents; + } else if (getConfigChoice().equals(JMSPublisherGui.USE_RANDOM_RSC)) { + // Maybe we should consider creating a global cache for the + // random files to make JMeter more efficient. + String fname = FSERVER.getRandomFile(getRandomPath(), new String[] { ".txt", ".obj" }) + .getAbsolutePath(); + return getFileContent(fname); + } else { + return getTextMessage(); + } + } + + /** + * The implementation uses TextFile to load the contents of the file and + * returns a string. + * + * @param path path to the file to read in + * @return the contents of the file + */ + public String getFileContent(String path) { + TextFile tf = new TextFile(path); + return tf.getText(); + } + + /** + * This method will load the contents for the JMS Object Message. + * The contents are either loaded from file (might be cached), random file + * or from the GUI text-area. + * + * @return Serialized object as loaded from the specified input file + */ + private Serializable getObjectContent() { + if (getConfigChoice().equals(JMSPublisherGui.USE_FILE_RSC)) { + // in the case the test uses a file, we set it locally and + // prevent loading the file repeatedly + if (object_msg_file_contents == null) { + object_msg_file_contents = getFileObjectContent(getInputFile()); + } + + return object_msg_file_contents; + } else if (getConfigChoice().equals(JMSPublisherGui.USE_RANDOM_RSC)) { + // Maybe we should consider creating a global cache for the + // random files to make JMeter more efficient. + final String fname = FSERVER.getRandomFile(getRandomPath(), new String[] {".txt", ".obj"}) + .getAbsolutePath(); + + return getFileObjectContent(fname); + } else { + final String xmlMessage = getTextMessage(); + return transformXmlToObjectMessage(xmlMessage); + } + } + + /** + * This method will load the contents for the JMS BytesMessage. + * The contents are either loaded from file (might be cached), random file + * + * @return byte[] as loaded from the specified input file + * @since 2.9 + */ + private byte[] getBytesContent() { + if (getConfigChoice().equals(JMSPublisherGui.USE_FILE_RSC)) { + // in the case the test uses a file, we set it locally and + // prevent loading the file repeatedly + if (bytes_msg_file_contents == null) { + bytes_msg_file_contents = getFileBytesContent(getInputFile()); + } + + return bytes_msg_file_contents; + } else if (getConfigChoice().equals(JMSPublisherGui.USE_RANDOM_RSC)) { + final String fname = FSERVER.getRandomFile(getRandomPath(), new String[] {".dat"}) + .getAbsolutePath(); + + return getFileBytesContent(fname); + } else { + throw new IllegalArgumentException("Type of input not handled:" + getConfigChoice()); + } + } + + /** + * Try to load an object from a provided file, so that it can be used as body + * for a JMS message. + * An {@link IllegalStateException} will be thrown if loading the object fails. + * + * @param path Path to the file that will be serialized + * @return byte[] instance + * @since 2.9 + */ + private static byte[] getFileBytesContent(final String path) { + InputStream inputStream = null; + try { + File file = new File(path); + inputStream = new BufferedInputStream(new FileInputStream(file)); + return IOUtils.toByteArray(inputStream, (int)file.length()); + } catch (Exception e) { + log.error(e.getLocalizedMessage(), e); + throw new IllegalStateException("Unable to load file", e); + } finally { + JOrphanUtils.closeQuietly(inputStream); + } + } + + /** + * Try to load an object from a provided file, so that it can be used as body + * for a JMS message. + * An {@link IllegalStateException} will be thrown if loading the object fails. + * + * @param path Path to the file that will be serialized + * @return Serialized object instance + */ + private static Serializable getFileObjectContent(final String path) { + Serializable readObject = null; + InputStream inputStream = null; + try { + inputStream = new BufferedInputStream(new FileInputStream(path)); + XStream xstream = new XStream(); + readObject = (Serializable) xstream.fromXML(inputStream, readObject); + } catch (Exception e) { + log.error(e.getLocalizedMessage(), e); + throw new IllegalStateException("Unable to load object instance from file", e); + } finally { + JOrphanUtils.closeQuietly(inputStream); + } + return readObject; + } + + /** + * Try to load an object via XStream from XML text, so that it can be used as body + * for a JMS message. + * An {@link IllegalStateException} will be thrown if transforming the XML to an object fails. + * + * @param xmlMessage String containing XML text as input for the transformation + * @return Serialized object instance + */ + private static Serializable transformXmlToObjectMessage(final String xmlMessage) { + Serializable readObject = null; + try { + XStream xstream = new XStream(); + readObject = (Serializable) xstream.fromXML(xmlMessage, readObject); + } catch (Exception e) { + log.error(e.getLocalizedMessage(), e); + throw new IllegalStateException("Unable to load object instance from text", e); + } + return readObject; + } + + // ------------- get/set properties ----------------------// + /** + * set the source of the message + * + * @param choice + * source of the messages. One of + * {@link JMSPublisherGui#USE_FILE_RSC}, + * {@link JMSPublisherGui#USE_RANDOM_RSC} or + * JMSPublisherGui#USE_TEXT_RSC + */ + public void setConfigChoice(String choice) { + setProperty(CONFIG_CHOICE, choice); + } + + // These static variables are only used to convert existing files + private static final String USE_FILE_LOCALNAME = JMeterUtils.getResString(JMSPublisherGui.USE_FILE_RSC); + private static final String USE_RANDOM_LOCALNAME = JMeterUtils.getResString(JMSPublisherGui.USE_RANDOM_RSC); + + /** + * return the source of the message + * Converts from old JMX files which used the local language string + * + * @return source of the messages + */ + public String getConfigChoice() { + // Allow for the old JMX file which used the local language string + String config = getPropertyAsString(CONFIG_CHOICE); + if (config.equals(USE_FILE_LOCALNAME) + || config.equals(JMSPublisherGui.USE_FILE_RSC)){ + return JMSPublisherGui.USE_FILE_RSC; + } + if (config.equals(USE_RANDOM_LOCALNAME) + || config.equals(JMSPublisherGui.USE_RANDOM_RSC)){ + return JMSPublisherGui.USE_RANDOM_RSC; + } + return config; // will be the 3rd option, which is not checked specifically + } + + /** + * set the type of the message + * + * @param choice type of the message (Text, Object, Map) + */ + public void setMessageChoice(String choice) { + setProperty(MESSAGE_CHOICE, choice); + } + + /** + * @return the type of the message (Text, Object, Map) + * + */ + public String getMessageChoice() { + return getPropertyAsString(MESSAGE_CHOICE); + } + + /** + * set the input file for the publisher + * + * @param file input file for the publisher + */ + public void setInputFile(String file) { + setProperty(INPUT_FILE, file); + } + + /** + * @return the path of the input file + * + */ + public String getInputFile() { + return getPropertyAsString(INPUT_FILE); + } + + /** + * set the random path for the messages + * + * @param path random path for the messages + */ + public void setRandomPath(String path) { + setProperty(RANDOM_PATH, path); + } + + /** + * @return the random path for messages + * + */ + public String getRandomPath() { + return getPropertyAsString(RANDOM_PATH); + } + + /** + * set the text for the message + * + * @param message text for the message + */ + public void setTextMessage(String message) { + setProperty(TEXT_MSG, message); + } + + /** + * @return the text for the message + * + */ + public String getTextMessage() { + return getPropertyAsString(TEXT_MSG); + } + + public String getExpiration() { + return getPropertyAsString(JMS_EXPIRATION, Utils.DEFAULT_NO_EXPIRY); + } + + public String getPriority() { + return getPropertyAsString(JMS_PRIORITY, Utils.DEFAULT_PRIORITY_4); + } + + public void setPriority(String s) { + setProperty(JMS_PRIORITY, s, Utils.DEFAULT_PRIORITY_4); + } + + public void setExpiration(String s) { + setProperty(JMS_EXPIRATION, s, Utils.DEFAULT_NO_EXPIRY); + } + + /** + * @param value boolean use NON_PERSISTENT + */ + public void setUseNonPersistentDelivery(boolean value) { + setProperty(NON_PERSISTENT_DELIVERY, value, false); + } + + /** + * @return true if NON_PERSISTENT delivery must be used + */ + public boolean getUseNonPersistentDelivery() { + return getPropertyAsBoolean(NON_PERSISTENT_DELIVERY, false); + } + + /** + * @return {@link JMSProperties} JMS Properties + */ + public JMSProperties getJMSProperties() { + Object o = getProperty(JMS_PROPERTIES).getObjectValue(); + JMSProperties jmsProperties = null; + // Backward compatibility with versions <= 2.10 + if(o instanceof Arguments) { + jmsProperties = Utils.convertArgumentsToJmsProperties((Arguments)o); + } else { + jmsProperties = (JMSProperties) o; + } + if(jmsProperties == null) { + jmsProperties = new JMSProperties(); + setJMSProperties(jmsProperties); + } + return jmsProperties; + } + + /** + * @param jmsProperties JMS Properties + */ + public void setJMSProperties(JMSProperties jmsProperties) { + setProperty(new TestElementProperty(JMS_PROPERTIES, jmsProperties)); + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/QueueExecutor.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/QueueExecutor.java new file mode 100644 index 00000000000..7882bc7bade --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/QueueExecutor.java @@ -0,0 +1,47 @@ +/* + * 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.protocol.jms.sampler; + +import javax.jms.JMSException; +import javax.jms.Message; + +/** + * Executor for (pseudo) synchronous communication.
+ * Created on: October 28, 2004 + * + * @version $Revision$ + */ +public interface QueueExecutor { + /** + * Sends and receives a message. + * + * @param request the message to send + * @param deliveryMode the delivery mode to use + * @param priority the priority for this message + * @param expiration messages lifetime in ms + * @return the received message or null + * @throws JMSException + * in case of an exception from the messaging system + */ + Message sendAndReceive(Message request, + int deliveryMode, + int priority, + long expiration) throws JMSException; + +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/Receiver.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/Receiver.java new file mode 100644 index 00000000000..08aaea2b82b --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/Receiver.java @@ -0,0 +1,154 @@ +/* + * 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.protocol.jms.sampler; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.Session; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Receiver of pseudo-synchronous reply messages. + * + */ +public final class Receiver implements Runnable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private volatile boolean active; + + private final Session session; + + private final MessageConsumer consumer; + + private final Connection conn; + + private final boolean useResMsgIdAsCorrelId; + + + /** + * Constructor + * @param factory + * @param receiveQueue Receive Queue + * @param principal Username + * @param credentials Password + * @param useResMsgIdAsCorrelId + * @param jmsSelector JMS Selector + * @throws JMSException + */ + private Receiver(ConnectionFactory factory, Destination receiveQueue, String principal, String credentials, boolean useResMsgIdAsCorrelId, String jmsSelector) throws JMSException { + if (null != principal && null != credentials) { + log.info("creating receiver WITH authorisation credentials. UseResMsgId="+useResMsgIdAsCorrelId); + conn = factory.createConnection(principal, credentials); + }else{ + log.info("creating receiver without authorisation credentials. UseResMsgId="+useResMsgIdAsCorrelId); + conn = factory.createConnection(); + } + session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + if(log.isDebugEnabled()) { + log.debug("Receiver - ctor. Creating consumer with JMS Selector:"+jmsSelector); + } + if(StringUtils.isEmpty(jmsSelector)) { + consumer = session.createConsumer(receiveQueue); + } else { + consumer = session.createConsumer(receiveQueue, jmsSelector); + } + this.useResMsgIdAsCorrelId = useResMsgIdAsCorrelId; + log.debug("Receiver - ctor. Starting connection now"); + conn.start(); + log.info("Receiver - ctor. Connection to messaging system established"); + } + + /** + * Create a receiver to process responses. + * + * @param factory + * connection factory to use + * @param receiveQueue + * name of the receiving queue + * @param principal + * user name to use for connecting to the queue + * @param credentials + * credentials to use for connecting to the queue + * @param useResMsgIdAsCorrelId + * true if should use JMSMessageId, + * false if should use JMSCorrelationId + * @param jmsSelector + * JMS selector + * @return the Receiver which will process the responses + * @throws JMSException + * when creating the receiver fails + */ + public static Receiver createReceiver(ConnectionFactory factory, Destination receiveQueue, + String principal, String credentials, boolean useResMsgIdAsCorrelId, String jmsSelector) + throws JMSException { + Receiver receiver = new Receiver(factory, receiveQueue, principal, credentials, useResMsgIdAsCorrelId, jmsSelector); + Thread thread = new Thread(receiver, Thread.currentThread().getName()+"-JMS-Receiver"); + thread.start(); + return receiver; + } + + @Override + public void run() { + active = true; + Message reply; + + while (active) { + reply = null; + try { + reply = consumer.receive(5000); + if (reply != null) { + String messageKey; + final MessageAdmin admin = MessageAdmin.getAdmin(); + if (useResMsgIdAsCorrelId){ + messageKey = reply.getJMSMessageID(); + synchronized (admin) {// synchronize with FixedQueueExecutor + admin.putReply(messageKey, reply); + } + } else { + messageKey = reply.getJMSCorrelationID(); + if (messageKey == null) {// JMSMessageID cannot be null + log.warn("Received message with correlation id null. Discarding message ..."); + } else { + admin.putReply(messageKey, reply); + } + } + } + + } catch (JMSException e1) { + log.error("Error handling receive",e1); + } + } + Utils.close(consumer, log); + Utils.close(session, log); + Utils.close(conn, log); + } + + public void deactivate() { + active = false; + } + +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/SubscriberSampler.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/SubscriberSampler.java new file mode 100644 index 00000000000..3787f5fbee6 --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/SubscriberSampler.java @@ -0,0 +1,512 @@ +/* + * 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.protocol.jms.sampler; + +import java.util.Enumeration; + +import javax.jms.BytesMessage; +import javax.jms.JMSException; +import javax.jms.MapMessage; +import javax.jms.Message; +import javax.jms.ObjectMessage; +import javax.jms.TextMessage; +import javax.naming.NamingException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jmeter.protocol.jms.client.InitialContextFactory; +import org.apache.jmeter.protocol.jms.client.ReceiveSubscriber; +import org.apache.jmeter.protocol.jms.control.gui.JMSSubscriberGui; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class implements the JMS Subscriber sampler. + * It supports both receive and onMessage strategies via the ReceiveSubscriber class. + * + */ +// TODO: do we need to implement any kind of connection pooling? +// If so, which connections should be shared? +// Should threads share connections to the same destination? +// What about cross-thread sharing? + +// Note: originally the code did use the ClientPool to "share" subscribers, however since the +// key was "this" and each sampler is unique - nothing was actually shared. + +public class SubscriberSampler extends BaseJMSSampler implements Interruptible, ThreadListener, TestStateListener { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Default wait (ms) for a message if timeouts are not enabled + // This is the maximum time the sampler can be blocked. + private static final long DEFAULT_WAIT = 500L; + + // No need to synch/ - only used by sampler + // Note: not currently added to the ClientPool + private transient ReceiveSubscriber SUBSCRIBER = null; + + private transient volatile boolean interrupted = false; + + private transient long timeout; + + private transient boolean useReceive; + + // This will be null if initialization succeeds. + private transient Exception exceptionDuringInit; + + // If true, start/stop subscriber for each sample + private transient boolean stopBetweenSamples; + + // Don't change the string, as it is used in JMX files + private static final String CLIENT_CHOICE = "jms.client_choice"; // $NON-NLS-1$ + private static final String TIMEOUT = "jms.timeout"; // $NON-NLS-1$ + private static final String TIMEOUT_DEFAULT = ""; // $NON-NLS-1$ + private static final String DURABLE_SUBSCRIPTION_ID = "jms.durableSubscriptionId"; // $NON-NLS-1$ + private static final String CLIENT_ID = "jms.clientId"; // $NON-NLS-1$ + private static final String JMS_SELECTOR = "jms.selector"; // $NON-NLS-1$ + private static final String DURABLE_SUBSCRIPTION_ID_DEFAULT = ""; + private static final String CLIENT_ID_DEFAULT = ""; // $NON-NLS-1$ + private static final String JMS_SELECTOR_DEFAULT = ""; // $NON-NLS-1$ + private static final String STOP_BETWEEN = "jms.stop_between_samples"; // $NON-NLS-1$ + private static final String SEPARATOR = "jms.separator"; // $NON-NLS-1$ + private static final String SEPARATOR_DEFAULT = ""; // $NON-NLS-1$ + + + private transient boolean START_ON_SAMPLE = false; + + private transient String separator; + + public SubscriberSampler() { + super(); + } + + /** + * Create the OnMessageSubscriber client and set the sampler as the message + * listener. + * @throws JMSException + * @throws NamingException + * + */ + private void initListenerClient() throws JMSException, NamingException { + SUBSCRIBER = new ReceiveSubscriber(0, getUseJNDIPropertiesAsBoolean(), getJNDIInitialContextFactory(), + getProviderUrl(), getConnectionFactory(), getDestination(), getDurableSubscriptionId(), + getClientId(), getJmsSelector(), isUseAuth(), getUsername(), getPassword()); + setupSeparator(); + log.debug("SubscriberSampler.initListenerClient called"); + } + + /** + * Create the ReceiveSubscriber client for the sampler. + * @throws NamingException + * @throws JMSException + */ + private void initReceiveClient() throws NamingException, JMSException { + SUBSCRIBER = new ReceiveSubscriber(getUseJNDIPropertiesAsBoolean(), + getJNDIInitialContextFactory(), getProviderUrl(), getConnectionFactory(), getDestination(), + getDurableSubscriptionId(), getClientId(), getJmsSelector(), isUseAuth(), getUsername(), getPassword()); + setupSeparator(); + log.debug("SubscriberSampler.initReceiveClient called"); + } + + /** + * sample method will check which client it should use and call the + * appropriate client specific sample method. + * + * @return the appropriate sample result + */ + // TODO - should we call start() and stop()? + @Override + public SampleResult sample() { + // run threadStarted only if Destination setup on each sample + if (!isDestinationStatic()) { + threadStarted(true); + } + SampleResult result = new SampleResult(); + result.setDataType(SampleResult.TEXT); + result.setSampleLabel(getName()); + result.sampleStart(); + if (exceptionDuringInit != null) { + result.sampleEnd(); + result.setSuccessful(false); + result.setResponseCode("000"); + result.setResponseMessage(exceptionDuringInit.toString()); + return result; + } + if (stopBetweenSamples){ // If so, we need to start collection here + try { + SUBSCRIBER.start(); + } catch (JMSException e) { + log.warn("Problem starting subscriber", e); + } + } + StringBuilder buffer = new StringBuilder(); + StringBuilder propBuffer = new StringBuilder(); + + int loop = getIterationCount(); + int read = 0; + + long until = 0L; + long now = System.currentTimeMillis(); + if (timeout > 0) { + until = timeout + now; + } + while (!interrupted + && (until == 0 || now < until) + && read < loop) { + Message msg; + try { + msg = SUBSCRIBER.getMessage(calculateWait(until, now)); + if (msg != null){ + read++; + extractContent(buffer, propBuffer, msg, (read == loop)); + } + } catch (JMSException e) { + log.warn("Error "+e.toString()); + } + now = System.currentTimeMillis(); + } + result.sampleEnd(); + result.setResponseMessage(read + " samples messages received"); + if (getReadResponseAsBoolean()) { + result.setResponseData(buffer.toString().getBytes()); // TODO - charset? + } else { + result.setBytes(buffer.toString().getBytes().length); // TODO - charset? + } + result.setResponseHeaders(propBuffer.toString()); + if (read == 0) { + result.setResponseCode("404"); // Not found + result.setSuccessful(false); + } else { // TODO set different status if not enough messages found? + result.setResponseCodeOK(); + result.setSuccessful(true); + } + result.setResponseMessage(read + " message(s) received successfully"); + result.setSamplerData(loop + " messages expected"); + result.setSampleCount(read); + + if (stopBetweenSamples){ + try { + SUBSCRIBER.stop(); + } catch (JMSException e) { + log.warn("Problem stopping subscriber", e); + } + } + // run threadFinished only if Destination setup on each sample (stop Listen queue) + if (!isDestinationStatic()) { + threadFinished(true); + } + return result; + } + + /** + * Calculate the wait time, will never be more than DEFAULT_WAIT. + * + * @param until target end time or 0 if timeouts not active + * @param now current time + * @return wait time + */ + private long calculateWait(long until, long now) { + if (until == 0) return DEFAULT_WAIT; // Timeouts not active + long wait = until - now; // How much left + return wait > DEFAULT_WAIT ? DEFAULT_WAIT : wait; + } + + private void extractContent(StringBuilder buffer, StringBuilder propBuffer, + Message msg, boolean isLast) { + if (msg != null) { + try { + if (msg instanceof TextMessage){ + buffer.append(((TextMessage) msg).getText()); + } else if (msg instanceof ObjectMessage){ + ObjectMessage objectMessage = (ObjectMessage) msg; + if(objectMessage.getObject() != null) { + buffer.append(objectMessage.getObject().getClass()); + } else { + buffer.append("object is null"); + } + } else if (msg instanceof BytesMessage){ + BytesMessage bytesMessage = (BytesMessage) msg; + buffer.append(bytesMessage.getBodyLength() + " bytes received in BytesMessage"); + } else if (msg instanceof MapMessage){ + MapMessage mapm = (MapMessage) msg; + @SuppressWarnings("unchecked") // MapNames are Strings + Enumeration enumb = mapm.getMapNames(); + while(enumb.hasMoreElements()){ + String name = enumb.nextElement(); + Object obj = mapm.getObject(name); + buffer.append(name); + buffer.append(","); + buffer.append(obj.getClass().getCanonicalName()); + buffer.append(","); + buffer.append(obj); + buffer.append("\n"); + } + } + Utils.messageProperties(propBuffer, msg); + if(!isLast && !StringUtils.isEmpty(separator)) { + propBuffer.append(separator); + buffer.append(separator); + } + } catch (JMSException e) { + log.error(e.getMessage()); + } + } + } + + /** + * Initialise the thread-local variables. + *
+ * {@inheritDoc} + */ + @Override + public void threadStarted() { + // Disabled thread start if listen on sample choice + if (isDestinationStatic() || START_ON_SAMPLE) { + timeout = getTimeoutAsLong(); + interrupted = false; + exceptionDuringInit = null; + useReceive = getClientChoice().equals(JMSSubscriberGui.RECEIVE_RSC); + stopBetweenSamples = isStopBetweenSamples(); + if (useReceive) { + try { + initReceiveClient(); + if (!stopBetweenSamples){ // Don't start yet if stop between samples + SUBSCRIBER.start(); + } + } catch (NamingException e) { + exceptionDuringInit = e; + } catch (JMSException e) { + exceptionDuringInit = e; + } + } else { + try { + initListenerClient(); + if (!stopBetweenSamples){ // Don't start yet if stop between samples + SUBSCRIBER.start(); + } + } catch (JMSException e) { + exceptionDuringInit = e; + } catch (NamingException e) { + exceptionDuringInit = e; + } + } + if (exceptionDuringInit != null){ + log.error("Could not initialise client",exceptionDuringInit); + } + } + } + + public void threadStarted(boolean wts) { + if (wts) { + START_ON_SAMPLE = true; // listen on sample + } + threadStarted(); + } + + /** + * Close subscriber. + *
+ * {@inheritDoc} + */ + @Override + public void threadFinished() { + if (SUBSCRIBER != null){ // Can be null if init fails + SUBSCRIBER.close(); + } + } + + public void threadFinished(boolean wts) { + if (wts) { + START_ON_SAMPLE = false; // listen on sample + } + threadFinished(); + } + + /** + * Handle an interrupt of the test. + */ + @Override + public boolean interrupt() { + boolean oldvalue = interrupted; + interrupted = true; // so we break the loops in SampleWithListener and SampleWithReceive + return !oldvalue; + } + + // ----------- get/set methods ------------------- // + /** + * Set the client choice. There are two options: ReceiveSusbscriber and + * OnMessageSubscriber. + * + * @param choice + * the client to use. One of {@link JMSSubscriberGui#RECEIVE_RSC + * RECEIVE_RSC} or {@link JMSSubscriberGui#ON_MESSAGE_RSC + * ON_MESSAGE_RSC} + */ + public void setClientChoice(String choice) { + setProperty(CLIENT_CHOICE, choice); + } + + /** + * Return the client choice. + * + * @return the client choice, either {@link JMSSubscriberGui#RECEIVE_RSC + * RECEIVE_RSC} or {@link JMSSubscriberGui#ON_MESSAGE_RSC + * ON_MESSAGE_RSC} + */ + public String getClientChoice() { + String choice = getPropertyAsString(CLIENT_CHOICE); + // Convert the old test plan entry (which is the language dependent string) to the resource name + if (choice.equals(RECEIVE_STR)){ + choice = JMSSubscriberGui.RECEIVE_RSC; + } else if (!choice.equals(JMSSubscriberGui.RECEIVE_RSC)){ + choice = JMSSubscriberGui.ON_MESSAGE_RSC; + } + return choice; + } + + public String getTimeout(){ + return getPropertyAsString(TIMEOUT, TIMEOUT_DEFAULT); + } + + public long getTimeoutAsLong(){ + return getPropertyAsLong(TIMEOUT, 0L); + } + + public void setTimeout(String timeout){ + setProperty(TIMEOUT, timeout, TIMEOUT_DEFAULT); + } + + public String getDurableSubscriptionId(){ + return getPropertyAsString(DURABLE_SUBSCRIPTION_ID); + } + + /** + * @return JMS Client ID + */ + public String getClientId() { + return getPropertyAsString(CLIENT_ID, CLIENT_ID_DEFAULT); + } + + /** + * @return JMS selector + */ + public String getJmsSelector() { + return getPropertyAsString(JMS_SELECTOR, JMS_SELECTOR_DEFAULT); + } + + public void setDurableSubscriptionId(String durableSubscriptionId){ + setProperty(DURABLE_SUBSCRIPTION_ID, durableSubscriptionId, DURABLE_SUBSCRIPTION_ID_DEFAULT); + } + + /** + * @param clientId JMS CLient id + */ + public void setClientID(String clientId) { + setProperty(CLIENT_ID, clientId, CLIENT_ID_DEFAULT); + } + + /** + * @param jmsSelector JMS Selector + */ + public void setJmsSelector(String jmsSelector) { + setProperty(JMS_SELECTOR, jmsSelector, JMS_SELECTOR_DEFAULT); + } + + /** + * @return Separator for sampler results + */ + public String getSeparator() { + return getPropertyAsString(SEPARATOR, SEPARATOR_DEFAULT); + } + + /** + * Separator for sampler results + * + * @param text + * separator to use for sampler results + */ + public void setSeparator(String text) { + setProperty(SEPARATOR, text, SEPARATOR_DEFAULT); + } + + // This was the old value that was checked for + private static final String RECEIVE_STR = JMeterUtils.getResString(JMSSubscriberGui.RECEIVE_RSC); // $NON-NLS-1$ + + public boolean isStopBetweenSamples() { + return getPropertyAsBoolean(STOP_BETWEEN, false); + } + + public void setStopBetweenSamples(boolean selected) { + setProperty(STOP_BETWEEN, selected, false); + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded() { + InitialContextFactory.close(); + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded(String host) { + testEnded(); + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted() { + testStarted(""); + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted(String host) { + // NOOP + } + + /** + * + */ + private void setupSeparator() { + separator = getSeparator(); + separator = separator.replace("\\t", "\t"); + separator = separator.replace("\\n", "\n"); + separator = separator.replace("\\r", "\r"); + } + + private Object readResolve(){ + setupSeparator(); + exceptionDuringInit=null; + return this; + } +} diff --git a/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/TemporaryQueueExecutor.java b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/TemporaryQueueExecutor.java new file mode 100644 index 00000000000..3d2df7f32fe --- /dev/null +++ b/src/protocol/jms/org/apache/jmeter/protocol/jms/sampler/TemporaryQueueExecutor.java @@ -0,0 +1,62 @@ +/* + * 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.protocol.jms.sampler; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Queue; +import javax.jms.QueueRequestor; +import javax.jms.QueueSession; + +/** + * Request/reply executor with a temporary reply queue.
+ * + * Used by JMS Sampler (Point to Point) + */ +public class TemporaryQueueExecutor implements QueueExecutor { + /** The sender and receiver. */ + private final QueueRequestor requestor; + + /** + * Constructor. + * + * @param session + * the session to use to send the message + * @param destination + * the queue to send the message on + * @throws JMSException + * when internally used {@link QueueRequestor} can not be + * constructed with session and + * destination + */ + public TemporaryQueueExecutor(QueueSession session, Queue destination) throws JMSException { + requestor = new QueueRequestor(session, destination); + } + + /** + * {@inheritDoc} + */ + @Override + public Message sendAndReceive(Message request, + int deliveryMode, + int priority, + long expiration) throws JMSException { + return requestor.request(request); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgument.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgument.java new file mode 100644 index 00000000000..db1aacbf4ac --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgument.java @@ -0,0 +1,179 @@ +/* + * 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.protocol.ldap.config.gui; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.StringProperty; + +/******************************************************************************* + * + * Class representing an argument. Each argument consists of a name/value and + * opcode combination, as well as (optional) metadata. + * + * author Dolf Smits(Dolf.Smits@Siemens.com) created Aug 09 2003 11:00 AM + * company Siemens Netherlands N.V.. + * + * Based on the work of: + * + * author Michael Stover author Mark Walsh + */ + +public class LDAPArgument extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + // ** These constants are used in the JMX files, and so must not be changed ** + + /** Name used to store the argument's name. */ + private static final String ARG_NAME = "Argument.name"; //$NON-NLS$ + + /** Name used to store the argument's value. */ + private static final String VALUE = "Argument.value"; //$NON-NLS$ + + /** Name used to store the argument's value. */ + private static final String OPCODE = "Argument.opcode"; //$NON-NLS$ + + /** Name used to store the argument's metadata. */ + private static final String METADATA = "Argument.metadata"; //$NON-NLS$ + + /** + * Create a new Argument without a name, value, or metadata. + */ + public LDAPArgument() { + } + + /** + * Create a new Argument with the specified name and value, and no metadata. + * + * @param name + * the argument name + * @param value + * the argument value + * @param opcode + * the operation to perform, may be one of add, + * delete, remove or + * modify. + */ + public LDAPArgument(String name, String value, String opcode) { + setProperty(new StringProperty(ARG_NAME, name)); + setProperty(new StringProperty(VALUE, value)); + setProperty(new StringProperty(OPCODE, opcode)); + } + + /** + * Create a new Argument with the specified name, value, and metadata. + * + * @param name + * the argument name + * @param value + * the argument value + * @param opcode + * the operation to perform, may be one of add, + * delete, remove or + * modify. + * @param metadata + * the argument metadata + */ + public LDAPArgument(String name, String value, String opcode, String metadata) { + setProperty(new StringProperty(ARG_NAME, name)); + setProperty(new StringProperty(VALUE, value)); + setProperty(new StringProperty(OPCODE, opcode)); + setProperty(new StringProperty(METADATA, metadata)); + } + + /** + * Set the name of the Argument. + * + * @param newName + * the new name + */ + @Override + public void setName(String newName) { + setProperty(new StringProperty(ARG_NAME, newName)); + } + + /** + * Get the name of the Argument. + * + * @return the attribute's name + */ + @Override + public String getName() { + return getPropertyAsString(ARG_NAME); + } + + /** + * Sets the value of the Argument. + * + * @param newValue + * the new value + */ + public void setValue(String newValue) { + setProperty(new StringProperty(VALUE, newValue)); + } + + /** + * Gets the value of the Argument object. + * + * @return the attribute's value + */ + public String getValue() { + return getPropertyAsString(VALUE); + } + + /** + * Sets the opcode of the Argument. + * + * @param newOpcode + * the new value + */ + public void setOpcode(String newOpcode) { + setProperty(new StringProperty(OPCODE, newOpcode)); + } + + /** + * Gets the opcode of the Argument object. + * + * @return the attribute's value + */ + public String getOpcode() { + return getPropertyAsString(OPCODE); + } + + /** + * Sets the Meta Data attribute of the Argument. + * + * @param newMetaData + * the new metadata + */ + public void setMetaData(String newMetaData) { + setProperty(new StringProperty(METADATA, newMetaData)); + } + + /** + * Gets the Meta Data attribute of the Argument. + * + * @return the MetaData value + */ + public String getMetaData() { + return getPropertyAsString(METADATA); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LDAPArguments.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LDAPArguments.java new file mode 100644 index 00000000000..80c4ef2db40 --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LDAPArguments.java @@ -0,0 +1,266 @@ +/* + * 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.protocol.ldap.config.gui; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.config.ConfigTestElement; + +/** + * A set of LDAPArgument objects. author Dolf Smits(Dolf.Smits@Siemens.com) + * created Aug 09 2003 11:00 AM company Siemens Netherlands N.V.. + * + * Based on the work of: + * + * author Michael Stover author Mark Walsh + */ + +public class LDAPArguments extends ConfigTestElement implements Serializable { + private static final long serialVersionUID = 240L; + + /** The name of the property used to store the arguments. */ + public static final String ARGUMENTS = "Arguments.arguments"; //$NON-NLS$ + + /** + * Create a new Arguments object with no arguments. + */ + public LDAPArguments() { + setProperty(new CollectionProperty(ARGUMENTS, new ArrayList())); + } + + /** + * Get the arguments. + * + * @return the arguments + */ + public CollectionProperty getArguments() { + return (CollectionProperty) getProperty(ARGUMENTS); + } + + /** + * Clear the arguments. + */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(ARGUMENTS, new ArrayList())); + } + + /** + * Set the list of arguments. Any existing arguments will be lost. + * + * @param arguments + * the new arguments + */ + public void setArguments(List arguments) { + setProperty(new CollectionProperty(ARGUMENTS, arguments)); + } + + /** + * Get the arguments as a Map. Each argument name is used as the key, and + * its value as the value. + * + * @return a new Map with String keys and values containing the arguments + */ + public Map getArgumentsAsMap() { + PropertyIterator iter = getArguments().iterator(); + Map argMap = new HashMap(); + while (iter.hasNext()) { + LDAPArgument arg = (LDAPArgument) iter.next().getObjectValue(); + argMap.put(arg.getName(), arg.getValue()); + } + return argMap; + } + + /** + * Add a new argument with the given name and value. + * + * @param name + * the name of the argument + * @param value + * the value of the argument + * @param opcode + * the operation to perform, may be one of add, + * delete, remove or + * modify. + */ + public void addArgument(String name, String value, String opcode) { + addArgument(new LDAPArgument(name, value, opcode, null)); + } + + /** + * Add a new argument. + * + * @param arg + * the new argument + */ + public void addArgument(LDAPArgument arg) { + TestElementProperty newArg = new TestElementProperty(arg.getName(), arg); + if (isRunningVersion()) { + this.setTemporary(newArg); + } + getArguments().addItem(newArg); + } + + /** + * Add a new argument with the given name, value, and metadata. + * + * @param name + * the name of the argument + * @param value + * the value of the argument + * @param opcode + * the operation to perform, may be one of add, + * delete, remove or + * modify. + * @param metadata + * the metadata for the argument + */ + public void addArgument(String name, String value, String opcode, String metadata) { + addArgument(new LDAPArgument(name, value, opcode, metadata)); + } + + /** + * Get a PropertyIterator of the arguments. + * + * @return an iteration of the arguments + */ + public PropertyIterator iterator() { + return getArguments().iterator(); + } + + /** + * Create a string representation of the arguments. + * + * @return the string representation of the arguments + */ + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + LDAPArgument arg = (LDAPArgument) iter.next().getObjectValue(); + final String metaData = arg.getMetaData(); + str.append(arg.getName()); + if (metaData == null) { + str.append("="); //$NON-NLS$ + } else { + str.append(metaData); + } + str.append(arg.getValue()); + if (iter.hasNext()) { + str.append("&"); //$NON-NLS$ + } + } + return str.toString(); + } + + /** + * Remove the specified argument from the list. + * + * @param row + * the index of the argument to remove + */ + public void removeArgument(int row) { + if (row < getArguments().size()) { + getArguments().remove(row); + } + } + + /** + * Remove the specified argument from the list. + * + * @param arg + * the argument to remove + */ + public void removeArgument(LDAPArgument arg) { + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + LDAPArgument item = (LDAPArgument) iter.next().getObjectValue(); + if (arg.equals(item)) { + iter.remove(); + } + } + } + + /** + * Remove the argument with the specified name. + * + * @param argName + * the name of the argument to remove + */ + public void removeArgument(String argName) { + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + LDAPArgument arg = (LDAPArgument) iter.next().getObjectValue(); + if (arg.getName().equals(argName)) { + iter.remove(); + } + } + } + + /** + * Remove all arguments from the list. + */ + public void removeAllArguments() { + getArguments().clear(); + } + + /** + * Add a new empty argument to the list. The new argument will have the + * empty string as its name and value, and null metadata. + */ + public void addEmptyArgument() { + addArgument(new LDAPArgument("", "", "", null)); + } + + /** + * Get the number of arguments in the list. + * + * @return the number of arguments + */ + public int getArgumentCount() { + return getArguments().size(); + } + + /** + * Get a single argument. + * + * @param row + * the index of the argument to return. + * @return the argument at the specified index, or null if no argument + * exists at that index. + */ + public LDAPArgument getArgument(int row) { + LDAPArgument argument = null; + + if (row < getArguments().size()) { + argument = (LDAPArgument) getArguments().get(row).getObjectValue(); + } + + return argument; + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgumentsPanel.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgumentsPanel.java new file mode 100644 index 00000000000..34db1b1bd56 --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgumentsPanel.java @@ -0,0 +1,373 @@ +/* + * 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.protocol.ldap.config.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Iterator; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +/** + * A GUI panel allowing the user to enter name-value argument pairs. These + * arguments (or parameters) are usually used to provide configuration values + * for some other component. + * + */ + +public class LDAPArgumentsPanel extends AbstractConfigGui implements ActionListener { + + private static final long serialVersionUID = 240L; + + /** Logging. */ + //private static final Logger log = LoggingManager.getLoggerForClass(); + + /** The title label for this component. */ + private JLabel tableLabel; + + /** The table containing the list of arguments. */ + private transient JTable table; + + /** The model for the arguments table. */ + // needs to be accessible from test code + transient ObjectTableModel tableModel; // Only contains LDAPArgument entries + + /** A button for removing arguments from the table. */ + private JButton delete; + + /** Command for adding a row to the table. */ + private static final String ADD = "add"; //$NON-NLS-1$ + + /** Command for removing a row from the table. */ + private static final String DELETE = "delete"; //$NON-NLS-1$ + + private static final String[] COLUMN_NAMES = { + "attribute", //$NON-NLS-1$ + "value", //$NON-NLS-1$ + "opcode", //$NON-NLS-1$ + "metadata" }; //$NON-NLS-1$ + + /** + * Create a new LDAPArgumentsPanel, using the default title. + */ + public LDAPArgumentsPanel() { + this(JMeterUtils.getResString("paramtable")); //$NON-NLS-1$ + } + + /** + * Create a new LDAPArgumentsPanel, using the specified title. + * + * @param label + * the title of the component + */ + public LDAPArgumentsPanel(String label) { + tableLabel = new JLabel(label); + init(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. The LDAPArgumentsPanel is not intended to be used as a standalone + * component, so this inplementation returns null. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return null; + } + + @Override + public String getLabelResource() { + return "ldapext_sample_title"; // $NON-NLS-1$ + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + LDAPArguments args = new LDAPArguments(); + modifyTestElement(args); + // TODO: Why do we clone the return value? This is the only reference + // to it (right?) so we shouldn't need a separate copy. + return (TestElement) args.clone(); + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + @Override + public void modifyTestElement(TestElement args) { + GuiUtils.stopTableEditing(table); + LDAPArguments arguments = null; + if (args instanceof LDAPArguments) { + arguments = (LDAPArguments) args; + arguments.clear(); + @SuppressWarnings("unchecked") // Only contains LDAPArgument entries + Iterator modelData = (Iterator) tableModel.iterator(); + while (modelData.hasNext()) { + LDAPArgument arg = modelData.next(); + arg.setMetaData("="); + arguments.addArgument(arg); + } + } + this.configureTestElement(args); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof LDAPArguments) { + tableModel.clearData(); + PropertyIterator iter = ((LDAPArguments) el).iterator(); + while (iter.hasNext()) { + LDAPArgument arg = (LDAPArgument) iter.next().getObjectValue(); + tableModel.addRow(arg); + } + } + checkDeleteStatus(); + } + + /** + * Enable or disable the delete button depending on whether or not there is + * a row to be deleted. + */ + private void checkDeleteStatus() { + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } else { + delete.setEnabled(true); + } + } + + /** + * Clear all rows from the table. T.Elanjchezhiyan(chezhiyan@siptech.co.in) + */ + public void clear() { + tableModel.clearData(); + } + + /** + * Invoked when an action occurs. This implementation supports the add and + * delete buttons. + * + * @param e + * the event that has occurred + */ + @Override + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(DELETE)) { + deleteArgument(); + } else if (action.equals(ADD)) { + addArgument(); + } + } + + /** + * Remove the currently selected argument from the table. + */ + private void deleteArgument() { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = table.getSelectedRow(); + if (rowSelected >= 0) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } + + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + + /** + * Add a new argument row to the table. + */ + private void addArgument() { + // If a table cell is being edited, we should accept the current value + // and stop the editing before adding a new row. + GuiUtils.stopTableEditing(table); + + tableModel.addRow(makeNewLDAPArgument()); + + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + + /** + * Create a new LDAPArgument object. + * + * @return a new LDAPArgument object + */ + private LDAPArgument makeNewLDAPArgument() { + return new LDAPArgument("", "", ""); + } + + /** + * Initialize the table model used for the arguments table. + */ + private void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { COLUMN_NAMES[0], COLUMN_NAMES[1], COLUMN_NAMES[2] }, + LDAPArgument.class, + new Functor[] { new Functor("getName"), new Functor("getValue"), new Functor("getOpcode") }, + new Functor[] { new Functor("setName"), new Functor("setValue"), new Functor("setOpcode") }, + new Class[] { String.class, String.class, String.class }); + } + + public static boolean testFunctors(){ + LDAPArgumentsPanel instance = new LDAPArgumentsPanel(); + instance.initializeTableModel(); + return instance.tableModel.checkFunctors(null,instance.getClass()); + } + + /* + * protected void initializeTableModel() { tableModel = new + * ObjectTableModel( new String[] { ArgumentsPanel.COLUMN_NAMES_0, + * ArgumentsPanel.COLUMN_NAMES_1, ENCODE_OR_NOT, INCLUDE_EQUALS }, new + * Functor[] { new Functor("getName"), new Functor("getValue"), new + * Functor("isAlwaysEncoded"), new Functor("isUseEquals") }, new Functor[] { + * new Functor("setName"), new Functor("setValue"), new + * Functor("setAlwaysEncoded"), new Functor("setUseEquals") }, new Class[] { + * String.class, String.class, Boolean.class, Boolean.class }); } + */ +// /** +// * Resize the table columns to appropriate widths. +// * +// * @param _table +// * the table to resize columns for +// */ +// private void sizeColumns(JTable _table) { +// } + + /** + * Create the main GUI panel which contains the argument table. + * + * @return the main GUI panel + */ + private Component makeMainPanel() { + initializeTableModel(); + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + return makeScrollPane(table); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + private Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + labelPanel.add(tableLabel); + return labelPanel; + } + + /** + * Create a panel containing the add and delete buttons. + * + * @return a GUI panel containing the buttons + */ + private JPanel makeButtonPanel() { + /** A button for adding new arguments to the table. */ + JButton add = new JButton(JMeterUtils.getResString("add")); //$NON-NLS-1$ + add.setActionCommand(ADD); + add.setEnabled(true); + + delete = new JButton(JMeterUtils.getResString("delete")); //$NON-NLS-1$ + delete.setActionCommand(DELETE); + + checkDeleteStatus(); + + JPanel buttonPanel = new JPanel(); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + add.addActionListener(this); + delete.addActionListener(this); + buttonPanel.add(add); + buttonPanel.add(delete); + return buttonPanel; + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout()); + + add(makeLabelPanel(), BorderLayout.NORTH); + add(makeMainPanel(), BorderLayout.CENTER); + // Force a minimum table height of 70 pixels + add(Box.createVerticalStrut(70), BorderLayout.WEST); + add(makeButtonPanel(), BorderLayout.SOUTH); + + table.revalidate(); + //sizeColumns(table); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LdapConfigGui.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LdapConfigGui.java new file mode 100644 index 00000000000..c53db4747e5 --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LdapConfigGui.java @@ -0,0 +1,444 @@ +/* + * 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.protocol.ldap.config.gui; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ldap.sampler.LDAPSampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class LdapConfigGui is user interface gui for getting all the + * configuration values from the user. + * + * Created Apr 29 2003 11:45 AM + * + */ +public class LdapConfigGui extends AbstractConfigGui implements ItemListener { + + private static final long serialVersionUID = 240L; + + private JTextField rootdn = new JTextField(20); + + private JTextField searchbase = new JTextField(20); + + private JTextField searchfilter = new JTextField(20); + + private JTextField delete = new JTextField(20); + + private JTextField add = new JTextField(20); + + private JTextField modify = new JTextField(20); + + private JTextField servername = new JTextField(20); + + private JTextField port = new JTextField(20); + + private JCheckBox user_Defined = new JCheckBox(JMeterUtils.getResString("user_defined_test")); // $NON-NLS-1$ + + private JRadioButton addTest = new JRadioButton(JMeterUtils.getResString("add_test")); // $NON-NLS-1$ + + private JRadioButton modifyTest = new JRadioButton(JMeterUtils.getResString("modify_test")); // $NON-NLS-1$ + + private JRadioButton deleteTest = new JRadioButton(JMeterUtils.getResString("delete_test")); // $NON-NLS-1$ + + private JRadioButton searchTest = new JRadioButton(JMeterUtils.getResString("search_test")); // $NON-NLS-1$ + + private ButtonGroup bGroup = new ButtonGroup(); + + private boolean displayName = true; + + private ArgumentsPanel tableAddPanel = new ArgumentsPanel(JMeterUtils.getResString("add_test")); // $NON-NLS-1$ + + private ArgumentsPanel tableModifyPanel = new ArgumentsPanel(JMeterUtils.getResString("modify_test")); // $NON-NLS-1$ + + private JPanel cards; + + /** + * Default constructor for LdapConfigGui. + */ + public LdapConfigGui() { + this(true); + } + + @Override + public String getLabelResource() { + return "ldap_sample_title"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + servername.setText(element.getPropertyAsString(LDAPSampler.SERVERNAME)); + port.setText(element.getPropertyAsString(LDAPSampler.PORT)); + rootdn.setText(element.getPropertyAsString(LDAPSampler.ROOTDN)); + CardLayout cl = (CardLayout) (cards.getLayout()); + final String testType = element.getPropertyAsString(LDAPSampler.TEST); + if (testType.equals(LDAPSampler.ADD)) { + addTest.setSelected(true); + add.setText(element.getPropertyAsString(LDAPSampler.BASE_ENTRY_DN)); + tableAddPanel.configure((TestElement) element.getProperty(LDAPSampler.ARGUMENTS).getObjectValue()); + cl.show(cards, "Add"); + } else if (testType.equals(LDAPSampler.MODIFY)) { + modifyTest.setSelected(true); + modify.setText(element.getPropertyAsString(LDAPSampler.BASE_ENTRY_DN)); + tableModifyPanel.configure((TestElement) element.getProperty(LDAPSampler.ARGUMENTS).getObjectValue()); + cl.show(cards, "Modify"); + } else if (testType.equals(LDAPSampler.DELETE)) { + deleteTest.setSelected(true); + delete.setText(element.getPropertyAsString(LDAPSampler.DELETE)); + cl.show(cards, "Delete"); + } else if (testType.equals(LDAPSampler.SEARCHBASE)) { + searchTest.setSelected(true); + searchbase.setText(element.getPropertyAsString(LDAPSampler.SEARCHBASE)); + searchfilter.setText(element.getPropertyAsString(LDAPSampler.SEARCHFILTER)); + cl.show(cards, "Search"); + } + + if (element.getPropertyAsBoolean(LDAPSampler.USER_DEFINED)) { + user_Defined.setSelected(true); + } else { + user_Defined.setSelected(false); + cl.show(cards, ""); // $NON-NLS-1$ + } + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + modifyTestElement(element); + return element; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement element) { + element.clear(); + configureTestElement(element); + element.setProperty(LDAPSampler.SERVERNAME, servername.getText()); + element.setProperty(LDAPSampler.PORT, port.getText()); + element.setProperty(LDAPSampler.ROOTDN, rootdn.getText()); + element.setProperty(new BooleanProperty(LDAPSampler.USER_DEFINED, user_Defined.isSelected())); + + if (addTest.isSelected()) { + element.setProperty(new StringProperty(LDAPSampler.TEST, LDAPSampler.ADD)); + element.setProperty(new StringProperty(LDAPSampler.BASE_ENTRY_DN, add.getText())); + element.setProperty(new TestElementProperty(LDAPSampler.ARGUMENTS, tableAddPanel.createTestElement())); + } + + if (modifyTest.isSelected()) { + element.setProperty(new StringProperty(LDAPSampler.TEST, LDAPSampler.MODIFY)); + element.setProperty(new StringProperty(LDAPSampler.BASE_ENTRY_DN, modify.getText())); + element.setProperty(new TestElementProperty(LDAPSampler.ARGUMENTS, tableModifyPanel.createTestElement())); + } + + if (deleteTest.isSelected()) { + element.setProperty(new StringProperty(LDAPSampler.TEST, LDAPSampler.DELETE)); + element.setProperty(new StringProperty(LDAPSampler.DELETE, delete.getText())); + } + + if (searchTest.isSelected()) { + element.setProperty(new StringProperty(LDAPSampler.TEST, LDAPSampler.SEARCHBASE)); + element.setProperty(new StringProperty(LDAPSampler.SEARCHBASE, searchbase.getText())); + element.setProperty(new StringProperty(LDAPSampler.SEARCHFILTER, searchfilter.getText())); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + rootdn.setText(""); //$NON-NLS-1$ + searchbase.setText(""); //$NON-NLS-1$ + searchfilter.setText(""); //$NON-NLS-1$ + delete.setText(""); //$NON-NLS-1$ + add.setText(""); //$NON-NLS-1$ + modify.setText(""); //$NON-NLS-1$ + servername.setText(""); //$NON-NLS-1$ + port.setText(""); //$NON-NLS-1$ + user_Defined.setSelected(false); + addTest.setSelected(true); + modifyTest.setSelected(false); + deleteTest.setSelected(false); + searchTest.setSelected(false); + } + + /** + * This itemStateChanged listener for changing the card layout for based on\ + * the test selected in the User defined test case. + */ + @Override + public void itemStateChanged(ItemEvent ie) { + CardLayout cl = (CardLayout) (cards.getLayout()); + if (user_Defined.isSelected()) { + if (addTest.isSelected()) { + cl.show(cards, "Add"); + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + delete.setText(""); + } else if (deleteTest.isSelected()) { + cl.show(cards, "Delete"); + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + } else if (searchTest.isSelected()) { + cl.show(cards, "Search"); + delete.setText(""); // $NON-NLS-1$ + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + } else if (modifyTest.isSelected()) { + cl.show(cards, "Modify"); + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + delete.setText(""); + } else { + cl.show(cards, ""); // $NON-NLS-1$ + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + delete.setText(""); // $NON-NLS-1$ + } + } else { + cl.show(cards, ""); // $NON-NLS-1$ + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + delete.setText(""); // $NON-NLS-1$ + } + } + + public LdapConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + /** + * This will create the servername panel in the LdapConfigGui. + */ + private JPanel createServernamePanel() { + JPanel serverPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("servername")); // $NON-NLS-1$ + label.setLabelFor(servername); + serverPanel.add(label, BorderLayout.WEST); + serverPanel.add(servername, BorderLayout.CENTER); + return serverPanel; + } + + /** + * This will create the port panel in the LdapConfigGui. + */ + private JPanel createPortPanel() { + JPanel portPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("port")); // $NON-NLS-1$ + label.setLabelFor(port); + portPanel.add(label, BorderLayout.WEST); + portPanel.add(port, BorderLayout.CENTER); + return portPanel; + } + + /** + * This will create the Root distinguised name panel in the LdapConfigGui. + */ + private JPanel createRootdnPanel() { + JPanel rootdnPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("dn")); // $NON-NLS-1$ + label.setLabelFor(rootdn); + rootdnPanel.add(label, BorderLayout.WEST); + rootdnPanel.add(rootdn, BorderLayout.CENTER); + return rootdnPanel; + } + + /** + * This will create the Search panel in the LdapConfigGui. + */ + private JPanel createSearchPanel() { + VerticalPanel searchPanel = new VerticalPanel(); + JPanel searchBPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("search_base")); // $NON-NLS-1$ + label.setLabelFor(searchbase); + searchBPanel.add(label, BorderLayout.WEST); + searchBPanel.add(searchbase, BorderLayout.CENTER); + JPanel searchFPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label2 = new JLabel(JMeterUtils.getResString("search_filter")); // $NON-NLS-1$ + label2.setLabelFor(searchfilter); + searchFPanel.add(label2, BorderLayout.WEST); + searchFPanel.add(searchfilter, BorderLayout.CENTER); + searchPanel.add(searchBPanel); + searchPanel.add(searchFPanel); + return searchPanel; + } + + /** + * This will create the Delete panel in the LdapConfigGui. + */ + private JPanel createDeletePanel() { + VerticalPanel panel = new VerticalPanel(); + JPanel deletePanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + label.setLabelFor(delete); + deletePanel.add(label, BorderLayout.WEST); + deletePanel.add(delete, BorderLayout.CENTER); + panel.add(deletePanel); + return panel; + } + + /** + * This will create the Add test panel in the LdapConfigGui. + */ + private JPanel createAddPanel() { + JPanel addPanel = new JPanel(new BorderLayout(5, 0)); + JPanel addInnerPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("entry_dn")); // $NON-NLS-1$ + label.setLabelFor(add); + addInnerPanel.add(label, BorderLayout.WEST); + addInnerPanel.add(add, BorderLayout.CENTER); + addPanel.add(addInnerPanel, BorderLayout.NORTH); + addPanel.add(tableAddPanel, BorderLayout.CENTER); + return addPanel; + } + + /** + * This will create the Modify panel in the LdapConfigGui. + */ + private JPanel createModifyPanel() { + JPanel modifyPanel = new JPanel(new BorderLayout(5, 0)); + JPanel modifyInnerPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("entry_dn")); // $NON-NLS-1$ + label.setLabelFor(modify); + modifyInnerPanel.add(label, BorderLayout.WEST); + modifyInnerPanel.add(modify, BorderLayout.CENTER); + modifyPanel.add(modifyInnerPanel, BorderLayout.NORTH); + modifyPanel.add(tableModifyPanel, BorderLayout.CENTER); + return modifyPanel; + } + + /** + * This will create the user defined test panel for create or modify or + * delete or search based on the panel selected in the itemevent in the + * LdapConfigGui. + */ + private JPanel testPanel() { + cards = new JPanel(new CardLayout()); + cards.add(new JPanel(), ""); + cards.add(createAddPanel(), "Add"); + cards.add(createModifyPanel(), "Modify"); + cards.add(createDeletePanel(), "Delete"); + cards.add(createSearchPanel(), "Search"); + return cards; + } + + /** + * This will create the test panel in the LdapConfigGui. + */ + private JPanel createTestPanel() { + JPanel testPanel = new JPanel(new BorderLayout()); + testPanel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("test_configuration"))); // $NON-NLS-1$ + + testPanel.add(new JLabel(JMeterUtils.getResString("test"))); // $NON-NLS-1$ + JPanel rowPanel = new JPanel(); + + rowPanel.add(addTest); + bGroup.add(addTest); + rowPanel.add(deleteTest); + bGroup.add(deleteTest); + rowPanel.add(searchTest); + bGroup.add(searchTest); + rowPanel.add(modifyTest); + bGroup.add(modifyTest); + testPanel.add(rowPanel, BorderLayout.NORTH); + testPanel.add(user_Defined, BorderLayout.CENTER); + return testPanel; + } + + /** + * This will initialise all the panel in the LdapConfigGui. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + VerticalPanel mainPanel = new VerticalPanel(); + mainPanel.add(createServernamePanel()); + mainPanel.add(createPortPanel()); + mainPanel.add(createRootdnPanel()); + mainPanel.add(createTestPanel()); + mainPanel.add(testPanel()); + add(mainPanel, BorderLayout.CENTER); + + user_Defined.addItemListener(this); + addTest.addItemListener(this); + modifyTest.addItemListener(this); + deleteTest.addItemListener(this); + searchTest.addItemListener(this); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LdapExtConfigGui.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LdapExtConfigGui.java new file mode 100644 index 00000000000..b8e7c20a4eb --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/config/gui/LdapExtConfigGui.java @@ -0,0 +1,708 @@ +/* + * 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.protocol.ldap.config.gui; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.JPasswordField; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ldap.sampler.LDAPExtSampler; +import org.apache.jmeter.protocol.ldap.config.gui.LDAPArgumentsPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; + +/******************************************************************************* + * author Dolf Smits(Dolf.Smits@Siemens.com) created Aug 09 2003 11:00 AM + * company Siemens Netherlands N.V.. + * + * Based on the work of: author T.Elanjchezhiyan(chezhiyan@siptech.co.in) + * created Apr 29 2003 11:00 AM company Sip Technologies and Exports Ltd. + ******************************************************************************/ + +/******************************************************************************* + * This class LdapConfigGui is user interface gui for getting all the + * configuration value from the user + ******************************************************************************/ + +public class LdapExtConfigGui extends AbstractConfigGui implements ItemListener { + + private static final long serialVersionUID = 240L; + + // private static final String ROOTDN = "rootDn"; + // private static final String TEST = "tesT"; + // private static String testValue="NNNN"; + + private JTextField rootdn = new JTextField(20); + + private JTextField searchbase = new JTextField(20); + + private JTextField searchfilter = new JTextField(20); + + private JTextField delete = new JTextField(20); + + private JTextField add = new JTextField(20); + + private JTextField modify = new JTextField(20); + + private JTextField servername = new JTextField(20); + + private JTextField port = new JTextField(20); + + /* + * N.B. These entry indexes MUST agree with the SearchControls SCOPE_LEVELS, i.e. + * + * javax.naming.directory.SearchControls.OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE + * + * These have the values 0,1,2 so can be used as indexes in the array + * as well as the value for the search itself. + * + * N.B. Although the strings are used to set and get the options, language change + * does not currently cause a problem, because that always saves the current settings first, + * and then recreates all the GUI classes. + */ + private final String[] SCOPE_STRINGS = new String[]{ + JMeterUtils.getResString("ldap_search_baseobject"),// $NON-NLS-1$ + JMeterUtils.getResString("ldap_search_onelevel"),// $NON-NLS-1$ + JMeterUtils.getResString("ldap_search_subtree"),// $NON-NLS-1$ + }; + + // Names for the cards + private static final String CARDS_DEFAULT = ""; // $NON-NLS-1$ + private static final String CARDS_ADD = "Add"; // $NON-NLS-1$ + private static final String CARDS_DELETE = "Delete"; // $NON-NLS-1$ + private static final String CARDS_BIND = "Bind"; // $NON-NLS-1$ + private static final String CARDS_RENAME = "Rename"; // $NON-NLS-1$ + private static final String CARDS_COMPARE = "Compare"; // $NON-NLS-1$ + private static final String CARDS_SEARCH = "Search"; // $NON-NLS-1$ + private static final String CARDS_MODIFY = "Modify"; // $NON-NLS-1$ + + private JLabeledChoice scope = + new JLabeledChoice(JMeterUtils.getResString("scope"), // $NON-NLS-1$ + SCOPE_STRINGS); + + private JTextField countlim = new JTextField(20); + + private JTextField timelim = new JTextField(20); + + private JTextField attribs = new JTextField(20); + + private JCheckBox retobj = new JCheckBox(JMeterUtils.getResString("retobj")); // $NON-NLS-1$ + + private JCheckBox deref = new JCheckBox(JMeterUtils.getResString("deref")); // $NON-NLS-1$ + + private JTextField userdn = new JTextField(20); + + private JTextField userpw = new JPasswordField(20); + + private JTextField comparedn = new JTextField(20); + + private JTextField comparefilt = new JTextField(20); + + private JTextField modddn = new JTextField(20); + + private JTextField newdn = new JTextField(20); + + private JTextField connto = new JTextField(20); + + private JCheckBox parseflag = new JCheckBox(JMeterUtils.getResString("ldap_parse_results")); // $NON-NLS-1$ + + private JCheckBox secure = new JCheckBox(JMeterUtils.getResString("ldap_secure")); // $NON-NLS-1$ + + private JRadioButton addTest = new JRadioButton(JMeterUtils.getResString("addtest")); // $NON-NLS-1$ + + private JRadioButton modifyTest = new JRadioButton(JMeterUtils.getResString("modtest")); // $NON-NLS-1$ + + private JRadioButton deleteTest = new JRadioButton(JMeterUtils.getResString("deltest")); // $NON-NLS-1$ + + private JRadioButton searchTest = new JRadioButton(JMeterUtils.getResString("searchtest")); // $NON-NLS-1$ + + private JRadioButton bind = new JRadioButton(JMeterUtils.getResString("bind")); // $NON-NLS-1$ + + private JRadioButton rename = new JRadioButton(JMeterUtils.getResString("rename")); // $NON-NLS-1$ + + private JRadioButton unbind = new JRadioButton(JMeterUtils.getResString("unbind")); // $NON-NLS-1$ + + private JRadioButton sbind = new JRadioButton(JMeterUtils.getResString("sbind")); // $NON-NLS-1$ + + private JRadioButton compare = new JRadioButton(JMeterUtils.getResString("compare")); // $NON-NLS-1$ + + private ButtonGroup bGroup = new ButtonGroup(); + + private boolean displayName = true; + + private ArgumentsPanel tableAddPanel = new ArgumentsPanel(JMeterUtils.getResString("addtest")); // $NON-NLS-1$ + + private LDAPArgumentsPanel tableModifyPanel = new LDAPArgumentsPanel(JMeterUtils.getResString("modtest")); // $NON-NLS-1$ + + private JPanel cards; + + /*************************************************************************** + * Default constructor for LdapConfigGui + **************************************************************************/ + public LdapExtConfigGui() { + this(true); + } + + /*************************************************************************** + * !ToDo (Constructor description) + * + * @param displayName + * !ToDo (Parameter description) + **************************************************************************/ + public LdapExtConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + @Override + public String getLabelResource() { + return "ldapext_sample_title"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + servername.setText(element.getPropertyAsString(LDAPExtSampler.SERVERNAME)); + port.setText(element.getPropertyAsString(LDAPExtSampler.PORT)); + rootdn.setText(element.getPropertyAsString(LDAPExtSampler.ROOTDN)); + scope.setSelectedIndex(element.getPropertyAsInt(LDAPExtSampler.SCOPE)); + countlim.setText(element.getPropertyAsString(LDAPExtSampler.COUNTLIM)); + timelim.setText(element.getPropertyAsString(LDAPExtSampler.TIMELIM)); + attribs.setText(element.getPropertyAsString(LDAPExtSampler.ATTRIBS)); + retobj.setSelected(element.getPropertyAsBoolean(LDAPExtSampler.RETOBJ)); + deref.setSelected(element.getPropertyAsBoolean(LDAPExtSampler.DEREF)); + connto.setText(element.getPropertyAsString(LDAPExtSampler.CONNTO)); + parseflag.setSelected(element.getPropertyAsBoolean(LDAPExtSampler.PARSEFLAG)); + secure.setSelected(element.getPropertyAsBoolean(LDAPExtSampler.SECURE)); + userpw.setText(element.getPropertyAsString(LDAPExtSampler.USERPW)); + userdn.setText(element.getPropertyAsString(LDAPExtSampler.USERDN)); + comparedn.setText(element.getPropertyAsString(LDAPExtSampler.COMPAREDN)); + comparefilt.setText(element.getPropertyAsString(LDAPExtSampler.COMPAREFILT)); + modddn.setText(element.getPropertyAsString(LDAPExtSampler.MODDDN)); + newdn.setText(element.getPropertyAsString(LDAPExtSampler.NEWDN)); + CardLayout cl = (CardLayout) (cards.getLayout()); + final String testType = element.getPropertyAsString(LDAPExtSampler.TEST); + if (testType.equals(LDAPExtSampler.UNBIND)) { + unbind.setSelected(true); + cl.show(cards, CARDS_DEFAULT); + } else if (testType.equals(LDAPExtSampler.BIND)) { + bind.setSelected(true); + cl.show(cards, CARDS_BIND); + } else if (testType.equals(LDAPExtSampler.SBIND)) { + sbind.setSelected(true); + cl.show(cards, CARDS_BIND); + } else if (testType.equals(LDAPExtSampler.COMPARE)) { + compare.setSelected(true); + cl.show(cards, CARDS_COMPARE); + } else if (testType.equals(LDAPExtSampler.ADD)) { + addTest.setSelected(true); + add.setText(element.getPropertyAsString(LDAPExtSampler.BASE_ENTRY_DN)); + tableAddPanel.configure((TestElement) element.getProperty(LDAPExtSampler.ARGUMENTS).getObjectValue()); + cl.show(cards, CARDS_ADD); + } else if (testType.equals(LDAPExtSampler.MODIFY)) { + modifyTest.setSelected(true); + modify.setText(element.getPropertyAsString(LDAPExtSampler.BASE_ENTRY_DN)); + tableModifyPanel + .configure((TestElement) element.getProperty(LDAPExtSampler.LDAPARGUMENTS).getObjectValue()); + cl.show(cards, CARDS_MODIFY); + } else if (testType.equals(LDAPExtSampler.DELETE)) { + deleteTest.setSelected(true); + delete.setText(element.getPropertyAsString(LDAPExtSampler.DELETE)); + cl.show(cards, CARDS_DELETE); + } else if (testType.equals(LDAPExtSampler.RENAME)) { + rename.setSelected(true); + cl.show(cards, CARDS_RENAME); + } else if (testType.equals(LDAPExtSampler.SEARCH)) { + searchTest.setSelected(true); + searchbase.setText(element.getPropertyAsString(LDAPExtSampler.SEARCHBASE)); + searchfilter.setText(element.getPropertyAsString(LDAPExtSampler.SEARCHFILTER)); + cl.show(cards, CARDS_SEARCH); + } + } + + /* Implements JMeterGUIComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + modifyTestElement(element); + return element; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement element) { + element.clear(); + configureTestElement(element); + element.setProperty(LDAPExtSampler.SERVERNAME, servername.getText()); + element.setProperty(LDAPExtSampler.PORT, port.getText()); + element.setProperty(LDAPExtSampler.ROOTDN, rootdn.getText()); + element.setProperty(LDAPExtSampler.SCOPE,String.valueOf(scope.getSelectedIndex())); + element.setProperty(LDAPExtSampler.COUNTLIM, countlim.getText()); + element.setProperty(LDAPExtSampler.TIMELIM, timelim.getText()); + element.setProperty(LDAPExtSampler.ATTRIBS, attribs.getText()); + element.setProperty(LDAPExtSampler.RETOBJ,Boolean.toString(retobj.isSelected())); + element.setProperty(LDAPExtSampler.DEREF,Boolean.toString(deref.isSelected())); + element.setProperty(LDAPExtSampler.CONNTO, connto.getText()); + element.setProperty(LDAPExtSampler.PARSEFLAG,Boolean.toString(parseflag.isSelected())); + element.setProperty(LDAPExtSampler.SECURE,Boolean.toString(secure.isSelected())); + element.setProperty(LDAPExtSampler.USERDN, userdn.getText()); + element.setProperty(LDAPExtSampler.USERPW, userpw.getText()); + element.setProperty(LDAPExtSampler.COMPAREDN, comparedn.getText()); + element.setProperty(LDAPExtSampler.COMPAREFILT, comparefilt.getText()); + element.setProperty(LDAPExtSampler.MODDDN, modddn.getText()); + element.setProperty(LDAPExtSampler.NEWDN, newdn.getText()); + if (addTest.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.ADD)); + element.setProperty(new StringProperty(LDAPExtSampler.BASE_ENTRY_DN, add.getText())); + element.setProperty(new TestElementProperty(LDAPExtSampler.ARGUMENTS, tableAddPanel.createTestElement())); + } + if (modifyTest.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.MODIFY)); + element.setProperty(new StringProperty(LDAPExtSampler.BASE_ENTRY_DN, modify.getText())); + element.setProperty(new TestElementProperty(LDAPExtSampler.LDAPARGUMENTS, tableModifyPanel + .createTestElement())); + } + if (deleteTest.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.DELETE)); + element.setProperty(new StringProperty(LDAPExtSampler.DELETE, delete.getText())); + } + if (searchTest.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.SEARCH)); + element.setProperty(new StringProperty(LDAPExtSampler.SEARCHBASE, searchbase.getText())); + element.setProperty(new StringProperty(LDAPExtSampler.SEARCHFILTER, searchfilter.getText())); + } + if (bind.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.BIND)); + } + if (sbind.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.SBIND)); + } + if (compare.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.COMPARE)); + } + if (rename.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.RENAME)); + } + if (unbind.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.UNBIND)); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + rootdn.setText(""); //$NON-NLS-1$ + searchbase.setText(""); //$NON-NLS-1$ + searchfilter.setText(""); //$NON-NLS-1$ + delete.setText(""); //$NON-NLS-1$ + add.setText(""); //$NON-NLS-1$ + modify.setText(""); //$NON-NLS-1$ + servername.setText(""); //$NON-NLS-1$ + port.setText(""); //$NON-NLS-1$ + add.setText(""); //$NON-NLS-1$ + scope.setSelectedIndex(SCOPE_STRINGS.length - 1); + countlim.setText(""); //$NON-NLS-1$ + timelim.setText(""); //$NON-NLS-1$ + attribs.setText(""); //$NON-NLS-1$ + userdn.setText(""); //$NON-NLS-1$ + userpw.setText(""); //$NON-NLS-1$ + comparedn.setText(""); //$NON-NLS-1$ + comparefilt.setText(""); //$NON-NLS-1$ + modddn.setText(""); //$NON-NLS-1$ + newdn.setText(""); //$NON-NLS-1$ + connto.setText(""); //$NON-NLS-1$ + retobj.setSelected(false); + deref.setSelected(false); + parseflag.setSelected(false); + secure.setSelected(false); + addTest.setSelected(false); + modifyTest.setSelected(false); + deleteTest.setSelected(false); + searchTest.setSelected(false); + bind.setSelected(false); + rename.setSelected(false); + unbind.setSelected(false); + sbind.setSelected(false); + compare.setSelected(false); + + tableAddPanel.clear(); + tableModifyPanel.clear(); + } + + /*************************************************************************** + * This itemStateChanged listener for changing the card layout for based on + * the test selected in the User defined test case. + **************************************************************************/ + @Override + public void itemStateChanged(ItemEvent ie) { + CardLayout cl = (CardLayout) (cards.getLayout()); + if (addTest.isSelected()) { + cl.show(cards, CARDS_ADD); + } else if (deleteTest.isSelected()) { + cl.show(cards, CARDS_DELETE); + } else if (bind.isSelected()) { + cl.show(cards, CARDS_BIND); + } else if (sbind.isSelected()) { + cl.show(cards, CARDS_BIND); + } else if (rename.isSelected()) { + cl.show(cards, CARDS_RENAME); + } else if (compare.isSelected()) { + cl.show(cards, CARDS_COMPARE); + } else if (searchTest.isSelected()) { + cl.show(cards, CARDS_SEARCH); + } else if (modifyTest.isSelected()) { + cl.show(cards, CARDS_MODIFY); + } else { // e.g unbind + cl.show(cards, CARDS_DEFAULT); + } + } + + /*************************************************************************** + * This will create the servername panel in the LdapConfigGui + **************************************************************************/ + private JPanel createServernamePanel() { + JPanel serverPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("servername")); // $NON-NLS-1$ + label.setLabelFor(servername); + serverPanel.add(label, BorderLayout.WEST); + serverPanel.add(servername, BorderLayout.CENTER); + return serverPanel; + } + + /*************************************************************************** + * This will create the port panel in the LdapConfigGui + **************************************************************************/ + private JPanel createPortPanel() { + JPanel portPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("port")); // $NON-NLS-1$ + label.setLabelFor(port); + portPanel.add(label, BorderLayout.WEST); + portPanel.add(port, BorderLayout.CENTER); + return portPanel; + } + + /*************************************************************************** + * This will create the Root distinguised name panel in the LdapConfigGui + **************************************************************************/ + private JPanel createRootdnPanel() { + JPanel rootdnPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("ddn")); // $NON-NLS-1$ + label.setLabelFor(rootdn); + rootdnPanel.add(label, BorderLayout.WEST); + rootdnPanel.add(rootdn, BorderLayout.CENTER); + return rootdnPanel; + } + + /*************************************************************************** + * This will create the bind/sbind panel in the LdapConfigGui + **************************************************************************/ + private JPanel createBindPanel() { + VerticalPanel bindPanel = new VerticalPanel(); + bindPanel.add(createServernamePanel()); + bindPanel.add(createPortPanel()); + bindPanel.add(createRootdnPanel()); + + JPanel BPanel = new JPanel(new BorderLayout(5, 0)); + JLabel Blabel0 = new JLabel(JMeterUtils.getResString("userdn")); // $NON-NLS-1$ + Blabel0.setLabelFor(userdn); + BPanel.add(Blabel0, BorderLayout.WEST); + BPanel.add(userdn, BorderLayout.CENTER); + bindPanel.add(BPanel); + + JPanel B1Panel = new JPanel(new BorderLayout(5, 0)); + JLabel Blabel1 = new JLabel(JMeterUtils.getResString("userpw")); // $NON-NLS-1$ + Blabel1.setLabelFor(userpw); + B1Panel.add(Blabel1, BorderLayout.WEST); + B1Panel.add(userpw, BorderLayout.CENTER); + bindPanel.add(B1Panel); + + JPanel B2Panel = new JPanel(new BorderLayout(5, 0)); + JLabel Blabel2 = new JLabel(JMeterUtils.getResString("ldap_connto")); // $NON-NLS-1$ + Blabel2.setLabelFor(connto); + B2Panel.add(Blabel2, BorderLayout.WEST); + B2Panel.add(connto, BorderLayout.CENTER); + bindPanel.add(B2Panel); + + bindPanel.add(secure); + return bindPanel; + } + + /*************************************************************************** + * This will create the bind panel in the LdapConfigGui + **************************************************************************/ + private JPanel createComparePanel() { + VerticalPanel cbindPanel = new VerticalPanel(); + JPanel cBPanel = new JPanel(new BorderLayout(5, 0)); + JLabel cBlabel0 = new JLabel(JMeterUtils.getResString("entrydn")); // $NON-NLS-1$ + cBlabel0.setLabelFor(comparedn); + cBPanel.add(cBlabel0, BorderLayout.WEST); + cBPanel.add(comparedn, BorderLayout.CENTER); + cbindPanel.add(cBPanel); + + JPanel cBPanel1 = new JPanel(new BorderLayout(5, 0)); + JLabel cBlabel1 = new JLabel(JMeterUtils.getResString("comparefilt")); // $NON-NLS-1$ + cBlabel1.setLabelFor(comparefilt); + cBPanel1.add(cBlabel1, BorderLayout.WEST); + cBPanel1.add(comparefilt, BorderLayout.CENTER); + cbindPanel.add(cBPanel1); + + return cbindPanel; + } + + /*************************************************************************** + * This will create the Search controls panel in the LdapConfigGui + **************************************************************************/ + private JPanel createSCPanel() { + VerticalPanel SCPanel = new VerticalPanel(); + + SCPanel.add(scope); + + JPanel SC1Panel = new JPanel(new BorderLayout(5, 0)); + JLabel label1 = new JLabel(JMeterUtils.getResString("countlim")); // $NON-NLS-1$ + label1.setLabelFor(countlim); + SC1Panel.add(label1, BorderLayout.WEST); + SC1Panel.add(countlim, BorderLayout.CENTER); + SCPanel.add(SC1Panel); + + JPanel SC2Panel = new JPanel(new BorderLayout(5, 0)); + JLabel label2 = new JLabel(JMeterUtils.getResString("timelim")); // $NON-NLS-1$ + label2.setLabelFor(timelim); + SC2Panel.add(label2, BorderLayout.WEST); + SC2Panel.add(timelim, BorderLayout.CENTER); + SCPanel.add(SC2Panel); + + JPanel SC3Panel = new JPanel(new BorderLayout(5, 0)); + JLabel label3 = new JLabel(JMeterUtils.getResString("attrs")); // $NON-NLS-1$ + label3.setLabelFor(attribs); + SC3Panel.add(label3, BorderLayout.WEST); + SC3Panel.add(attribs, BorderLayout.CENTER); + SCPanel.add(SC3Panel); + + SCPanel.add(retobj); + SCPanel.add(deref); + SCPanel.add(parseflag); + + return SCPanel; + } + + /*************************************************************************** + * This will create the Search panel in the LdapConfigGui + **************************************************************************/ + + private JPanel createSearchPanel() { + VerticalPanel searchPanel = new VerticalPanel(); + + JPanel searchBPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("searchbase")); // $NON-NLS-1$ + label.setLabelFor(searchbase); + searchBPanel.add(label, BorderLayout.WEST); + searchBPanel.add(searchbase, BorderLayout.CENTER); + + JPanel searchFPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label20 = new JLabel(JMeterUtils.getResString("searchfilter")); // $NON-NLS-1$ + label20.setLabelFor(searchfilter); + searchFPanel.add(label20, BorderLayout.WEST); + searchFPanel.add(searchfilter, BorderLayout.CENTER); + + searchPanel.add(searchBPanel); + searchPanel.add(searchFPanel); + searchPanel.add(createSCPanel()); + + return searchPanel; + } + + /*************************************************************************** + * This will create the Moddn panel in the LdapConfigGui + **************************************************************************/ + + private JPanel createModdnPanel() { + VerticalPanel modPanel = new VerticalPanel(); + + JPanel renamePanel = new JPanel(new BorderLayout(5, 0)); + JLabel labelmod = new JLabel(JMeterUtils.getResString("modddn")); // $NON-NLS-1$ + labelmod.setLabelFor(modddn); + renamePanel.add(labelmod, BorderLayout.WEST); + renamePanel.add(modddn, BorderLayout.CENTER); + + JPanel rename2Panel = new JPanel(new BorderLayout(5, 0)); + JLabel labelmod2 = new JLabel(JMeterUtils.getResString("newdn")); // $NON-NLS-1$ + labelmod2.setLabelFor(newdn); + rename2Panel.add(labelmod2, BorderLayout.WEST); + rename2Panel.add(newdn, BorderLayout.CENTER); + + modPanel.add(renamePanel); + modPanel.add(rename2Panel); + return modPanel; + } + + /*************************************************************************** + * This will create the Delete panel in the LdapConfigGui + **************************************************************************/ + private JPanel createDeletePanel() { + VerticalPanel panel = new VerticalPanel(); + JPanel deletePanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + label.setLabelFor(delete); + deletePanel.add(label, BorderLayout.WEST); + deletePanel.add(delete, BorderLayout.CENTER); + panel.add(deletePanel); + return panel; + } + + /*************************************************************************** + * This will create the Add test panel in the LdapConfigGui + **************************************************************************/ + private JPanel createAddPanel() { + JPanel addPanel = new JPanel(new BorderLayout(5, 0)); + JPanel addInnerPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("entrydn")); // $NON-NLS-1$ + label.setLabelFor(add); + addInnerPanel.add(label, BorderLayout.WEST); + addInnerPanel.add(add, BorderLayout.CENTER); + addPanel.add(addInnerPanel, BorderLayout.NORTH); + addPanel.add(tableAddPanel, BorderLayout.CENTER); + return addPanel; + } + + /*************************************************************************** + * This will create the Modify panel in the LdapConfigGui + **************************************************************************/ + private JPanel createModifyPanel() { + JPanel modifyPanel = new JPanel(new BorderLayout(5, 0)); + JPanel modifyInnerPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("entrydn")); // $NON-NLS-1$ + label.setLabelFor(modify); + modifyInnerPanel.add(label, BorderLayout.WEST); + modifyInnerPanel.add(modify, BorderLayout.CENTER); + modifyPanel.add(modifyInnerPanel, BorderLayout.NORTH); + modifyPanel.add(tableModifyPanel, BorderLayout.CENTER); + return modifyPanel; + } + + /*************************************************************************** + * This will create the user defined test panel for create or modify or + * delete or search based on the panel selected in the itemevent in the + * LdapConfigGui + **************************************************************************/ + private JPanel testPanel() { + cards = new JPanel(new CardLayout()); + cards.add(new JPanel(), CARDS_DEFAULT); + cards.add(createAddPanel(), CARDS_ADD); + cards.add(createModifyPanel(), CARDS_MODIFY); + cards.add(createModdnPanel(), CARDS_RENAME); + cards.add(createDeletePanel(), CARDS_DELETE); + cards.add(createSearchPanel(), CARDS_SEARCH); + cards.add(createBindPanel(), CARDS_BIND); + cards.add(createComparePanel(), CARDS_COMPARE); + return cards; + } + + /*************************************************************************** + * This will create the test panel in the LdapConfigGui + **************************************************************************/ + private JPanel createTestPanel() { + JPanel testPanel = new JPanel(new BorderLayout()); + testPanel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("test_configuration"))); // $NON-NLS-1$ + + testPanel.add(new JLabel(JMeterUtils.getResString("testt"))); // $NON-NLS-1$ + JPanel rowPanel = new JPanel(); + JPanel row2Panel = new JPanel(); + + rowPanel.add(bind); + bGroup.add(bind); + rowPanel.add(unbind); + bGroup.add(unbind); + rowPanel.add(sbind); + bGroup.add(sbind); + rowPanel.add(rename); + bGroup.add(rename); + row2Panel.add(addTest); + bGroup.add(addTest); + row2Panel.add(deleteTest); + bGroup.add(deleteTest); + row2Panel.add(searchTest); + bGroup.add(searchTest); + row2Panel.add(compare); + bGroup.add(compare); + row2Panel.add(modifyTest); + bGroup.add(modifyTest); + testPanel.add(rowPanel, BorderLayout.NORTH); + testPanel.add(row2Panel, BorderLayout.SOUTH); + return testPanel; + } + + /*************************************************************************** + * This will initalise all the panel in the LdapConfigGui + **************************************************************************/ + private void init() { + setLayout(new BorderLayout(0, 5)); + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + VerticalPanel mainPanel = new VerticalPanel(); + mainPanel.add(createTestPanel()); + mainPanel.add(testPanel()); + add(mainPanel, BorderLayout.CENTER); + // Take note of when buttong are changed so can change panel + bind.addItemListener(this); + sbind.addItemListener(this); + unbind.addItemListener(this); + compare.addItemListener(this); + addTest.addItemListener(this); + modifyTest.addItemListener(this); + rename.addItemListener(this); + deleteTest.addItemListener(this); + searchTest.addItemListener(this); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/control/gui/LdapExtTestSamplerGui.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/control/gui/LdapExtTestSamplerGui.java new file mode 100644 index 00000000000..6d952d0339e --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/control/gui/LdapExtTestSamplerGui.java @@ -0,0 +1,111 @@ +/* + * 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.protocol.ldap.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.JPanel; + +import org.apache.jmeter.protocol.ldap.config.gui.LdapExtConfigGui; +import org.apache.jmeter.protocol.ldap.sampler.LDAPExtSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; + +/******************************************************************************* + * + * author Dolf Smits(Dolf.Smits@Siemens.com) created Aug 09 2003 11:00 AM + * company Siemens Netherlands N.V.. + * + * Based on the work of: author T.Elanjchezhiyan(chezhiyan@siptech.co.in) + * created Apr 29 2003 11:00 AM company Sip Technologies and Exports Ltd. + * + ******************************************************************************/ + +public class LdapExtTestSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + private LdapExtConfigGui ldapDefaultPanel; + + /*************************************************************************** + * !ToDo (Constructor description) + **************************************************************************/ + public LdapExtTestSamplerGui() { + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + ldapDefaultPanel.configure(element); + } + + @Override + public TestElement createTestElement() { + LDAPExtSampler sampler = new LDAPExtSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + ((LDAPExtSampler) sampler).addTestElement(ldapDefaultPanel.createTestElement()); + this.configureTestElement(sampler); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + ldapDefaultPanel.clearGui(); + } + + @Override + public String getLabelResource() { + return "ldapext_testing_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + // MAIN PANEL + JPanel mainPanel = new JPanel(new BorderLayout(0, 5)); + ldapDefaultPanel = new LdapExtConfigGui(false); + mainPanel.add(ldapDefaultPanel); + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/control/gui/LdapTestSamplerGui.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/control/gui/LdapTestSamplerGui.java new file mode 100644 index 00000000000..6761d84b83c --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/control/gui/LdapTestSamplerGui.java @@ -0,0 +1,109 @@ +/* + * 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.protocol.ldap.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; + +import org.apache.jmeter.config.gui.LoginConfigGui; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui; +import org.apache.jmeter.protocol.ldap.sampler.LDAPSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class LdapTestSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + private LoginConfigGui loginPanel; + + private LdapConfigGui ldapDefaultPanel; + + public LdapTestSamplerGui() { + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + loginPanel.configure(element); + ldapDefaultPanel.configure(element); + } + + @Override + public TestElement createTestElement() { + LDAPSampler sampler = new LDAPSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + ((LDAPSampler) sampler).addTestElement(ldapDefaultPanel.createTestElement()); + ((LDAPSampler) sampler).addTestElement(loginPanel.createTestElement()); + this.configureTestElement(sampler); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + ldapDefaultPanel.clearGui(); + loginPanel.clearGui(); + } + + @Override + public String getLabelResource() { + return "ldap_testing_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + // MAIN PANEL + VerticalPanel mainPanel = new VerticalPanel(); + loginPanel = new LoginConfigGui(false); + ldapDefaultPanel = new LdapConfigGui(false); + loginPanel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("login_config"))); // $NON-NLS-1$ + add(makeTitlePanel(), BorderLayout.NORTH); + mainPanel.add(loginPanel); + mainPanel.add(ldapDefaultPanel); + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LDAPExtSampler.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LDAPExtSampler.java new file mode 100644 index 00000000000..9b654ccadbd --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LDAPExtSampler.java @@ -0,0 +1,1106 @@ +/* + * 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.protocol.ldap.sampler; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchResult; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.ldap.config.gui.LDAPArgument; +import org.apache.jmeter.protocol.ldap.config.gui.LDAPArguments; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.XMLBuffer; +import org.apache.log.Logger; + +/******************************************************************************* + * Ldap Sampler class is main class for the LDAP test. This will control all the + * test available in the LDAP Test. + ******************************************************************************/ + +public class LDAPExtSampler extends AbstractSampler implements TestStateListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui", + "org.apache.jmeter.protocol.ldap.config.gui.LdapExtConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + /* + * The following strings are used in the test plan, and the values must not be changed + * if test plans are to be upwardly compatible. + */ + public static final String SERVERNAME = "servername"; // $NON-NLS-1$ + + public static final String PORT = "port"; // $NON-NLS-1$ + + public static final String SECURE = "secure"; // $NON-NLS-1$ + + public static final String ROOTDN = "rootdn"; // $NON-NLS-1$ + + public static final String TEST = "test"; // $NON-NLS-1$ + + // These are values for the TEST attribute above + public static final String ADD = "add"; // $NON-NLS-1$ + + public static final String MODIFY = "modify"; // $NON-NLS-1$ + + public static final String BIND = "bind"; // $NON-NLS-1$ + + public static final String UNBIND = "unbind"; // $NON-NLS-1$ + + public static final String DELETE = "delete"; // $NON-NLS-1$ + + public static final String SEARCH = "search"; // $NON-NLS-1$ + // end of TEST values + + public static final String SEARCHBASE = "search"; // $NON-NLS-1$ + + public static final String SEARCHFILTER = "searchfilter"; // $NON-NLS-1$ + + public static final String ARGUMENTS = "arguments"; // $NON-NLS-1$ + + public static final String LDAPARGUMENTS = "ldaparguments"; // $NON-NLS-1$ + + public static final String BASE_ENTRY_DN = "base_entry_dn"; // $NON-NLS-1$ + + public static final String SCOPE = "scope"; // $NON-NLS-1$ + + public static final String COUNTLIM = "countlimit"; // $NON-NLS-1$ + + public static final String TIMELIM = "timelimit"; // $NON-NLS-1$ + + public static final String ATTRIBS = "attributes"; // $NON-NLS-1$ + + public static final String RETOBJ = "return_object"; // $NON-NLS-1$ + + public static final String DEREF = "deref_aliases"; // $NON-NLS-1$ + + public static final String USERDN = "user_dn"; // $NON-NLS-1$ + + public static final String USERPW = "user_pw"; // $NON-NLS-1$ + + public static final String SBIND = "sbind"; // $NON-NLS-1$ + + public static final String COMPARE = "compare"; // $NON-NLS-1$ + + public static final String CONNTO = "connection_timeout"; // $NON-NLS-1$ + + public static final String COMPAREDN = "comparedn"; // $NON-NLS-1$ + + public static final String COMPAREFILT = "comparefilt"; // $NON-NLS-1$ + + public static final String PARSEFLAG = "parseflag"; // $NON-NLS-1$ + + public static final String RENAME = "rename"; // $NON-NLS-1$ + + public static final String MODDDN = "modddn"; // $NON-NLS-1$ + + public static final String NEWDN = "newdn"; // $NON-NLS-1$ + + private static final String SEMI_COLON = ";"; // $NON-NLS-1$ + + + private static final ConcurrentHashMap ldapContexts = + new ConcurrentHashMap(); + + private static final int MAX_SORTED_RESULTS = + JMeterUtils.getPropDefault("ldapsampler.max_sorted_results", 1000); // $NON-NLS-1$ + + /*************************************************************************** + * !ToDo (Constructor description) + **************************************************************************/ + public LDAPExtSampler() { + } + + public void setConnTimeOut(String connto) { + setProperty(new StringProperty(CONNTO, connto)); + } + + public String getConnTimeOut() { + return getPropertyAsString(CONNTO); + } + + public void setSecure(String sec) { + setProperty(new StringProperty(SECURE, sec)); + } + + public boolean isSecure() { + return getPropertyAsBoolean(SECURE); + } + + + public boolean isParseFlag() { + return getPropertyAsBoolean(PARSEFLAG); + } + + public void setParseFlag(String parseFlag) { + setProperty(new StringProperty(PARSEFLAG, parseFlag)); + } + + /*************************************************************************** + * Gets the username attribute of the LDAP object + * + * @return The username + **************************************************************************/ + + public String getUserDN() { + return getPropertyAsString(USERDN); + } + + /*************************************************************************** + * Sets the username attribute of the LDAP object + * + * @param newUserDN + * distinguished name of the user + **************************************************************************/ + + public void setUserDN(String newUserDN) { + setProperty(new StringProperty(USERDN, newUserDN)); + } + + /*************************************************************************** + * Gets the password attribute of the LDAP object + * + * @return The password + **************************************************************************/ + + public String getUserPw() { + return getPropertyAsString(USERPW); + } + + /*************************************************************************** + * Sets the password attribute of the LDAP object + * + * @param newUserPw + * password of the user + **************************************************************************/ + + public void setUserPw(String newUserPw) { + setProperty(new StringProperty(USERPW, newUserPw)); + } + + /*************************************************************************** + * Sets the Servername attribute of the ServerConfig object + * + * @param servername + * The new servername value + **************************************************************************/ + public void setServername(String servername) { + setProperty(new StringProperty(SERVERNAME, servername)); + } + + /*************************************************************************** + * Sets the Port attribute of the ServerConfig object + * + * @param port + * The new Port value + **************************************************************************/ + public void setPort(String port) { + setProperty(new StringProperty(PORT, port)); + } + + /*************************************************************************** + * Gets the servername attribute of the LDAPSampler object + * + * @return The Servername value + **************************************************************************/ + + public String getServername() { + return getPropertyAsString(SERVERNAME); + } + + /*************************************************************************** + * Gets the Port attribute of the LDAPSampler object + * + * @return The Port value + **************************************************************************/ + + public String getPort() { + return getPropertyAsString(PORT); + } + + /*************************************************************************** + * Sets the Rootdn attribute of the LDAPSampler object + * + * @param newRootdn + * The new rootdn value + **************************************************************************/ + public void setRootdn(String newRootdn) { + this.setProperty(ROOTDN, newRootdn); + } + + /*************************************************************************** + * Gets the Rootdn attribute of the LDAPSampler object + * + * @return The Rootdn value + **************************************************************************/ + public String getRootdn() { + return getPropertyAsString(ROOTDN); + } + + /*************************************************************************** + * Gets the search scope attribute of the LDAPSampler object + * + * @return The scope value + **************************************************************************/ + public String getScope() { + return getPropertyAsString(SCOPE); + } + + public int getScopeAsInt() { + return getPropertyAsInt(SCOPE); + } + + /*************************************************************************** + * Sets the search scope attribute of the LDAPSampler object + * + * @param newScope + * The new scope value + **************************************************************************/ + public void setScope(String newScope) { + this.setProperty(SCOPE, newScope); + } + + /*************************************************************************** + * Gets the size limit attribute of the LDAPSampler object + * + * @return The size limit + **************************************************************************/ + public String getCountlim() { + return getPropertyAsString(COUNTLIM); + } + + public long getCountlimAsLong() { + return getPropertyAsLong(COUNTLIM); + } + + /*************************************************************************** + * Sets the size limit attribute of the LDAPSampler object + * + * @param newClim + * The new size limit value + **************************************************************************/ + public void setCountlim(String newClim) { + this.setProperty(COUNTLIM, newClim); + } + + /*************************************************************************** + * Gets the time limit attribute of the LDAPSampler object + * + * @return The time limit + **************************************************************************/ + public String getTimelim() { + return getPropertyAsString(TIMELIM); + } + + public int getTimelimAsInt() { + return getPropertyAsInt(TIMELIM); + } + + /*************************************************************************** + * Sets the time limit attribute of the LDAPSampler object + * + * @param newTlim + * The new time limit value + **************************************************************************/ + public void setTimelim(String newTlim) { + this.setProperty(TIMELIM, newTlim); + } + + /*************************************************************************** + * Gets the return objects attribute of the LDAPSampler object + * + * @return if the object(s) are to be returned + **************************************************************************/ + public boolean isRetobj() { + return getPropertyAsBoolean(RETOBJ); + } + + /*************************************************************************** + * Sets the return objects attribute of the LDAPSampler object + * + * @param newRobj + * whether the objects should be returned + **************************************************************************/ + public void setRetobj(String newRobj) { + this.setProperty(RETOBJ, newRobj); + } + + /*************************************************************************** + * Gets the deref attribute of the LDAPSampler object + * + * @return if dereferencing is required + **************************************************************************/ + public boolean isDeref() { + return getPropertyAsBoolean(DEREF); + } + + /*************************************************************************** + * Sets the deref attribute of the LDAPSampler object + * + * @param newDref + * The new deref value + **************************************************************************/ + public void setDeref(String newDref) { + this.setProperty(DEREF, newDref); + } + + /*************************************************************************** + * Sets the Test attribute of the LdapConfig object + * + * @param newTest + * The new test value(Add,Modify,Delete and search) + **************************************************************************/ + public void setTest(String newTest) { + this.setProperty(TEST, newTest); + } + + /*************************************************************************** + * Gets the test attribute of the LDAPSampler object + * + * @return The test value (Add,Modify,Delete and search) + **************************************************************************/ + public String getTest() { + return getPropertyAsString(TEST); + } + + /*************************************************************************** + * Sets the attributes of the LdapConfig object + * + * @param newAttrs + * The new attributes value + **************************************************************************/ + public void setAttrs(String newAttrs) { + this.setProperty(ATTRIBS, newAttrs); + } + + /*************************************************************************** + * Gets the attributes of the LDAPSampler object + * + * @return The attributes + **************************************************************************/ + public String getAttrs() { + return getPropertyAsString(ATTRIBS); + } + + /*************************************************************************** + * Sets the Base Entry DN attribute of the LDAPSampler object + * + * @param newbaseentry + * The new Base entry DN value + **************************************************************************/ + public void setBaseEntryDN(String newbaseentry) { + setProperty(new StringProperty(BASE_ENTRY_DN, newbaseentry)); + } + + /*************************************************************************** + * Gets the BaseEntryDN attribute of the LDAPSampler object + * + * @return The Base entry DN value + **************************************************************************/ + public String getBaseEntryDN() { + return getPropertyAsString(BASE_ENTRY_DN); + } + + /*************************************************************************** + * Sets the Arguments attribute of the LdapConfig object This will collect + * values from the table for user defined test case + * + * @param value + * The arguments + **************************************************************************/ + public void setArguments(Arguments value) { + setProperty(new TestElementProperty(ARGUMENTS, value)); + } + + /*************************************************************************** + * Gets the Arguments attribute of the LdapConfig object + * + * @return The arguments user defined test case + **************************************************************************/ + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /*************************************************************************** + * Sets the Arguments attribute of the LdapConfig object This will collect + * values from the table for user defined test case + * + * @param value + * The arguments + **************************************************************************/ + public void setLDAPArguments(LDAPArguments value) { + setProperty(new TestElementProperty(LDAPARGUMENTS, value)); + } + + /*************************************************************************** + * Gets the LDAPArguments attribute of the LdapConfig object + * + * @return The LDAParguments user defined modify test case + **************************************************************************/ + public LDAPArguments getLDAPArguments() { + return (LDAPArguments) getProperty(LDAPARGUMENTS).getObjectValue(); + } + + /*************************************************************************** + * Collect all the values from the table (Arguments), using this create the + * Attributes, this will create the Attributes for the User + * defined TestCase for Add Test + * + * @return The Attributes + **************************************************************************/ + private Attributes getUserAttributes() { + Attributes attrs = new BasicAttributes(true); + Attribute attr; + PropertyIterator iter = getArguments().iterator(); + + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + attr = attrs.get(item.getName()); + if (attr == null) { + attr = getBasicAttribute(item.getName(), item.getValue()); + } else { + attr.add(item.getValue()); + } + attrs.put(attr); + } + return attrs; + } + + /*************************************************************************** + * Collect all the value from the table (Arguments), using this create the + * basicAttributes This will create the Basic Attributes for the User + * defined TestCase for Modify test + * + * @return The BasicAttributes + **************************************************************************/ + private ModificationItem[] getUserModAttributes() { + ModificationItem[] mods = new ModificationItem[getLDAPArguments().getArguments().size()]; + BasicAttribute attr; + PropertyIterator iter = getLDAPArguments().iterator(); + int count = 0; + while (iter.hasNext()) { + LDAPArgument item = (LDAPArgument) iter.next().getObjectValue(); + if ((item.getValue()).length()==0) { + attr = new BasicAttribute(item.getName()); + } else { + attr = getBasicAttribute(item.getName(), item.getValue()); + } + + final String opcode = item.getOpcode(); + if ("add".equals(opcode)) { // $NON-NLS-1$ + mods[count++] = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr); + } else if ("delete".equals(opcode) // $NON-NLS-1$ + || "remove".equals(opcode)) { // $NON-NLS-1$ + mods[count++] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr); + } else if("replace".equals(opcode)) { // $NON-NLS-1$ + mods[count++] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); + } else { + log.warn("Invalid opCode: "+opcode); + } + } + return mods; + } + + /*************************************************************************** + * Collect all the value from the table (Arguments), using this create the + * Attributes This will create the Basic Attributes for the User defined + * TestCase for search test + * + * @return The BasicAttributes + **************************************************************************/ + private String[] getRequestAttributes(String reqAttr) { + int index; + String[] mods; + int count = 0; + if (reqAttr.length() == 0) { + return null; + } + if (!reqAttr.endsWith(SEMI_COLON)) { + reqAttr = reqAttr + SEMI_COLON; + } + String attr = reqAttr; + + while (attr.length() > 0) { + index = attr.indexOf(SEMI_COLON); + count += 1; + attr = attr.substring(index + 1); + } + if (count > 0) { + mods = new String[count]; + attr = reqAttr; + count = 0; + while (attr.length() > 0) { + index = attr.indexOf(SEMI_COLON); + mods[count] = attr.substring(0, index); + count += 1; + attr = attr.substring(index + 1); + } + } else { + mods = null; + } + return mods; + } + + /*************************************************************************** + * This will create the Basic Attribute for the give name value pair + * + * @return The BasicAttribute + **************************************************************************/ + private BasicAttribute getBasicAttribute(String name, String value) { + return new BasicAttribute(name, value); + } + + /** + * Returns a formatted string label describing this sampler Example output: + * + * @return a formatted string label describing this sampler + */ + public String getLabel() { + return ("ldap://" + this.getServername() //$NON-NLS-1$ + + ":" + getPort() //$NON-NLS-1$ + + "/" + this.getRootdn()); //$NON-NLS-1$ + } + + /*************************************************************************** + * This will do the add test for the User defined TestCase + * + **************************************************************************/ + private void addTest(DirContext dirContext, SampleResult res) throws NamingException { + try { + res.sampleStart(); + DirContext ctx = LdapExtClient.createTest(dirContext, getUserAttributes(), getBaseEntryDN()); + ctx.close(); // the createTest() method creates an extra context which needs to be closed + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do the delete test for the User defined TestCase + * + **************************************************************************/ + private void deleteTest(DirContext dirContext, SampleResult res) throws NamingException { + try { + res.sampleStart(); + LdapExtClient.deleteTest(dirContext, getPropertyAsString(DELETE)); + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do the modify test for the User defined TestCase + * + **************************************************************************/ + private void modifyTest(DirContext dirContext, SampleResult res) throws NamingException { + try { + res.sampleStart(); + LdapExtClient.modifyTest(dirContext, getUserModAttributes(), getBaseEntryDN()); + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do the bind for the User defined Thread, this bind is used for + * the whole context + * + **************************************************************************/ + private void bindOp(SampleResult res) throws NamingException { + DirContext ctx = ldapContexts.remove(getThreadName()); + if (ctx != null) { + log.warn("Closing previous context for thread: " + getThreadName()); + ctx.close(); + } + try { + res.sampleStart(); + ctx = LdapExtClient.connect(getServername(), getPort(), getRootdn(), getUserDN(), getUserPw(),getConnTimeOut(),isSecure()); + } finally { + res.sampleEnd(); + } + ldapContexts.put(getThreadName(), ctx); + } + + /*************************************************************************** + * This will do the bind and unbind for the User defined TestCase + * + **************************************************************************/ + private void singleBindOp(SampleResult res) throws NamingException { + try { + res.sampleStart(); + DirContext ctx = LdapExtClient.connect(getServername(), getPort(), getRootdn(), getUserDN(), getUserPw(),getConnTimeOut(),isSecure()); + LdapExtClient.disconnect(ctx); + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do a moddn Opp for the User new DN defined + * + **************************************************************************/ + private void renameTest(DirContext dirContext, SampleResult res) throws NamingException { + try { + res.sampleStart(); + LdapExtClient.moddnOp(dirContext, getPropertyAsString(MODDDN), getPropertyAsString(NEWDN)); + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do the unbind for the User defined TestCase as well as inbuilt + * test case + * + **************************************************************************/ + private void unbindOp(DirContext dirContext, SampleResult res) { + try { + res.sampleStart(); + LdapExtClient.disconnect(dirContext); + } finally { + res.sampleEnd(); + } + ldapContexts.remove(getThreadName()); + log.info("context and LdapExtClients removed"); + } + + /*************************************************************************** + * !ToDo (Method description) + * + * @param e + * !ToDo (Parameter description) + * @return !ToDo (Return description) + **************************************************************************/ + @Override + public SampleResult sample(Entry e) { + XMLBuffer xmlBuffer = new XMLBuffer(); + xmlBuffer.openTag("ldapanswer"); // $NON-NLS-1$ + SampleResult res = new SampleResult(); + res.setResponseData("successfull", null); + res.setResponseMessage("Success"); // $NON-NLS-1$ + res.setResponseCode("0"); // $NON-NLS-1$ + res.setContentType("text/xml");// $NON-NLS-1$ + boolean isSuccessful = true; + res.setSampleLabel(getName()); + DirContext dirContext = ldapContexts.get(getThreadName()); + + try { + xmlBuffer.openTag("operation"); // $NON-NLS-1$ + final String testType = getTest(); + xmlBuffer.tag("opertype", testType); // $NON-NLS-1$ + log.debug("performing test: " + testType); + if (testType.equals(UNBIND)) { + res.setSamplerData("Unbind"); + xmlBuffer.tag("baseobj",getRootdn()); // $NON-NLS-1$ + xmlBuffer.tag("binddn",getUserDN()); // $NON-NLS-1$ + unbindOp(dirContext, res); + } else if (testType.equals(BIND)) { + res.setSamplerData("Bind as "+getUserDN()); + xmlBuffer.tag("baseobj",getRootdn()); // $NON-NLS-1$ + xmlBuffer.tag("binddn",getUserDN()); // $NON-NLS-1$ + xmlBuffer.tag("connectionTO",getConnTimeOut()); // $NON-NLS-1$ + bindOp(res); + } else if (testType.equals(SBIND)) { + res.setSamplerData("SingleBind as "+getUserDN()); + xmlBuffer.tag("baseobj",getRootdn()); // $NON-NLS-1$ + xmlBuffer.tag("binddn",getUserDN()); // $NON-NLS-1$ + xmlBuffer.tag("connectionTO",getConnTimeOut()); // $NON-NLS-1$ + singleBindOp(res); + } else if (testType.equals(COMPARE)) { + res.setSamplerData("Compare "+getPropertyAsString(COMPAREFILT) + " " + + getPropertyAsString(COMPAREDN)); + xmlBuffer.tag("comparedn",getPropertyAsString(COMPAREDN)); // $NON-NLS-1$ + xmlBuffer.tag("comparefilter",getPropertyAsString(COMPAREFILT)); // $NON-NLS-1$ + NamingEnumeration cmp=null; + try { + res.sampleStart(); + cmp = LdapExtClient.compare(dirContext, getPropertyAsString(COMPAREFILT), + getPropertyAsString(COMPAREDN)); + if (!cmp.hasMore()) { + res.setResponseCode("5"); // $NON-NLS-1$ + res.setResponseMessage("compareFalse"); + isSuccessful = false; + } + } finally { + res.sampleEnd(); + if (cmp != null) { + cmp.close(); + } + } + } else if (testType.equals(ADD)) { + res.setSamplerData("Add object " + getBaseEntryDN()); + xmlBuffer.tag("attributes",getArguments().toString()); // $NON-NLS-1$ + xmlBuffer.tag("dn",getBaseEntryDN()); // $NON-NLS-1$ + addTest(dirContext, res); + } else if (testType.equals(DELETE)) { + res.setSamplerData("Delete object " + getBaseEntryDN()); + xmlBuffer.tag("dn",getBaseEntryDN()); // $NON-NLS-1$ + deleteTest(dirContext, res); + } else if (testType.equals(MODIFY)) { + res.setSamplerData("Modify object " + getBaseEntryDN()); + xmlBuffer.tag("dn",getBaseEntryDN()); // $NON-NLS-1$ + xmlBuffer.tag("attributes",getLDAPArguments().toString()); // $NON-NLS-1$ + modifyTest(dirContext, res); + } else if (testType.equals(RENAME)) { + res.setSamplerData("ModDN object " + getPropertyAsString(MODDDN) + " to " + getPropertyAsString(NEWDN)); + xmlBuffer.tag("dn",getPropertyAsString(MODDDN)); // $NON-NLS-1$ + xmlBuffer.tag("newdn",getPropertyAsString(NEWDN)); // $NON-NLS-1$ + renameTest(dirContext, res); + } else if (testType.equals(SEARCH)) { + final String scopeStr = getScope(); + final int scope = getScopeAsInt(); + final String searchFilter = getPropertyAsString(SEARCHFILTER); + final String searchBase = getPropertyAsString(SEARCHBASE); + final String timeLimit = getTimelim(); + final String countLimit = getCountlim(); + + res.setSamplerData("Search with filter " + searchFilter); + xmlBuffer.tag("searchfilter", StringEscapeUtils.escapeXml10(searchFilter)); // $NON-NLS-1$ + xmlBuffer.tag("baseobj",getRootdn()); // $NON-NLS-1$ + xmlBuffer.tag("searchbase",searchBase);// $NON-NLS-1$ + xmlBuffer.tag("scope" , scopeStr); // $NON-NLS-1$ + xmlBuffer.tag("countlimit",countLimit); // $NON-NLS-1$ + xmlBuffer.tag("timelimit",timeLimit); // $NON-NLS-1$ + + NamingEnumeration srch=null; + try { + res.sampleStart(); + srch = LdapExtClient.searchTest( + dirContext, searchBase, searchFilter, + scope, getCountlimAsLong(), + getTimelimAsInt(), + getRequestAttributes(getAttrs()), + isRetobj(), + isDeref()); + if (isParseFlag()) { + try { + xmlBuffer.openTag("searchresults"); // $NON-NLS-1$ + writeSearchResults(xmlBuffer, srch); + } finally { + xmlBuffer.closeTag("searchresults"); // $NON-NLS-1$ + } + } else { + xmlBuffer.tag("searchresults", // $NON-NLS-1$ + "hasElements="+srch.hasMoreElements()); // $NON-NLS-1$ + } + } finally { + if (srch != null){ + srch.close(); + } + res.sampleEnd(); + } + + } + + } catch (NamingException ex) { + //log.warn("DEBUG",ex); +// e.g. javax.naming.SizeLimitExceededException: [LDAP: error code 4 - Sizelimit Exceeded]; remaining name '' +// 123456789012345678901 + // TODO: tidy this up + String returnData = ex.toString(); + final int indexOfLDAPErrCode = returnData.indexOf("LDAP: error code"); + if (indexOfLDAPErrCode >= 0) { + res.setResponseMessage(returnData.substring(indexOfLDAPErrCode + 21, returnData + .indexOf(']'))); // $NON-NLS-1$ + res.setResponseCode(returnData.substring(indexOfLDAPErrCode + 17, indexOfLDAPErrCode + 19)); + } else { + res.setResponseMessage(returnData); + res.setResponseCode("800"); // $NON-NLS-1$ + } + isSuccessful = false; + } finally { + xmlBuffer.closeTag("operation"); // $NON-NLS-1$ + xmlBuffer.tag("responsecode",res.getResponseCode()); // $NON-NLS-1$ + xmlBuffer.tag("responsemessage",res.getResponseMessage()); // $NON-NLS-1$ + res.setResponseData(xmlBuffer.toString(), null); + res.setDataType(SampleResult.TEXT); + res.setSuccessful(isSuccessful); + } + return res; + } + + /* + * Write out search results in a stable order (including order of all subelements which might + * be reordered like attributes and their values) so that simple textual comparison can be done, + * unless the number of results exceeds {@link #MAX_SORTED_RESULTS} in which case just stream + * the results out without sorting. + */ + private void writeSearchResults(final XMLBuffer xmlb, final NamingEnumeration srch) + throws NamingException + { + + final ArrayList sortedResults = new ArrayList(MAX_SORTED_RESULTS); + final String searchBase = getPropertyAsString(SEARCHBASE); + final String rootDn = getRootdn(); + + // read all sortedResults into memory so we can guarantee ordering + try { + while (srch.hasMore() && (sortedResults.size() < MAX_SORTED_RESULTS)) { + final SearchResult sr = srch.next(); + + // must be done prior to sorting + normaliseSearchDN(sr, searchBase, rootDn); + sortedResults.add(sr); + } + } finally { // show what we did manage to retrieve + + sortResults(sortedResults); + + for (Iterator it = sortedResults.iterator(); it.hasNext();) + { + final SearchResult sr = it.next(); + writeSearchResult(sr, xmlb); + } + } + + while (srch.hasMore()) { // If there's anything left ... + final SearchResult sr = srch.next(); + + normaliseSearchDN(sr, searchBase, rootDn); + writeSearchResult(sr, xmlb); + } + } + + private void writeSearchResult(final SearchResult sr, final XMLBuffer xmlb) + throws NamingException + { + final Attributes attrs = sr.getAttributes(); + final int size = attrs.size(); + final ArrayList sortedAttrs = new ArrayList(size); + + xmlb.openTag("searchresult"); // $NON-NLS-1$ + xmlb.tag("dn", sr.getName()); // $NON-NLS-1$ + xmlb.tag("returnedattr",Integer.toString(size)); // $NON-NLS-1$ + xmlb.openTag("attributes"); // $NON-NLS-1$ + + try { + for (NamingEnumeration en = attrs.getAll(); en.hasMore(); ) + { + final Attribute attr = en.next(); + + sortedAttrs.add(attr); + } + sortAttributes(sortedAttrs); + for (Iterator ait = sortedAttrs.iterator(); ait.hasNext();) + { + final Attribute attr = ait.next(); + + StringBuilder sb = new StringBuilder(); + if (attr.size() == 1) { + sb.append(getWriteValue(attr.get())); + } else { + final ArrayList sortedVals = new ArrayList(attr.size()); + boolean first = true; + + for (NamingEnumeration ven = attr.getAll(); ven.hasMore(); ) + { + final Object value = getWriteValue(ven.next()); + sortedVals.add(value.toString()); + } + + Collections.sort(sortedVals); + + for (Iterator vit = sortedVals.iterator(); vit.hasNext();) + { + final String value = vit.next(); + if (first) { + first = false; + } else { + sb.append(", "); // $NON-NLS-1$ + } + sb.append(value); + } + } + xmlb.tag(attr.getID(),sb); + } + } finally { + xmlb.closeTag("attributes"); // $NON-NLS-1$ + xmlb.closeTag("searchresult"); // $NON-NLS-1$ + } + } + + private void sortAttributes(final List sortedAttrs) { + Collections.sort(sortedAttrs, new Comparator() + { + @Override + public int compare(Attribute o1, Attribute o2) + { + String nm1 = o1.getID(); + String nm2 = o2.getID(); + + return nm1.compareTo(nm2); + } + }); + } + + private void sortResults(final List sortedResults) { + Collections.sort(sortedResults, new Comparator() + { + private int compareToReverse(final String s1, final String s2) + { + int len1 = s1.length(); + int len2 = s2.length(); + int s1i = len1 - 1; + int s2i = len2 - 1; + + for ( ; (s1i >= 0) && (s2i >= 0); s1i--, s2i--) + { + char c1 = s1.charAt(s1i); + char c2 = s2.charAt(s2i); + + if (c1 != c2) { + return c1 - c2; + } + } + return len1 - len2; + } + + @Override + public int compare(SearchResult o1, SearchResult o2) + { + String nm1 = o1.getName(); + String nm2 = o2.getName(); + + if (nm1 == null) { + nm1 = ""; + } + if (nm2 == null) { + nm2 = ""; + } + return compareToReverse(nm1, nm2); + } + }); + } + + private String normaliseSearchDN(final SearchResult sr, final String searchBase, final String rootDn) + { + String srName = sr.getName(); + + if (!srName.endsWith(searchBase)) + { + if (srName.length() > 0) { + srName = srName + ','; + } + srName = srName + searchBase; + } + if ((rootDn.length() > 0) && !srName.endsWith(rootDn)) + { + if (srName.length() > 0) { + srName = srName + ','; + } + srName = srName + rootDn; + } + sr.setName(srName); + return srName; + } + + private String getWriteValue(final Object value) + { + if (value instanceof String) { + // assume it's senstive data + return StringEscapeUtils.escapeXml10((String)value); + } + if (value instanceof byte[]) { + try + { + return StringEscapeUtils.escapeXml10(new String((byte[])value, "UTF-8")); //$NON-NLS-1$ + } + catch (UnsupportedEncodingException e) + { + log.error("this can't happen: UTF-8 character encoding not supported", e); + } + } + return StringEscapeUtils.escapeXml10(value.toString()); + } + + @Override + public void testStarted() { + testStarted(""); // $NON-NLS-1$ + } + + @Override + public void testEnded() { + testEnded(""); // $NON-NLS-1$ + } + + @Override + public void testStarted(String host) { + // ignored + } + + // Ensure any remaining contexts are closed + @Override + public void testEnded(String host) { + for (Map.Entry entry : ldapContexts.entrySet()) { + DirContext dc = entry.getValue(); + try { + log.warn("Tidying old Context for thread: " + entry.getKey()); + dc.close(); + } catch (NamingException ignored) { + // ignored + } + } + ldapContexts.clear(); + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LDAPSampler.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LDAPSampler.java new file mode 100644 index 00000000000..fb57a8f245b --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LDAPSampler.java @@ -0,0 +1,491 @@ +/* + * 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.protocol.ldap.sampler; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Ldap Sampler class is main class for the LDAP test. This will control all the + * test available in the LDAP Test. + * + */ +public class LDAPSampler extends AbstractSampler { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.LoginConfigGui", + "org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + public static final String SERVERNAME = "servername"; //$NON-NLS-1$ + + public static final String PORT = "port"; //$NON-NLS-1$ + + public static final String ROOTDN = "rootdn"; //$NON-NLS-1$ + + public static final String TEST = "test"; //$NON-NLS-1$ + + public static final String ADD = "add"; //$NON-NLS-1$ + + public static final String MODIFY = "modify"; //$NON-NLS-1$ + + public static final String DELETE = "delete"; //$NON-NLS-1$ + + public static final String SEARCHBASE = "search"; //$NON-NLS-1$ + + public static final String SEARCHFILTER = "searchfilter"; //$NON-NLS-1$ + + public static final String USER_DEFINED = "user_defined"; //$NON-NLS-1$ + + public static final String ARGUMENTS = "arguments"; //$NON-NLS-1$ + + public static final String BASE_ENTRY_DN = "base_entry_dn"; //$NON-NLS-1$ + + // For In build test case using this counter + // create the new entry in the server + private static volatile int counter = 0; + + private boolean searchFoundEntries;// TODO turn into parameter? + + public LDAPSampler() { + } + + /** + * Gets the username attribute of the LDAP object. + * + * @return the username + */ + public String getUsername() { + return getPropertyAsString(ConfigTestElement.USERNAME); + } + + /** + * Gets the password attribute of the LDAP object. + * + * @return the password + */ + public String getPassword() { + return getPropertyAsString(ConfigTestElement.PASSWORD); + } + + /** + * Sets the Servername attribute of the ServerConfig object. + * + * @param servername + * the new servername value + */ + public void setServername(String servername) { + setProperty(new StringProperty(SERVERNAME, servername)); + } + + /** + * Sets the Port attribute of the ServerConfig object. + * + * @param port + * the new Port value + */ + public void setPort(String port) { + setProperty(new StringProperty(PORT, port)); + } + + /** + * Gets the servername attribute of the LDAPSampler object. + * + * @return the Servername value + */ + public String getServername() { + return getPropertyAsString(SERVERNAME); + } + + /** + * Gets the Port attribute of the LDAPSampler object. + * + * @return the Port value + */ + public String getPort() { + return getPropertyAsString(PORT); + } + + /** + * Sets the Rootdn attribute of the LDAPSampler object. + * + * @param newRootdn + * the new rootdn value + */ + public void setRootdn(String newRootdn) { + this.setProperty(ROOTDN, newRootdn); + } + + /** + * Gets the Rootdn attribute of the LDAPSampler object. + * + * @return the Rootdn value + */ + public String getRootdn() { + return getPropertyAsString(ROOTDN); + } + + /** + * Sets the Test attribute of the LdapConfig object. + * + * @param newTest + * the new test value(Add,Modify,Delete and search) + */ + public void setTest(String newTest) { + this.setProperty(TEST, newTest); + } + + /** + * Gets the test attribute of the LDAPSampler object. + * + * @return the test value (Add, Modify, Delete and search) + */ + public String getTest() { + return getPropertyAsString(TEST); + } + + /** + * Sets the UserDefinedTest attribute of the LDAPSampler object. + * + * @param value + * the new UserDefinedTest value + */ + public void setUserDefinedTest(boolean value) { + setProperty(new BooleanProperty(USER_DEFINED, value)); + } + + /** + * Gets the UserDefinedTest attribute of the LDAPSampler object. + * + * @return the test value true or false. If true it will do the + * UserDefinedTest else our own inbuild test case. + */ + public boolean getUserDefinedTest() { + return getPropertyAsBoolean(USER_DEFINED); + } + + /** + * Sets the Base Entry DN attribute of the LDAPSampler object. + * + * @param newbaseentry + * the new Base entry DN value + */ + public void setBaseEntryDN(String newbaseentry) { + setProperty(new StringProperty(BASE_ENTRY_DN, newbaseentry)); + } + + /** + * Gets the BaseEntryDN attribute of the LDAPSampler object. + * + * @return the Base entry DN value + */ + public String getBaseEntryDN() { + return getPropertyAsString(BASE_ENTRY_DN); + } + + /** + * Sets the Arguments attribute of the LdapConfig object. This will collect + * values from the table for user defined test case. + * + * @param value + * the arguments + */ + public void setArguments(Arguments value) { + setProperty(new TestElementProperty(ARGUMENTS, value)); + } + + /** + * Gets the Arguments attribute of the LdapConfig object. + * + * @return the arguments. User defined test case. + */ + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /** + * Collect all the value from the table (Arguments), using this create the + * basicAttributes. This will create the Basic Attributes for the User + * defined TestCase for Add Test. + * + * @return the BasicAttributes + */ + private BasicAttributes getUserAttributes() { + BasicAttribute basicattribute = new BasicAttribute("objectclass"); //$NON-NLS-1$ + basicattribute.add("top"); //$NON-NLS-1$ + basicattribute.add("person"); //$NON-NLS-1$ + basicattribute.add("organizationalPerson"); //$NON-NLS-1$ + basicattribute.add("inetOrgPerson"); //$NON-NLS-1$ + BasicAttributes attrs = new BasicAttributes(true); + attrs.put(basicattribute); + BasicAttribute attr; + PropertyIterator iter = getArguments().iterator(); + + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + attr = getBasicAttribute(item.getName(), item.getValue()); + attrs.put(attr); + } + return attrs; + } + + /** + * Collect all the value from the table (Arguments), using this create the + * basicAttributes. This will create the Basic Attributes for the User + * defined TestCase for Modify test. + * + * @return the BasicAttributes + */ + private ModificationItem[] getUserModAttributes() { + ModificationItem[] mods = new ModificationItem[getArguments().getArguments().size()]; + BasicAttribute attr; + PropertyIterator iter = getArguments().iterator(); + int count = 0; + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + attr = getBasicAttribute(item.getName(), item.getValue()); + mods[count] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); + count = +1; + } + return mods; + } + + /** + * This will create the Basic Attributes for the Inbuilt TestCase for Modify + * test. + * + * @return the BasicAttributes + */ + private ModificationItem[] getModificationItem() { + ModificationItem[] mods = new ModificationItem[2]; + // replace (update) attribute + Attribute mod0 = new BasicAttribute("userpassword", "secret"); //$NON-NLS-1$ //$NON-NLS-2$ + // add mobile phone number attribute + Attribute mod1 = new BasicAttribute("mobile", "123-456-1234"); //$NON-NLS-1$ //$NON-NLS-2$ + + mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0); + mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1); + + return mods; + } + + /** + * This will create the Basic Attributes for the In build TestCase for Add + * Test. + * + * @return the BasicAttributes + */ + private BasicAttributes getBasicAttributes() { + BasicAttributes basicattributes = new BasicAttributes(); + BasicAttribute basicattribute = new BasicAttribute("objectclass"); //$NON-NLS-1$ + basicattribute.add("top"); //$NON-NLS-1$ + basicattribute.add("person"); //$NON-NLS-1$ + basicattribute.add("organizationalPerson"); //$NON-NLS-1$ + basicattribute.add("inetOrgPerson"); //$NON-NLS-1$ + basicattributes.put(basicattribute); + String s1 = "User"; //$NON-NLS-1$ + String s3 = "Test"; //$NON-NLS-1$ + String s5 = "user"; //$NON-NLS-1$ + String s6 = "test"; //$NON-NLS-1$ + counter += 1; + basicattributes.put(new BasicAttribute("givenname", s1)); //$NON-NLS-1$ + basicattributes.put(new BasicAttribute("sn", s3)); //$NON-NLS-1$ + basicattributes.put(new BasicAttribute("cn", "TestUser" + counter)); //$NON-NLS-1$ //$NON-NLS-2$ + basicattributes.put(new BasicAttribute("uid", s5)); //$NON-NLS-1$ + basicattributes.put(new BasicAttribute("userpassword", s6)); //$NON-NLS-1$ + setProperty(new StringProperty(ADD, "cn=TestUser" + counter)); //$NON-NLS-1$ + return basicattributes; + } + + /** + * This will create the Basic Attribute for the given name value pair. + * + * @return the BasicAttribute + */ + private BasicAttribute getBasicAttribute(String name, String value) { + BasicAttribute attr = new BasicAttribute(name, value); + return attr; + } + + /** + * Returns a formatted string label describing this sampler + * + * @return a formatted string label describing this sampler + */ + public String getLabel() { + return ("ldap://" + this.getServername() + ":" + getPort() + "/" + this.getRootdn()); + } + + /** + * This will do the add test for the User defined TestCase as well as + * inbuilt test case. + * + */ + private void addTest(LdapClient ldap, SampleResult res) throws NamingException { + if (getPropertyAsBoolean(USER_DEFINED)) { + res.sampleStart(); + ldap.createTest(getUserAttributes(), getPropertyAsString(BASE_ENTRY_DN)); + res.sampleEnd(); + } else { + res.sampleStart(); + ldap.createTest(getBasicAttributes(), getPropertyAsString(ADD)); + res.sampleEnd(); + ldap.deleteTest(getPropertyAsString(ADD)); + } + } + + /** + * This will do the delete test for the User defined TestCase as well as + * inbuilt test case. + * + */ + private void deleteTest(LdapClient ldap, SampleResult res) throws NamingException { + if (!getPropertyAsBoolean(USER_DEFINED)) { + ldap.createTest(getBasicAttributes(), getPropertyAsString(ADD)); + setProperty(new StringProperty(DELETE, getPropertyAsString(ADD))); + } + res.sampleStart(); + ldap.deleteTest(getPropertyAsString(DELETE)); + res.sampleEnd(); + } + + /** + * This will do the search test for the User defined TestCase as well as + * inbuilt test case. + * + */ + private void searchTest(LdapClient ldap, SampleResult res) throws NamingException { + if (!getPropertyAsBoolean(USER_DEFINED)) { + ldap.createTest(getBasicAttributes(), getPropertyAsString(ADD)); + setProperty(new StringProperty(SEARCHBASE, getPropertyAsString(ADD))); + setProperty(new StringProperty(SEARCHFILTER, getPropertyAsString(ADD))); + } + res.sampleStart(); + searchFoundEntries = ldap.searchTest(getPropertyAsString(SEARCHBASE), getPropertyAsString(SEARCHFILTER)); + res.sampleEnd(); + if (!getPropertyAsBoolean(USER_DEFINED)) { + ldap.deleteTest(getPropertyAsString(ADD)); + } + } + + /** + * This will do the search test for the User defined TestCase as well as + * inbuilt test case. + * + */ + private void modifyTest(LdapClient ldap, SampleResult res) throws NamingException { + if (getPropertyAsBoolean(USER_DEFINED)) { + res.sampleStart(); + ldap.modifyTest(getUserModAttributes(), getPropertyAsString(BASE_ENTRY_DN)); + res.sampleEnd(); + } else { + ldap.createTest(getBasicAttributes(), getPropertyAsString(ADD)); + setProperty(new StringProperty(MODIFY, getPropertyAsString(ADD))); + res.sampleStart(); + ldap.modifyTest(getModificationItem(), getPropertyAsString(MODIFY)); + res.sampleEnd(); + ldap.deleteTest(getPropertyAsString(ADD)); + } + } + + @Override + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + boolean isSuccessful = false; + res.setSampleLabel(getName()); + res.setSamplerData(getPropertyAsString(TEST));// TODO improve this + LdapClient ldap = new LdapClient(); + + try { + ldap.connect(getServername(), getPort(), getRootdn(), getUsername(), getPassword()); + + if (getPropertyAsString(TEST).equals(ADD)) { + addTest(ldap, res); + } else if (getPropertyAsString(TEST).equals(DELETE)) { + deleteTest(ldap, res); + } else if (getPropertyAsString(TEST).equals(MODIFY)) { + modifyTest(ldap, res); + } else if (getPropertyAsString(TEST).equals(SEARCHBASE)) { + searchTest(ldap, res); + } + + // TODO - needs more work ... + if (getPropertyAsString(TEST).equals(SEARCHBASE) && !searchFoundEntries) { + res.setResponseCode("201");// TODO is this a sensible number? //$NON-NLS-1$ + res.setResponseMessage("OK - no results"); + res.setResponseData("successful - no results", null); + } else { + res.setResponseCodeOK(); + res.setResponseMessage("OK"); //$NON-NLS-1$ + res.setResponseData("successful", null); + } + res.setDataType(SampleResult.TEXT); + isSuccessful = true; + } catch (Exception ex) { + log.error("Ldap client - ", ex); + // Could time this + // res.sampleEnd(); + // if sampleEnd() is not called, elapsed time will remain zero + res.setResponseCode("500");// TODO distinguish errors better //$NON-NLS-1$ + res.setResponseMessage(ex.toString()); + isSuccessful = false; + } finally { + ldap.disconnect(); + } + + // Set if we were successful or not + res.setSuccessful(isSuccessful); + return res; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LdapClient.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LdapClient.java new file mode 100644 index 00000000000..55b71fc5227 --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LdapClient.java @@ -0,0 +1,165 @@ +/* + * 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.protocol.ldap.sampler; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +// import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +// import javax.naming.directory.SearchResult; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Ldap Client class is main class to create, modify, search and delete all the + * LDAP functionality available. + * + */ +public class LdapClient { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private DirContext dirContext = null; + + /** + * Constructor for the LdapClient object. + */ + public LdapClient() { + } + + /** + * Connect to server. + * + * @param host + * name of the ldap server + * @param port + * port of the ldap server + * @param rootdn + * base dn to start ldap operations from + * @param username + * user name to use for binding + * @param password + * password to use for binding + * @throws NamingException + * if {@link InitialDirContext} can not be build using the above + * parameters + */ + public void connect(String host, String port, String rootdn, String username, String password) + throws NamingException { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); //$NON-NLS-1$ + env.put(Context.PROVIDER_URL, "ldap://" + host + ":" + port + "/" + rootdn); //$NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + env.put(Context.REFERRAL, "throw"); //$NON-NLS-1$ + env.put(Context.SECURITY_CREDENTIALS, password); + env.put(Context.SECURITY_PRINCIPAL, username); + dirContext = new InitialDirContext(env); + } + + /** + * Disconnect from the server. + */ + public void disconnect() { + try { + if (dirContext != null) { + dirContext.close(); + dirContext = null; + } + } catch (NamingException e) { + log.error("Ldap client - ", e); + } + } + + /** + * Filter the data in the ldap directory for the given search base. + * + * @param searchBase + * where the search should start + * @param searchFilter + * filter this value from the base + * @return true when the search yields results, + * false otherwise + * @throws NamingException + * when searching fails + */ + public boolean searchTest(String searchBase, String searchFilter) throws NamingException { + // System.out.println("Base="+searchBase+" Filter="+searchFilter); + SearchControls searchcontrols = new SearchControls(SearchControls.SUBTREE_SCOPE, + 1L, // count limit + 0, // time limit + null,// attributes (null = all) + false,// return object ? + false);// dereference links? + NamingEnumeration ne = dirContext.search(searchBase, searchFilter, searchcontrols); + // System.out.println("Loop "+ne.toString()+" "+ne.hasMore()); + // while (ne.hasMore()){ + // Object tmp = ne.next(); + // System.out.println(tmp.getClass().getName()); + // SearchResult sr = (SearchResult) tmp; + // Attributes at = sr.getAttributes(); + // System.out.println(at.get("cn")); + // } + // System.out.println("Done "+ne.hasMore()); + return ne.hasMore(); + } + + /** + * Modify the attribute in the ldap directory for the given string. + * + * @param mods + * list of all {@link ModificationItem}s to apply + * @param string + * dn of the object to modify + * @throws NamingException when modification fails + */ + public void modifyTest(ModificationItem[] mods, String string) throws NamingException { + dirContext.modifyAttributes(string, mods); + } + + /** + * Create the attribute in the ldap directory for the given string. + * + * @param basicattributes + * add all the entry in to the basicattribute + * @param string + * the string (dn) value + * @throws NamingException when creating subcontext fails + */ + public void createTest(BasicAttributes basicattributes, String string) throws NamingException { + // DirContext dc = //TODO perhaps return this? + dirContext.createSubcontext(string, basicattributes); + } + + /** + * Delete the attribute from the ldap directory. + * + * @param string + * the string (dn) value + * @throws NamingException when destroying sub context fails + */ + public void deleteTest(String string) throws NamingException { + dirContext.destroySubcontext(string); + } +} diff --git a/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LdapExtClient.java b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LdapExtClient.java new file mode 100644 index 00000000000..bc24780944e --- /dev/null +++ b/src/protocol/ldap/org/apache/jmeter/protocol/ldap/sampler/LdapExtClient.java @@ -0,0 +1,287 @@ +/* + * 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.protocol.ldap.sampler; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.NamingEnumeration; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/******************************************************************************* + * + * author Dolf Smits(Dolf.Smits@Siemens.com) created Aug 09 2003 11:00 AM + * company Siemens Netherlands N.V.. + * + * Based on the work of: author T.Elanjchezhiyan(chezhiyan@siptech.co.in) + * created Apr 29 2003 11:00 AM company Sip Technologies and Exports Ltd. + * + ******************************************************************************/ + +/******************************************************************************* + * Ldap Client class is main class to create ,modify, search and delete all the + * LDAP functionality available + ******************************************************************************/ +public class LdapExtClient { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String CONTEXT_IS_NULL = "Context is null"; + + /** + * Constructor for the LdapClient object + */ + public LdapExtClient() { + } + + /** + * connect to server + * + * @param host + * name of the server to connect + * @param port + * port of the server to connect + * @param rootdn + * base of the tree to operate on + * @param username + * name of the user to use for binding + * @param password + * password to use for binding + * @param connTimeOut + * connection timeout for connecting the server see + * "com.sun.jndi.ldap.connect.timeout" + * @param secure + * flag whether ssl should be used + * @return newly created {@link DirContext} + * @exception NamingException + * when creating the {@link DirContext} fails + */ + public static DirContext connect(String host, String port, String rootdn, String username, String password, String connTimeOut, boolean secure) + throws NamingException { + DirContext dirContext; + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // $NON-NLS-1$ + StringBuilder sb = new StringBuilder(80); + if (secure) { + sb.append("ldaps://"); // $NON-NLS-1$ + } else { + sb.append("ldap://"); // $NON-NLS-1$ + } + sb.append(host); + if (port.length()>0){ + sb.append(":"); // $NON-NLS-1$ + sb.append(port); + } + sb.append("/"); // $NON-NLS-1$ + sb.append(rootdn); + env.put(Context.PROVIDER_URL,sb.toString()); + log.info("prov_url= " + env.get(Context.PROVIDER_URL)); // $NON-NLS-1$ + if (connTimeOut.length()> 0) { + env.put("com.sun.jndi.ldap.connect.timeout", connTimeOut); // $NON-NLS-1$ + } + env.put(Context.REFERRAL, "throw"); // $NON-NLS-1$ + env.put("java.naming.batchsize", "0"); // $NON-NLS-1$ // $NON-NLS-2$ + env.put(Context.SECURITY_CREDENTIALS, password); + env.put(Context.SECURITY_PRINCIPAL, username); + dirContext = new InitialDirContext(env); + return dirContext; + } + + /** + * disconnect from the server + * + * @param dirContext + * context do disconnect (may be null) + */ + public static void disconnect(DirContext dirContext) { + if (dirContext == null) { + log.info("Cannot disconnect null context"); + return; + } + + try { + dirContext.close(); + } catch (NamingException e) { + log.warn("Ldap client disconnect - ", e); + } + } + + /*************************************************************************** + * Filter the data in the ldap directory for the given search base + * + * @param dirContext + * context to perform the search on + * + * @param searchBase + * base where the search should start + * @param searchFilter + * filter this value from the base + * @param scope + * scope for search. May be one of + * {@link SearchControls#OBJECT_SCOPE}, + * {@link SearchControls#ONELEVEL_SCOPE} or + * {@link SearchControls#SUBTREE_SCOPE} + * @param countlim + * max number of results to get, 0 for all entries + * @param timelim + * max time to wait for entries (in milliseconds), 0 + * for unlimited time + * @param attrs + * list of attributes to return. If null all + * attributes will be returned. If empty, none will be returned + * @param retobj + * flag whether the objects should be returned + * @param deref + * flag whether objects should be dereferenced + * @return result of the search + * @throws NamingException + * when searching fails + **************************************************************************/ + public static NamingEnumeration searchTest(DirContext dirContext, String searchBase, String searchFilter, int scope, long countlim, + int timelim, String[] attrs, boolean retobj, boolean deref) throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + if (log.isDebugEnabled()){ + log.debug( + "searchBase=" + searchBase + + " scope=" + scope + + " countlim=" + countlim + + " timelim=" + timelim + + " attrs=" + JMeterUtils.unsplit(attrs,",") + + " retobj=" + retobj + + " deref=" + deref + + " filter=" + searchFilter + ); + } + SearchControls searchcontrols = null; + searchcontrols = new SearchControls(scope, countlim, timelim, attrs, retobj, deref); + return dirContext.search(searchBase, searchFilter, searchcontrols); + } + + /*************************************************************************** + * Filter the data in the ldap directory + * + * @param dirContext + * the context to operate on + * @param filter + * filter this value from the base + * @param entrydn + * distinguished name of entry to compare + * @return result of the search + * @throws NamingException + * when searching fails + **************************************************************************/ + public static NamingEnumeration compare(DirContext dirContext, String filter, String entrydn) throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + SearchControls searchcontrols = new SearchControls(0, 1, 0, new String[0], false, false); + return dirContext.search(entrydn, filter, searchcontrols); + } + + /*************************************************************************** + * ModDN the data in the ldap directory for the given search base + * + * @param dirContext + * context to operate on + * @param ddn + * distinguished name name of object to rename + * @param newdn + * new distinguished name of object + * @throws NamingException + * when renaming fails + * + **************************************************************************/ + public static void moddnOp(DirContext dirContext, String ddn, String newdn) throws NamingException { + log.debug("ddn and newDn= " + ddn + "@@@@" + newdn); + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + dirContext.rename(ddn, newdn); + } + + /*************************************************************************** + * Modify the attribute in the ldap directory for the given string + * + * @param dirContext + * context to operate on + * @param mods + * list of all the {@link ModificationItem}s to apply on + * string + * @param string + * distinguished name of the object to modify + * @throws NamingException + * when modification fails + **************************************************************************/ + public static void modifyTest(DirContext dirContext, ModificationItem[] mods, String string) throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + dirContext.modifyAttributes(string, mods); + + } + + /*************************************************************************** + * Create the entry in the ldap directory for the given string + * + * @param dirContext + * context to operate on + * @param attributes + * add all the attributes and values from the attributes object + * @param string + * distinguished name of the subcontext to create + * @return newly created subcontext + * @throws NamingException + * when creating subcontext fails + **************************************************************************/ + public static DirContext createTest(DirContext dirContext, Attributes attributes, String string) + throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + return dirContext.createSubcontext(string, attributes); + } + + /*************************************************************************** + * Delete the attribute from the ldap directory + * + * @param dirContext + * context to operate on + * @param string + * distinguished name of the subcontext to destroy + * @throws NamingException + * when destroying the subcontext fails + **************************************************************************/ + public static void deleteTest(DirContext dirContext, String string) throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + dirContext.destroySubcontext(string); + } +} diff --git a/src/protocol/mail/META-INF/javamail.providers b/src/protocol/mail/META-INF/javamail.providers new file mode 100644 index 00000000000..562dc62ee8a --- /dev/null +++ b/src/protocol/mail/META-INF/javamail.providers @@ -0,0 +1,16 @@ +# 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. +# +protocol=file; type=store; class=org.apache.jmeter.protocol.mail.sampler.MailFileStore; vendor=ASF \ No newline at end of file diff --git a/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailFileFolder.java b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailFileFolder.java new file mode 100644 index 00000000000..429e0494d22 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailFileFolder.java @@ -0,0 +1,192 @@ +/* + * 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.protocol.mail.sampler; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.io.InputStream; + +import javax.mail.Flags; +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Store; +import javax.mail.URLName; + +import org.apache.commons.io.IOUtils; + +public class MailFileFolder extends Folder { + + private static final String FILENAME_FORMAT = "%d.msg"; + private static final String FILENAME_REGEX = "\\d+\\.msg"; + private boolean isOpen; + private final File folderPath;// Parent folder (or single message file) + private final boolean isFile; + private static final FilenameFilter FILENAME_FILTER = new FilenameFilter(){ + @Override + public boolean accept(File dir, String name) { + return name.matches(FILENAME_REGEX); + } + + }; + + public MailFileFolder(Store store, String path) { + super(store); + String base = store.getURLName().getHost(); // == ServerName from mail sampler + File parentFolder = new File(base); + isFile = parentFolder.isFile(); + if (isFile){ + folderPath = new File(base); + } else { + folderPath = new File(base,path); + } + } + + public MailFileFolder(Store store, URLName path) { + this(store, path.getFile()); + } + + @Override + public void appendMessages(Message[] messages) throws MessagingException { + throw new MessagingException("Not supported"); + } + + @Override + public void close(boolean expunge) throws MessagingException { + this.store.close(); + isOpen = false; + } + + @Override + public boolean create(int type) throws MessagingException { + return false; + } + + @Override + public boolean delete(boolean recurse) throws MessagingException { + return false; + } + + @Override + public boolean exists() throws MessagingException { + return true; + } + + @Override + public Message[] expunge() throws MessagingException { + return new Message[0]; + } + + @Override + public Folder getFolder(String name) throws MessagingException { + return this; + } + + @Override + public String getFullName() { + return this.toString(); + } + + @Override + public Message getMessage(int index) throws MessagingException { + File f; + if (isFile) { + f = folderPath; + } else { + f = new File(folderPath,String.format(FILENAME_FORMAT, Integer.valueOf(index))); + } + try { + InputStream fis = new BufferedInputStream(new FileInputStream(f)); + try { + Message m = new MailFileMessage(this, fis, index); + return m; + } finally { + IOUtils.closeQuietly(fis); + } + } catch (FileNotFoundException e) { + throw new MessagingException("Cannot open folder: "+e.getMessage(), e); + } + } + + @Override + public int getMessageCount() throws MessagingException { + if (!isOpen) return -1; + if (isFile) return 1; + File[] listFiles = folderPath.listFiles(FILENAME_FILTER); + return listFiles != null ? listFiles.length : 0; + } + + @Override + public String getName() { + return this.toString(); + } + + @Override + public Folder getParent() throws MessagingException { + return null; + } + + @Override + public Flags getPermanentFlags() { + return null; + } + + @Override + public char getSeparator() throws MessagingException { + return '/'; + } + + @Override + public int getType() throws MessagingException { + return HOLDS_MESSAGES; + } + + @Override + public boolean hasNewMessages() throws MessagingException { + return false; + } + + @Override + public boolean isOpen() { + return isOpen; + } + + @Override + public Folder[] list(String pattern) throws MessagingException { + return new Folder[]{this}; + } + + @Override + public void open(int mode) throws MessagingException { + if (mode != READ_ONLY) { + throw new MessagingException("Implementation only supports read-only access"); + } + this.mode = mode; + isOpen = true; + } + + @Override + public boolean renameTo(Folder newName) throws MessagingException { + return false; + } + +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailFileMessage.java b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailFileMessage.java new file mode 100644 index 00000000000..bb7de6d9140 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailFileMessage.java @@ -0,0 +1,36 @@ +/* + * 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.protocol.mail.sampler; + +import java.io.InputStream; + +import javax.mail.Folder; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +// Needed to get access to the protected constructor in MimeMessage from MailFileFolder +// [This class and its ctor could perhaps be package-protected] +public class MailFileMessage extends MimeMessage { + + protected MailFileMessage(Folder folder, InputStream in, int number) + throws MessagingException { + super(folder, in, number); + } + +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailFileStore.java b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailFileStore.java new file mode 100644 index 00000000000..23ae10e601c --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailFileStore.java @@ -0,0 +1,59 @@ +/* + * 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.protocol.mail.sampler; + +import java.io.File; + +import javax.mail.Folder; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.URLName; + +public class MailFileStore extends Store { + + public MailFileStore(Session s, URLName u){ + super(s,u); + } + + @Override + protected boolean protocolConnect(String host, int port, String user, String password) + throws MessagingException { + File base = new File(host); + if (base.isDirectory() || base.isFile()) { + return true; + } + throw new MessagingException("Host must be a valid directory or file"); + } + + @Override + public Folder getDefaultFolder() throws MessagingException { + return new MailFileFolder(this,""); + } + + @Override + public Folder getFolder(String path) throws MessagingException { + return new MailFileFolder(this, path); + } + + @Override + public Folder getFolder(URLName path) throws MessagingException { + return new MailFileFolder(this, path); + } +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailReaderSampler.java b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailReaderSampler.java new file mode 100644 index 00000000000..bff7fa230c9 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/MailReaderSampler.java @@ -0,0 +1,615 @@ +/* + * 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.protocol.mail.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import javax.mail.Address; +import javax.mail.BodyPart; +import javax.mail.Flags; +import javax.mail.Folder; +import javax.mail.Header; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimeUtility; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.smtp.sampler.gui.SecuritySettingsPanel; +import org.apache.jmeter.protocol.smtp.sampler.protocol.LocalTrustStoreSSLSocketFactory; +import org.apache.jmeter.protocol.smtp.sampler.protocol.TrustAllSSLSocketFactory; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Sampler that can read from POP3 and IMAP mail servers + */ +public class MailReaderSampler extends AbstractSampler implements Interruptible { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + //+ JMX attributes - do not change the values + private static final String SERVER_TYPE = "host_type"; // $NON-NLS-1$ + private static final String SERVER = "host"; // $NON-NLS-1$ + private static final String PORT = "port"; // $NON-NLS-1$ + private static final String USERNAME = "username"; // $NON-NLS-1$ + private static final String PASSWORD = "password"; // $NON-NLS-1$ + private static final String FOLDER = "folder"; // $NON-NLS-1$ + private static final String DELETE = "delete"; // $NON-NLS-1$ + private static final String NUM_MESSAGES = "num_messages"; // $NON-NLS-1$ + private static final String NEW_LINE = "\n"; // $NON-NLS-1$ + private static final String STORE_MIME_MESSAGE = "storeMimeMessage"; // $NON-NLS-1$ + private static final String HEADER_ONLY = "headerOnly"; // $NON-NLS-1$ + private static final boolean HEADER_ONLY_DEFAULT = false; + //- + + private static final String RFC_822_DEFAULT_ENCODING = "iso-8859-1"; // RFC 822 uses ascii per default + + public static final String DEFAULT_PROTOCOL = "pop3"; // $NON-NLS-1$ + + // Use the actual class so the name must be correct. + private static final String TRUST_ALL_SOCKET_FACTORY = TrustAllSSLSocketFactory.class.getName(); + + private static final String FALSE = "false"; // $NON-NLS-1$ + + private static final String TRUE = "true"; // $NON-NLS-1$ + + public boolean isUseLocalTrustStore() { + return getPropertyAsBoolean(SecuritySettingsPanel.USE_LOCAL_TRUSTSTORE); + } + + public String getTrustStoreToUse() { + return getPropertyAsString(SecuritySettingsPanel.TRUSTSTORE_TO_USE); + } + + + public boolean isUseSSL() { + return getPropertyAsBoolean(SecuritySettingsPanel.USE_SSL); + } + + + public boolean isUseStartTLS() { + return getPropertyAsBoolean(SecuritySettingsPanel.USE_STARTTLS); + } + + + public boolean isTrustAllCerts() { + return getPropertyAsBoolean(SecuritySettingsPanel.SSL_TRUST_ALL_CERTS); + } + + + public boolean isEnforceStartTLS() { + return getPropertyAsBoolean(SecuritySettingsPanel.ENFORCE_STARTTLS); + + } + + public static final int ALL_MESSAGES = -1; // special value + + private volatile boolean busy; + + public MailReaderSampler() { + setServerType(DEFAULT_PROTOCOL); + setFolder("INBOX"); // $NON-NLS-1$ + setNumMessages(ALL_MESSAGES); + setDeleteMessages(false); + } + + /** + * {@inheritDoc} + */ + @Override + public SampleResult sample(Entry e) { + SampleResult parent = new SampleResult(); + boolean isOK = false; // Did sample succeed? + final boolean deleteMessages = getDeleteMessages(); + final String serverProtocol = getServerType(); + + parent.setSampleLabel(getName()); + + String samplerString = toString(); + parent.setSamplerData(samplerString); + + /* + * Perform the sampling + */ + parent.sampleStart(); // Start timing + try { + // Create empty properties + Properties props = new Properties(); + + if (isUseStartTLS()) { + props.setProperty(mailProp(serverProtocol, "starttls.enable"), TRUE); // $NON-NLS-1$ + if (isEnforceStartTLS()){ + // Requires JavaMail 1.4.2+ + props.setProperty(mailProp(serverProtocol, "starttls.require"), TRUE); // $NON-NLS-1$ + } + } + + if (isTrustAllCerts()) { + if (isUseSSL()) { + props.setProperty(mailProp(serverProtocol, "ssl.socketFactory.class"), TRUST_ALL_SOCKET_FACTORY); // $NON-NLS-1$ + props.setProperty(mailProp(serverProtocol, "ssl.socketFactory.fallback"), FALSE); // $NON-NLS-1$ + } else if (isUseStartTLS()) { + props.setProperty(mailProp(serverProtocol, "ssl.socketFactory.class"), TRUST_ALL_SOCKET_FACTORY); // $NON-NLS-1$ + props.setProperty(mailProp(serverProtocol, "ssl.socketFactory.fallback"), FALSE); // $NON-NLS-1$ + } + } else if (isUseLocalTrustStore()){ + File truststore = new File(getTrustStoreToUse()); + log.info("load local truststore - try to load truststore from: "+truststore.getAbsolutePath()); + if(!truststore.exists()){ + log.info("load local truststore -Failed to load truststore from: "+truststore.getAbsolutePath()); + truststore = new File(FileServer.getFileServer().getBaseDir(), getTrustStoreToUse()); + log.info("load local truststore -Attempting to read truststore from: "+truststore.getAbsolutePath()); + if(!truststore.exists()){ + log.info("load local truststore -Failed to load truststore from: "+truststore.getAbsolutePath() + ". Local truststore not available, aborting execution."); + throw new IOException("Local truststore file not found. Also not available under : " + truststore.getAbsolutePath()); + } + } + if (isUseSSL()) { + // Requires JavaMail 1.4.2+ + props.put(mailProp(serverProtocol, "ssl.socketFactory"), // $NON-NLS-1$ + new LocalTrustStoreSSLSocketFactory(truststore)); + props.put(mailProp(serverProtocol, "ssl.socketFactory.fallback"), FALSE); // $NON-NLS-1$ + } else if (isUseStartTLS()) { + // Requires JavaMail 1.4.2+ + props.put(mailProp(serverProtocol, "ssl.socketFactory"), // $NON-NLS-1$ + new LocalTrustStoreSSLSocketFactory(truststore)); + props.put(mailProp(serverProtocol, "ssl.socketFactory.fallback"), FALSE); // $NON-NLS-1$ + } + } + + // Get session + Session session = Session.getInstance(props, null); + + // Get the store + Store store = session.getStore(serverProtocol); + store.connect(getServer(), getPortAsInt(), getUserName(), getPassword()); + + // Get folder + Folder folder = store.getFolder(getFolder()); + if (deleteMessages) { + folder.open(Folder.READ_WRITE); + } else { + folder.open(Folder.READ_ONLY); + } + + final int messageTotal = folder.getMessageCount(); + int n = getNumMessages(); + if (n == ALL_MESSAGES || n > messageTotal) { + n = messageTotal; + } + + // Get directory + Message messages[] = folder.getMessages(1,n); + StringBuilder pdata = new StringBuilder(); + pdata.append(messages.length); + pdata.append(" messages found\n"); + parent.setResponseData(pdata.toString(),null); + parent.setDataType(SampleResult.TEXT); + parent.setContentType("text/plain"); // $NON-NLS-1$ + + final boolean headerOnly = getHeaderOnly(); + busy = true; + for (Message message : messages) { + StringBuilder cdata = new StringBuilder(); + SampleResult child = new SampleResult(); + child.sampleStart(); + + cdata.append("Message "); // $NON-NLS-1$ + cdata.append(message.getMessageNumber()); + child.setSampleLabel(cdata.toString()); + child.setSamplerData(cdata.toString()); + cdata.setLength(0); + + final String contentType = message.getContentType(); + child.setContentType(contentType);// Store the content-type + child.setDataEncoding(RFC_822_DEFAULT_ENCODING); // RFC 822 uses ascii per default + child.setEncodingAndType(contentType);// Parse the content-type + + if (isStoreMimeMessage()) { + // Don't save headers - they are already in the raw message + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + message.writeTo(bout); + child.setResponseData(bout.toByteArray()); // Save raw message + child.setDataType(SampleResult.TEXT); + } else { + @SuppressWarnings("unchecked") // Javadoc for the API says this is OK + Enumeration
hdrs = message.getAllHeaders(); + while(hdrs.hasMoreElements()){ + Header hdr = hdrs.nextElement(); + String value = hdr.getValue(); + try { + value = MimeUtility.decodeText(value); + } catch (UnsupportedEncodingException uce) { + // ignored + } + cdata.append(hdr.getName()).append(": ").append(value).append("\n"); + } + child.setResponseHeaders(cdata.toString()); + cdata.setLength(0); + if (!headerOnly) { + appendMessageData(child, message); + } + } + + if (deleteMessages) { + message.setFlag(Flags.Flag.DELETED, true); + } + child.setResponseOK(); + if (child.getEndTime()==0){// Avoid double-call if addSubResult was called. + child.sampleEnd(); + } + parent.addSubResult(child); + } + + // Close connection + folder.close(true); + store.close(); + + parent.setResponseCodeOK(); + parent.setResponseMessageOK(); + isOK = true; + } catch (NoClassDefFoundError ex) { + log.debug("",ex);// No need to log normally, as we set the status + parent.setResponseCode("500"); // $NON-NLS-1$ + parent.setResponseMessage(ex.toString()); + } catch (MessagingException ex) { + log.debug("", ex);// No need to log normally, as we set the status + parent.setResponseCode("500"); // $NON-NLS-1$ + parent.setResponseMessage(ex.toString() + "\n" + samplerString); // $NON-NLS-1$ + } catch (IOException ex) { + log.debug("", ex);// No need to log normally, as we set the status + parent.setResponseCode("500"); // $NON-NLS-1$ + parent.setResponseMessage(ex.toString()); + } finally { + busy = false; + } + + if (parent.getEndTime()==0){// not been set by any child samples + parent.sampleEnd(); + } + parent.setSuccessful(isOK); + return parent; + } + + private void appendMessageData(SampleResult child, Message message) + throws MessagingException, IOException { + StringBuilder cdata = new StringBuilder(); + cdata.append("Date: "); // $NON-NLS-1$ + cdata.append(message.getSentDate());// TODO - use a different format here? + cdata.append(NEW_LINE); + + cdata.append("To: "); // $NON-NLS-1$ + Address[] recips = message.getAllRecipients(); // may be null + for (int j = 0; recips != null && j < recips.length; j++) { + cdata.append(recips[j].toString()); + if (j < recips.length - 1) { + cdata.append("; "); // $NON-NLS-1$ + } + } + cdata.append(NEW_LINE); + + cdata.append("From: "); // $NON-NLS-1$ + Address[] from = message.getFrom(); // may be null + for (int j = 0; from != null && j < from.length; j++) { + cdata.append(from[j].toString()); + if (j < from.length - 1) { + cdata.append("; "); // $NON-NLS-1$ + } + } + cdata.append(NEW_LINE); + + cdata.append("Subject: "); // $NON-NLS-1$ + cdata.append(message.getSubject()); + cdata.append(NEW_LINE); + + cdata.append(NEW_LINE); + Object content = message.getContent(); + if (content instanceof MimeMultipart) { + appendMultiPart(child, cdata, (MimeMultipart) content); + } else if (content instanceof InputStream){ + child.setResponseData(IOUtils.toByteArray((InputStream) content)); + } else { + cdata.append(content); + child.setResponseData(cdata.toString(),child.getDataEncodingNoDefault()); + } + } + + private void appendMultiPart(SampleResult child, StringBuilder cdata, + MimeMultipart mmp) throws MessagingException, IOException { + String preamble = mmp.getPreamble(); + if (preamble != null ){ + cdata.append(preamble); + } + child.setResponseData(cdata.toString(),child.getDataEncodingNoDefault()); + int count = mmp.getCount(); + for (int j=0; j 0){ + sb.append(name); + sb.append("@"); + } + sb.append(getServer()); + int port=getPortAsInt(); + if (port != -1){ + sb.append(":").append(port); + } + sb.append("/"); + sb.append(getFolder()); + sb.append("["); + sb.append(getNumMessages()); + sb.append("]"); + return sb.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean interrupt() { + boolean wasbusy = busy; + busy = false; + return wasbusy; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } + + public boolean getHeaderOnly() { + return getPropertyAsBoolean(HEADER_ONLY, HEADER_ONLY_DEFAULT); + } + + public void setHeaderOnly(boolean selected) { + setProperty(HEADER_ONLY, selected, HEADER_ONLY_DEFAULT); + } + + /** + * Build a property name of the form "mail.pop3s.starttls.require" + * + * @param protocol the protocol, i.e. "pop3s" in the example + * @param propname the property name suffix, i.e. "starttls.require" in the example + * @return the constructed name + */ + private String mailProp(String protocol, String propname) { + StringBuilder sb = new StringBuilder(); + sb.append("mail.").append(protocol).append("."); + sb.append(propname); + return sb.toString(); + } +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/gui/MailReaderSamplerGui.java b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/gui/MailReaderSamplerGui.java new file mode 100644 index 00000000000..7fd29d82d0c --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/mail/sampler/gui/MailReaderSamplerGui.java @@ -0,0 +1,335 @@ +/* + * 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.protocol.mail.sampler.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.mail.sampler.MailReaderSampler; +import org.apache.jmeter.protocol.smtp.sampler.gui.SecuritySettingsPanel; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class MailReaderSamplerGui extends AbstractSamplerGui implements ActionListener, FocusListener { + + private static final long serialVersionUID = 240L; + + // Gui Components + private JTextField serverTypeBox; + + private JTextField serverBox; + + private JTextField portBox; + + private JTextField usernameBox; + + private JTextField passwordBox; + + private JTextField folderBox; + + private JLabel folderLabel; + + private JRadioButton allMessagesButton; + + private JRadioButton someMessagesButton; + + private JTextField someMessagesField; + + private JCheckBox deleteBox; + + private JCheckBox storeMimeMessageBox; + + private JCheckBox headerOnlyBox; + + // Labels - don't make these static, else language change will not work + + private final String ServerTypeLabel = JMeterUtils.getResString("mail_reader_server_type");// $NON-NLS-1$ + + private final String ServerLabel = JMeterUtils.getResString("mail_reader_server");// $NON-NLS-1$ + + private final String PortLabel = JMeterUtils.getResString("mail_reader_port");// $NON-NLS-1$ + + private final String AccountLabel = JMeterUtils.getResString("mail_reader_account");// $NON-NLS-1$ + + private final String PasswordLabel = JMeterUtils.getResString("mail_reader_password");// $NON-NLS-1$ + + private final String NumMessagesLabel = JMeterUtils.getResString("mail_reader_num_messages");// $NON-NLS-1$ + + private final String AllMessagesLabel = JMeterUtils.getResString("mail_reader_all_messages");// $NON-NLS-1$ + + private final String DeleteLabel = JMeterUtils.getResString("mail_reader_delete");// $NON-NLS-1$ + + private final String FolderLabel = JMeterUtils.getResString("mail_reader_folder");// $NON-NLS-1$ + + private final String STOREMIME = JMeterUtils.getResString("mail_reader_storemime");// $NON-NLS-1$ + + private final String headerOnlyLabel = JMeterUtils.getResString("mail_reader_header_only");// $NON-NLS-1$ + + private static final String INBOX = "INBOX"; // $NON-NLS-1$ + + private SecuritySettingsPanel securitySettingsPanel; + + public MailReaderSamplerGui() { + init(); + initGui(); + } + + @Override + public String getLabelResource() { + return "mail_reader_title"; // $NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement element) { + MailReaderSampler mrs = (MailReaderSampler) element; + serverTypeBox.setText(mrs.getServerType()); + folderBox.setText(mrs.getFolder()); + serverBox.setText(mrs.getServer()); + portBox.setText(mrs.getPort()); + usernameBox.setText(mrs.getUserName()); + passwordBox.setText(mrs.getPassword()); + if (mrs.getNumMessages() == MailReaderSampler.ALL_MESSAGES) { + allMessagesButton.setSelected(true); + someMessagesField.setText("0"); // $NON-NLS-1$ + } else { + someMessagesButton.setSelected(true); + someMessagesField.setText(mrs.getNumMessagesString()); + } + headerOnlyBox.setSelected(mrs.getHeaderOnly()); + deleteBox.setSelected(mrs.getDeleteMessages()); + storeMimeMessageBox.setSelected(mrs.isStoreMimeMessage()); + securitySettingsPanel.configure(element); + super.configure(element); + } + + /** + * {@inheritDoc} + */ + @Override + public TestElement createTestElement() { + MailReaderSampler sampler = new MailReaderSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement te) { + te.clear(); + configureTestElement(te); + + MailReaderSampler mrs = (MailReaderSampler) te; + + mrs.setServerType(serverTypeBox.getText()); + mrs.setFolder(folderBox.getText()); + mrs.setServer(serverBox.getText()); + mrs.setPort(portBox.getText()); + mrs.setUserName(usernameBox.getText()); + mrs.setPassword(passwordBox.getText()); + if (allMessagesButton.isSelected()) { + mrs.setNumMessages(MailReaderSampler.ALL_MESSAGES); + } else { + mrs.setNumMessages(someMessagesField.getText()); + } + mrs.setHeaderOnly(headerOnlyBox.isSelected()); + mrs.setDeleteMessages(deleteBox.isSelected()); + mrs.setStoreMimeMessage(storeMimeMessageBox.isSelected()); + + securitySettingsPanel.modifyTestElement(te); + } + + /* + * Helper method to set up the GUI screen + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + JPanel settingsPanel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = getConstraints(); + + serverTypeBox = new JTextField(20); + serverTypeBox.addActionListener(this); + serverTypeBox.addFocusListener(this); + addField(settingsPanel, ServerTypeLabel, serverTypeBox, gbc); + + serverBox = new JTextField(20); + addField(settingsPanel, ServerLabel, serverBox, gbc); + + portBox = new JTextField(20); + addField(settingsPanel, PortLabel, portBox, gbc); + + usernameBox = new JTextField(20); + addField(settingsPanel, AccountLabel, usernameBox, gbc); + + passwordBox = new JPasswordField(20); + addField(settingsPanel, PasswordLabel, passwordBox, gbc); + + folderLabel = new JLabel(FolderLabel); + folderBox = new JTextField(INBOX, 20); + addField(settingsPanel, folderLabel, folderBox, gbc); + + HorizontalPanel numMessagesPanel = new HorizontalPanel(); + numMessagesPanel.add(new JLabel(NumMessagesLabel)); + ButtonGroup nmbg = new ButtonGroup(); + allMessagesButton = new JRadioButton(AllMessagesLabel); + allMessagesButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (allMessagesButton.isSelected()) { + someMessagesField.setEnabled(false); + } + } + }); + someMessagesButton = new JRadioButton(); + someMessagesButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (someMessagesButton.isSelected()) { + someMessagesField.setEnabled(true); + } + } + }); + nmbg.add(allMessagesButton); + nmbg.add(someMessagesButton); + someMessagesField = new JTextField(5); + allMessagesButton.setSelected(true); + numMessagesPanel.add(allMessagesButton); + numMessagesPanel.add(someMessagesButton); + numMessagesPanel.add(someMessagesField); + + headerOnlyBox = new JCheckBox(headerOnlyLabel); + + deleteBox = new JCheckBox(DeleteLabel); + + storeMimeMessageBox = new JCheckBox(STOREMIME); + + securitySettingsPanel = new SecuritySettingsPanel(); + + JPanel settings = new VerticalPanel(); + settings.add(Box.createVerticalStrut(5)); + settings.add(settingsPanel); + settings.add(numMessagesPanel); + settings.add(headerOnlyBox); + settings.add(deleteBox); + settings.add(storeMimeMessageBox); + settings.add(securitySettingsPanel); + + add(makeTitlePanel(), BorderLayout.NORTH); + add(settings, BorderLayout.CENTER); + } + + private void addField(JPanel panel, JLabel label, JComponent field, GridBagConstraints gbc) { + gbc.fill=GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + panel.add(label, gbc); + gbc.gridx++; + gbc.weightx = 1; + gbc.fill=GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + panel.add(field, gbc); + nextLine(gbc); + } + + private void addField(JPanel panel, String text, JComponent field, GridBagConstraints gbc) { + addField(panel, new JLabel(text), field, gbc); + } + + private void nextLine(GridBagConstraints gbc) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0; + } + + private GridBagConstraints getConstraints() { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridheight = 1; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0; + gbc.weighty = 0; + return gbc; + } + + @Override + public void clearGui() { + super.clearGui(); + initGui(); + } + + private void initGui() { + allMessagesButton.setSelected(true); + headerOnlyBox.setSelected(false); + deleteBox.setSelected(false); + storeMimeMessageBox.setSelected(false); + folderBox.setText(INBOX); + serverTypeBox.setText(MailReaderSampler.DEFAULT_PROTOCOL); + passwordBox.setText("");// $NON-NLS-1$ + serverBox.setText("");// $NON-NLS-1$ + portBox.setText("");// $NON-NLS-1$ + usernameBox.setText("");// $NON-NLS-1$ + } + + @Override + public void actionPerformed(ActionEvent e) { + final String item = serverTypeBox.getText(); + if (item.equals("pop3")||item.equals("pop3s")) { + folderLabel.setEnabled(false); + folderBox.setText(INBOX); + folderBox.setEnabled(false); + } else { + folderLabel.setEnabled(true); + folderBox.setEnabled(true); + } + } + + @Override + public void focusGained(FocusEvent e) { + } + + @Override + public void focusLost(FocusEvent e) { + actionPerformed(null); + } +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/SmtpSampler.java b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/SmtpSampler.java new file mode 100644 index 00000000000..52d1f52d017 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/SmtpSampler.java @@ -0,0 +1,399 @@ +/* + * 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.protocol.smtp.sampler; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.mail.AuthenticationFailedException; +import javax.mail.BodyPart; +import javax.mail.Header; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Part; +import javax.mail.internet.AddressException; +import javax.mail.internet.ContentType; +import javax.mail.internet.InternetAddress; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.smtp.sampler.gui.SecuritySettingsPanel; +import org.apache.jmeter.protocol.smtp.sampler.protocol.SendMailCommand; +import org.apache.jmeter.protocol.smtp.sampler.tools.CounterOutputStream; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Sampler-Class for JMeter - builds, starts and interprets the results of the + * sampler. Has to implement some standard-methods for JMeter in order to be + * integrated in the framework. All getter/setter methods just deliver/set + * values from/to the sampler, not from/to the message-object. Therefore, all + * these methods are also present in class SendMailCommand. + */ +public class SmtpSampler extends AbstractSampler { + + private static final long serialVersionUID = 1L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //+JMX file attribute names - do not change any values! + public static final String SERVER = "SMTPSampler.server"; // $NON-NLS-1$ + + + public static final String SERVER_PORT = "SMTPSampler.serverPort"; // $NON-NLS-1$ + public static final String SERVER_TIMEOUT = "SMTPSampler.serverTimeout"; // $NON-NLS-1$ + public static final String SERVER_CONNECTION_TIMEOUT = "SMTPSampler.serverConnectionTimeout"; // $NON-NLS-1$ + public static final String USE_AUTH = "SMTPSampler.useAuth"; // $NON-NLS-1$ + public static final String USERNAME = "SMTPSampler.username"; // $NON-NLS-1$ + public static final String PASSWORD = "SMTPSampler.password"; // $NON-NLS-1$ + public static final String MAIL_FROM = "SMTPSampler.mailFrom"; // $NON-NLS-1$ + public static final String MAIL_REPLYTO = "SMTPSampler.replyTo"; // $NON-NLS-1$ + public static final String RECEIVER_TO = "SMTPSampler.receiverTo"; // $NON-NLS-1$ + public static final String RECEIVER_CC = "SMTPSampler.receiverCC"; // $NON-NLS-1$ + public static final String RECEIVER_BCC = "SMTPSampler.receiverBCC"; // $NON-NLS-1$ + + public static final String SUBJECT = "SMTPSampler.subject"; // $NON-NLS-1$ + public static final String SUPPRESS_SUBJECT = "SMTPSampler.suppressSubject"; // $NON-NLS-1$ + public static final String MESSAGE = "SMTPSampler.message"; // $NON-NLS-1$ + public static final String PLAIN_BODY = "SMTPSampler.plainBody"; // $NON-NLS-1$ + public static final String INCLUDE_TIMESTAMP = "SMTPSampler.include_timestamp"; // $NON-NLS-1$ + public static final String ATTACH_FILE = "SMTPSampler.attachFile"; // $NON-NLS-1$ + public static final String MESSAGE_SIZE_STATS = "SMTPSampler.messageSizeStatistics"; // $NON-NLS-1$ + public static final String HEADER_FIELDS = "SMTPSampler.headerFields"; // $NON-NLS-1$ + + public static final String USE_EML = "SMTPSampler.use_eml"; // $NON-NLS-1$ + public static final String EML_MESSAGE_TO_SEND = "SMTPSampler.emlMessageToSend"; // $NON-NLS-1$ + public static final String ENABLE_DEBUG = "SMTPSampler.enableDebug"; // $NON-NLS-1$ + + // Used to separate attachment file names in JMX fields - do not change! + public static final String FILENAME_SEPARATOR = ";"; + //-JMX file attribute names + + + public SmtpSampler() { + } + + /** + * Performs the sample, and returns the result + * + * @param e + * Standard-method-header from JMeter + * @return sampleresult Result of the sample + * @see org.apache.jmeter.samplers.Sampler#sample(org.apache.jmeter.samplers.Entry) + */ + @Override + public SampleResult sample(Entry e) { + Message message = null; + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + boolean isOK = false; // Did sample succeed? + SendMailCommand instance = new SendMailCommand(); + instance.setSmtpServer(getPropertyAsString(SmtpSampler.SERVER)); + instance.setSmtpPort(getPropertyAsString(SmtpSampler.SERVER_PORT)); + instance.setConnectionTimeOut(getPropertyAsString(SmtpSampler.SERVER_CONNECTION_TIMEOUT)); + instance.setTimeOut(getPropertyAsString(SmtpSampler.SERVER_TIMEOUT)); + + instance.setUseSSL(getPropertyAsBoolean(SecuritySettingsPanel.USE_SSL)); + instance.setUseStartTLS(getPropertyAsBoolean(SecuritySettingsPanel.USE_STARTTLS)); + instance.setTrustAllCerts(getPropertyAsBoolean(SecuritySettingsPanel.SSL_TRUST_ALL_CERTS)); + instance.setEnforceStartTLS(getPropertyAsBoolean(SecuritySettingsPanel.ENFORCE_STARTTLS)); + + instance.setUseAuthentication(getPropertyAsBoolean(USE_AUTH)); + instance.setUsername(getPropertyAsString(USERNAME)); + instance.setPassword(getPropertyAsString(PASSWORD)); + + instance.setUseLocalTrustStore(getPropertyAsBoolean(SecuritySettingsPanel.USE_LOCAL_TRUSTSTORE)); + instance.setTrustStoreToUse(getPropertyAsString(SecuritySettingsPanel.TRUSTSTORE_TO_USE)); + instance.setEmlMessage(getPropertyAsString(EML_MESSAGE_TO_SEND)); + instance.setUseEmlMessage(getPropertyAsBoolean(USE_EML)); + + instance.setEnableDebug(getPropertyAsBoolean(ENABLE_DEBUG)); + + if (getPropertyAsString(MAIL_FROM).matches(".*@.*")) { + instance.setSender(getPropertyAsString(MAIL_FROM)); + } + + final String receiverTo = getPropertyAsString(SmtpSampler.RECEIVER_TO).trim(); + final String receiverCC = getPropertyAsString(SmtpSampler.RECEIVER_CC).trim(); + final String receiverBcc = getPropertyAsString(SmtpSampler.RECEIVER_BCC).trim(); + final String replyTo = getPropertyAsString(SmtpSampler.MAIL_REPLYTO).trim(); + + try { + // Process address lists + instance.setReceiverTo(getPropNameAsAddresses(receiverTo)); + instance.setReceiverCC(getPropNameAsAddresses(receiverCC)); + instance.setReceiverBCC(getPropNameAsAddresses(receiverBcc)); + instance.setReplyTo(getPropNameAsAddresses(replyTo)); + + if(getPropertyAsBoolean(SUPPRESS_SUBJECT)){ + instance.setSubject(null); + }else{ + String subject = getPropertyAsString(SUBJECT); + if (getPropertyAsBoolean(INCLUDE_TIMESTAMP)){ + StringBuilder sb = new StringBuilder(subject); + sb.append(" <<< current timestamp: "); + sb.append(new Date().getTime()); + sb.append(" >>>"); + subject = sb.toString(); + } + instance.setSubject(subject); + } + + if (!getPropertyAsBoolean(USE_EML)) { // part is only needed if we + // don't send an .eml-file + instance.setMailBody(getPropertyAsString(MESSAGE)); + instance.setPlainBody(getPropertyAsBoolean(PLAIN_BODY)); + final String filesToAttach = getPropertyAsString(ATTACH_FILE); + if (!filesToAttach.equals("")) { + String[] attachments = filesToAttach.split(FILENAME_SEPARATOR); + for (String attachment : attachments) { + File file = new File(attachment); + if(!file.isAbsolute() && !file.exists()){ + log.debug("loading file with relative path: " +attachment); + file = new File(FileServer.getFileServer().getBaseDir(), attachment); + log.debug("file path set to: "+attachment); + } + instance.addAttachment(file); + } + } + + } + + // needed for measuring sending time + instance.setSynchronousMode(true); + + instance.setHeaderFields((CollectionProperty)getProperty(SmtpSampler.HEADER_FIELDS)); + message = instance.prepareMessage(); + + if (getPropertyAsBoolean(MESSAGE_SIZE_STATS)) { + // calculate message size + CounterOutputStream cs = new CounterOutputStream(); + message.writeTo(cs); + res.setBytes(cs.getCount()); + } else { + res.setBytes(-1); + } + + } catch (Exception ex) { + log.warn("Error while preparing message", ex); + res.setResponseCode("500"); + res.setResponseMessage(ex.toString()); + return res; + } + + // Set up the sample result details + res.setDataType(SampleResult.TEXT); + try { + res.setRequestHeaders(getRequestHeaders(message)); + res.setSamplerData(getSamplerData(message)); + } catch (MessagingException e1) { + res.setSamplerData("Error occurred trying to save request info: "+e1); + log.warn("Error occurred trying to save request info",e1); + } catch (IOException e1) { + res.setSamplerData("Error occurred trying to save request info: "+e1); + log.warn("Error occurred trying to save request info",e1); + } + + // Perform the sampling + res.sampleStart(); + + try { + instance.execute(message); + + res.setResponseCodeOK(); + /* + * TODO if(instance.getSMTPStatusCode == 250) + * res.setResponseMessage("Message successfully sent!"); else + * res.setResponseMessage(instance.getSMTPStatusCodeIncludingMessage); + */ + res.setResponseMessage("Message successfully sent!\n" + + instance.getServerResponse()); + isOK = true; + } + // username / password incorrect + catch (AuthenticationFailedException afex) { + log.warn("", afex); + res.setResponseCode("500"); + res.setResponseMessage("AuthenticationFailedException: authentication failed - wrong username / password!\n" + + afex); + // SSL not supported, startTLS not supported, other messagingException + } catch (MessagingException mex) { + log.warn("",mex); + res.setResponseCode("500"); + if (mex.getMessage().matches(".*Could not connect to SMTP host.*465.*") + && mex.getCause().getMessage().matches(".*Connection timed out.*")) { + res.setResponseMessage("MessagingException: Probably, SSL is not supported by the SMTP-Server!\n" + + mex); + } else if (mex.getMessage().matches(".*StartTLS failed.*")) { + res.setResponseMessage("MessagingException: StartTLS not supported by server or initializing failed!\n" + + mex); + } else if (mex.getMessage().matches(".*send command to.*") + && mex.getCause().getMessage().matches( + ".*unable to find valid certification path to requested target.*")) { + res.setResponseMessage("MessagingException: Server certificate not trusted - perhaps you have to restart JMeter!\n" + + mex); + } else { + res.setResponseMessage("Other MessagingException: " + mex.toString()); + } + } catch (Exception ex) { // general exception + log.warn("",ex); + res.setResponseCode("500"); + if (null != ex.getMessage() + && ex.getMessage().matches("Failed to build truststore")) { + res.setResponseMessage("Failed to build truststore - did not try to send mail!"); + } else { + res.setResponseMessage("Other Exception: " + ex.toString()); + } + } + + res.sampleEnd(); + + try { + // process the sampler result + InputStream is = message.getInputStream(); + StringBuilder sb = new StringBuilder(); + byte[] buf = new byte[1024]; + int read = is.read(buf); + while (read > 0) { + sb.append(new String(buf, 0, read)); // TODO - charset? + read = is.read(buf); + } + // TODO - charset? + res.setResponseData(sb.toString().getBytes()); // TODO this should really be request data, but there is none + } catch (IOException ex) { + log.warn("",ex); + } catch (MessagingException ex) { + log.warn("",ex); + } + + res.setSuccessful(isOK); + + return res; + } + + private String getRequestHeaders(Message message) throws MessagingException { + StringBuilder sb = new StringBuilder(); + @SuppressWarnings("unchecked") // getAllHeaders() is not yet genericised + Enumeration
headers = message.getAllHeaders(); // throws ME + writeHeaders(headers, sb); + return sb.toString(); + } + + private String getSamplerData(Message message) throws MessagingException, IOException { + StringBuilder sb = new StringBuilder(); + Object content = message.getContent(); // throws ME + if (content instanceof Multipart) { + Multipart multipart = (Multipart) content; + String contentType = multipart.getContentType(); + ContentType ct = new ContentType(contentType); + String boundary=ct.getParameter("boundary"); + for (int i = 0; i < multipart.getCount(); i++) { // throws ME + sb.append("--"); + sb.append(boundary); + sb.append("\n"); + BodyPart bodyPart = multipart.getBodyPart(i); // throws ME + writeBodyPart(sb, bodyPart); // throws IOE, ME + } + sb.append("--"); + sb.append(boundary); + sb.append("--"); + sb.append("\n"); + } else if(content instanceof BodyPart){ + BodyPart bodyPart = (BodyPart) content; + writeBodyPart(sb, bodyPart); // throws IOE, ME + } else if (content instanceof String){ + sb.append(content); + } else { + sb.append("Content has class: "+content.getClass().getCanonicalName()); + } + return sb.toString(); + } + + private void writeHeaders(Enumeration
headers, StringBuilder sb) { + while (headers.hasMoreElements()) { + Header header = headers.nextElement(); + sb.append(header.getName()); + sb.append(": "); + sb.append(header.getValue()); + sb.append("\n"); + } + } + + private void writeBodyPart(StringBuilder sb, BodyPart bodyPart) + throws MessagingException, IOException { + @SuppressWarnings("unchecked") // API not yet generic + Enumeration
allHeaders = bodyPart.getAllHeaders(); // throws ME + writeHeaders(allHeaders, sb); + String disposition = bodyPart.getDisposition(); // throws ME + sb.append("\n"); + if (Part.ATTACHMENT.equals(disposition)) { + sb.append(""); + } else { + sb.append(bodyPart.getContent()); // throws IOE, ME + } + sb.append("\n"); + } + + /** + * Get the list of addresses or null. + * Null is treated differently from an empty list. + * @param propValue addresses separated by ";" + * @return the list or null if the input was the empty string + * @throws AddressException + */ + private List getPropNameAsAddresses(String propValue) throws AddressException{ + if (propValue.length() > 0){ // we have at least one potential address + List addresses = new ArrayList(); + for (String address : propValue.split(";")){ + addresses.add(new InternetAddress(address.trim())); + } + return addresses; + } else { + return null; + } + } + + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/gui/SecuritySettingsPanel.java b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/gui/SecuritySettingsPanel.java new file mode 100644 index 00000000000..214ff746c92 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/gui/SecuritySettingsPanel.java @@ -0,0 +1,419 @@ +/* + * 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.protocol.smtp.sampler.gui; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class SecuritySettingsPanel extends JPanel{ + + private static final long serialVersionUID = 1L; + + //++JMX attribute names - do not change the values! + // These were moved from SMTPSampler, which is why the prefix is still SMTSampler + public static final String USE_SSL = "SMTPSampler.useSSL"; // $NON-NLS-1$ + public static final String USE_STARTTLS = "SMTPSampler.useStartTLS"; // $NON-NLS-1$ + public static final String SSL_TRUST_ALL_CERTS = "SMTPSampler.trustAllCerts"; // $NON-NLS-1$ + public static final String ENFORCE_STARTTLS = "SMTPSampler.enforceStartTLS"; // $NON-NLS-1$ + public static final String USE_LOCAL_TRUSTSTORE = "SMTPSampler.useLocalTrustStore"; // $NON-NLS-1$ + public static final String TRUSTSTORE_TO_USE = "SMTPSampler.trustStoreToUse"; // $NON-NLS-1$ + //--JMX attribute names + + private ButtonGroup bgSecuritySettings; + + private JRadioButton rbUseNone; + + private JRadioButton rbUseSSL; + + private JRadioButton rbUseStartTLS; + + private JCheckBox cbTrustAllCerts; + + private JCheckBox cbEnforceStartTLS; + + private JCheckBox cbUseLocalTrustStore; + + private JLabel jlTrustStoreToUse; + + private JTextField tfTrustStoreToUse; + + + public SecuritySettingsPanel() { + super(); + init(); + } + + public void init(){ + this.setLayout(new GridBagLayout()); + this.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_security_settings"))); // $NON-NLS-1$ + + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2); + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.WEST; + gridBagConstraints.weightx = 0.5; + + rbUseNone = new JRadioButton(JMeterUtils.getResString("smtp_usenone")); // $NON-NLS-1$ + rbUseSSL = new JRadioButton(JMeterUtils.getResString("smtp_usessl")); // $NON-NLS-1$ + rbUseStartTLS = new JRadioButton(JMeterUtils.getResString("smtp_usestarttls")); // $NON-NLS-1$ + + cbTrustAllCerts = new JCheckBox(JMeterUtils.getResString("smtp_trustall")); // $NON-NLS-1$ + cbEnforceStartTLS = new JCheckBox(JMeterUtils.getResString("smtp_enforcestarttls")); // $NON-NLS-1$ + cbUseLocalTrustStore = new JCheckBox(JMeterUtils.getResString("smtp_usetruststore")); // $NON-NLS-1$ + + jlTrustStoreToUse = new JLabel(JMeterUtils.getResString("smtp_truststore")); // $NON-NLS-1$ + + tfTrustStoreToUse = new JTextField(20); + + rbUseNone.setSelected(true); + bgSecuritySettings = new ButtonGroup(); + bgSecuritySettings.add(rbUseNone); + bgSecuritySettings.add(rbUseSSL); + bgSecuritySettings.add(rbUseStartTLS); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + this.add(rbUseNone, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + this.add(rbUseSSL, gridBagConstraints); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + this.add(rbUseStartTLS, gridBagConstraints); + + rbUseNone.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent evt) { + rbSecuritySettingsItemStateChanged(evt); + } + }); + rbUseSSL.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent evt) { + rbSecuritySettingsItemStateChanged(evt); + } + }); + rbUseStartTLS.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent evt) { + rbSecuritySettingsItemStateChanged(evt); + } + }); + + cbTrustAllCerts.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbTrustAllCerts.setMargin(new java.awt.Insets(0, 0, 0, 0)); + cbTrustAllCerts.setEnabled(false); + cbTrustAllCerts.setToolTipText(JMeterUtils.getResString("smtp_trustall_tooltip")); // $NON-NLS-1$ + cbTrustAllCerts.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + cbTrustAllCertsActionPerformed(evt); + } + }); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + this.add(cbTrustAllCerts, gridBagConstraints); + + cbEnforceStartTLS.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbEnforceStartTLS.setMargin(new java.awt.Insets(0, 0, 0, 0)); + cbEnforceStartTLS.setEnabled(false); + cbEnforceStartTLS.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + cbEnforceStartTLSActionPerformed(evt); + } + }); + cbEnforceStartTLS.setToolTipText(JMeterUtils.getResString("smtp_enforcestarttls_tooltip")); // $NON-NLS-1$ + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + this.add(cbEnforceStartTLS, gridBagConstraints); + + cbUseLocalTrustStore.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbUseLocalTrustStore.setMargin(new java.awt.Insets(0, 0, 0, 0)); + cbUseLocalTrustStore.setEnabled(false); + cbUseLocalTrustStore.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + cbUseLocalTrustStoreActionPerformed(evt); + } + }); + + cbUseLocalTrustStore.setToolTipText(JMeterUtils.getResString("smtp_usetruststore_tooltip")); // $NON-NLS-1$ + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 2; + this.add(cbUseLocalTrustStore, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 1; + jlTrustStoreToUse.setToolTipText(JMeterUtils.getResString("smtp_truststore_tooltip")); // $NON-NLS-1$ + this.add(jlTrustStoreToUse, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + tfTrustStoreToUse.setToolTipText(JMeterUtils.getResString("smtp_truststore_tooltip")); // $NON-NLS-1$ + this.add(tfTrustStoreToUse, gridBagConstraints); + } + + /** + * ActionPerformed-method for checkbox "useLocalTrustStore" + * + * @param evt + * ActionEvent to be handled + */ + private void cbUseLocalTrustStoreActionPerformed( + ActionEvent evt) { + final boolean selected = cbUseLocalTrustStore.isSelected(); + tfTrustStoreToUse.setEditable(selected); // must follow the checkbox setting + if (selected) { + cbTrustAllCerts.setSelected(false); // not compatible + } + } + /** + * ActionPerformed-method for checkbox "cbTrustAllCerts" + * + * @param evt + * ActionEvent to be handled + */ + private void cbTrustAllCertsActionPerformed( + ActionEvent evt) { + final boolean selected = cbTrustAllCerts.isSelected(); + if (selected) { + cbUseLocalTrustStore.setSelected(false); // not compatible + tfTrustStoreToUse.setEditable(false); // must follow the checkbox setting + } + } + + /** + * ActionPerformed-method for checkbox "enforceStartTLS", empty method + * header + * + * @param evt + * ActionEvent to be handled + */ + private void cbEnforceStartTLSActionPerformed(ActionEvent evt) { + } + + /** + * ItemStateChanged-method for radiobutton "securitySettings" + * + * @param evt + * ItemEvent to be handled + */ + private void rbSecuritySettingsItemStateChanged(ItemEvent evt) { + final Object source = evt.getSource(); + if (source == rbUseNone) { + cbTrustAllCerts.setEnabled(false); + cbTrustAllCerts.setSelected(false); + cbEnforceStartTLS.setEnabled(false); + cbEnforceStartTLS.setSelected(false); + cbUseLocalTrustStore.setSelected(false); + cbUseLocalTrustStore.setEnabled(false); + tfTrustStoreToUse.setEditable(false); + } else if (source == rbUseSSL) { + cbTrustAllCerts.setEnabled(true); + cbEnforceStartTLS.setEnabled(false); + cbEnforceStartTLS.setSelected(false); + cbUseLocalTrustStore.setEnabled(true); + tfTrustStoreToUse.setEditable(false); + } else if (source == rbUseStartTLS) { + cbTrustAllCerts.setEnabled(true); + cbTrustAllCerts.setSelected(false); + cbEnforceStartTLS.setEnabled(true); + cbUseLocalTrustStore.setEnabled(true); + cbUseLocalTrustStore.setSelected(false); + tfTrustStoreToUse.setEditable(false); + } + } + /** + * Returns if SSL is used to secure the SMTP-connection (checkbox) + * + * @return true if SSL is used to secure the SMTP-connection + */ + public boolean isUseSSL() { + return rbUseSSL.isSelected(); + } + + /** + * Sets SSL to be used to secure the SMTP-connection (checkbox) + * + * @param useSSL + * Use SSL to secure the connection + */ + public void setUseSSL(boolean useSSL) { + rbUseSSL.setSelected(useSSL); + } + + /** + * Returns if StartTLS is used to secure the connection (checkbox) + * + * @return true if StartTLS is used to secure the connection + */ + public boolean isUseStartTLS() { + return rbUseStartTLS.isSelected(); + } + + /** + * Sets StartTLS to be used to secure the SMTP-connection (checkbox) + * + * @param useStartTLS + * Use StartTLS to secure the connection + */ + public void setUseStartTLS(boolean useStartTLS) { + rbUseStartTLS.setSelected(useStartTLS); + } + + /** + * Returns if StartTLS is enforced (normally, SMTP uses plain + * SMTP-connection as fallback if "250-STARTTLS" isn't sent from the + * mailserver) (checkbox) + * + * @return true if StartTLS is enforced + */ + public boolean isEnforceStartTLS() { + return cbEnforceStartTLS.isSelected(); + } + + /** + * Enforces StartTLS to secure the SMTP-connection (checkbox) + * + * @param enforceStartTLS + * Enforce the use of StartTLS to secure the connection + * @see #isEnforceStartTLS() + */ + public void setEnforceStartTLS(boolean enforceStartTLS) { + cbEnforceStartTLS.setSelected(enforceStartTLS); + } + /** + * Returns if local (pre-installed) truststore is used to avoid + * SSL-connection-exceptions (checkbox) + * + * @return true if a local truststore is used + */ + public boolean isUseLocalTrustStore() { + return cbUseLocalTrustStore.isSelected(); + } + + /** + * Set the use of a local (pre-installed) truststore to avoid + * SSL-connection-exceptions (checkbox) + * + * @param useLocalTrustStore + * Use local keystore + */ + public void setUseLocalTrustStore(boolean useLocalTrustStore) { + cbUseLocalTrustStore.setSelected(useLocalTrustStore); + tfTrustStoreToUse.setEditable(useLocalTrustStore); // ensure correctly set on initial display + } + + /** + * Returns the path to the local (pre-installed) truststore to be used to + * avoid SSL-connection-exceptions + * + * @return Path to local truststore + */ + public String getTrustStoreToUse() { + return tfTrustStoreToUse.getText(); + } + + /** + * Set the path to local (pre-installed) truststore to be used to avoid + * SSL-connection-exceptions + * + * @param trustStoreToUse + * Path to local truststore + */ + public void setTrustStoreToUse(String trustStoreToUse) { + tfTrustStoreToUse.setText(trustStoreToUse); + } + public void setUseNoSecurity(boolean selected) { + rbUseNone.setSelected(selected); + } + /** + * Returns if all certificates are blindly trusted (using according + * SocketFactory) (checkbox) + * + * @return true if all certificates are blindly trusted + */ + public boolean isTrustAllCerts() { + return cbTrustAllCerts.isSelected(); + } + + /** + * Enforces JMeter to trust all certificates, no matter what CA is issuer + * (checkbox) + * + * @param trustAllCerts + * Trust all certificates + * @see #isTrustAllCerts() + */ + public void setTrustAllCerts(boolean trustAllCerts) { + cbTrustAllCerts.setSelected(trustAllCerts); + } + + public void clear() { + tfTrustStoreToUse.setText(""); + rbUseNone.setSelected(true); + } + + public void configure(TestElement element) { + setUseSSL(element.getPropertyAsBoolean(USE_SSL)); + setUseStartTLS(element.getPropertyAsBoolean(USE_STARTTLS)); + if(!element.getPropertyAsBoolean(USE_STARTTLS) && !element.getPropertyAsBoolean(USE_SSL)){ + setUseNoSecurity(true); + } + setTrustAllCerts(element.getPropertyAsBoolean(SSL_TRUST_ALL_CERTS)); + setEnforceStartTLS(element.getPropertyAsBoolean(ENFORCE_STARTTLS)); + setUseLocalTrustStore(element.getPropertyAsBoolean(USE_LOCAL_TRUSTSTORE)); + setTrustStoreToUse(element.getPropertyAsString(TRUSTSTORE_TO_USE)); + } + + public void modifyTestElement(TestElement te) { + te.setProperty(USE_SSL, Boolean.toString(isUseSSL())); + te.setProperty(USE_STARTTLS, Boolean.toString(isUseStartTLS())); + te.setProperty(SSL_TRUST_ALL_CERTS, Boolean.toString(isTrustAllCerts())); + te.setProperty(ENFORCE_STARTTLS, Boolean.toString(isEnforceStartTLS())); + te.setProperty(USE_LOCAL_TRUSTSTORE, Boolean.toString(isUseLocalTrustStore())); + te.setProperty(TRUSTSTORE_TO_USE, getTrustStoreToUse()); + } + +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpPanel.java b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpPanel.java new file mode 100644 index 00000000000..112b22175be --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpPanel.java @@ -0,0 +1,1166 @@ +/* + * 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.protocol.smtp.sampler.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.smtp.sampler.SmtpSampler; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Class to build gui-components for SMTP-sampler. Getter-methods serve the + * input-data to the sampler-object, which provides them to the + * SendMailCommand-object. + */ +public class SmtpPanel extends JPanel { + + private static final long serialVersionUID = 1L; + + // local vars + private JTextField tfMailFrom; + private JTextField tfMailReplyTo; + private JButton browseButton; + private JButton emlBrowseButton; + private JCheckBox cbUseAuth; + private JTextField tfMailServer; + private JTextField tfMailServerPort; + private JTextField tfMailServerTimeout; + private JTextField tfMailServerConnectionTimeout; + private JTextField tfMailTo; + private JTextField tfMailToCC; + private JTextField tfMailToBCC; + private JTextField tfAttachment; + private JTextField tfEmlMessage; + private JTextArea taMessage; + private JCheckBox cbPlainBody; + + private JLabel jlAddressFrom; + private JLabel jlAddressReplyTo; + private JLabel jlAddressTo; + private JLabel jlAddressToCC; + private JLabel jlAddressToBCC; + private JLabel jlMailServerPort; + private JLabel jlMailServerTimeout; + private JLabel jlMailServerConnectionTimeout; + private JLabel jlMailServer; + private JLabel jlAttachFile; + private JLabel jlDutPortStandard; + private JLabel jlPassword; + private JLabel jlSubject; + private JLabel jlUsername; + private JLabel jlMessage; + + private JFileChooser attachmentFileChooser; + private JFileChooser emlFileChooser; + private JTextField tfAuthPassword; + private JTextField tfAuthUsername; + private JTextField tfSubject; + private JCheckBox cbSuppressSubject; + private JCheckBox cbIncludeTimestamp; + private JCheckBox cbMessageSizeStats; + private JCheckBox cbEnableDebug; + private JCheckBox cbUseEmlMessage; + + private JPanel headerFieldsPanel; + private JButton addHeaderFieldButton; + private JLabel headerFieldName; + private JLabel headerFieldValue; + private Map headerFields = new HashMap(); + private Map removeButtons = new HashMap(); + private int headerGridY = 0; + + private SecuritySettingsPanel securitySettingsPanel; + + /** + * Creates new form SmtpPanel, standard constructer. Calls + * initComponents();. + */ + public SmtpPanel() { + initComponents(); + } + + /** + * Returns sender-address for e-mail from textfield + * + * @return Sender + */ + public String getMailFrom() { + return tfMailFrom.getText(); + } + + /** + * Returns receiver in field "to" from textfield + * + * @return Receiver "to" + */ + public String getReceiverTo() { + return tfMailTo.getText(); + } + + /** + * Returns receiver in field "cc" from textfield + * + * @return Receiver "cc" + */ + public String getReceiverCC() { + return tfMailToCC.getText(); + } + + /** + * Returns receiver in field "bcc" from textfield + * + * @return Receiver "bcc" + */ + public String getReceiverBCC() { + return tfMailToBCC.getText(); + } + + /** + * Returns message body, i.e. main-mime-part of message (from textfield) + * + * @return Message body + */ + public String getBody() { + return taMessage.getText(); + } + + /** + * Sets message body, i.e. main-mime-part of message in textfield + * + * @param messageBodyText + * Message body + */ + public void setBody(String messageBodyText) { + taMessage.setText(messageBodyText); + } + + /** + * Sets sender-address of e-mail in textfield + * + * @param mailFrom + * Sender + */ + public void setMailFrom(String mailFrom) { + tfMailFrom.setText(mailFrom); + } + + /** + * Sets receiver in textfield "to" + * + * @param mailTo + * Receiver "to" + */ + public void setReceiverTo(String mailTo) { + tfMailTo.setText(mailTo); + } + + /** + * Sets receiver in textfield "cc" + * + * @param mailToCC + * Receiver "cc" + */ + public void setReceiverCC(String mailToCC) { + tfMailToCC.setText(mailToCC); + } + + /** + * Sets receiver in textfield "bcc" + * + * @param mailToBCC + * Receiver "bcc" + */ + public void setReceiverBCC(String mailToBCC) { + tfMailToBCC.setText(mailToBCC); + } + + /** + * Returns path of file(s) to be attached in e-mail from textfield + * + * @return File to attach + */ + public String getAttachments() { + return tfAttachment.getText(); + } + + /** + * Sets path of file to be attached in e-mail in textfield + * + * @param attachments + * File to attach + */ + public void setAttachments(String attachments) { + tfAttachment.setText(attachments); + } + + /** + * Returns port of mail-server (standard 25 for SMTP/SMTP with StartTLS, 465 + * for SSL) from textfield + * + * @return Mail-server port + */ + public String getPort() { + return tfMailServerPort.getText(); + } + + /** + * Sets port of mail-server + * + * @param port + * Mail-server port + */ + public void setPort(String port) { + tfMailServerPort.setText(port); + } + + /** + * Returns mail-server to be used to send message (from textfield) + * + * @return FQDN or IP of mail-server + */ + public String getServer() { + return tfMailServer.getText(); + } + + /** + * Sets mail-server to be used to send message in textfield + * + * @param server + * FQDN or IP of mail-server + */ + public void setServer(String server) { + tfMailServer.setText(server); + } + + /** + * Returns timeout for SMTP connection from textfield + * + * @return Smtp timeout + */ + public String getTimeout() { + return tfMailServerTimeout.getText(); + } + + /** + * Sets timeout (ms) for SMTP connection + * + * @param timeout + * SMTP Timeout (ms) + */ + public void setTimeout(String timeout) { + tfMailServerTimeout.setText(timeout); + } + + /** + * Returns connection timeout for SMTP connection from textfield + * + * @return SMTP connection timeout + */ + public String getConnectionTimeout() { + return tfMailServerConnectionTimeout.getText(); + } + + /** + * Sets connection timeout (ms) for SMTP connection + * + * @param connectionTimeout + * SMTP Connection Timeout (ms) + */ + public void setConnectionTimeout(String connectionTimeout) { + tfMailServerConnectionTimeout.setText(connectionTimeout); + } + + /** + * Returns subject of the e-mail from textfield + * + * @return Subject of e-mail + */ + public String getSubject() { + return tfSubject.getText(); + } + + /** + * Sets subject of the e-mail in textfield + * + * @param subject + * Subject of e-mail + */ + public void setSubject(String subject) { + tfSubject.setText(subject); + } + + /** + * Returns true if subject header should be suppressed + * + * @return true if subject header should be suppressed + */ + public boolean isSuppressSubject() { + return cbSuppressSubject.isSelected(); + } + + /** + * Sets the property that defines if the subject header should be suppressed + * + * @param emptySubject flag whether subject header should be suppressed + * + */ + public void setSuppressSubject(boolean emptySubject) { + cbSuppressSubject.setSelected(emptySubject); + } + + /** + * Returns true if message body should be plain (i.e. not multipart/mixed) + * + * @return true if using plain message body (i.e. not multipart/mixed) + */ + public boolean isPlainBody() { + return cbPlainBody.isSelected(); + } + + /** + * Sets the property that defines if the body should be plain (i.e. not multipart/mixed) + * + * @param plainBody whether to use a plain body (i.e. not multipart/mixed) + */ + public void setPlainBody(boolean plainBody) { + cbPlainBody.setSelected(plainBody); + } + + /** + * Returns if mail-server needs authentication (checkbox) + * + * @return true if authentication is used + */ + public boolean isUseAuth() { + return cbUseAuth.isSelected(); + } + + /** + * Set whether mail server needs auth. + * + * @param selected flag whether mail server needs auth + */ + public void setUseAuth(boolean selected){ + cbUseAuth.setSelected(selected); + tfAuthPassword.setEditable(selected); // ensure correctly set on initial display + tfAuthUsername.setEditable(selected); // ensure correctly set on initial display + } + + + public boolean isEnableDebug() { + return cbEnableDebug.isSelected(); + } + + public void setEnableDebug(boolean selected){ + cbEnableDebug.setSelected(selected); + } + + + + /** + * Returns if an .eml-message is sent instead of the content of message-text + * area + * + * @return true if .eml is sent, false if text area content is sent in + * e-mail + */ + public boolean isUseEmlMessage() { + return cbUseEmlMessage.isSelected(); + } + + /** + * Set the use of an .eml-message instead of the content of message-text + * area + * + * @param useEmlMessage + * Use eml message + */ + public void setUseEmlMessage(boolean useEmlMessage) { + cbUseEmlMessage.setSelected(useEmlMessage); + } + + /** + * Returns path to eml message to be sent + * + * @return path to eml message to be sent + */ + public String getEmlMessage() { + return tfEmlMessage.getText(); + } + + /** + * Set path to eml message to be sent + * + * @param emlMessage + * path to eml message to be sent + */ + public void setEmlMessage(String emlMessage) { + tfEmlMessage.setText(emlMessage); + } + + /** + * Returns if current timestamp is included in the subject (checkbox) + * + * @return true if current timestamp is included in subject + */ + public boolean isIncludeTimestamp() { + return cbIncludeTimestamp.isSelected(); + } + + /** + * Set timestamp to be included in the message-subject (checkbox) + * + * @param includeTimestamp + * Should timestamp be included in subject? + */ + public void setIncludeTimestamp(boolean includeTimestamp) { + cbIncludeTimestamp.setSelected(includeTimestamp); + } + + /** + * Returns if message size statistics are processed. Output of processing + * will be included in sample result. (checkbox) + * + * @return True if message size will be calculated + */ + public boolean isMessageSizeStatistics() { + return cbMessageSizeStats.isSelected(); + } + + /** + * Set message size to be calculated and included in sample result + * (checkbox) + * + * @param val + * Schould message size be calculated? + */ + public void setMessageSizeStatistic(boolean val) { + cbMessageSizeStats.setSelected(val); + } + + public String getPassword() { + return tfAuthPassword.getText(); + } + + public void setPassword(String authPassword) { + tfAuthPassword.setText(authPassword); + } + + public String getUsername() { + return tfAuthUsername.getText(); + } + + public void setUsername(String username) { + tfAuthUsername.setText(username); + } + + public CollectionProperty getHeaderFields() { + CollectionProperty result = new CollectionProperty(); + result.setName(SmtpSampler.HEADER_FIELDS); + for (Iterator iterator = headerFields.keySet().iterator(); iterator.hasNext();) { + JTextField headerName = iterator.next(); + String name = headerName.getText(); + String value = headerFields.get(headerName).getText(); + Argument argument = new Argument(name, value); + result.addItem(argument); + } + return result; + } + + public void setHeaderFields(CollectionProperty fields) { + clearHeaderFields(); + for (int i = 0; i < fields.size(); i++) { + Argument argument = (Argument)((TestElementProperty)fields.get(i)).getObjectValue(); + String name = argument.getName(); + JButton removeButton = addHeaderActionPerformed(null); + JTextField nameTF = removeButtons.get(removeButton); + nameTF.setText(name); + JTextField valueTF = headerFields.get(nameTF); + valueTF.setText(argument.getValue()); + } + validate(); + } + + public String getMailReplyTo() { + return tfMailReplyTo.getText(); + } + + public void setMailReplyTo(String replyTo) { + tfMailReplyTo.setText(replyTo); + } + + + /** + * Main method of class, builds all gui-components for SMTP-sampler. + */ + private void initComponents() { + GridBagConstraints gridBagConstraints, gridBagConstraintsMain; + + jlAddressReplyTo = new JLabel(JMeterUtils.getResString("smtp_replyto")); // $NON-NLS-1$ + jlAddressFrom = new JLabel(JMeterUtils.getResString("smtp_from")); // $NON-NLS-1$ + jlAddressTo = new JLabel(JMeterUtils.getResString("smtp_to")); // $NON-NLS-1$ + jlAddressToCC = new JLabel(JMeterUtils.getResString("smtp_cc")); // $NON-NLS-1$ + jlAddressToBCC = new JLabel(JMeterUtils.getResString("smtp_bcc")); // $NON-NLS-1$ + jlMailServerPort = new JLabel(JMeterUtils.getResString("smtp_server_port")); // $NON-NLS-1$ + jlMailServer = new JLabel(JMeterUtils.getResString("smtp_server")); // $NON-NLS-1$ + jlMailServerTimeout = new JLabel(JMeterUtils.getResString("smtp_server_timeout")); // $NON-NLS-1$ + jlMailServerConnectionTimeout = new JLabel(JMeterUtils.getResString("smtp_server_connection_timeout")); // $NON-NLS-1$ + jlAttachFile = new JLabel(JMeterUtils.getResString("smtp_attach_file")); // $NON-NLS-1$ + jlDutPortStandard = new JLabel(JMeterUtils.getResString("smtp_default_port")); // $NON-NLS-1$ + jlUsername = new JLabel(JMeterUtils.getResString("smtp_username")); // $NON-NLS-1$ + jlPassword = new JLabel(JMeterUtils.getResString("smtp_password")); // $NON-NLS-1$ + jlSubject = new JLabel(JMeterUtils.getResString("smtp_subject")); // $NON-NLS-1$ + jlMessage = new JLabel(JMeterUtils.getResString("smtp_message")); // $NON-NLS-1$ + + tfMailServer = new JTextField(30); + tfMailServerPort = new JTextField(6); + tfMailServerTimeout = new JTextField(6); + tfMailServerConnectionTimeout = new JTextField(6); + tfMailFrom = new JTextField(25); + tfMailReplyTo = new JTextField(25); + tfMailTo = new JTextField(25); + tfMailToCC = new JTextField(25); + tfMailToBCC = new JTextField(25); + tfAuthUsername = new JTextField(20); + tfAuthPassword = new JPasswordField(20); + tfSubject = new JTextField(20); + tfAttachment = new JTextField(30); + tfEmlMessage = new JTextField(30); + + taMessage = new JTextArea(5, 20); + + cbPlainBody = new JCheckBox(JMeterUtils.getResString("smtp_plainbody")); // $NON-NLS-1$ + + cbSuppressSubject = new JCheckBox(JMeterUtils.getResString("smtp_suppresssubj")); // $NON-NLS-1$ + cbSuppressSubject.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent evt) { + emptySubjectActionPerformed(evt); + } + }); + + cbUseAuth = new JCheckBox(JMeterUtils.getResString("smtp_useauth")); // $NON-NLS-1$ + + cbIncludeTimestamp = new JCheckBox(JMeterUtils.getResString("smtp_timestamp")); // $NON-NLS-1$ + cbMessageSizeStats = new JCheckBox(JMeterUtils.getResString("smtp_messagesize")); // $NON-NLS-1$ + cbEnableDebug = new JCheckBox(JMeterUtils.getResString("smtp_enabledebug")); // $NON-NLS-1$ + cbUseEmlMessage = new JCheckBox(JMeterUtils.getResString("smtp_eml")); // $NON-NLS-1$ + + attachmentFileChooser = new JFileChooser(); + emlFileChooser = new JFileChooser(); + + browseButton = new JButton(JMeterUtils.getResString("browse")); // $NON-NLS-1$ + emlBrowseButton = new JButton(JMeterUtils.getResString("browse")); // $NON-NLS-1$ + + attachmentFileChooser + .addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + attachmentFolderFileChooserActionPerformed(evt); + } + }); + + emlFileChooser.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + emlFileChooserActionPerformed(evt); + } + }); + + setLayout(new GridBagLayout()); + + gridBagConstraintsMain = new GridBagConstraints(); + gridBagConstraintsMain.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraintsMain.anchor = GridBagConstraints.WEST; + gridBagConstraintsMain.weightx = 0.5; + + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2); + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.WEST; + gridBagConstraints.weightx = 0.5; + + /* + * Server Settings + */ + JPanel panelServerSettings = new VerticalPanel(); + panelServerSettings.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_server_settings"))); // $NON-NLS-1$ + + JPanel panelMailServer = new JPanel(new BorderLayout(5, 0)); + panelMailServer.add(jlMailServer, BorderLayout.WEST); + panelMailServer.add(tfMailServer, BorderLayout.CENTER); + JPanel panelMailServerPort = new JPanel(new BorderLayout(5, 0)); + panelMailServerPort.add(jlMailServerPort, BorderLayout.WEST); + panelMailServerPort.add(tfMailServerPort, BorderLayout.CENTER); + panelMailServerPort.add(jlDutPortStandard, BorderLayout.EAST); + + panelServerSettings.add(panelMailServer, BorderLayout.CENTER); + panelServerSettings.add(panelMailServerPort, BorderLayout.SOUTH); + + JPanel panelServerTimeoutsSettings = new VerticalPanel(); + panelServerTimeoutsSettings.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_server_timeouts_settings"))); // $NON-NLS-1$ + + JPanel panelMailServerConnectionTimeout = new JPanel(new BorderLayout(5, 0)); + panelMailServerConnectionTimeout.add(jlMailServerConnectionTimeout, BorderLayout.WEST); + panelMailServerConnectionTimeout.add(tfMailServerConnectionTimeout, BorderLayout.CENTER); + JPanel panelMailServerTimeout = new JPanel(new BorderLayout(5, 0)); + panelMailServerTimeout.add(jlMailServerTimeout, BorderLayout.WEST); + panelMailServerTimeout.add(tfMailServerTimeout, BorderLayout.CENTER); + + panelServerTimeoutsSettings.add(panelMailServerConnectionTimeout, BorderLayout.CENTER); + panelServerTimeoutsSettings.add(panelMailServerTimeout, BorderLayout.SOUTH); + + JPanel panelServerConfig = new HorizontalPanel(); + panelServerConfig.add(panelServerSettings, BorderLayout.CENTER); + panelServerConfig.add(panelServerTimeoutsSettings, BorderLayout.EAST); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 0; + add(panelServerConfig, gridBagConstraintsMain); + + /* + * E-Mail Settings + */ + JPanel panelMailSettings = new JPanel(new GridBagLayout()); + panelMailSettings.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_mail_settings"))); // $NON-NLS-1$ + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelMailSettings.add(jlAddressFrom, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + panelMailSettings.add(tfMailFrom, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panelMailSettings.add(jlAddressTo, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panelMailSettings.add(tfMailTo, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + panelMailSettings.add(jlAddressToCC, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + panelMailSettings.add(tfMailToCC, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + panelMailSettings.add(jlAddressToBCC, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + panelMailSettings.add(tfMailToBCC, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + panelMailSettings.add(jlAddressReplyTo, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 4; + panelMailSettings.add(tfMailReplyTo, gridBagConstraints); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 1; + add(panelMailSettings, gridBagConstraintsMain); + + /* + * Auth Settings + */ + JPanel panelAuthSettings = new JPanel(new GridBagLayout()); + panelAuthSettings.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_auth_settings"))); // $NON-NLS-1$ + + cbUseAuth.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbUseAuth.setMargin(new java.awt.Insets(0, 0, 0, 0)); + cbUseAuth.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + cbUseAuthActionPerformed(evt); + } + }); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelAuthSettings.add(cbUseAuth, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 1; + gridBagConstraints.weightx = 0; + panelAuthSettings.add(jlUsername, gridBagConstraints); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.weightx = 0.5; + panelAuthSettings.add(tfAuthUsername, gridBagConstraints); + tfAuthUsername.setEditable(false); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 1; + gridBagConstraints.weightx = 0; + panelAuthSettings.add(jlPassword, gridBagConstraints); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + gridBagConstraints.weightx = 0.5; + panelAuthSettings.add(tfAuthPassword, gridBagConstraints); + tfAuthPassword.setEditable(false); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 2; + add(panelAuthSettings, gridBagConstraintsMain); + + /* + * Security Settings + */ + securitySettingsPanel = new SecuritySettingsPanel(); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 3; + add(securitySettingsPanel, gridBagConstraintsMain); + + /* + * (non-Javadoc) Message Settings + */ + JPanel panelMessageSettings = new JPanel(new GridBagLayout()); + panelMessageSettings.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_message_settings"))); // $NON-NLS-1$ + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelMessageSettings.add(jlSubject, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + panelMessageSettings.add(tfSubject, gridBagConstraints); + + cbSuppressSubject.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbSuppressSubject.setMargin(new java.awt.Insets(0, 0, 0, 0)); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(cbSuppressSubject, gridBagConstraints); + + cbIncludeTimestamp.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbIncludeTimestamp.setMargin(new java.awt.Insets(0, 0, 0, 0)); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(cbIncludeTimestamp, gridBagConstraints); + + /* + * Add the header panel + */ + + addHeaderFieldButton = new JButton(JMeterUtils.getResString("smtp_header_add")); // $NON-NLS-1$ + addHeaderFieldButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + addHeaderActionPerformed(evt); + } + }); + headerFieldName = new JLabel(JMeterUtils.getResString("smtp_header_name")); // $NON-NLS-1$ + headerFieldValue = new JLabel(JMeterUtils.getResString("smtp_header_value")); // $NON-NLS-1$ + headerFieldsPanel = new JPanel(new GridBagLayout()); + + headerFieldName.setVisible(false); + headerFieldValue.setVisible(false); + + headerGridY=0; + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = headerGridY++; + headerFieldsPanel.add(addHeaderFieldButton, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = headerGridY; + headerFieldsPanel.add(headerFieldName, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = headerGridY++; + headerFieldsPanel.add(headerFieldValue, gridBagConstraints); + + gridBagConstraintsMain.gridx = 1; + gridBagConstraintsMain.gridy = 2; + panelMessageSettings.add(headerFieldsPanel, gridBagConstraintsMain); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + panelMessageSettings.add(jlMessage, gridBagConstraints); + + taMessage.setBorder(BorderFactory.createBevelBorder(1)); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + gridBagConstraints.fill = GridBagConstraints.BOTH; + panelMessageSettings.add(taMessage, gridBagConstraints); + + cbPlainBody.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbPlainBody.setMargin(new java.awt.Insets(0, 0, 0, 0)); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 3; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(cbPlainBody, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(jlAttachFile, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 4; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + panelMessageSettings.add(tfAttachment, gridBagConstraints); + tfAttachment.setToolTipText(JMeterUtils.getResString("smtp_attach_file_tooltip")); // $NON-NLS-1$ + + browseButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + browseButtonActionPerformed(evt); + } + }); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 4; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(browseButton, gridBagConstraints); + + cbUseEmlMessage.setSelected(false); + cbUseEmlMessage.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + cbUseEmlMessageActionPerformed(evt); + } + }); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 5; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(cbUseEmlMessage, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 5; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + tfEmlMessage.setEnabled(false); + panelMessageSettings.add(tfEmlMessage, gridBagConstraints); + + emlBrowseButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + emlBrowseButtonActionPerformed(evt); + } + }); + emlBrowseButton.setEnabled(false); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 5; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(emlBrowseButton, gridBagConstraints); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 6; + add(panelMessageSettings, gridBagConstraintsMain); + + /* + * Additional Settings + */ + JPanel panelAdditionalSettings = new JPanel(new GridBagLayout()); + panelAdditionalSettings.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_additional_settings"))); // $NON-NLS-1$ + + cbMessageSizeStats.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbMessageSizeStats.setMargin(new java.awt.Insets(0, 0, 0, 0)); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelAdditionalSettings.add(cbMessageSizeStats, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + panelAdditionalSettings.add(cbEnableDebug, gridBagConstraints); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 7; + add(panelAdditionalSettings, gridBagConstraintsMain); + } + + /** + * ActionPerformed-method for checkbox "useAuth" + * + * @param evt + * ActionEvent to be handled + */ + private void cbUseAuthActionPerformed(ActionEvent evt) { + tfAuthUsername.setEditable(cbUseAuth.isSelected()); + tfAuthPassword.setEditable(cbUseAuth.isSelected()); + } + + + + /** + * ActionPerformed-method for filechoser "attachmentFileChoser", creates + * FileChoser-Object + * + * @param evt + * ActionEvent to be handled + */ + private void attachmentFolderFileChooserActionPerformed(ActionEvent evt) { + File chosen = attachmentFileChooser.getSelectedFile(); + if (chosen == null){ + return; + } + final String attachments = tfAttachment.getText().trim(); + if (null != attachments && attachments.length() > 0) { + tfAttachment.setText(attachments + + SmtpSampler.FILENAME_SEPARATOR + + chosen.getAbsolutePath()); + } else { + tfAttachment.setText(chosen.getAbsolutePath()); + } + + } + + /** + * ActionPerformed-method for button "browseButton", opens FileDialog-Object + * + * @param evt + * ActionEvent to be handled + */ + private void browseButtonActionPerformed(ActionEvent evt) { + attachmentFileChooser.showOpenDialog(this); + } + + private void cbUseEmlMessageActionPerformed(ActionEvent evt) { + if (cbUseEmlMessage.isSelected()) { + tfEmlMessage.setEnabled(true); + emlBrowseButton.setEnabled(true); + + /*tfMailFrom.setEnabled(false); + tfMailTo.setEnabled(false); + tfMailToCC.setEnabled(false); + tfMailToBCC.setEnabled(false); + tfSubject.setEnabled(false);*/ + taMessage.setEnabled(false); + tfAttachment.setEnabled(false); + browseButton.setEnabled(false); + } else { + tfEmlMessage.setEnabled(false); + emlBrowseButton.setEnabled(false); + + /*tfMailFrom.setEnabled(true); + tfMailTo.setEnabled(true); + tfMailToCC.setEnabled(true); + tfMailToBCC.setEnabled(true); + tfSubject.setEnabled(true);*/ + taMessage.setEnabled(true); + tfAttachment.setEnabled(true); + browseButton.setEnabled(true); + } + } + + /** + * ActionPerformed-method for filechoser "emlFileChoser", creates + * FileChoser-Object + * + * @param evt + * ActionEvent to be handled + */ + private void emlFileChooserActionPerformed(ActionEvent evt) { + tfEmlMessage.setText(emlFileChooser.getSelectedFile().getAbsolutePath()); + } + + /** + * ActionPerformed-method for button "emlButton", opens FileDialog-Object + * + * @param evt + * ActionEvent to be handled + */ + private void emlBrowseButtonActionPerformed(ActionEvent evt) { + emlFileChooser.showOpenDialog(this); + } + + + + /** + * Reset all the Gui fields. + */ + public void clear() { + cbIncludeTimestamp.setSelected(false); + cbMessageSizeStats.setSelected(false); + cbEnableDebug.setSelected(false); + cbUseEmlMessage.setSelected(false); + cbUseAuth.setSelected(false); + taMessage.setText(""); + tfAttachment.setText(""); + tfAuthPassword.setText(""); + tfAuthUsername.setText(""); + tfEmlMessage.setText(""); + tfMailFrom.setText(""); + tfMailReplyTo.setText(""); + tfMailServer.setText(""); + tfMailServerPort.setText(""); + tfMailServerConnectionTimeout.setText(""); + tfMailServerTimeout.setText(""); + tfMailTo.setText(""); + tfMailToBCC.setText(""); + tfMailToCC.setText(""); + tfSubject.setText(""); + cbPlainBody.setSelected(false); + cbSuppressSubject.setSelected(false); + securitySettingsPanel.clear(); + clearHeaderFields(); + validate(); + } + + private void clearHeaderFields() { + headerFieldName.setVisible(false); + headerFieldValue.setVisible(false); + + for (Iterator iterator = removeButtons.keySet().iterator(); iterator.hasNext();) { + JButton removeButton = iterator.next(); + JTextField headerName = removeButtons.get(removeButton); + JTextField headerValue = headerFields.get(headerName); + + headerFieldsPanel.remove(headerName); + if (headerValue != null){ // Can be null (not sure why) + headerFieldsPanel.remove(headerValue); + } + headerFieldsPanel.remove(removeButton); + headerFields.remove(headerName); + iterator.remove(); + } + } + + private JButton addHeaderActionPerformed(ActionEvent evt){ + if(headerFields.size() == 0){ + headerFieldName.setVisible(true); + headerFieldValue.setVisible(true); + } + JTextField nameTF = new JTextField(); + JTextField valueTF = new JTextField(); + JButton removeButton = new JButton(JMeterUtils.getResString("smtp_header_remove")); // $NON-NLS-1$ + headerFields.put(nameTF, valueTF); + removeButtons.put(removeButton, nameTF); + + removeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + removeHeaderActionPerformed(evt); + } + }); + + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2); + gridBagConstraints.weightx = 0.5; + gridBagConstraints.anchor = GridBagConstraints.WEST; + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = headerGridY; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + headerFieldsPanel.add(nameTF, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = headerGridY; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + headerFieldsPanel.add(valueTF, gridBagConstraints); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = headerGridY++; + gridBagConstraints.fill = GridBagConstraints.NONE; + headerFieldsPanel.add(removeButton, gridBagConstraints); + + validate(); + return removeButton; + } + public SecuritySettingsPanel getSecuritySettingsPanel() { + return securitySettingsPanel; + } + + public void setSecuritySettingsPanel(SecuritySettingsPanel securitySettingsPanel) { + this.securitySettingsPanel = securitySettingsPanel; + } + + private void removeHeaderActionPerformed(ActionEvent evt){ + final Object source = evt.getSource(); + if(source != null && source instanceof JButton){ + if(headerFields.size() == 1){ + headerFieldName.setVisible(false); + headerFieldValue.setVisible(false); + } + JTextField nameTF = removeButtons.get(source); + JTextField valueTF = headerFields.get(nameTF); + headerFields.remove(nameTF); + + headerFieldsPanel.remove(nameTF); + headerFieldsPanel.remove(valueTF); + headerFieldsPanel.remove((JButton)source); + validate(); + } + } + private void emptySubjectActionPerformed(ChangeEvent evt) { + final Object source = evt.getSource(); + if(source != null && source instanceof JCheckBox){ + if(cbSuppressSubject.isSelected()){ + tfSubject.setEnabled(false); + cbIncludeTimestamp.setEnabled(false); + }else{ + tfSubject.setEnabled(true); + cbIncludeTimestamp.setEnabled(true); + } + } + } + +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpSamplerGui.java b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpSamplerGui.java new file mode 100644 index 00000000000..3fb167b29b7 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpSamplerGui.java @@ -0,0 +1,193 @@ +/* + * 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.protocol.smtp.sampler.gui; + +import java.awt.BorderLayout; +import java.awt.Component; + +import org.apache.jmeter.protocol.smtp.sampler.SmtpSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; + +/** + * Class to build superstructure-gui for SMTP-panel, sets/gets value for a JMeter's testElement-object (i.e. also for save/load-purposes). + * This class extends AbstractSamplerGui, therefor most implemented methods are defined by JMeter's structure. + */ +public class SmtpSamplerGui extends AbstractSamplerGui { + + /** + * + */ + private static final long serialVersionUID = 1L; + private SmtpPanel smtpPanel; + + /** + * Creates new SmtpSamplerGui, standard constructer. Calls init(); + */ + public SmtpSamplerGui() { + init(); + } + + /** + * Method to be implemented by interface, overwritten by getStaticLabel(). Method has to be implemented by interface + * @return Null-String + * @see org.apache.jmeter.gui.JMeterGUIComponent#getLabelResource() + */ + @Override + public String getLabelResource() { + return "smtp_sampler_title"; + } + + /** + * Copy the data from the test element to the GUI, method has to be implemented by interface + * @param element Test-element to be used as data-input + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#configure(org.apache.jmeter.testelement.TestElement) + */ + @Override + public void configure(TestElement element) { + if (smtpPanel == null){ + smtpPanel = new SmtpPanel(); + } + smtpPanel.setServer(element.getPropertyAsString(SmtpSampler.SERVER)); + smtpPanel.setPort(element.getPropertyAsString(SmtpSampler.SERVER_PORT)); + smtpPanel.setTimeout(element.getPropertyAsString(SmtpSampler.SERVER_TIMEOUT)); + smtpPanel.setConnectionTimeout(element.getPropertyAsString(SmtpSampler.SERVER_CONNECTION_TIMEOUT)); + smtpPanel.setMailFrom(element.getPropertyAsString(SmtpSampler.MAIL_FROM)); + smtpPanel.setMailReplyTo(element.getPropertyAsString(SmtpSampler.MAIL_REPLYTO)); + smtpPanel.setReceiverTo(element.getPropertyAsString(SmtpSampler.RECEIVER_TO)); + smtpPanel.setReceiverCC(element.getPropertyAsString(SmtpSampler.RECEIVER_CC)); + smtpPanel.setReceiverBCC(element.getPropertyAsString(SmtpSampler.RECEIVER_BCC)); + + smtpPanel.setBody(element.getPropertyAsString(SmtpSampler.MESSAGE)); + smtpPanel.setPlainBody(element.getPropertyAsBoolean(SmtpSampler.PLAIN_BODY)); + smtpPanel.setSubject(element.getPropertyAsString(SmtpSampler.SUBJECT)); + smtpPanel.setSuppressSubject(element.getPropertyAsBoolean(SmtpSampler.SUPPRESS_SUBJECT)); + smtpPanel.setIncludeTimestamp(element.getPropertyAsBoolean(SmtpSampler.INCLUDE_TIMESTAMP)); + JMeterProperty headers = element.getProperty(SmtpSampler.HEADER_FIELDS); + if (headers instanceof CollectionProperty) { // Might be NullProperty + smtpPanel.setHeaderFields((CollectionProperty)headers); + } else { + smtpPanel.setHeaderFields(new CollectionProperty()); + } + smtpPanel.setAttachments(element.getPropertyAsString(SmtpSampler.ATTACH_FILE)); + + smtpPanel.setUseEmlMessage(element.getPropertyAsBoolean(SmtpSampler.USE_EML)); + smtpPanel.setEmlMessage(element.getPropertyAsString(SmtpSampler.EML_MESSAGE_TO_SEND)); + + SecuritySettingsPanel secPanel = smtpPanel.getSecuritySettingsPanel(); + secPanel.configure(element); + + smtpPanel.setUseAuth(element.getPropertyAsBoolean(SmtpSampler.USE_AUTH)); + smtpPanel.setUsername(element.getPropertyAsString(SmtpSampler.USERNAME)); + smtpPanel.setPassword(element.getPropertyAsString(SmtpSampler.PASSWORD)); + + smtpPanel.setMessageSizeStatistic(element.getPropertyAsBoolean(SmtpSampler.MESSAGE_SIZE_STATS)); + smtpPanel.setEnableDebug(element.getPropertyAsBoolean(SmtpSampler.ENABLE_DEBUG)); + + super.configure(element); + } + + /** + * Creates a new TestElement and set up its data + * @return Test-element for JMeter + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + @Override + public TestElement createTestElement() { + SmtpSampler sampler = new SmtpSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components + * @param te TestElement for JMeter + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(org.apache.jmeter.testelement.TestElement) + */ + @Override + public void modifyTestElement(TestElement te) { + te.clear(); + super.configureTestElement(te); + te.setProperty(SmtpSampler.SERVER, smtpPanel.getServer()); + te.setProperty(SmtpSampler.SERVER_PORT, smtpPanel.getPort()); + te.setProperty(SmtpSampler.SERVER_TIMEOUT, smtpPanel.getTimeout(), ""); // $NON-NLS-1$ + te.setProperty(SmtpSampler.SERVER_CONNECTION_TIMEOUT, smtpPanel.getConnectionTimeout(), ""); // $NON-NLS-1$ + te.setProperty(SmtpSampler.MAIL_FROM, smtpPanel.getMailFrom()); + te.setProperty(SmtpSampler.MAIL_REPLYTO, smtpPanel.getMailReplyTo()); + te.setProperty(SmtpSampler.RECEIVER_TO, smtpPanel.getReceiverTo()); + te.setProperty(SmtpSampler.RECEIVER_CC, smtpPanel.getReceiverCC()); + te.setProperty(SmtpSampler.RECEIVER_BCC, smtpPanel.getReceiverBCC()); + te.setProperty(SmtpSampler.SUBJECT, smtpPanel.getSubject()); + te.setProperty(SmtpSampler.SUPPRESS_SUBJECT, Boolean.toString(smtpPanel.isSuppressSubject())); + te.setProperty(SmtpSampler.INCLUDE_TIMESTAMP, Boolean.toString(smtpPanel.isIncludeTimestamp())); + te.setProperty(SmtpSampler.MESSAGE, smtpPanel.getBody()); + te.setProperty(SmtpSampler.PLAIN_BODY, Boolean.toString(smtpPanel.isPlainBody())); + te.setProperty(SmtpSampler.ATTACH_FILE, smtpPanel.getAttachments()); + + SecuritySettingsPanel secPanel = smtpPanel.getSecuritySettingsPanel(); + secPanel.modifyTestElement(te); + + te.setProperty(SmtpSampler.USE_EML, smtpPanel.isUseEmlMessage()); + te.setProperty(SmtpSampler.EML_MESSAGE_TO_SEND, smtpPanel.getEmlMessage()); + + te.setProperty(SmtpSampler.USE_AUTH, Boolean.toString(smtpPanel.isUseAuth())); + te.setProperty(SmtpSampler.PASSWORD, smtpPanel.getPassword()); + te.setProperty(SmtpSampler.USERNAME, smtpPanel.getUsername()); + + te.setProperty(SmtpSampler.MESSAGE_SIZE_STATS, Boolean.toString(smtpPanel.isMessageSizeStatistics())); + te.setProperty(SmtpSampler.ENABLE_DEBUG, Boolean.toString(smtpPanel.isEnableDebug())); + + te.setProperty(smtpPanel.getHeaderFields()); + } + + /** + * Helper method to set up the GUI screen + */ + private void init() { + // Standard setup + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); // Add the standard title + add(makeDataPanel(), BorderLayout.CENTER); + } + + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + if (smtpPanel != null) { + smtpPanel.clear(); + } + } + /** + * Creates a sampler-gui-object, singleton-method + * @return Panel for entering the data + */ + private Component makeDataPanel() { + if (smtpPanel == null) + smtpPanel = new SmtpPanel(); + return smtpPanel; + } +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/LocalTrustStoreSSLSocketFactory.java b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/LocalTrustStoreSSLSocketFactory.java new file mode 100644 index 00000000000..07e5cefe9b2 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/LocalTrustStoreSSLSocketFactory.java @@ -0,0 +1,136 @@ +/* + * 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.protocol.smtp.sampler.protocol; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.security.KeyStore; +import java.security.SecureRandom; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.commons.io.IOUtils; + +/** + * This class implements an SSLSocketFactory which supports a local truststore. + */ +public class LocalTrustStoreSSLSocketFactory extends SSLSocketFactory { + + private final SSLSocketFactory factory; + + public LocalTrustStoreSSLSocketFactory(File truststore){ + SSLContext sslcontext = null; + try { + KeyStore ks = KeyStore.getInstance("JKS"); // $NON-NLS-1$ + InputStream stream = null; + try { + stream = new BufferedInputStream(new FileInputStream(truststore)); + ks.load(stream, null); + } finally { + IOUtils.closeQuietly(stream); + } + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + TrustManager[] trustmanagers = tmf.getTrustManagers(); + sslcontext = SSLContext.getInstance("TLS"); // $NON-NLS-1$ + sslcontext.init( null, trustmanagers, new SecureRandom()); + } catch (Exception e) { + throw new RuntimeException("Could not create the SSL context",e); + } + factory = sslcontext.getSocketFactory(); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( Socket socket, String s, int i, boolean + flag) + throws IOException { + return factory.createSocket( socket, s, i, flag); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( InetAddress inaddr, int i, + InetAddress inaddr1, int j) throws IOException { + return factory.createSocket( inaddr, i, inaddr1, j); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( InetAddress inaddr, int i) throws + IOException { + return factory.createSocket( inaddr, i); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( String s, int i, InetAddress inaddr, int j) + throws IOException { + return factory.createSocket( s, i, inaddr, j); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( String s, int i) throws IOException { + return factory.createSocket( s, i); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket() throws IOException { + return factory.createSocket(); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getDefaultCipherSuites() { + return factory.getSupportedCipherSuites(); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getSupportedCipherSuites() { + return factory.getSupportedCipherSuites(); + } +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/SendMailCommand.java b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/SendMailCommand.java new file mode 100644 index 00000000000..012153d3b5a --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/SendMailCommand.java @@ -0,0 +1,841 @@ +/* + * 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.protocol.smtp.sampler.protocol; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import javax.activation.DataHandler; +import javax.activation.FileDataSource; +import javax.mail.BodyPart; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class performs all tasks necessary to send a message (build message, + * prepare connection, send message). Provides getter-/setter-methods for an + * SmtpSampler-object to configure transport and message settings. The + * send-mail-command itself is started by the SmtpSampler-object. + */ +public class SendMailCommand { + + // local vars + private static final Logger logger = LoggingManager.getLoggerForClass(); + + // Use the actual class so the name must be correct. + private static final String TRUST_ALL_SOCKET_FACTORY = TrustAllSSLSocketFactory.class.getName(); + + private boolean useSSL = false; + private boolean useStartTLS = false; + private boolean trustAllCerts = false; + private boolean enforceStartTLS = false; + private boolean sendEmlMessage = false; + private boolean enableDebug; + private String smtpServer; + private String smtpPort; + private String sender; + private List replyTo; + private String emlMessage; + private List receiverTo; + private List receiverCC; + private List receiverBCC; + private CollectionProperty headerFields; + private String subject = ""; + + private boolean useAuthentication = false; + private String username; + private String password; + + private boolean useLocalTrustStore; + private String trustStoreToUse; + + private List attachments; + + private String mailBody; + + private String timeOut; // Socket read timeout value in milliseconds. This timeout is implemented by java.net.Socket. + private String connectionTimeOut; // Socket connection timeout value in milliseconds. This timeout is implemented by java.net.Socket. + + // case we are measuring real time of spedition + private boolean synchronousMode; + + private Session session; + + private StringBuilder serverResponse = new StringBuilder(); // TODO this is not populated currently + + /** send plain body, i.e. not multipart/mixed */ + private boolean plainBody; + + /** + * Standard-Constructor + */ + public SendMailCommand() { + headerFields = new CollectionProperty(); + attachments = new ArrayList(); + } + + /** + * Prepares message prior to be sent via execute()-method, i.e. sets + * properties such as protocol, authentication, etc. + * + * @return Message-object to be sent to execute()-method + * @throws MessagingException + * when problems constructing or sending the mail occur + * @throws IOException + * when the mail content can not be read or truststore problems + * are detected + */ + public Message prepareMessage() throws MessagingException, IOException { + + Properties props = new Properties(); + + String protocol = getProtocol(); + + // set properties using JAF + props.setProperty("mail." + protocol + ".host", smtpServer); + props.setProperty("mail." + protocol + ".port", getPort()); + props.setProperty("mail." + protocol + ".auth", Boolean.toString(useAuthentication)); + + // set timeout + props.setProperty("mail." + protocol + ".timeout", getTimeout()); + props.setProperty("mail." + protocol + ".connectiontimeout", getConnectionTimeout()); + + if (enableDebug) { + props.setProperty("mail.debug","true"); + } + + if (useStartTLS) { + props.setProperty("mail.smtp.starttls.enable", "true"); + if (enforceStartTLS){ + // Requires JavaMail 1.4.2+ + props.setProperty("mail.smtp.starttls.require", "true"); + } + } + + if (trustAllCerts) { + if (useSSL) { + props.setProperty("mail.smtps.ssl.socketFactory.class", TRUST_ALL_SOCKET_FACTORY); + props.setProperty("mail.smtps.ssl.socketFactory.fallback", "false"); + } else if (useStartTLS) { + props.setProperty("mail.smtp.ssl.socketFactory.class", TRUST_ALL_SOCKET_FACTORY); + props.setProperty("mail.smtp.ssl.socketFactory.fallback", "false"); + } + } else if (useLocalTrustStore){ + File truststore = new File(trustStoreToUse); + logger.info("load local truststore - try to load truststore from: "+truststore.getAbsolutePath()); + if(!truststore.exists()){ + logger.info("load local truststore -Failed to load truststore from: "+truststore.getAbsolutePath()); + truststore = new File(FileServer.getFileServer().getBaseDir(), trustStoreToUse); + logger.info("load local truststore -Attempting to read truststore from: "+truststore.getAbsolutePath()); + if(!truststore.exists()){ + logger.info("load local truststore -Failed to load truststore from: "+truststore.getAbsolutePath() + ". Local truststore not available, aborting execution."); + throw new IOException("Local truststore file not found. Also not available under : " + truststore.getAbsolutePath()); + } + } + if (useSSL) { + // Requires JavaMail 1.4.2+ + props.put("mail.smtps.ssl.socketFactory", new LocalTrustStoreSSLSocketFactory(truststore)); + props.put("mail.smtps.ssl.socketFactory.fallback", "false"); + } else if (useStartTLS) { + // Requires JavaMail 1.4.2+ + props.put("mail.smtp.ssl.socketFactory", new LocalTrustStoreSSLSocketFactory(truststore)); + props.put("mail.smtp.ssl.socketFactory.fallback", "false"); + } + } + + session = Session.getInstance(props, null); + + Message message; + + if (sendEmlMessage) { + message = new MimeMessage(session, new BufferedInputStream(new FileInputStream(emlMessage))); + } else { + message = new MimeMessage(session); + // handle body and attachments + Multipart multipart = new MimeMultipart(); + final int attachmentCount = attachments.size(); + if (plainBody && + (attachmentCount == 0 || (mailBody.length() == 0 && attachmentCount == 1))) { + if (attachmentCount == 1) { // i.e. mailBody is empty + File first = attachments.get(0); + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(first)); + message.setText(IOUtils.toString(is)); + } finally { + IOUtils.closeQuietly(is); + } + } else { + message.setText(mailBody); + } + } else { + BodyPart body = new MimeBodyPart(); + body.setText(mailBody); + multipart.addBodyPart(body); + for (File f : attachments) { + BodyPart attach = new MimeBodyPart(); + attach.setFileName(f.getName()); + attach.setDataHandler(new DataHandler(new FileDataSource(f.getAbsolutePath()))); + multipart.addBodyPart(attach); + } + message.setContent(multipart); + } + } + + // set from field and subject + if (null != sender) { + message.setFrom(new InternetAddress(sender)); + } + + if (null != replyTo) { + InternetAddress[] to = new InternetAddress[replyTo.size()]; + message.setReplyTo(replyTo.toArray(to)); + } + + if(null != subject) { + message.setSubject(subject); + } + + if (receiverTo != null) { + InternetAddress[] to = new InternetAddress[receiverTo.size()]; + receiverTo.toArray(to); + message.setRecipients(Message.RecipientType.TO, to); + } + + if (receiverCC != null) { + InternetAddress[] cc = new InternetAddress[receiverCC.size()]; + receiverCC.toArray(cc); + message.setRecipients(Message.RecipientType.CC, cc); + } + + if (receiverBCC != null) { + InternetAddress[] bcc = new InternetAddress[receiverBCC.size()]; + receiverBCC.toArray(bcc); + message.setRecipients(Message.RecipientType.BCC, bcc); + } + + for (int i = 0; i < headerFields.size(); i++) { + Argument argument = (Argument)((TestElementProperty)headerFields.get(i)).getObjectValue(); + message.setHeader(argument.getName(), argument.getValue()); + } + + message.saveChanges(); + return message; + } + + /** + * Sends message to mailserver, waiting for delivery if using synchronous + * mode. + * + * @param message + * Message previously prepared by prepareMessage() + * @throws MessagingException + * when problems sending the mail arise + * @throws IOException + * TODO can not see how + * @throws InterruptedException + * when interrupted while waiting for delivery in synchronous + * modus + */ + public void execute(Message message) throws MessagingException, IOException, InterruptedException { + + Transport tr = session.getTransport(getProtocol()); + SynchronousTransportListener listener = null; + + if (synchronousMode) { + listener = new SynchronousTransportListener(); + tr.addTransportListener(listener); + } + + if (useAuthentication) { + tr.connect(smtpServer, username, password); + } else { + tr.connect(); + } + + tr.sendMessage(message, message.getAllRecipients()); + + if (listener != null /*synchronousMode==true*/) { + listener.attend(); // listener cannot be null here + } + + tr.close(); + logger.debug("transport closed"); + + logger.debug("message sent"); + return; + } + + /** + * Processes prepareMessage() and execute() + * + * @throws InterruptedException + * when interrupted while waiting for delivery in synchronous + * modus + * @throws IOException + * when the mail content can not be read or truststore problems + * are detected + * @throws MessagingException + * when problems constructing or sending the mail occur + */ + public void execute() throws MessagingException, IOException, InterruptedException { + execute(prepareMessage()); + } + + /** + * Returns FQDN or IP of SMTP-server to be used to send message - standard + * getter + * + * @return FQDN or IP of SMTP-server + */ + public String getSmtpServer() { + return smtpServer; + } + + /** + * Sets FQDN or IP of SMTP-server to be used to send message - to be called + * by SmtpSampler-object + * + * @param smtpServer + * FQDN or IP of SMTP-server + */ + public void setSmtpServer(String smtpServer) { + this.smtpServer = smtpServer; + } + + /** + * Returns sender-address for current message - standard getter + * + * @return sender-address + */ + public String getSender() { + return sender; + } + + /** + * Sets the sender-address for the current message - to be called by + * SmtpSampler-object + * + * @param sender + * Sender-address for current message + */ + public void setSender(String sender) { + this.sender = sender; + } + + /** + * Returns subject for current message - standard getter + * + * @return Subject of current message + */ + public String getSubject() { + return subject; + } + + /** + * Sets subject for current message - called by SmtpSampler-object + * + * @param subject + * Subject for message of current message - may be null + */ + public void setSubject(String subject) { + this.subject = subject; + } + + /** + * Returns username to authenticate at the mailserver - standard getter + * + * @return Username for mailserver + */ + public String getUsername() { + return username; + } + + /** + * Sets username to authenticate at the mailserver - to be called by + * SmtpSampler-object + * + * @param username + * Username for mailserver + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Returns password to authenticate at the mailserver - standard getter + * + * @return Password for mailserver + */ + public String getPassword() { + return password; + } + + /** + * Sets password to authenticate at the mailserver - to be called by + * SmtpSampler-object + * + * @param password + * Password for mailserver + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Sets receivers of current message ("to") - to be called by + * SmtpSampler-object + * + * @param receiverTo + * List of receivers + */ + public void setReceiverTo(List receiverTo) { + this.receiverTo = receiverTo; + } + + /** + * Returns receivers of current message as {@link InternetAddress} ("cc") - standard + * getter + * + * @return List of receivers + */ + public List getReceiverCC() { + return receiverCC; + } + + /** + * Sets receivers of current message ("cc") - to be called by + * SmtpSampler-object + * + * @param receiverCC + * List of receivers + */ + public void setReceiverCC(List receiverCC) { + this.receiverCC = receiverCC; + } + + /** + * Returns receivers of current message as {@link InternetAddress} ("bcc") - standard + * getter + * + * @return List of receivers + */ + public List getReceiverBCC() { + return receiverBCC; + } + + /** + * Sets receivers of current message ("bcc") - to be called by + * SmtpSampler-object + * + * @param receiverBCC + * List of receivers + */ + public void setReceiverBCC(List receiverBCC) { + this.receiverBCC = receiverBCC; + } + + /** + * Returns if authentication is used to access the mailserver - standard + * getter + * + * @return True if authentication is used to access mailserver + */ + public boolean isUseAuthentication() { + return useAuthentication; + } + + /** + * Sets if authentication should be used to access the mailserver - to be + * called by SmtpSampler-object + * + * @param useAuthentication + * Should authentication be used to access mailserver? + */ + public void setUseAuthentication(boolean useAuthentication) { + this.useAuthentication = useAuthentication; + } + + /** + * Returns if SSL is used to send message - standard getter + * + * @return True if SSL is used to transmit message + */ + public boolean getUseSSL() { + return useSSL; + } + + /** + * Sets SSL to secure the delivery channel for the message - to be called by + * SmtpSampler-object + * + * @param useSSL + * Should StartTLS be used to secure SMTP-connection? + */ + public void setUseSSL(boolean useSSL) { + this.useSSL = useSSL; + } + + /** + * Returns if StartTLS is used to transmit message - standard getter + * + * @return True if StartTLS is used to transmit message + */ + public boolean getUseStartTLS() { + return useStartTLS; + } + + /** + * Sets StartTLS to secure the delivery channel for the message - to be + * called by SmtpSampler-object + * + * @param useStartTLS + * Should StartTLS be used to secure SMTP-connection? + */ + public void setUseStartTLS(boolean useStartTLS) { + this.useStartTLS = useStartTLS; + } + + /** + * Returns port to be used for SMTP-connection (standard 25 or 465) - + * standard getter + * + * @return Port to be used for SMTP-connection + */ + public String getSmtpPort() { + return smtpPort; + } + + /** + * Sets port to be used for SMTP-connection (standard 25 or 465) - to be + * called by SmtpSampler-object + * + * @param smtpPort + * Port to be used for SMTP-connection + */ + public void setSmtpPort(String smtpPort) { + this.smtpPort = smtpPort; + } + + /** + * Returns if sampler should trust all certificates - standard getter + * + * @return True if all Certificates are trusted + */ + public boolean isTrustAllCerts() { + return trustAllCerts; + } + + /** + * Determines if SMTP-sampler should trust all certificates, no matter what + * CA - to be called by SmtpSampler-object + * + * @param trustAllCerts + * Should all certificates be trusted? + */ + public void setTrustAllCerts(boolean trustAllCerts) { + this.trustAllCerts = trustAllCerts; + } + + /** + * Instructs object to enforce StartTLS and not to fallback to plain + * SMTP-connection - to be called by SmtpSampler-object + * + * @param enforceStartTLS + * Should StartTLS be enforced? + */ + public void setEnforceStartTLS(boolean enforceStartTLS) { + this.enforceStartTLS = enforceStartTLS; + } + + /** + * Returns if StartTLS is enforced to secure the connection, i.e. no + * fallback is used (plain SMTP) - standard getter + * + * @return True if StartTLS is enforced + */ + public boolean isEnforceStartTLS() { + return enforceStartTLS; + } + + /** + * Returns headers for current message - standard getter + * + * @return CollectionProperty of headers for current message + */ + public CollectionProperty getHeaders() { + return headerFields; + } + + /** + * Sets headers for current message + * + * @param headerFields + * CollectionProperty of headers for current message + */ + public void setHeaderFields(CollectionProperty headerFields) { + this.headerFields = headerFields; + } + + /** + * Adds a header-part to current HashMap of headers - to be called by + * SmtpSampler-object + * + * @param headerName + * Key for current header + * @param headerValue + * Value for current header + */ + public void addHeader(String headerName, String headerValue) { + if (this.headerFields == null){ + this.headerFields = new CollectionProperty(); + } + Argument argument = new Argument(headerName, headerValue); + this.headerFields.addItem(argument); + } + + /** + * Deletes all current headers in HashMap + */ + public void clearHeaders() { + if (this.headerFields == null){ + this.headerFields = new CollectionProperty(); + }else{ + this.headerFields.clear(); + } + } + + /** + * Returns all attachment for current message - standard getter + * + * @return List of attachments for current message + */ + public List getAttachments() { + return attachments; + } + + /** + * Adds attachments to current message + * + * @param attachments + * List of files to be added as attachments to current message + */ + public void setAttachments(List attachments) { + this.attachments = attachments; + } + + /** + * Adds an attachment to current message - to be called by + * SmtpSampler-object + * + * @param attachment + * File-object to be added as attachment to current message + */ + public void addAttachment(File attachment) { + this.attachments.add(attachment); + } + + /** + * Clear all attachments for current message + */ + public void clearAttachments() { + this.attachments.clear(); + } + + /** + * Returns if synchronous-mode is used for current message (i.e. time for + * delivery, ... is measured) - standard getter + * + * @return True if synchronous-mode is used + */ + public boolean isSynchronousMode() { + return synchronousMode; + } + + /** + * Sets the use of synchronous-mode (i.e. time for delivery, ... is + * measured) - to be called by SmtpSampler-object + * + * @param synchronousMode + * Should synchronous-mode be used? + */ + public void setSynchronousMode(boolean synchronousMode) { + this.synchronousMode = synchronousMode; + } + + /** + * Returns which protocol should be used to transport message (smtps for + * SSL-secured connections or smtp for plain SMTP / StartTLS) + * + * @return Protocol that is used to transport message + */ + private String getProtocol() { + return (useSSL) ? "smtps" : "smtp"; + } + + /** + * Returns port to be used for SMTP-connection - returns the + * default port for the protocol if no port has been supplied. + * + * @return Port to be used for SMTP-connection + */ + private String getPort() { + String port = smtpPort.trim(); + if (port.length() > 0) { // OK, it has been supplied + return port; + } + if (useSSL){ + return "465"; + } + if (useStartTLS) { + return "587"; + } + return "25"; + } + + /** + * @param timeOut the timeOut to set + */ + public void setTimeOut(String timeOut) { + this.timeOut = timeOut; + } + + /** + * Returns timeout for the SMTP-connection - returns the + * default timeout if no value has been supplied. + * + * @return Timeout to be set for SMTP-connection + */ + public String getTimeout() { + String timeout = timeOut.trim(); + if (timeout.length() > 0) { // OK, it has been supplied + return timeout; + } + return "0"; // Default is infinite timeout (value 0). + } + + /** + * @param connectionTimeOut the connectionTimeOut to set + */ + public void setConnectionTimeOut(String connectionTimeOut) { + this.connectionTimeOut = connectionTimeOut; + } + + /** + * Returns connection timeout for the SMTP-connection - returns the + * default connection timeout if no value has been supplied. + * + * @return Connection timeout to be set for SMTP-connection + */ + public String getConnectionTimeout() { + String connectionTimeout = connectionTimeOut.trim(); + if (connectionTimeout.length() > 0) { // OK, it has been supplied + return connectionTimeout; + } + return "0"; // Default is infinite timeout (value 0). + } + + /** + * Assigns the object to use a local truststore for SSL / StartTLS - to be + * called by SmtpSampler-object + * + * @param useLocalTrustStore + * Should a local truststore be used? + */ + public void setUseLocalTrustStore(boolean useLocalTrustStore) { + this.useLocalTrustStore = useLocalTrustStore; + } + + /** + * Sets the path to the local truststore to be used for SSL / StartTLS - to + * be called by SmtpSampler-object + * + * @param trustStoreToUse + * Path to local truststore + */ + public void setTrustStoreToUse(String trustStoreToUse) { + this.trustStoreToUse = trustStoreToUse; + } + + public void setUseEmlMessage(boolean sendEmlMessage) { + this.sendEmlMessage = sendEmlMessage; + } + + /** + * Sets eml-message to be sent + * + * @param emlMessage + * path to eml-message + */ + public void setEmlMessage(String emlMessage) { + this.emlMessage = emlMessage; + } + + /** + * Set the mail body. + * + * @param body the body of the mail + */ + public void setMailBody(String body){ + mailBody = body; + } + + /** + * Set whether to send a plain body (i.e. not multipart/mixed) + * + * @param plainBody true if sending a plain body (i.e. not multipart/mixed) + */ + public void setPlainBody(boolean plainBody){ + this.plainBody = plainBody; + } + + public String getServerResponse() { + return this.serverResponse.toString(); + } + + public void setEnableDebug(boolean selected) { + enableDebug = selected; + + } + + public void setReplyTo(List replyTo) { + this.replyTo = replyTo; + } +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/SynchronousTransportListener.java b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/SynchronousTransportListener.java new file mode 100644 index 00000000000..86fcddeedc0 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/SynchronousTransportListener.java @@ -0,0 +1,102 @@ +/* + * 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.protocol.smtp.sampler.protocol; + +import javax.mail.event.TransportAdapter; +import javax.mail.event.TransportEvent; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; // this comes out of logkit.jar and not + +// commons-logger + +/** + * This class implements a listener for SMTP events and a monitor for all + * threads sending mail. The main purpose is to synchronize the send action with + * the end of communication with remote smtp server, so that sending time can be + * measured. + */ +public class SynchronousTransportListener extends TransportAdapter { + + private static final Logger logger = LoggingManager.getLoggerForClass(); + + private boolean finished = false; + + private final Object LOCK = new Object(); + + /** + * Creates a new instance of SynchronousTransportListener + */ + public SynchronousTransportListener() { + } + + /** + * {@inheritDoc} + */ + @Override + public void messageDelivered(TransportEvent e) { + logger.debug("Message delivered"); + finish(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messageNotDelivered(TransportEvent e) { + logger.debug("Message not delivered"); + finish(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messagePartiallyDelivered(TransportEvent e) { + logger.debug("Message partially delivered"); + finish(); + } + + /** + * Synchronized-method + *

+ * Waits until {@link #finish()} was called and thus the end of the mail + * sending was signalled. + * + * @throws InterruptedException + * when interrupted while waiting with the lock + */ + public void attend() throws InterruptedException { + synchronized(LOCK) { + while (!finished) { + LOCK.wait(); + } + } + } + + /** + * Synchronized-method + */ + public void finish() { + finished = true; + synchronized(LOCK) { + LOCK.notify(); + } + } + +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/TrustAllSSLSocketFactory.java b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/TrustAllSSLSocketFactory.java new file mode 100644 index 00000000000..e49e08bb8f4 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/protocol/TrustAllSSLSocketFactory.java @@ -0,0 +1,149 @@ +/* + * 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.protocol.smtp.sampler.protocol; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.security.cert.X509Certificate; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +/** + * This class can be used as a SocketFactory with SSL-connections. + * Its purpose is to ensure that all certificates - no matter from which CA - are accepted to secure the SSL-connection. + */ +public class TrustAllSSLSocketFactory extends SSLSocketFactory { + + private final SSLSocketFactory factory; + + // Empty arrays are immutable + private static final X509Certificate[] EMPTY_X509Certificate = new X509Certificate[0]; + + /** + * Standard constructor + */ + public TrustAllSSLSocketFactory(){ + SSLContext sslcontext = null; + try { + sslcontext = SSLContext.getInstance("TLS"); // $NON-NLS-1$ + sslcontext.init( null, new TrustManager[]{ + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return EMPTY_X509Certificate; + } + @Override + public void checkClientTrusted( + X509Certificate[] certs, String authType) { + } + @Override + public void checkServerTrusted( + X509Certificate[] certs, String authType) { + } + } + }, + new java.security.SecureRandom()); + } catch (Exception e) { + throw new RuntimeException("Could not create the SSL context",e); + } + factory = sslcontext.getSocketFactory(); + } + + /** + * Factory method + * @return New TrustAllSSLSocketFactory + */ + public static synchronized SocketFactory getDefault() { + return new TrustAllSSLSocketFactory(); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( Socket socket, String s, int i, boolean + flag) + throws IOException { + return factory.createSocket( socket, s, i, flag); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( InetAddress inaddr, int i, + InetAddress inaddr1, int j) throws IOException { + return factory.createSocket( inaddr, i, inaddr1, j); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( InetAddress inaddr, int i) throws + IOException { + return factory.createSocket( inaddr, i); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( String s, int i, InetAddress inaddr, int j) + throws IOException { + return factory.createSocket( s, i, inaddr, j); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( String s, int i) throws IOException { + return factory.createSocket( s, i); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket() throws IOException { + return factory.createSocket(); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getDefaultCipherSuites() { + return factory.getSupportedCipherSuites(); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getSupportedCipherSuites() { + return factory.getSupportedCipherSuites(); + } +} diff --git a/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/tools/CounterOutputStream.java b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/tools/CounterOutputStream.java new file mode 100644 index 00000000000..62febe36985 --- /dev/null +++ b/src/protocol/mail/org/apache/jmeter/protocol/smtp/sampler/tools/CounterOutputStream.java @@ -0,0 +1,64 @@ +/* + * 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.protocol.smtp.sampler.tools; + +import java.io.OutputStream; + +/** + * Utility-class to calculate message size. + */ +public class CounterOutputStream extends OutputStream { + int count = 0; + + /** + * {@inheritDoc} + */ + @Override + + public void close() {} + /** + * {@inheritDoc} + */ + @Override + public void flush() {} + + /** + * {@inheritDoc} + */ + @Override + public void write(byte[] b, int off, int len) { + count += len; + } + + /** + * {@inheritDoc} + */ + @Override + public void write(int b) { + count++; + } + + /** + * Returns message size + * @return Message size + */ + public int getCount() { + return count; + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoDBHolder.java b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoDBHolder.java new file mode 100644 index 00000000000..7fb740850ae --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoDBHolder.java @@ -0,0 +1,53 @@ +/* + * 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.protocol.mongodb.config; + +import org.apache.jmeter.protocol.mongodb.mongo.MongoDB; +import org.apache.jmeter.threads.JMeterContextService; + +import com.mongodb.DB; + +/** + * Public API to access MongoDB {@link DB} object created by {@link MongoSourceElement} + */ +public final class MongoDBHolder { + + /** + * Get access to MongoDB object + * @param varName String MongoDB source + * @param dbName Mongo DB database name + * @return {@link DB} + */ + public static DB getDBFromSource(String varName, String dbName) { + return getDBFromSource(varName, dbName, null, null); + } + + /** + * Get access to MongoDB object + * @param varName String MongoDB source + * @param dbName Mongo DB database name + * @param login name to use for login + * @param password password to use for login + * @return {@link DB} + */ + public static DB getDBFromSource(String varName, String dbName, String login, String password) { + MongoDB mongodb = (MongoDB) JMeterContextService.getContext().getVariables().getObject(varName); + return mongodb.getDB(dbName, login, password); + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElement.java b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElement.java new file mode 100644 index 00000000000..17e5dc4b5b3 --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElement.java @@ -0,0 +1,398 @@ +/* + * 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.protocol.mongodb.config; + +import java.net.UnknownHostException; + +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.mongodb.mongo.MongoDB; +import org.apache.jmeter.protocol.mongodb.mongo.MongoUtils; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.mongodb.MongoClientOptions; +import com.mongodb.WriteConcern; + +/** + */ +public class MongoSourceElement + extends ConfigTestElement + implements TestStateListener, TestBean { + + /** + * + */ + private static final long serialVersionUID = 2100L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private String connection; + private String source; + private boolean autoConnectRetry; + private int connectionsPerHost; + private int connectTimeout; + private long maxAutoConnectRetryTime; + private int maxWaitTime; + private int socketTimeout; + private boolean socketKeepAlive; + private int threadsAllowedToBlockForConnectionMultiplier; + private boolean fsync; + private boolean safe; + private boolean waitForJournaling; + private int writeOperationNumberOfServers; + private int writeOperationTimeout; + private boolean continueOnInsertError; + +// public final static String CONNECTION = "MongoSourceElement.connection"; //$NON-NLS-1$ +// public final static String SOURCE = "MongoSourceElement.source"; //$NON-NLS-1$ +// +// public final static String AUTO_CONNECT_RETRY = "MongoSourceElement.autoConnectRetry"; //$NON-NLS-1$ +// public final static String CONNECTIONS_PER_HOST = "MongoSourceElement.connectionsPerHost"; //$NON-NLS-1$ +// public final static String CONNECT_TIMEOUT = "MongoSourceElement.connectTimeout"; //$NON-NLS-1$ +// public final static String CONTINUE_ON_INSERT_ERROR = "MongoSourceElement.continueOnInsertError"; //$NON-NLS-1$ +// public final static String MAX_AUTO_CONNECT_RETRY_TIME = "MongoSourceElement.maxAutoConnectRetryTime"; //$NON-NLS-1$ +// public final static String MAX_WAIT_TIME = "MongoSourceElement.maxWaitTime"; //$NON-NLS-1$ +// public final static String SOCKET_TIMEOUT = "MongoSourceElement.socketTimeout"; //$NON-NLS-1$ +// public final static String SOCKET_KEEP_ALIVE = "MongoSourceElement.socketKeepAlive"; //$NON-NLS-1$ +// public final static String THREADS_ALLOWED_TO_BLOCK_MULTIPLIER = "MongoSourceElement.threadsAllowedToBlockForConnectionMultiplier"; //$NON-NLS-1$ +// +// public final static String FSYNC = "MongoSourceElement.fsync"; //$NON-NLS-1$ +// public final static String SAFE = "MongoSourceElement.safe"; //$NON-NLS-1$ +// public final static String WAIT_FOR_JOURNALING = "MongoSourceElement.waitForJournaling"; //$NON-NLS-1$ +// public final static String WRITE_OPERATION_NUMBER_OF_SERVERS = "MongoSourceElement.writeOperationNumberOfServers"; //$NON-NLS-1$ +// public final static String WRITE_OPERATION_TIMEOUT = "MongoSourceElement.writeOperationTimeout"; //$NON-NLS-1$ + + public String getTitle() { + return this.getName(); + } + + public String getConnection() { + return connection; + } + + public void setConnection(String connection) { + this.connection = connection; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + + + public static MongoDB getMongoDB(String source) { + + Object mongoSource = JMeterContextService.getContext().getVariables().getObject(source); + + if(mongoSource == null) { + throw new IllegalStateException("mongoSource is null"); + } + else { + if(mongoSource instanceof MongoDB) { + return (MongoDB)mongoSource; + } + else { + throw new IllegalStateException("Variable:"+ source +" is not a MongoDB instance, class:"+mongoSource.getClass()); + } + } + } + + @Override + public void addConfigElement(ConfigElement configElement) { + } + + @Override + public boolean expectsModification() { + return false; + } + + @Override + public void testStarted() { + if(log.isDebugEnabled()) { + log.debug(getTitle() + " testStarted"); + } + + MongoClientOptions.Builder builder = MongoClientOptions.builder() + .autoConnectRetry(getAutoConnectRetry()) + .connectTimeout(getConnectTimeout()) + .connectionsPerHost(getConnectionsPerHost()) + .maxAutoConnectRetryTime(getMaxAutoConnectRetryTime()) + .maxWaitTime(getMaxWaitTime()) + .socketKeepAlive(getSocketKeepAlive()) + .socketTimeout(getSocketTimeout()) + .threadsAllowedToBlockForConnectionMultiplier( + getThreadsAllowedToBlockForConnectionMultiplier()); + + if(getSafe()) { + builder.writeConcern(WriteConcern.SAFE); + } else { + builder.writeConcern(new WriteConcern( + getWriteOperationNumberOfServers(), + getWriteOperationTimeout(), + getFsync(), + getWaitForJournaling(), + getContinueOnInsertError() + )); + } + MongoClientOptions mongoOptions = builder.build(); + + if(log.isDebugEnabled()) { + log.debug("options : " + mongoOptions.toString()); + } + + if(getThreadContext().getVariables().getObject(getSource()) != null) { + if(log.isWarnEnabled()) { + log.warn(getSource() + " has already been defined."); + } + } + else { + if(log.isDebugEnabled()) { + log.debug(getSource() + " is being defined."); + } + try { + getThreadContext().getVariables().putObject(getSource(), new MongoDB(MongoUtils.toServerAddresses(getConnection()), mongoOptions)); + } catch (UnknownHostException e) { + throw new IllegalStateException(e); + } + } + } + + @Override + public void testStarted(String s) { + testStarted(); + } + + @Override + public void testEnded() { + if(log.isDebugEnabled()) { + log.debug(getTitle() + " testEnded"); + } + ((MongoDB)getThreadContext().getVariables().getObject(getSource())).clear(); + } + + @Override + public void testEnded(String s) { + testEnded(); + } + + /** + * @return the autoConnectRetry + */ + public boolean getAutoConnectRetry() { + return autoConnectRetry; + } + + /** + * @param autoConnectRetry the autoConnectRetry to set + */ + public void setAutoConnectRetry(boolean autoConnectRetry) { + this.autoConnectRetry = autoConnectRetry; + } + + /** + * @return the connectionsPerHost + */ + public int getConnectionsPerHost() { + return connectionsPerHost; + } + + /** + * @param connectionsPerHost the connectionsPerHost to set + */ + public void setConnectionsPerHost(int connectionsPerHost) { + this.connectionsPerHost = connectionsPerHost; + } + + /** + * @return the connectTimeout + */ + public int getConnectTimeout() { + return connectTimeout; + } + + /** + * @param connectTimeout the connectTimeout to set + */ + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + /** + * @return the maxAutoConnectRetryTime + */ + public long getMaxAutoConnectRetryTime() { + return maxAutoConnectRetryTime; + } + + /** + * @param maxAutoConnectRetryTime the maxAutoConnectRetryTime to set + */ + public void setMaxAutoConnectRetryTime(long maxAutoConnectRetryTime) { + this.maxAutoConnectRetryTime = maxAutoConnectRetryTime; + } + + /** + * @return the maxWaitTime + */ + public int getMaxWaitTime() { + return maxWaitTime; + } + + /** + * @param maxWaitTime the maxWaitTime to set + */ + public void setMaxWaitTime(int maxWaitTime) { + this.maxWaitTime = maxWaitTime; + } + + /** + * @return the socketTimeout + */ + public int getSocketTimeout() { + return socketTimeout; + } + + /** + * @param socketTimeout the socketTimeout to set + */ + public void setSocketTimeout(int socketTimeout) { + this.socketTimeout = socketTimeout; + } + + /** + * @return the socketKeepAlive + */ + public boolean getSocketKeepAlive() { + return socketKeepAlive; + } + + /** + * @param socketKeepAlive the socketKeepAlive to set + */ + public void setSocketKeepAlive(boolean socketKeepAlive) { + this.socketKeepAlive = socketKeepAlive; + } + + /** + * @return the threadsAllowedToBlockForConnectionMultiplier + */ + public int getThreadsAllowedToBlockForConnectionMultiplier() { + return threadsAllowedToBlockForConnectionMultiplier; + } + + /** + * @param threadsAllowedToBlockForConnectionMultiplier the threadsAllowedToBlockForConnectionMultiplier to set + */ + public void setThreadsAllowedToBlockForConnectionMultiplier( + int threadsAllowedToBlockForConnectionMultiplier) { + this.threadsAllowedToBlockForConnectionMultiplier = threadsAllowedToBlockForConnectionMultiplier; + } + + /** + * @return the fsync + */ + public boolean getFsync() { + return fsync; + } + + /** + * @param fsync the fsync to set + */ + public void setFsync(boolean fsync) { + this.fsync = fsync; + } + + /** + * @return the safe + */ + public boolean getSafe() { + return safe; + } + + /** + * @param safe the safe to set + */ + public void setSafe(boolean safe) { + this.safe = safe; + } + + /** + * @return the waitForJournaling + */ + public boolean getWaitForJournaling() { + return waitForJournaling; + } + + /** + * @param waitForJournaling the waitForJournaling to set + */ + public void setWaitForJournaling(boolean waitForJournaling) { + this.waitForJournaling = waitForJournaling; + } + + /** + * @return the writeOperationNumberOfServers + */ + public int getWriteOperationNumberOfServers() { + return writeOperationNumberOfServers; + } + + /** + * @param writeOperationNumberOfServers the writeOperationNumberOfServers to set + */ + public void setWriteOperationNumberOfServers(int writeOperationNumberOfServers) { + this.writeOperationNumberOfServers = writeOperationNumberOfServers; + } + + /** + * @return the writeOperationTimeout + */ + public int getWriteOperationTimeout() { + return writeOperationTimeout; + } + + /** + * @param writeOperationTimeout the writeOperationTimeout to set + */ + public void setWriteOperationTimeout(int writeOperationTimeout) { + this.writeOperationTimeout = writeOperationTimeout; + } + + /** + * @return the continueOnInsertError + */ + public boolean getContinueOnInsertError() { + return continueOnInsertError; + } + + /** + * @param continueOnInsertError the continueOnInsertError to set + */ + public void setContinueOnInsertError(boolean continueOnInsertError) { + this.continueOnInsertError = continueOnInsertError; + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementBeanInfo.java b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementBeanInfo.java new file mode 100644 index 00000000000..23d1c53578c --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementBeanInfo.java @@ -0,0 +1,120 @@ +/* + * 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.protocol.mongodb.config; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + */ +public class MongoSourceElementBeanInfo + extends BeanInfoSupport { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public MongoSourceElementBeanInfo() { + super(MongoSourceElement.class); + + //http://api.mongodb.org/java/2.7.2/com/mongodb/Mongo.html + createPropertyGroup("mongodb", new String[] { + "connection", + "source"}); + + //http://api.mongodb.org/java/2.7.2/com/mongodb/MongoOptions.html/ + createPropertyGroup("options", new String[]{ + "autoConnectRetry", + "connectionsPerHost", + "connectTimeout", + "maxAutoConnectRetryTime", + "maxWaitTime", + "socketTimeout", + "socketKeepAlive", + "threadsAllowedToBlockForConnectionMultiplier"}); + + //http://api.mongodb.org/java/2.7.2/com/mongodb/MongoOptions.html/ + createPropertyGroup("writeConcern", new String[] { + "safe", + "fsync", + "waitForJournaling", + "writeOperationNumberOfServers", + "writeOperationTimeout", + "continueOnInsertError"}); + + PropertyDescriptor p = property("connection"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("source"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("autoConnectRetry"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + p = property("connectionsPerHost"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Integer.valueOf(10)); + p = property("connectTimeout"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Integer.valueOf(0)); + p = property("threadsAllowedToBlockForConnectionMultiplier"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Integer.valueOf(5)); + p = property("maxAutoConnectRetryTime"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Long.valueOf(0)); + p = property("maxWaitTime"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Integer.valueOf(120000)); + p = property("socketTimeout"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Integer.valueOf(0)); + p = property("socketKeepAlive"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + p = property("fsync"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + p = property("safe"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + p = property("waitForJournaling"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + p = property("writeOperationNumberOfServers"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Integer.valueOf(0)); + p = property("writeOperationTimeout"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Integer.valueOf(0)); + p = property("continueOnInsertError"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + if(log.isDebugEnabled()) { + for (PropertyDescriptor pd : getPropertyDescriptors()) { + log.debug(pd.getName()); + log.debug(pd.getDisplayName()); + } + } + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementResources.properties b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementResources.properties new file mode 100644 index 00000000000..2fcfe886677 --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementResources.properties @@ -0,0 +1,55 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autoConnectRetry.displayName=Keep trying +autoConnectRetry.shortDescription=If true, the driver will keep trying to connect to the same server in case that the socket cannot be established.

There is maximum amount of time to keep retrying, which is 15s by default.

This can be useful to avoid some exceptions being thrown when a server is down temporarily by blocking the operations.

It also can be useful to smooth the transition to a new master (so that a new master is elected within the retry time).

Note that when using this flag\:
- for a replica set, the driver will trying to connect to the old master for that time, instead of failing over to the new one right away -
this does not prevent exception from being thrown in read/write operations on the socket, which must be handled by application.

Even if this flag is false, the driver already has mechanisms to automatically recreate broken connections and retry the read operations.

Default is false. +connectTimeout.displayName=Connection timeout +connectTimeout.shortDescription=The connection timeout in milliseconds.

It is used solely when establishing a new connection Socket.connect(java.net.SocketAddress, int)

Default is 0 and means no timeout. +connection.displayName=Server Address List +connection.shortDescription=Server Address List +connectionsPerHost.displayName=Maximum connections Per Host +connectionsPerHost.shortDescription=The maximum number of connections allowed per host for this Mongo instance.

Those connections will be kept in a pool when idle.

Once the pool is exhausted, any operation requiring a connection will block waiting for an available connection.

Default is 10. +continueOnInsertError.displayName=Continue on Error +continueOnInsertError.shortDescription=If batch inserts should continue after the first error +displayName=MongoDB Source Config +fsync.displayName=Fsync +fsync.shortDescription=The fsync value of the global WriteConcern.

Default is false. +maxAutoConnectRetryTime.displayName=Maximum retry time +maxAutoConnectRetryTime.shortDescription=The maximum amount of time in MS to spend retrying to open connection to the same server.

Default is 0, which means to use the default 15s if autoConnectRetry is on. +maxWaitTime.displayName=Maximum wait time +maxWaitTime.shortDescription=The maximum wait time in ms that a thread may wait for a connection to become available.

Default is 120,000. +mongodb.displayName=MongoDB Connection +mongodb.shortDescription=Configure the connection +options.displayName=MongoDB Options +options.shortDescription=Various settings for the driver +safe.displayName=Safe +safe.shortDescription=If true the driver will use a WriteConcern of WriteConcern.SAFE for all operations.

If w, wtimeout, fsync or j are specified, this setting is ignored.

Default is false. +socketKeepAlive.displayName=Socket keep alive +socketKeepAlive.shortDescription=This flag controls the socket keep alive feature that keeps a connection alive through firewalls Socket.setKeepAlive(boolean)

Default is false. +socketTimeout.displayName=Socket timeout +socketTimeout.shortDescription=The socket timeout in milliseconds It is used for I/O socket read and write operations Socket.setSoTimeout(int)

Default is 0 and means no timeout. +source.displayName=MongoDB Source +source.shortDescription=Configure the Source +threadsAllowedToBlockForConnectionMultiplier.displayName=Block Multiplier +threadsAllowedToBlockForConnectionMultiplier.shortDescription=This multiplier, multiplied with the connectionsPerHost setting, gives the maximum number of threads that may be waiting for a connection to become available from the pool.

All further threads will get an exception right away.

For example if connectionsPerHost is 10 and threadsAllowedToBlockForConnectionMultiplier is 5, then up to 50 threads can wait for a connection.

Default is 5. +waitForJournaling.displayName=Wait for Journal +waitForJournaling.shortDescription=The j value of the global WriteConcern.

Default is false. +writeConcern.displayName=Write Concern Options +writeConcern.shortDescription=Various settings for the driver +writeOperationNumberOfServers.displayName=Wait for Servers +writeOperationNumberOfServers.shortDescription=The w value of the global WriteConcern.

Default is 0. +writeOperationTimeout.displayName=Wait Timeout +writeOperationTimeout.shortDescription=The wtimeout value of the global WriteConcern.

Default is 0. diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementResources_fr.properties b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementResources_fr.properties new file mode 100644 index 00000000000..a8caa6ffa2f --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementResources_fr.properties @@ -0,0 +1,55 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autoConnectRetry.displayName=Essayer de garder la connexion +autoConnectRetry.shortDescription=Si positionn\u00E9 \u00E0 True, le gestionnaire continuera d'essayer de se reconnecter au m\u00EAme serveur dans le cas o\u00F9 la connexion socket ne peut \u00EAtre \u00E9tablie.
Le d\u00E9lai d'attente maximum pour ressayer est de 15 sec par d\u00E9faut.

Cela peut \u00EAtre utile pour \u00E9viter certaines exceptions lev\u00E9es quand un serveur est arr\u00EAt\u00E9 temporairement, bloquant ainsi les op\u00E9rations.
Il peut \u00E9galement \u00EAtre utile pour adoucir la transition vers un nouveau ma\u00EEtre (de sorte qu'un nouveau ma\u00EEtre est \u00E9lu au sein le nombre de tentatives de temps)

N.B. Lorsque vous utilisez cette option \:
- pour un jeu de r\u00E9plica, le gestionnaire tente de se connecter \u00E0 l'ancien ma\u00EEtre de ce moment-l\u00E0, au lieu de basculer vers le nouveau -
cela n'emp\u00EAche pas l'exception d'\u00EAtre lev\u00E9e en lecture / \u00E9criture sur la socket, qui doit \u00EAtre trait\u00E9e par l'application
M\u00EAme si cet indicateur est False, le gestionnaire dispose d\u00E9j\u00E0 de m\u00E9canismes pour recr\u00E9er automatiquement connexions interrompues et de r\u00E9essayer les op\u00E9rations de lecture.

La valeur par d\u00E9faut est False. +connectTimeout.displayName=Delai d'expiration de connexion +connectTimeout.shortDescription=Le d\u00E9lai d'expiration de connexion, en millisecondes.
Il est utilis\u00E9 uniquement lors de l'\u00E9tablissement d'une nouvelle connexion Socket.connect(java.net.SocketAddress, int)

La valeur par d\u00E9faut est 0 et signifie aucun d\u00E9lai d'attente. +connection.displayName=Liste adresse serveur +connection.shortDescription=Liste adresse serveur +connectionsPerHost.displayName=Maximum de connexions par h\u00F4te +connectionsPerHost.shortDescription=Le nombre maximum de connexions autoris\u00E9es par h\u00F4te for cette instance de Mongo.

Ces connexions seront gard\u00E9es dans un pool quand elles seront disponibles.

Une fois que le pool est atteint, toute op\u00E9ration qui n\u00E9cessite une connexion sera bloqu\u00E9e en attendant une connexion disponible.

La valeur par d\u00E9faut est 10. +continueOnInsertError.displayName=Continuer en cas d'erreur +continueOnInsertError.shortDescription=Indique si les insertions en batch doivent se poursuivre apr\u00E8s la premi\u00E8re erreur +displayName=Gestionnaire de connexion MongoDB +fsync.displayName=Fsync +fsync.shortDescription=Valeur Fsync pour l'\u00E9l\u00E9ment global WriteConcern.

La valeur par d\u00E9faut est False. +maxAutoConnectRetryTime.displayName=Temps de re-tentative maximum +maxAutoConnectRetryTime.shortDescription=Le d\u00E9lai maximal de temps en milli-secondes pour tenter d'ouvrir une connexion au m\u00EAme serveur.

La valeur par d\u00E9faut est 0, ce qui signifie que pour utiliser les 15 sec. par d\u00E9faut si autoConnectRetry est activ\u00E9. +maxWaitTime.displayName=Temps d'attente maximum +maxWaitTime.shortDescription=Le temps d'attente maximum in milli-secondes qu'une unit\u00E9 d'ex\u00E9cution peut attendre pour qu'une connexion devienne disponible.

La valeur par d\u00E9faut est 120000. +mongodb.displayName=Connexion MongoDB +mongodb.shortDescription=Configurer la connexion +options.displayName=Options MongoDB +options.shortDescription=Divers param\u00E8tres pour le gestionnaire +safe.displayName=S\u00FBret\u00E9 (Safe) +safe.shortDescription=Si positionn\u00E9 \u00E0 True, le gestionnaire utilisera un WriteConcern de WriteConcern.SAFE pour toutes les op\u00E9rations.

Si w, wtimeout, fsync or j sont sp\u00E9cifi\u00E9s, ce param\u00E8tre est ignor\u00E9.

La valeur par d\u00E9faut est False. +socketKeepAlive.displayName=Socket persistante +socketKeepAlive.shortDescription=Cet indicateur contr\u00F4le la fonctionnalit\u00E9 de garder la socket persistante \u00E0 travers un pare-feu Socket.setKeepAlive(boolean)

La valeur par d\u00E9faut est false. +socketTimeout.displayName=D\u00E9lai d'expiration Socket +socketTimeout.shortDescription=Le d\u00E9lai d'expiration de Socket en milli-secondes. Il est utilis\u00E9 pour en E/S de socket pour les op\u00E9rations de lecture et \u00E9criture Socket.setSoTimeout(int)

La valeur par d\u00E9faut est 0 et signifie \: pas de d\u00E9lai. +source.displayName=Source MongoDB +source.shortDescription=Configurer la source +threadsAllowedToBlockForConnectionMultiplier.displayName=Multiplicateur de blocage +threadsAllowedToBlockForConnectionMultiplier.shortDescription=Ce multiplicateur, multipli\u00E9 avec le param\u00E8tre connectionsPerHost, donne le nombre maximal d'unit\u00E9s qui peuvent \u00EAtre en attente qu'une connexion se lib\u00E8re du pool.
Au d\u00E9l\u00E0 une exception sera lev\u00E9e imm\u00E9diatement.

Par exemple, si connectionsPerHost est de 10 et threadsAllowedToBlockForConnectionMultiplier est de 5, Alors jusqu'\u00E0 50 threads peuvent attendre une connexion.
La valeur par d\u00E9faut est 5. +waitForJournaling.displayName=Attente du Journal +waitForJournaling.shortDescription=La valeur j pour l'\u00E9l\u00E9ment global WriteConcern.

La valeur par d\u00E9faut est False. +writeConcern.displayName=Options Write Concern +writeConcern.shortDescription=Divers param\u00E8tres pour le gestionnaire +writeOperationNumberOfServers.displayName=Attente des serveurs +writeOperationNumberOfServers.shortDescription=La valeur w pour l'\u00E9l\u00E9ment global WriteConcern.

La valeur par d\u00E9faut est 0. +writeOperationTimeout.displayName=D\u00E9lai d'attente +writeOperationTimeout.shortDescription=La valeur wtimeout pour l'\u00E9l\u00E9ment global WriteConcern.

La valeur par d\u00E9faut est 0. diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/mongo/EvalResultHandler.java b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/mongo/EvalResultHandler.java new file mode 100644 index 00000000000..b9c5e82a0d5 --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/mongo/EvalResultHandler.java @@ -0,0 +1,67 @@ +/* + * 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.protocol.mongodb.mongo; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + */ +public class EvalResultHandler { + + //This can lead to code smell, meh! Do we care + public String handle(Object o) { + if(o == null) { + return "ok"; + } + + if(o instanceof Double) { + return this.handle((Double)o); + } + else if(o instanceof Integer) { + return this.handle((Integer)o); + } + else if(o instanceof String) { + return this.handle((String)o); + } + else if(o instanceof DBObject) { + return this.handle((DBObject)o); + } + else { + return "return type not handled"; + } + } + + public String handle(Integer o) { + return o.toString(); + } + + public String handle(String o) { + return o; + } + + public String handle(Double o) { + return o.toString(); + } + + + public String handle(DBObject o) { + return JSON.serialize(o); + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/mongo/MongoDB.java b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/mongo/MongoDB.java new file mode 100644 index 00000000000..1a42c01ac4d --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/mongo/MongoDB.java @@ -0,0 +1,75 @@ +/* + * 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.protocol.mongodb.mongo; + +import java.util.List; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.mongodb.DB; +import com.mongodb.Mongo; +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.ServerAddress; + +/** + */ +public class MongoDB { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Mongo is Thread Safe + private Mongo mongo = null; + + public MongoDB( + List serverAddresses, + MongoClientOptions mongoOptions) { + mongo = new MongoClient(serverAddresses, mongoOptions); + } + + public DB getDB(String database, String username, String password) { + + if(log.isDebugEnabled()) { + log.debug("username: " + username+", password: " + password+", database: " + database); + } + DB db = mongo.getDB(database); + boolean authenticated = db.isAuthenticated(); + + if(!authenticated) { + if(username != null && password != null && username.length() > 0 && password.length() > 0) { + authenticated = db.authenticate(username, password.toCharArray()); + } + } + if(log.isDebugEnabled()) { + log.debug("authenticated: " + authenticated); + } + return db; + } + + public void clear() { + if(log.isDebugEnabled()) { + log.debug("clearing"); + } + + mongo.close(); + //there is no harm in trying to clear up + mongo = null; + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/mongo/MongoUtils.java b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/mongo/MongoUtils.java new file mode 100644 index 00000000000..61de075b062 --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/mongo/MongoUtils.java @@ -0,0 +1,51 @@ +/* + * 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.protocol.mongodb.mongo; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import com.mongodb.ServerAddress; + +/** + */ +public class MongoUtils { + /** + * MongoDB default connection port + */ + public static final int DEFAULT_PORT = 27017; + + public static List toServerAddresses(String connections) throws UnknownHostException { + + List addresses = new ArrayList(); + for(String connection : Arrays.asList(connections.split(","))) { + int port = DEFAULT_PORT; + String[] hostPort = connection.split(":"); + if(hostPort.length > 1 && !StringUtils.isEmpty(hostPort[1])) { + port = Integer.parseInt(hostPort[1].trim()); + } + addresses.add(new ServerAddress(hostPort[0], port)); + } + return addresses; + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptRunner.java b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptRunner.java new file mode 100644 index 00000000000..86501753117 --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptRunner.java @@ -0,0 +1,68 @@ +/* + * 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.protocol.mongodb.sampler; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.mongodb.DB; + +/** + */ +public class MongoScriptRunner { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public MongoScriptRunner() { + super(); + } + + /** + * Evaluate a script on the database + * + * @param db + * database connection to use + * @param script + * script to evaluate on the database + * @return result of evaluation on the database + * @throws Exception + * when evaluation on the database fails + */ + public Object evaluate(DB db, String script) + throws Exception { + + if(log.isDebugEnabled()) { + log.debug("database: " + db.getName()+", script: " + script); + } + + db.requestStart(); + try { + db.requestEnsureConnection(); + + Object result = db.eval(script); + + if(log.isDebugEnabled()) { + log.debug("Result : " + result); + } + return result; + } finally { + db.requestDone(); + } + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSampler.java b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSampler.java new file mode 100644 index 00000000000..08679d9f610 --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSampler.java @@ -0,0 +1,144 @@ +/* + * 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.protocol.mongodb.sampler; + +import org.apache.jmeter.protocol.mongodb.config.MongoSourceElement; +import org.apache.jmeter.protocol.mongodb.mongo.EvalResultHandler; +import org.apache.jmeter.protocol.mongodb.mongo.MongoDB; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.mongodb.DB; + +/** + */ +public class MongoScriptSampler + extends AbstractSampler + implements TestBean { + + private static final long serialVersionUID = -7789012234636439896L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public final static String SOURCE = "MongoScriptSampler.source"; //$NON-NLS-1$ + + public final static String DATABASE = "MongoScriptSampler.database"; //$NON-NLS-1$ + public final static String USERNAME = "MongoScriptSampler.username"; //$NON-NLS-1$ + public final static String PASSWORD = "MongoScriptSampler.password"; //$NON-NLS-1$ + public final static String SCRIPT = "MongoScriptSampler.script"; //$NON-NLS-1$ + + + public MongoScriptSampler() { + trace("MongoScriptSampler()"); + } + + @Override + public SampleResult sample(Entry e) { + trace("sample()"); + + SampleResult res = new SampleResult(); + String data = getScript(); + + res.setSampleLabel(getTitle()); + res.setResponseCodeOK(); + res.setResponseCode("200"); // $NON-NLS-1$ + res.setSuccessful(true); + res.setResponseMessageOK(); + res.setSamplerData(data); + res.setDataType(SampleResult.TEXT); + res.setContentType("text/plain"); // $NON-NLS-1$ + res.sampleStart(); + + try { + MongoDB mongoDB = MongoSourceElement.getMongoDB(getSource()); + MongoScriptRunner runner = new MongoScriptRunner(); + DB db = mongoDB.getDB(getDatabase(), getUsername(), getPassword()); + res.latencyEnd(); + Object result = runner.evaluate(db, data); + EvalResultHandler handler = new EvalResultHandler(); + String resultAsString = handler.handle(result); + res.setResponseData(resultAsString.getBytes()); + } catch (Exception ex) { + res.setResponseCode("500"); // $NON-NLS-1$ + res.setSuccessful(false); + res.setResponseMessage(ex.toString()); + res.setResponseData(ex.getMessage().getBytes()); + } finally { + res.sampleEnd(); + } + return res; + } + + public String getTitle() { + return this.getName(); + } + + public String getScript() { + return getPropertyAsString(SCRIPT); + } + + public void setScript(String script) { + setProperty(SCRIPT, script); + } + + public String getDatabase() { + return getPropertyAsString(DATABASE); + } + + public void setDatabase(String database) { + setProperty(DATABASE, database); + } + + public String getUsername() { + return getPropertyAsString(USERNAME); + } + + public void setUsername(String username) { + setProperty(USERNAME, username); + } + + public String getPassword() { + return getPropertyAsString(PASSWORD); + } + + public void setPassword(String password) { + setProperty(PASSWORD, password); + } + + public String getSource() { + return getPropertyAsString(SOURCE); + } + + public void setSource(String source) { + setProperty(SOURCE, source); + } + + /* + * Helper + */ + private void trace(String s) { + if(log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " (" + getTitle() + " " + s + " " + this.toString()); + } + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerBeanInfo.java b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerBeanInfo.java new file mode 100644 index 00000000000..f94105e5ca6 --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerBeanInfo.java @@ -0,0 +1,74 @@ +/* + * 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.protocol.mongodb.sampler; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TypeEditor; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + */ +public class MongoScriptSamplerBeanInfo + extends BeanInfoSupport { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public MongoScriptSamplerBeanInfo() { + super(MongoScriptSampler.class); + + //http://api.mongodb.org/java/2.7.2/com/mongodb/Mongo.html + createPropertyGroup("mongodb", new String[] { + "source", + "database", + "username", + "password" }); + + createPropertyGroup("sampler", new String[]{ + "script"}); + + PropertyDescriptor p = property("database"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("username"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("password", TypeEditor.PasswordEditor); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("source"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("script", TypeEditor.TextAreaEditor); + p.setValue(NOT_UNDEFINED, Boolean.FALSE); + p.setValue(DEFAULT, ""); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(TEXT_LANGUAGE, "javascript"); // $NON-NLS-1$ + + if(log.isDebugEnabled()) { + for (PropertyDescriptor pd : getPropertyDescriptors()) { + log.debug(pd.getName()); + log.debug(pd.getDisplayName()); + } + } + } +} diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerResources.properties b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerResources.properties new file mode 100644 index 00000000000..ece9a53ee8f --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerResources.properties @@ -0,0 +1,27 @@ +# 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. + +#Stored by I18NEdit, may be edited! +database.displayName=Database Name +displayName=MongoDB Script +mongodb.displayName=MongoDB Connection +mongodb.shortDescription=Configure the connection +password.displayName=Password +sampler.displayName=Script +script.displayName=The script to run +script.shortDescription=Add your mongo shell script as you would via the mongo shell. +source.displayName=MongoDB Source +source.shortDescription=Configure the Source +username.displayName=Username diff --git a/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerResources_fr.properties b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerResources_fr.properties new file mode 100644 index 00000000000..41d20a1e56f --- /dev/null +++ b/src/protocol/mongodb/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerResources_fr.properties @@ -0,0 +1,27 @@ +# 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. + +#Stored by I18NEdit, may be edited! +database.displayName=Nom base de donn\u00E9es +displayName=Script MongoDB +mongodb.displayName=Connexion MongoDB +mongodb.shortDescription=Configurer la connexion +password.displayName=Mot de passe +sampler.displayName=Script +script.displayName=Le script \u00E0 ex\u00E9cuter +script.shortDescription=Ajouter votre script shell mongo comme vous le feriez dans le shell mongo. +source.displayName=Source MongoDB +source.shortDescription=Configurer la Source +username.displayName=Utilisateur diff --git a/src/protocol/native/org/apache/jmeter/protocol/system/NativeCommand.java b/src/protocol/native/org/apache/jmeter/protocol/system/NativeCommand.java new file mode 100644 index 00000000000..aa7cd168bb8 --- /dev/null +++ b/src/protocol/native/org/apache/jmeter/protocol/system/NativeCommand.java @@ -0,0 +1,53 @@ +/* + * 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.protocol.system; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +/** + * Native Command + * @deprecated (2.10) use {@link org.apache.jorphan.exec.SystemCommand} instead + */ +@Deprecated +public class NativeCommand extends org.apache.jorphan.exec.SystemCommand { + + /** + * @param env Environment variables appended to environment + * @param directory File working directory + */ + public NativeCommand(File directory, Map env) { + super(directory, env); + } + + /** + * + * @param env Environment variables appended to environment + * @param directory File working directory + * @param stdin File name that will contain data to be input to process + * @param stdout File name that will contain out stream + * @param stderr File name that will contain err stream + * @throws IOException if any of the files are not accessible + */ + public NativeCommand(File directory, Map env, String stdin, String stdout, String stderr) throws IOException { + super(directory, 0L, POLL_INTERVAL, env, stdin, stdout, stderr); + } + +} diff --git a/src/protocol/native/org/apache/jmeter/protocol/system/SystemSampler.java b/src/protocol/native/org/apache/jmeter/protocol/system/SystemSampler.java new file mode 100644 index 00000000000..e2aa64d80eb --- /dev/null +++ b/src/protocol/native/org/apache/jmeter/protocol/system/SystemSampler.java @@ -0,0 +1,332 @@ +/* + * 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.protocol.system; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.exec.SystemCommand; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler for executing a System function. + */ +public class SystemSampler extends AbstractSampler { + + private static final int POLL_INTERVAL = JMeterUtils.getPropDefault("os_sampler.poll_for_timeout", SystemCommand.POLL_INTERVAL); + + private static final long serialVersionUID = 1; + + // + JMX names, do not change their values + public static final String COMMAND = "SystemSampler.command"; + + public static final String DIRECTORY = "SystemSampler.directory"; + + public static final String ARGUMENTS = "SystemSampler.arguments"; + + public static final String ENVIRONMENT = "SystemSampler.environment"; + + public static final String CHECK_RETURN_CODE = "SystemSampler.checkReturnCode"; + + public static final String EXPECTED_RETURN_CODE = "SystemSampler.expectedReturnCode"; + + private static final String STDOUT = "SystemSampler.stdout"; + + private static final String STDERR = "SystemSampler.stderr"; + + private static final String STDIN = "SystemSampler.stdin"; + + private static final String TIMEOUT = "SystemSampler.timeout"; + + // - JMX names + + /** + * Logging + */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + public static final int DEFAULT_RETURN_CODE = 0; + + + /** + * Create a SystemSampler. + */ + public SystemSampler() { + super(); + } + + /** + * Performs a test sample. + * + * @param entry + * the Entry for this sample + * @return test SampleResult + */ + @Override + public SampleResult sample(Entry entry) { + SampleResult results = new SampleResult(); + results.setDataType(SampleResult.TEXT); + results.setSampleLabel(getName()); + + String command = getCommand(); + Arguments args = getArguments(); + Arguments environment = getEnvironmentVariables(); + boolean checkReturnCode = getCheckReturnCode(); + int expectedReturnCode = getExpectedReturnCode(); + List cmds = new ArrayList(args.getArgumentCount()+1); + StringBuilder cmdLine = new StringBuilder((null == command) ? "" : command); + cmds.add(command); + for (int i=0;i env = new HashMap(); + for (int i=0;i " + returnCode); + } + + if (checkReturnCode && (returnCode != expectedReturnCode)) { + results.setSuccessful(false); + results.setResponseMessage("Uexpected return code. Expected ["+expectedReturnCode+"]. Actual ["+returnCode+"]."); + } else { + results.setSuccessful(true); + results.setResponseMessage("OK"); + } + } catch (IOException ioe) { + results.sampleEnd(); + results.setSuccessful(false); + // results.setResponseCode("???"); TODO what code should be set here? + results.setResponseMessage("Exception occured whilst executing System Call: " + ioe); + } catch (InterruptedException ie) { + results.sampleEnd(); + results.setSuccessful(false); + // results.setResponseCode("???"); TODO what code should be set here? + results.setResponseMessage("System Sampler Interupted whilst executing System Call: " + ie); + } + + if (nativeCommand != null) { + results.setResponseData(nativeCommand.getOutResult().getBytes()); // default charset is deliberate here + } + + return results; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } + + /** + * @return working directory to use for system commands + */ + public String getDirectory() { + return getPropertyAsString(DIRECTORY, FileServer.getDefaultBase()); + } + + /** + * Set the working directory to use for system commands + * + * @param directory + * working directory to use for system commands + */ + public void setDirectory(String directory) { + setProperty(DIRECTORY, directory, FileServer.getDefaultBase()); + } + + /** + * Sets the Command attribute of the JavaConfig object + * + * @param command + * the new Command value + */ + public void setCommand(String command) { + setProperty(COMMAND, command); + } + + /** + * Gets the Command attribute of the JavaConfig object + * + * @return the Command value + */ + public String getCommand() { + return getPropertyAsString(COMMAND); + } + + /** + * Set the arguments (parameters) for the JavaSamplerClient to be executed + * with. + * + * @param args + * the new arguments. These replace any existing arguments. + */ + public void setArguments(Arguments args) { + setProperty(new TestElementProperty(ARGUMENTS, args)); + } + + /** + * Get the arguments (parameters) for the JavaSamplerClient to be executed + * with. + * + * @return the arguments + */ + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /** + * @param checkit boolean indicates if we check or not return code + */ + public void setCheckReturnCode(boolean checkit) { + setProperty(CHECK_RETURN_CODE, checkit); + } + + /** + * @return boolean indicating if we check or not return code + */ + public boolean getCheckReturnCode() { + return getPropertyAsBoolean(CHECK_RETURN_CODE); + } + + /** + * @param code expected return code + */ + public void setExpectedReturnCode(int code) { + setProperty(EXPECTED_RETURN_CODE, Integer.toString(code)); + } + + /** + * @return expected return code + */ + public int getExpectedReturnCode() { + return getPropertyAsInt(EXPECTED_RETURN_CODE); + } + + /** + * @param arguments Env vars + */ + public void setEnvironmentVariables(Arguments arguments) { + setProperty(new TestElementProperty(ENVIRONMENT, arguments)); + } + + /** + * Get the env variables + * + * @return the arguments + */ + public Arguments getEnvironmentVariables() { + return (Arguments) getProperty(ENVIRONMENT).getObjectValue(); + } + + public String getStdout() { + return getPropertyAsString(STDOUT, ""); + } + + public void setStdout(String filename) { + setProperty(STDOUT, filename, ""); + } + + public String getStderr() { + return getPropertyAsString(STDERR, ""); + } + + public void setStderr(String filename) { + setProperty(STDERR, filename, ""); + } + + public String getStdin() { + return getPropertyAsString(STDIN, ""); + } + + public void setStdin(String filename) { + setProperty(STDIN, filename, ""); + } + + public long getTimeout() { + return getPropertyAsLong(TIMEOUT, 0L); + } + + public void setTimout(long timeoutMs) { + setProperty(TIMEOUT, timeoutMs, 0L); + } +} diff --git a/src/protocol/native/org/apache/jmeter/protocol/system/gui/SystemSamplerGui.java b/src/protocol/native/org/apache/jmeter/protocol/system/gui/SystemSamplerGui.java new file mode 100644 index 00000000000..b4a567ca4f5 --- /dev/null +++ b/src/protocol/native/org/apache/jmeter/protocol/system/gui/SystemSamplerGui.java @@ -0,0 +1,282 @@ +/* + * 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.protocol.system.gui; + +import java.awt.BorderLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JPanel; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.FilePanelEntry; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.system.SystemSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.log.Logger; + +/** + * GUI for {@link SystemSampler} + */ +public class SystemSamplerGui extends AbstractSamplerGui implements ItemListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * + */ + private static final long serialVersionUID = -2413845772703695934L; + + private JCheckBox checkReturnCode; + private JLabeledTextField desiredReturnCode; + private final FilePanelEntry stdin = new FilePanelEntry(JMeterUtils.getResString("system_sampler_stdin")); // $NON-NLS-1$ + private final FilePanelEntry stdout = new FilePanelEntry(JMeterUtils.getResString("system_sampler_stdout")); // $NON-NLS-1$ + private final FilePanelEntry stderr = new FilePanelEntry(JMeterUtils.getResString("system_sampler_stderr")); // $NON-NLS-1$ + private JLabeledTextField directory; + private JLabeledTextField command; + private JLabeledTextField timeout; + private ArgumentsPanel argsPanel; + private ArgumentsPanel envPanel; + + /** + * Constructor for JavaTestSamplerGui + */ + public SystemSamplerGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "system_sampler_title"; // $NON-NLS-1$ + } + + @Override + public String getStaticLabel() { + return JMeterUtils.getResString(getLabelResource()); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + add(makeCommandPanel(), BorderLayout.CENTER); + + JPanel streamsCodePane = new JPanel(new BorderLayout()); + streamsCodePane.add(makeStreamsPanel(), BorderLayout.NORTH); + streamsCodePane.add(makeReturnCodePanel(), BorderLayout.CENTER); + streamsCodePane.add(makeTimeoutPanel(), BorderLayout.SOUTH); + add(streamsCodePane, BorderLayout.SOUTH); + } + + /* Implements JMeterGuiComponent.createTestElement() */ + @Override + public TestElement createTestElement() { + SystemSampler sampler = new SystemSampler(); + modifyTestElement(sampler); + return sampler; + } + + @Override + public void modifyTestElement(TestElement sampler) { + super.configureTestElement(sampler); + SystemSampler systemSampler = (SystemSampler)sampler; + systemSampler.setCheckReturnCode(checkReturnCode.isSelected()); + if(checkReturnCode.isSelected()) { + if(!StringUtils.isEmpty(desiredReturnCode.getText())) { + systemSampler.setExpectedReturnCode(Integer.parseInt(desiredReturnCode.getText())); + } else { + systemSampler.setExpectedReturnCode(SystemSampler.DEFAULT_RETURN_CODE); + } + } else { + systemSampler.setExpectedReturnCode(SystemSampler.DEFAULT_RETURN_CODE); + } + systemSampler.setCommand(command.getText()); + systemSampler.setArguments((Arguments)argsPanel.createTestElement()); + systemSampler.setEnvironmentVariables((Arguments)envPanel.createTestElement()); + systemSampler.setDirectory(directory.getText()); + systemSampler.setStdin(stdin.getFilename()); + systemSampler.setStdout(stdout.getFilename()); + systemSampler.setStderr(stderr.getFilename()); + if(!StringUtils.isEmpty(timeout.getText())) { + try { + systemSampler.setTimout(Long.parseLong(timeout.getText())); + } catch (NumberFormatException e) { + log.error("Error parsing timeout field value:"+timeout.getText(), e); + } + } + } + + /* Overrides AbstractJMeterGuiComponent.configure(TestElement) */ + @Override + public void configure(TestElement el) { + super.configure(el); + SystemSampler systemSampler = (SystemSampler) el; + checkReturnCode.setSelected(systemSampler.getCheckReturnCode()); + desiredReturnCode.setText(Integer.toString(systemSampler.getExpectedReturnCode())); + desiredReturnCode.setEnabled(checkReturnCode.isSelected()); + command.setText(systemSampler.getCommand()); + argsPanel.configure(systemSampler.getArguments()); + envPanel.configure(systemSampler.getEnvironmentVariables()); + directory.setText(systemSampler.getDirectory()); + stdin.setFilename(systemSampler.getStdin()); + stdout.setFilename(systemSampler.getStdout()); + stderr.setFilename(systemSampler.getStderr()); + timeout.setText(systemSampler.getTimeout() == 0L ? "": // $NON-NLS-1$ + Long.toString(systemSampler.getTimeout())); // not sure if replace 0L to empty string is the good way. + } + + /** + * @return JPanel return code config + */ + private JPanel makeReturnCodePanel() { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("return_code_config_box_title"))); // $NON-NLS-1$ + checkReturnCode = new JCheckBox(JMeterUtils.getResString("check_return_code_title")); // $NON-NLS-1$ + checkReturnCode.addItemListener(this); + desiredReturnCode = new JLabeledTextField(JMeterUtils.getResString("expected_return_code_title")); // $NON-NLS-1$ + desiredReturnCode.setSize(desiredReturnCode.getSize().height, 30); + panel.add(checkReturnCode); + panel.add(Box.createHorizontalStrut(5)); + panel.add(desiredReturnCode); + checkReturnCode.setSelected(true); + return panel; + } + + /** + * @return JPanel timeout config + */ + private JPanel makeTimeoutPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("timeout_config_box_title"))); // $NON-NLS-1$ + timeout = new JLabeledTextField(JMeterUtils.getResString("timeout_title")); // $NON-NLS-1$ + timeout.setSize(timeout.getSize().height, 30); + panel.add(timeout); + return panel; + } + + /** + * @return JPanel Command + directory + */ + private JPanel makeCommandPanel() { + JPanel cmdPanel = new JPanel(); + cmdPanel.setLayout(new BoxLayout(cmdPanel, BoxLayout.X_AXIS)); + + JPanel cmdWkDirPane = new JPanel(new BorderLayout()); + command = new JLabeledTextField(JMeterUtils.getResString("command_field_title")); // $NON-NLS-1$ + cmdWkDirPane.add(command, BorderLayout.CENTER); + directory = new JLabeledTextField(JMeterUtils.getResString("directory_field_title")); // $NON-NLS-1$ + cmdWkDirPane.add(directory, BorderLayout.EAST); + cmdPanel.add(cmdWkDirPane); + + JPanel panel = new VerticalPanel(); + panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("command_config_box_title"))); // $NON-NLS-1$ + panel.add(cmdPanel, BorderLayout.NORTH); + panel.add(makeArgumentsPanel(), BorderLayout.CENTER); + panel.add(makeEnvironmentPanel(), BorderLayout.SOUTH); + return panel; + } + + /** + * @return JPanel Arguments Panel + */ + private JPanel makeArgumentsPanel() { + argsPanel = new ArgumentsPanel(JMeterUtils.getResString("arguments_panel_title"), null, true, false , // $NON-NLS-1$ + new ObjectTableModel(new String[] { ArgumentsPanel.COLUMN_RESOURCE_NAMES_1 }, + Argument.class, + new Functor[] { + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + new Functor("setValue") }, // $NON-NLS-1$ + new Class[] {String.class })); + return argsPanel; + } + + /** + * @return JPanel Environment Panel + */ + private JPanel makeEnvironmentPanel() { + envPanel = new ArgumentsPanel(JMeterUtils.getResString("environment_panel_title")); // $NON-NLS-1$ + return envPanel; + } + + /** + * @return JPanel Streams Panel + */ + private JPanel makeStreamsPanel() { + JPanel stdPane = new JPanel(new BorderLayout()); + stdPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("command_config_std_streams_title"))); // $NON-NLS-1$ + stdPane.add(stdin, BorderLayout.NORTH); + stdPane.add(stdout, BorderLayout.CENTER); + stdPane.add(stderr, BorderLayout.SOUTH); + return stdPane; + } + + /** + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#clearGui() + */ + @Override + public void clearGui() { + super.clearGui(); + directory.setText(""); // $NON-NLS-1$ + command.setText(""); // $NON-NLS-1$ + argsPanel.clearGui(); + envPanel.clearGui(); + desiredReturnCode.setText(""); // $NON-NLS-1$ + checkReturnCode.setSelected(false); + desiredReturnCode.setEnabled(false); + stdin.clearGui(); + stdout.clearGui(); + stderr.clearGui(); + timeout.setText(""); // $NON-NLS-1$ + } + + @Override + public void itemStateChanged(ItemEvent e) { + if(e.getSource()==checkReturnCode) { + desiredReturnCode.setEnabled(e.getStateChange() == ItemEvent.SELECTED); + } + } +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/config/gui/TCPConfigGui.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/config/gui/TCPConfigGui.java new file mode 100644 index 00000000000..63a9d217d17 --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/config/gui/TCPConfigGui.java @@ -0,0 +1,285 @@ +/* + * 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.protocol.tcp.config.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.ServerPanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; +import org.apache.jmeter.gui.util.TristateCheckBox; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.tcp.sampler.TCPSampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; + +public class TCPConfigGui extends AbstractConfigGui { + + private static final long serialVersionUID = 240L; + + private ServerPanel serverPanel; + + private JLabeledTextField classname; + + private JCheckBox reUseConnection; + + // NOTUSED yet private JTextField filename; + + private TristateCheckBox setNoDelay; + + private TristateCheckBox closeConnection; + + private JTextField soLinger; + + private JTextField eolByte; + + private JSyntaxTextArea requestData; + + private boolean displayName = true; + + public TCPConfigGui() { + this(true); + } + + public TCPConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + @Override + public String getLabelResource() { + return "tcp_config_title"; // $NON-NLS-1$ + } + + @Override + public void configure(TestElement element) { + super.configure(element); + // N.B. this will be a config element, so we cannot use the getXXX() methods + classname.setText(element.getPropertyAsString(TCPSampler.CLASSNAME)); + serverPanel.setServer(element.getPropertyAsString(TCPSampler.SERVER)); + // Default to original behaviour, i.e. re-use connection + reUseConnection.setSelected(element.getPropertyAsBoolean(TCPSampler.RE_USE_CONNECTION, TCPSampler.RE_USE_CONNECTION_DEFAULT)); + serverPanel.setPort(element.getPropertyAsString(TCPSampler.PORT)); + // filename.setText(element.getPropertyAsString(TCPSampler.FILENAME)); + serverPanel.setResponseTimeout(element.getPropertyAsString(TCPSampler.TIMEOUT)); + serverPanel.setConnectTimeout(element.getPropertyAsString(TCPSampler.TIMEOUT_CONNECT)); + setNoDelay.setTristateFromProperty(element, TCPSampler.NODELAY); +// setNoDelay.setSelected(element.getPropertyAsBoolean(TCPSampler.NODELAY)); + requestData.setInitialText(element.getPropertyAsString(TCPSampler.REQUEST)); + requestData.setCaretPosition(0); + closeConnection.setTristateFromProperty(element, TCPSampler.CLOSE_CONNECTION); +// closeConnection.setSelected(element.getPropertyAsBoolean(TCPSampler.CLOSE_CONNECTION, TCPSampler.CLOSE_CONNECTION_DEFAULT)); + soLinger.setText(element.getPropertyAsString(TCPSampler.SO_LINGER)); + eolByte.setText(element.getPropertyAsString(TCPSampler.EOL_BYTE)); + } + + @Override + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + modifyTestElement(element); + return element; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement element) { + configureTestElement(element); + // N.B. this will be a config element, so we cannot use the setXXX() methods + element.setProperty(TCPSampler.CLASSNAME, classname.getText(), ""); + element.setProperty(TCPSampler.SERVER, serverPanel.getServer()); + element.setProperty(TCPSampler.RE_USE_CONNECTION, reUseConnection.isSelected()); + element.setProperty(TCPSampler.PORT, serverPanel.getPort()); + // element.setProperty(TCPSampler.FILENAME, filename.getText()); + setNoDelay.setPropertyFromTristate(element, TCPSampler.NODELAY); +// element.setProperty(TCPSampler.NODELAY, setNoDelay.isSelected()); + element.setProperty(TCPSampler.TIMEOUT, serverPanel.getResponseTimeout()); + element.setProperty(TCPSampler.TIMEOUT_CONNECT, serverPanel.getConnectTimeout(),""); + element.setProperty(TCPSampler.REQUEST, requestData.getText()); + closeConnection.setPropertyFromTristate(element, TCPSampler.CLOSE_CONNECTION); // Don't use default for saving tristates +// element.setProperty(TCPSampler.CLOSE_CONNECTION, closeConnection.isSelected(), TCPSampler.CLOSE_CONNECTION_DEFAULT); + element.setProperty(TCPSampler.SO_LINGER, soLinger.getText(), ""); + element.setProperty(TCPSampler.EOL_BYTE, eolByte.getText(), ""); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + serverPanel.clear(); + classname.setText(""); //$NON-NLS-1$ + requestData.setInitialText(""); //$NON-NLS-1$ + reUseConnection.setSelected(true); + setNoDelay.setSelected(false); // TODO should this be indeterminate? + closeConnection.setSelected(TCPSampler.CLOSE_CONNECTION_DEFAULT); // TODO should this be indeterminate? + soLinger.setText(""); //$NON-NLS-1$ + eolByte.setText(""); //$NON-NLS-1$ + } + + + private JPanel createNoDelayPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("tcp_nodelay")); // $NON-NLS-1$ + + setNoDelay = new TristateCheckBox(); + label.setLabelFor(setNoDelay); + + JPanel nodelayPanel = new JPanel(new FlowLayout()); + nodelayPanel.add(label); + nodelayPanel.add(setNoDelay); + return nodelayPanel; + } + + private JPanel createClosePortPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("reuseconnection")); //$NON-NLS-1$ + + reUseConnection = new JCheckBox("", true); + reUseConnection.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(final ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + closeConnection.setEnabled(true); + } else { + closeConnection.setEnabled(false); + } + } + }); + label.setLabelFor(reUseConnection); + + JPanel closePortPanel = new JPanel(new FlowLayout()); + closePortPanel.add(label); + closePortPanel.add(reUseConnection); + return closePortPanel; + } + + private JPanel createCloseConnectionPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("closeconnection")); // $NON-NLS-1$ + + closeConnection = new TristateCheckBox("", TCPSampler.CLOSE_CONNECTION_DEFAULT); + label.setLabelFor(closeConnection); + + JPanel closeConnectionPanel = new JPanel(new FlowLayout()); + closeConnectionPanel.add(label); + closeConnectionPanel.add(closeConnection); + return closeConnectionPanel; + } + + private JPanel createSoLingerOption() { + JLabel label = new JLabel(JMeterUtils.getResString("solinger")); //$NON-NLS-1$ + + soLinger = new JTextField(5); // 5 columns size + soLinger.setMaximumSize(new Dimension(soLinger.getPreferredSize())); + label.setLabelFor(soLinger); + + JPanel soLingerPanel = new JPanel(new FlowLayout()); + soLingerPanel.add(label); + soLingerPanel.add(soLinger); + return soLingerPanel; + } + + private JPanel createEolBytePanel() { + JLabel label = new JLabel(JMeterUtils.getResString("eolbyte")); //$NON-NLS-1$ + + eolByte = new JTextField(3); // 3 columns size + eolByte.setMaximumSize(new Dimension(eolByte.getPreferredSize())); + label.setLabelFor(eolByte); + + JPanel eolBytePanel = new JPanel(new FlowLayout()); + eolBytePanel.add(label); + eolBytePanel.add(eolByte); + return eolBytePanel; + } + + private JPanel createRequestPanel() { + JLabel reqLabel = new JLabel(JMeterUtils.getResString("tcp_request_data")); // $NON-NLS-1$ + requestData = new JSyntaxTextArea(15, 80); + requestData.setLanguage("text"); //$NON-NLS-1$ + reqLabel.setLabelFor(requestData); + + JPanel reqDataPanel = new JPanel(new BorderLayout(5, 0)); + reqDataPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder())); + + reqDataPanel.add(reqLabel, BorderLayout.WEST); + reqDataPanel.add(new JTextScrollPane(requestData), BorderLayout.CENTER); + return reqDataPanel; + } + + // private JPanel createFilenamePanel()//Not used yet + // { + // + // JLabel label = new JLabel(JMeterUtils.getResString("file_to_retrieve")); // $NON-NLS-1$ + // + // filename = new JTextField(10); + // filename.setName(FILENAME); + // label.setLabelFor(filename); + // + // JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + // filenamePanel.add(label, BorderLayout.WEST); + // filenamePanel.add(filename, BorderLayout.CENTER); + // return filenamePanel; + // } + + private void init() { + setLayout(new BorderLayout(0, 5)); + + serverPanel = new ServerPanel(); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + VerticalPanel mainPanel = new VerticalPanel(); + classname = new JLabeledTextField(JMeterUtils.getResString("tcp_classname")); // $NON-NLS-1$ + mainPanel.add(classname); + mainPanel.add(serverPanel); + + HorizontalPanel optionsPanel = new HorizontalPanel(); + optionsPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder())); + optionsPanel.add(createClosePortPanel()); + optionsPanel.add(createCloseConnectionPanel()); + optionsPanel.add(createNoDelayPanel()); + optionsPanel.add(createSoLingerOption()); + optionsPanel.add(createEolBytePanel()); + mainPanel.add(optionsPanel); + mainPanel.add(createRequestPanel()); + + // mainPanel.add(createFilenamePanel()); + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/control/gui/TCPSamplerGui.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/control/gui/TCPSamplerGui.java new file mode 100644 index 00000000000..49e4ab0a35f --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/control/gui/TCPSamplerGui.java @@ -0,0 +1,104 @@ +/* + * 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.protocol.tcp.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import org.apache.jmeter.config.gui.LoginConfigGui; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui; +import org.apache.jmeter.protocol.tcp.sampler.TCPSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class TCPSamplerGui extends AbstractSamplerGui { + + private static final long serialVersionUID = 240L; + + private LoginConfigGui loginPanel; + + private TCPConfigGui tcpDefaultPanel; + + public TCPSamplerGui() { + init(); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + loginPanel.configure(element); + tcpDefaultPanel.configure(element); + } + + @Override + public TestElement createTestElement() { + TCPSampler sampler = new TCPSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + sampler.addTestElement(tcpDefaultPanel.createTestElement()); + sampler.addTestElement(loginPanel.createTestElement()); + this.configureTestElement(sampler); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + tcpDefaultPanel.clearGui(); + loginPanel.clearGui(); + } + + @Override + public String getLabelResource() { + return "tcp_sample_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + VerticalPanel mainPanel = new VerticalPanel(); + + tcpDefaultPanel = new TCPConfigGui(false); + mainPanel.add(tcpDefaultPanel); + + loginPanel = new LoginConfigGui(false); + loginPanel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("login_config"))); // $NON-NLS-1$ + mainPanel.add(loginPanel); + + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/AbstractTCPClient.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/AbstractTCPClient.java new file mode 100644 index 00000000000..7e6183029e5 --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/AbstractTCPClient.java @@ -0,0 +1,79 @@ +/* + * 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.protocol.tcp.sampler; + +/** + * Basic implementation of TCPClient interface. + */ +public abstract class AbstractTCPClient implements TCPClient { + private String charset; + protected byte eolByte; + protected boolean useEolByte = false; + + /** + * {@inheritDoc} + */ + @Override + public byte getEolByte() { + return eolByte; + } + + /** + * {@inheritDoc} + */ + @Override + public void setEolByte(int eolInt) { + if (eolInt >= Byte.MIN_VALUE && eolInt <= Byte.MAX_VALUE) { + this.eolByte = (byte) eolInt; + useEolByte = true; + } else { + useEolByte = false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setupTest() { + } + + /** + * {@inheritDoc} + */ + @Override + public void teardownTest() { + } + + /** + * @return the charset + */ + @Override + public String getCharset() { + return charset; + } + + /** + * @param charset the charset to set + */ + public void setCharset(String charset) { + this.charset = charset; + } + +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImpl.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImpl.java new file mode 100644 index 00000000000..0b6ecb632e1 --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImpl.java @@ -0,0 +1,144 @@ +/* + * 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. + * + */ + +/* + * TCP Sampler Client implementation which reads and writes binary data. + * + * Input/Output strings are passed as hex-encoded binary strings. + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * TCPClient implementation. + * Reads data until the defined EOM byte is reached. + * If there is no EOM byte defined, then reads until + * the end of the stream is reached. + * The EOM byte is defined by the property "tcp.BinaryTCPClient.eomByte". + * + * Input data is assumed to be in hex, and is converted to binary + */ +public class BinaryTCPClientImpl extends AbstractTCPClient { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int eomInt = JMeterUtils.getPropDefault("tcp.BinaryTCPClient.eomByte", 1000); // $NON_NLS-1$ + + public BinaryTCPClientImpl() { + super(); + setEolByte(eomInt); + if (useEolByte) { + log.info("Using eomByte=" + eolByte); + } + } + + /** + * Convert hex string to binary byte array. + * + * @param hexEncodedBinary - hex-encoded binary string + * @return Byte array containing binary representation of input hex-encoded string + * @throws IllegalArgumentException if string is not an even number of hex digits + */ + public static final byte[] hexStringToByteArray(String hexEncodedBinary) { + if (hexEncodedBinary.length() % 2 == 0) { + char[] sc = hexEncodedBinary.toCharArray(); + byte[] ba = new byte[sc.length / 2]; + + for (int i = 0; i < ba.length; i++) { + int nibble0 = Character.digit(sc[i * 2], 16); + int nibble1 = Character.digit(sc[i * 2 + 1], 16); + if (nibble0 == -1 || nibble1 == -1){ + throw new IllegalArgumentException( + "Hex-encoded binary string contains an invalid hex digit in '"+sc[i * 2]+sc[i * 2 + 1]+"'"); + } + ba[i] = (byte) ((nibble0 << 4) | (nibble1)); + } + + return ba; + } else { + throw new IllegalArgumentException( + "Hex-encoded binary string contains an uneven no. of digits"); + } + } + + /** + * Input (hex) string is converted to binary and written to the output stream. + * @param os output stream + * @param hexEncodedBinary hex-encoded binary + */ + @Override + public void write(OutputStream os, String hexEncodedBinary) throws IOException{ + os.write(hexStringToByteArray(hexEncodedBinary)); + os.flush(); + if(log.isDebugEnabled()) { + log.debug("Wrote: " + hexEncodedBinary); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void write(OutputStream os, InputStream is) { + throw new UnsupportedOperationException( + "Method not supported for Length-Prefixed data."); + } + + /** + * Reads data until the defined EOM byte is reached. + * If there is no EOM byte defined, then reads until + * the end of the stream is reached. + * Response data is converted to hex-encoded binary + * @return hex-encoded binary string + * @throws ReadException when reading fails + */ + @Override + public String read(InputStream is) throws ReadException { + ByteArrayOutputStream w = new ByteArrayOutputStream(); + try { + byte[] buffer = new byte[4096]; + int x = 0; + while ((x = is.read(buffer)) > -1) { + w.write(buffer, 0, x); + if (useEolByte && (buffer[x - 1] == eolByte)) { + break; + } + } + + IOUtils.closeQuietly(w); // For completeness + final String hexString = JOrphanUtils.baToHexString(w.toByteArray()); + if(log.isDebugEnabled()) { + log.debug("Read: " + w.size() + "\n" + hexString); + } + return hexString; + } catch (IOException e) { + throw new ReadException("", e, JOrphanUtils.baToHexString(w.toByteArray())); + } + } + +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImpl.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImpl.java new file mode 100644 index 00000000000..c9c2dc182a6 --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImpl.java @@ -0,0 +1,121 @@ +/* + * 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. + * + */ + +/* + * TCP Sampler Client implementation which reads and writes length-prefixed binary data. + * + * Input/Output strings are passed as hex-encoded binary strings. + * + * 2-Byte or 4-Byte length prefixes are supported. + * + * Length prefix is binary of length specified by property "tcp.length.prefix.length". + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Implements binary length-prefixed binary data. + * This is used in ISO8583 for example. + */ +public class LengthPrefixedBinaryTCPClientImpl extends TCPClientDecorator { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final int lengthPrefixLen = JMeterUtils.getPropDefault("tcp.binarylength.prefix.length", 2); // $NON-NLS-1$ + + public LengthPrefixedBinaryTCPClientImpl() { + super(new BinaryTCPClientImpl()); + tcpClient.setEolByte(Byte.MAX_VALUE+1); + } + + + /** + * {@inheritDoc} + */ + @Override + public void write(OutputStream os, String s) throws IOException{ + os.write(intToByteArray(s.length()/2,lengthPrefixLen)); + if(log.isDebugEnabled()) { + log.debug("Wrote: " + s.length()/2 + " bytes"); + } + this.tcpClient.write(os, s); + } + + /** + * {@inheritDoc} + */ + @Override + public void write(OutputStream os, InputStream is) throws IOException { + this.tcpClient.write(os, is); + } + + /** + * {@inheritDoc} + */ + @Override + public String read(InputStream is) throws ReadException{ + byte[] msg = new byte[0]; + int msgLen = 0; + byte[] lengthBuffer = new byte[lengthPrefixLen]; + try { + if (is.read(lengthBuffer, 0, lengthPrefixLen) == lengthPrefixLen) { + msgLen = byteArrayToInt(lengthBuffer); + msg = new byte[msgLen]; + int bytes = JOrphanUtils.read(is, msg, 0, msgLen); + if (bytes < msgLen) { + log.warn("Incomplete message read, expected: "+msgLen+" got: "+bytes); + } + } + + String buffer = JOrphanUtils.baToHexString(msg); + if(log.isDebugEnabled()) { + log.debug("Read: " + msgLen + "\n" + buffer); + } + return buffer; + } + catch(IOException e) { + throw new ReadException("", e, JOrphanUtils.baToHexString(msg)); + } + } + + /** + * Not useful, as the byte is never used. + *

+ * {@inheritDoc} + */ + @Override + public byte getEolByte() { + return tcpClient.getEolByte(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setEolByte(int eolInt) { + throw new UnsupportedOperationException("Cannot set eomByte for prefixed messages"); + } +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/ReadException.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/ReadException.java new file mode 100644 index 00000000000..3ecadada404 --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/ReadException.java @@ -0,0 +1,53 @@ +/* + * 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.protocol.tcp.sampler; + +/** + * Exception that contains partial response (Text read until exception occured) + */ +public class ReadException extends Exception { + + private static final long serialVersionUID = -2770054697780959330L; + private final String partialResponse; + + /** + * @deprecated For use by test code only (serialisation tests) + */ + @Deprecated + public ReadException() { + this(null, null, null); + } + + /** + * Constructor + * @param message Message + * @param cause Source cause + * @param partialResponse Text read until error occured + */ + public ReadException(String message, Throwable cause, String partialResponse) { + super(message, cause); + this.partialResponse = partialResponse; + } + + /** + * @return the partialResponse Text read until error occured + */ + public String getPartialResponse() { + return partialResponse; + } +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPClient.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPClient.java new file mode 100644 index 00000000000..e8a222cbb5b --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPClient.java @@ -0,0 +1,96 @@ +/* + * 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. + * + */ + +/* + * Created on 24-Sep-2003 + * + * Interface for generic TCP protocol handler + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Interface required by TCPSampler for TCPClient implementations. + */ +public interface TCPClient { + + /** + * Versions of JMeter after 2.3.2 invoke this method when the thread starts. + */ + void setupTest(); + + /** + * Versions of JMeter after 2.3.2 invoke this method when the thread ends. + */ + void teardownTest(); + + /** + * + * @param os - + * OutputStream for socket + * @param is - + * InputStream to be written to Socket + * @throws IOException when writing fails + */ + void write(OutputStream os, InputStream is) throws IOException; + + /** + * + * @param os - + * OutputStream for socket + * @param s - + * String to write + * @throws IOException when writing fails + */ + void write(OutputStream os, String s) throws IOException; + + /** + * + * @param is - + * InputStream for socket + * @return String read from socket + * @throws ReadException exception that can contain partial response (Response until error occured) + */ + String read(InputStream is) throws ReadException; + + /** + * Get the end-of-line/end-of-message byte. + * @return Returns the eolByte. + */ + byte getEolByte(); + + + /** + * Get the charset. + * @return Returns the charset. + */ + String getCharset(); + + /** + * Set the end-of-line/end-of-message byte. + * If the value is out of range of a byte, then it is to be ignored. + * + * @param eolInt + * The value to set + */ + void setEolByte(int eolInt); +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecorator.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecorator.java new file mode 100644 index 00000000000..3c95bde0e75 --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecorator.java @@ -0,0 +1,85 @@ +/* + * 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. + * + */ + +/* + * TCP Sampler Client decorator to permit wrapping base client implementations with length prefixes. + * For example, character data or binary data with character length or binary length + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +public abstract class TCPClientDecorator extends AbstractTCPClient { + + protected final TCPClient tcpClient; // the data implementation + + public TCPClientDecorator(TCPClient tcpClient) { + this.tcpClient = tcpClient; + } + + /** + * Convert int to byte array. + * + * @param value + * - int to be converted + * @param len + * - length of required byte array + * @return Byte array representation of input value + * @throws IllegalArgumentException if not length 2 or 4 or outside range of a short int. + */ + public static byte[] intToByteArray(int value, int len) { + if (len == 2 || len == 4) { + if (len == 2 && (value < Short.MIN_VALUE || value > Short.MAX_VALUE)) { + throw new IllegalArgumentException("Value outside range for signed short int."); + } else { + byte[] b = new byte[len]; + for (int i = 0; i < len; i++) { + int offset = (b.length - 1 - i) * 8; + b[i] = (byte) ((value >>> offset) & 0xFF); + } + return b; + } + } else { + throw new IllegalArgumentException( + "Length must be specified as either 2 or 4."); + } + } + + /** + * Convert byte array to int. + * + * @param b + * - Byte array to be converted + * @return Integer value of input byte array + * @throws IllegalArgumentException if ba is null or not length 2 or 4 + */ + public static int byteArrayToInt(byte[] b) { + if (b != null && (b.length == 2 || b.length == 4)) { + // Preserve sign on first byte + int value = b[0] << ((b.length - 1) * 8); + + for (int i = 1; i < b.length; i++) { + int offset = (b.length - 1 - i) * 8; + value += (b[i] & 0xFF) << offset; + } + return value; + } else { + throw new IllegalArgumentException( + "Byte array is null or invalid length."); + } + } +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPClientImpl.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPClientImpl.java new file mode 100644 index 00000000000..84513b43feb --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPClientImpl.java @@ -0,0 +1,138 @@ +/* + * 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. + * + */ + +/* + * Basic TCP Sampler Client class + * + * Can be used to test the TCP Sampler against an HTTP server + * + * The protocol handler class name is defined by the property tcp.handler + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Sample TCPClient implementation. + * Reads data until the defined EOL byte is reached. + * If there is no EOL byte defined, then reads until + * the end of the stream is reached. + * The EOL byte is defined by the property "tcp.eolByte". + */ +public class TCPClientImpl extends AbstractTCPClient { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int eolInt = JMeterUtils.getPropDefault("tcp.eolByte", 1000); // $NON-NLS-1$ + private static final String charset = JMeterUtils.getPropDefault("tcp.charset", Charset.defaultCharset().name()); // $NON-NLS-1$ + // default is not in range of a byte + + public TCPClientImpl() { + super(); + setEolByte(eolInt); + if (useEolByte) { + log.info("Using eolByte=" + eolByte); + } + setCharset(charset); + String configuredCharset = JMeterUtils.getProperty("tcp.charset"); + if(StringUtils.isEmpty(configuredCharset)) { + log.info("Using platform default charset:"+charset); + } else { + log.info("Using charset:"+configuredCharset); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void write(OutputStream os, String s) throws IOException{ + if(log.isDebugEnabled()) { + log.debug("WriteS: " + showEOL(s)); + } + os.write(s.getBytes(charset)); + os.flush(); + } + + /** + * {@inheritDoc} + */ + @Override + public void write(OutputStream os, InputStream is) throws IOException{ + byte buff[]=new byte[512]; + while(is.read(buff) > 0){ + if(log.isDebugEnabled()) { + log.debug("WriteIS: " + showEOL(new String(buff, charset))); + } + os.write(buff); + os.flush(); + } + } + + /** + * Reads data until the defined EOL byte is reached. + * If there is no EOL byte defined, then reads until + * the end of the stream is reached. + */ + @Override + public String read(InputStream is) throws ReadException{ + ByteArrayOutputStream w = new ByteArrayOutputStream(); + try { + byte[] buffer = new byte[4096]; + int x = 0; + while ((x = is.read(buffer)) > -1) { + w.write(buffer, 0, x); + if (useEolByte && (buffer[x - 1] == eolByte)) { + break; + } + } + + // do we need to close byte array (or flush it?) + if(log.isDebugEnabled()) { + log.debug("Read: " + w.size() + "\n" + w.toString()); + } + return w.toString(charset); + } catch (IOException e) { + throw new ReadException("Error reading from server, bytes read: " + w.size(), e, w.toString()); + } + } + + private String showEOL(final String input) { + StringBuilder sb = new StringBuilder(input.length()*2); + for(int i=0; i < input.length(); i++) { + char ch = input.charAt(i); + if (ch < ' ') { + sb.append('['); + sb.append((int)ch); + sb.append(']'); + } else { + sb.append(ch); + } + } + return sb.toString(); + } +} diff --git a/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPSampler.java b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPSampler.java new file mode 100644 index 00000000000..987bb9e6d5b --- /dev/null +++ b/src/protocol/tcp/org/apache/jmeter/protocol/tcp/sampler/TCPSampler.java @@ -0,0 +1,582 @@ +/* + * 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.protocol.tcp.sampler; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * A sampler which understands Tcp requests. + * + */ +public class TCPSampler extends AbstractSampler implements ThreadListener, Interruptible { + private static final long serialVersionUID = 280L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.LoginConfigGui", + "org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + //++ JMX file constants - do not change + public static final String SERVER = "TCPSampler.server"; //$NON-NLS-1$ + + public static final String PORT = "TCPSampler.port"; //$NON-NLS-1$ + + public static final String FILENAME = "TCPSampler.filename"; //$NON-NLS-1$ + + public static final String CLASSNAME = "TCPSampler.classname";//$NON-NLS-1$ + + public static final String NODELAY = "TCPSampler.nodelay"; //$NON-NLS-1$ + + public static final String TIMEOUT = "TCPSampler.timeout"; //$NON-NLS-1$ + + public static final String TIMEOUT_CONNECT = "TCPSampler.ctimeout"; //$NON-NLS-1$ + + public static final String REQUEST = "TCPSampler.request"; //$NON-NLS-1$ + + public static final String RE_USE_CONNECTION = "TCPSampler.reUseConnection"; //$NON-NLS-1$ + public static final boolean RE_USE_CONNECTION_DEFAULT = true; + + public static final String CLOSE_CONNECTION = "TCPSampler.closeConnection"; //$NON-NLS-1$ + public static final boolean CLOSE_CONNECTION_DEFAULT = false; + + public static final String SO_LINGER = "TCPSampler.soLinger"; //$NON-NLS-1$ + + public static final String EOL_BYTE = "TCPSampler.EolByte"; //$NON-NLS-1$ + + //-- JMX file constants - do not change + + private static final String TCPKEY = "TCP"; //$NON-NLS-1$ key for HashMap + + private static final String ERRKEY = "ERR"; //$NON-NLS-1$ key for HashMap + + // If set, this is the regex that is used to extract the status from the + // response + // NOT implemented yet private static final String STATUS_REGEX = + // JMeterUtils.getPropDefault("tcp.status.regex",""); + + // Otherwise, the response is scanned for these strings + private static final String STATUS_PREFIX = JMeterUtils.getPropDefault("tcp.status.prefix", ""); //$NON-NLS-1$ + + private static final String STATUS_SUFFIX = JMeterUtils.getPropDefault("tcp.status.suffix", ""); //$NON-NLS-1$ + + private static final String STATUS_PROPERTIES = JMeterUtils.getPropDefault("tcp.status.properties", ""); //$NON-NLS-1$ + + private static final Properties statusProps = new Properties(); + + private static final boolean haveStatusProps; + + static { + boolean hsp = false; + log.debug("Status prefix=" + STATUS_PREFIX); //$NON-NLS-1$ + log.debug("Status suffix=" + STATUS_SUFFIX); //$NON-NLS-1$ + log.debug("Status properties=" + STATUS_PROPERTIES); //$NON-NLS-1$ + if (STATUS_PROPERTIES.length() > 0) { + File f = new File(STATUS_PROPERTIES); + FileInputStream fis = null; + try { + fis = new FileInputStream(f); + statusProps.load(fis); + log.debug("Successfully loaded properties"); //$NON-NLS-1$ + hsp = true; + } catch (FileNotFoundException e) { + log.debug("Property file not found"); //$NON-NLS-1$ + } catch (IOException e) { + log.debug("Property file error " + e.toString()); //$NON-NLS-1$ + } finally { + JOrphanUtils.closeQuietly(fis); + } + } + haveStatusProps = hsp; + } + + /** the cache of TCP Connections */ + // KEY = TCPKEY or ERRKEY, Entry= Socket or String + private static final ThreadLocal> tp = + new ThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + private transient TCPClient protocolHandler; + + private transient boolean firstSample; // Are we processing the first sample? + + private transient volatile Socket currentSocket; // used for handling interrupt + + public TCPSampler() { + log.debug("Created " + this); //$NON-NLS-1$ + } + + private String getError() { + Map cp = tp.get(); + return (String) cp.get(ERRKEY); + } + + private Socket getSocket(String socketKey) { + Map cp = tp.get(); + Socket con = null; + if (isReUseConnection()) { + con = (Socket) cp.get(socketKey); + if (con != null) { + log.debug(this + " Reusing connection " + con); //$NON-NLS-1$ + } + } + if (con == null) { + // Not in cache, so create new one and cache it + try { + closeSocket(socketKey); // Bug 44910 - close previous socket (if any) + SocketAddress sockaddr = new InetSocketAddress(getServer(), getPort()); + con = new Socket(); + if (getPropertyAsString(SO_LINGER,"").length() > 0){ + con.setSoLinger(true, getSoLinger()); + } + con.connect(sockaddr, getConnectTimeout()); + if(log.isDebugEnabled()) { + log.debug("Created new connection " + con); //$NON-NLS-1$ + } + cp.put(socketKey, con); + } catch (UnknownHostException e) { + log.warn("Unknown host for " + getLabel(), e);//$NON-NLS-1$ + cp.put(ERRKEY, e.toString()); + return null; + } catch (IOException e) { + log.warn("Could not create socket for " + getLabel(), e); //$NON-NLS-1$ + cp.put(ERRKEY, e.toString()); + return null; + } + } + // (re-)Define connection params - Bug 50977 + try { + con.setSoTimeout(getTimeout()); + con.setTcpNoDelay(getNoDelay()); + if(log.isDebugEnabled()) { + log.debug(this + " Timeout " + getTimeout() + " NoDelay " + getNoDelay()); //$NON-NLS-1$ + } + } catch (SocketException se) { + log.warn("Could not set timeout or nodelay for " + getLabel(), se); //$NON-NLS-1$ + cp.put(ERRKEY, se.toString()); + } + return con; + } + + /** + * @return String socket key in cache Map + */ + private final String getSocketKey() { + return TCPKEY+"#"+getServer()+"#"+getPort()+"#"+getUsername()+"#"+getPassword(); + } + + public String getUsername() { + return getPropertyAsString(ConfigTestElement.USERNAME); + } + + public String getPassword() { + return getPropertyAsString(ConfigTestElement.PASSWORD); + } + + public void setServer(String newServer) { + this.setProperty(SERVER, newServer); + } + + public String getServer() { + return getPropertyAsString(SERVER); + } + + public boolean isReUseConnection() { + return getPropertyAsBoolean(RE_USE_CONNECTION, RE_USE_CONNECTION_DEFAULT); + } + + public void setCloseConnection(String close) { + this.setProperty(CLOSE_CONNECTION, close, ""); + } + + public boolean isCloseConnection() { + return getPropertyAsBoolean(CLOSE_CONNECTION, CLOSE_CONNECTION_DEFAULT); + } + + public void setSoLinger(String soLinger) { + this.setProperty(SO_LINGER, soLinger, ""); + } + + public int getSoLinger() { + return getPropertyAsInt(SO_LINGER); + } + + public void setEolByte(String eol) { + this.setProperty(EOL_BYTE, eol, ""); + } + + public int getEolByte() { + return getPropertyAsInt(EOL_BYTE); + } + + + public void setPort(String newFilename) { + this.setProperty(PORT, newFilename); + } + + public int getPort() { + return getPropertyAsInt(PORT); + } + + public void setFilename(String newFilename) { + this.setProperty(FILENAME, newFilename); + } + + public String getFilename() { + return getPropertyAsString(FILENAME); + } + + public void setRequestData(String newRequestData) { + this.setProperty(REQUEST, newRequestData); + } + + public String getRequestData() { + return getPropertyAsString(REQUEST); + } + + public void setTimeout(String newTimeout) { + this.setProperty(TIMEOUT, newTimeout); + } + + public int getTimeout() { + return getPropertyAsInt(TIMEOUT); + } + + public void setConnectTimeout(String newTimeout) { + this.setProperty(TIMEOUT_CONNECT, newTimeout, ""); + } + + public int getConnectTimeout() { + return getPropertyAsInt(TIMEOUT_CONNECT, 0); + } + + public boolean getNoDelay() { + return getPropertyAsBoolean(NODELAY); + } + + public void setClassname(String classname) { + this.setProperty(CLASSNAME, classname, ""); //$NON-NLS-1$ + } + + public String getClassname() { + String clazz = getPropertyAsString(CLASSNAME,""); + if (clazz==null || clazz.length()==0){ + clazz = JMeterUtils.getPropDefault("tcp.handler", "TCPClientImpl"); //$NON-NLS-1$ $NON-NLS-2$ + } + return clazz; + } + + /** + * Returns a formatted string label describing this sampler Example output: + * Tcp://Tcp.nowhere.com/pub/README.txt + * + * @return a formatted string label describing this sampler + */ + public String getLabel() { + return ("tcp://" + this.getServer() + ":" + this.getPort());//$NON-NLS-1$ $NON-NLS-2$ + } + + private static final String protoPrefix = "org.apache.jmeter.protocol.tcp.sampler."; //$NON-NLS-1$ + + private Class getClass(String className) { + Class c = null; + try { + c = Class.forName(className, false, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + try { + c = Class.forName(protoPrefix + className, false, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e1) { + log.error("Could not find protocol class '" + className+"'"); //$NON-NLS-1$ + } + } + return c; + + } + + private TCPClient getProtocol() { + TCPClient TCPClient = null; + Class javaClass = getClass(getClassname()); + if (javaClass == null){ + return null; + } + try { + TCPClient = (TCPClient) javaClass.newInstance(); + if (getPropertyAsString(EOL_BYTE, "").length()>0){ + TCPClient.setEolByte(getEolByte()); + log.info("Using eolByte=" + getEolByte()); + } + + if (log.isDebugEnabled()) { + log.debug(this + "Created: " + getClassname() + "@" + Integer.toHexString(TCPClient.hashCode())); //$NON-NLS-1$ + } + } catch (Exception e) { + log.error(this + " Exception creating: " + getClassname(), e); //$NON-NLS-1$ + } + return TCPClient; + } + + @Override + public SampleResult sample(Entry e)// Entry tends to be ignored ... + { + if (firstSample) { // Do stuff we cannot do as part of threadStarted() + initSampling(); + firstSample=false; + } + final boolean reUseConnection = isReUseConnection(); + final boolean closeConnection = isCloseConnection(); + String socketKey = getSocketKey(); + if (log.isDebugEnabled()){ + log.debug(getLabel() + " " + getFilename() + " " + getUsername() + " " + getPassword()); + } + SampleResult res = new SampleResult(); + boolean isSuccessful = false; + res.setSampleLabel(getName());// Use the test element name for the label + StringBuilder sb = new StringBuilder(); + sb.append("Host: ").append(getServer()); // $NON-NLS-1$ + sb.append(" Port: ").append(getPort()); // $NON-NLS-1$ + sb.append("\n"); // $NON-NLS-1$ + sb.append("Reuse: ").append(reUseConnection); // $NON-NLS-1$ + sb.append(" Close: ").append(closeConnection); // $NON-NLS-1$ + sb.append("\n["); // $NON-NLS-1$ + sb.append("SOLINGER: ").append(getSoLinger()); // $NON-NLS-1$ + sb.append(" EOL: ").append(getEolByte()); // $NON-NLS-1$ + sb.append(" noDelay: ").append(getNoDelay()); // $NON-NLS-1$ + sb.append("]"); // $NON-NLS-1$ + res.setSamplerData(sb.toString()); + res.sampleStart(); + try { + Socket sock = getSocket(socketKey); + if (sock == null) { + res.setResponseCode("500"); //$NON-NLS-1$ + res.setResponseMessage(getError()); + } else if (protocolHandler == null){ + res.setResponseCode("500"); //$NON-NLS-1$ + res.setResponseMessage("Protocol handler not found"); + } else { + currentSocket = sock; + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + String req = getRequestData(); + // TODO handle filenames + res.setSamplerData(req); + protocolHandler.write(os, req); + String in = protocolHandler.read(is); + isSuccessful = setupSampleResult(res, in, null, protocolHandler.getCharset()); + } + } catch (ReadException ex) { + log.error("", ex); + isSuccessful=setupSampleResult(res, ex.getPartialResponse(), ex,protocolHandler.getCharset()); + closeSocket(socketKey); + } catch (Exception ex) { + log.error("", ex); + isSuccessful=setupSampleResult(res, "", ex, protocolHandler.getCharset()); + closeSocket(socketKey); + } finally { + currentSocket = null; + // Calculate response time + res.sampleEnd(); + + // Set if we were successful or not + res.setSuccessful(isSuccessful); + + if (!reUseConnection || closeConnection) { + closeSocket(socketKey); + } + } + return res; + } + + /** + * Fills SampleResult object + * @param sampleResult {@link SampleResult} + * @param readResponse Response read until error occured + * @param exception Source exception + * @param encoding sample encoding + * @return boolean if sample is considered as successful + */ + private boolean setupSampleResult(SampleResult sampleResult, + String readResponse, + Exception exception, + String encoding) { + sampleResult.setResponseData(readResponse, encoding); + sampleResult.setDataType(SampleResult.TEXT); + if(exception==null) { + sampleResult.setResponseCodeOK(); + sampleResult.setResponseMessage("OK"); //$NON-NLS-1$ + } else { + sampleResult.setResponseCode("500"); //$NON-NLS-1$ + sampleResult.setResponseMessage(exception.toString()); //$NON-NLS-1$ + } + boolean isSuccessful = exception == null; + // Reset the status code if the message contains one + if (!StringUtils.isEmpty(readResponse) && STATUS_PREFIX.length() > 0) { + int i = readResponse.indexOf(STATUS_PREFIX); + int j = readResponse.indexOf(STATUS_SUFFIX, i + STATUS_PREFIX.length()); + if (i != -1 && j > i) { + String rc = readResponse.substring(i + STATUS_PREFIX.length(), j); + sampleResult.setResponseCode(rc); + isSuccessful = isSuccessful && checkResponseCode(rc); + if (haveStatusProps) { + sampleResult.setResponseMessage(statusProps.getProperty(rc, "Status code not found in properties")); //$NON-NLS-1$ + } else { + sampleResult.setResponseMessage("No status property file"); + } + } else { + sampleResult.setResponseCode("999"); //$NON-NLS-1$ + sampleResult.setResponseMessage("Status value not found"); + isSuccessful = false; + } + } + return isSuccessful; + } + + /** + * @param rc response code + * @return whether this represents success or not + */ + private boolean checkResponseCode(String rc) { + if (rc.compareTo("400") >= 0 && rc.compareTo("499") <= 0) { //$NON-NLS-1$ $NON-NLS-2$ + return false; + } + if (rc.compareTo("500") >= 0 && rc.compareTo("599") <= 0) { //$NON-NLS-1$ $NON-NLS-2$ + return false; + } + return true; + } + + @Override + public void threadStarted() { + log.debug("Thread Started"); //$NON-NLS-1$ + firstSample = true; + } + + // Cannot do this as part of threadStarted() because the Config elements have not been processed. + private void initSampling(){ + protocolHandler = getProtocol(); + log.debug("Using Protocol Handler: " + //$NON-NLS-1$ + (protocolHandler == null ? "NONE" : protocolHandler.getClass().getName())); //$NON-NLS-1$ + if (protocolHandler != null){ + protocolHandler.setupTest(); + } + } + + /** + * Close socket of current sampler + */ + private void closeSocket(String socketKey) { + Map cp = tp.get(); + Socket con = (Socket) cp.remove(socketKey); + if (con != null) { + log.debug(this + " Closing connection " + con); //$NON-NLS-1$ + try { + con.close(); + } catch (IOException e) { + log.warn("Error closing socket "+e); //$NON-NLS-1$ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void threadFinished() { + log.debug("Thread Finished"); //$NON-NLS-1$ + tearDown(); + if (protocolHandler != null){ + protocolHandler.teardownTest(); + } + } + + /** + * Closes all connections, clears Map and remove thread local Map + */ + private void tearDown() { + Map cp = tp.get(); + for (Map.Entry element : cp.entrySet()) { + if(element.getKey().startsWith(TCPKEY)) { + try { + ((Socket)element.getValue()).close(); + } catch (IOException e) { + // NOOP + } + } + } + cp.clear(); + tp.remove(); + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } + + @Override + public boolean interrupt() { + Socket sock = currentSocket; // fetch in case gets nulled later + if (sock != null) { + try { + sock.close(); + } catch (IOException e) { + // ignored + } + return true; + } + return false; + } +} diff --git a/test/src/org/apache/commons/cli/avalon/ClutilTestCase.java b/test/src/org/apache/commons/cli/avalon/ClutilTestCase.java new file mode 100644 index 00000000000..e79f5d35329 --- /dev/null +++ b/test/src/org/apache/commons/cli/avalon/ClutilTestCase.java @@ -0,0 +1,967 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public final class ClutilTestCase extends TestCase { + private static final String[] ARGLIST1 = new String[] { "--you", "are", "--all", "-cler", "kid" }; + + private static final String[] ARGLIST2 = new String[] { "-Dstupid=idiot", "are", "--all", "here", "-d" }; + + private static final String[] ARGLIST3 = new String[] { + // duplicates + "-Dstupid=idiot", "are", "--all", "--all", "here" }; + + private static final String[] ARGLIST4 = new String[] { + // incompatable (blee/all) + "-Dstupid", "idiot", "are", "--all", "--blee", "here" }; + + private static final String[] ARGLIST5 = new String[] { "-f", "myfile.txt" }; + + private static final int DEFINE_OPT = 'D'; + + private static final int CASE_CHECK_OPT = 'd'; + + private static final int YOU_OPT = 'y'; + + private static final int ALL_OPT = 'a'; + + private static final int CLEAR1_OPT = 'c'; + + private static final int CLEAR2_OPT = 'l'; + + private static final int CLEAR3_OPT = 'e'; + + private static final int CLEAR5_OPT = 'r'; + + private static final int BLEE_OPT = 'b'; + + private static final int FILE_OPT = 'f'; + + private static final int TAINT_OPT = 'T'; + + private static final CLOptionDescriptor DEFINE = new CLOptionDescriptor("define", + CLOptionDescriptor.ARGUMENTS_REQUIRED_2, DEFINE_OPT, "define"); + + private static final CLOptionDescriptor DEFINE_MANY = new CLOptionDescriptor("define", + CLOptionDescriptor.ARGUMENTS_REQUIRED_2 | CLOptionDescriptor.DUPLICATES_ALLOWED, DEFINE_OPT, "define"); + + private static final CLOptionDescriptor CASE_CHECK = new CLOptionDescriptor("charCheck", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CASE_CHECK_OPT, "check character case sensitivity"); + + private static final CLOptionDescriptor YOU = new CLOptionDescriptor("you", CLOptionDescriptor.ARGUMENT_DISALLOWED, + YOU_OPT, "you"); + + private static final CLOptionDescriptor CLEAR1 = new CLOptionDescriptor("c", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CLEAR1_OPT, "c"); + + private static final CLOptionDescriptor CLEAR2 = new CLOptionDescriptor("l", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CLEAR2_OPT, "l"); + + private static final CLOptionDescriptor CLEAR3 = new CLOptionDescriptor("e", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CLEAR3_OPT, "e"); + + private static final CLOptionDescriptor CLEAR5 = new CLOptionDescriptor("r", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CLEAR5_OPT, "r"); + + private static final CLOptionDescriptor BLEE = new CLOptionDescriptor("blee", + CLOptionDescriptor.ARGUMENT_DISALLOWED, BLEE_OPT, "blee"); + + private static final CLOptionDescriptor ALL = new CLOptionDescriptor("all", + CLOptionDescriptor.ARGUMENT_DISALLOWED, + ALL_OPT, "all", new CLOptionDescriptor[] { BLEE }); + + private static final CLOptionDescriptor FILE = new CLOptionDescriptor("file", + CLOptionDescriptor.ARGUMENT_REQUIRED, FILE_OPT, "the build file."); + + private static final CLOptionDescriptor TAINT = new CLOptionDescriptor("taint", + CLOptionDescriptor.ARGUMENT_OPTIONAL, TAINT_OPT, "turn on tainting checks (optional level)."); + + private static final CLOptionDescriptor [] OPTIONS = new CLOptionDescriptor [] { + new CLOptionDescriptor("none", + CLOptionDescriptor.ARGUMENT_DISALLOWED | CLOptionDescriptor.DUPLICATES_ALLOWED, + '0', "no parameter"), + + new CLOptionDescriptor("optional", + CLOptionDescriptor.ARGUMENT_OPTIONAL | CLOptionDescriptor.DUPLICATES_ALLOWED, + '?', "optional parameter"), + + new CLOptionDescriptor("one", + CLOptionDescriptor.ARGUMENT_REQUIRED | CLOptionDescriptor.DUPLICATES_ALLOWED, + '1', "one parameter"), + + new CLOptionDescriptor("two", + CLOptionDescriptor.ARGUMENTS_REQUIRED_2 | CLOptionDescriptor.DUPLICATES_ALLOWED, + '2', "two parameters") + }; + + + + + public ClutilTestCase() { + this("Command Line Interpreter Test Case"); + } + + public ClutilTestCase(String name) { + super(name); + } + + public void testOptionalArgWithSpace() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T", "param", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals("Option count", 3, size); + + final CLOption option0 = clOptions.get(0); + assertEquals("Option Code: " + option0.getDescriptor().getId(), TAINT_OPT, option0.getDescriptor().getId()); + assertEquals("Option Arg: " + option0.getArgument(0), null, option0.getArgument(0)); + + final CLOption option1 = clOptions.get(1); + assertEquals(option1.getDescriptor().getId(), CLOption.TEXT_ARGUMENT); + assertEquals(option1.getArgument(0), "param"); + + final CLOption option2 = clOptions.get(2); + assertEquals(option2.getDescriptor().getId(), ALL_OPT); + assertEquals(option2.getArgument(0), null); + } + + public void testOptionalArgLong() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + // Check that optional args work woth long options + final String[] args = new String[] { "--taint", "param", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals("Option count", 3, size); + + final CLOption option0 = clOptions.get(0); + assertEquals("Option Code: " + option0.getDescriptor().getId(), TAINT_OPT, option0.getDescriptor().getId()); + assertEquals("Option Arg: " + option0.getArgument(0), null, option0.getArgument(0)); + + final CLOption option1 = clOptions.get(1); + assertEquals(CLOption.TEXT_ARGUMENT, option1.getDescriptor().getId()); + assertEquals("param", option1.getArgument(0)); + + final CLOption option2 = clOptions.get(2); + assertEquals(option2.getDescriptor().getId(), ALL_OPT); + assertEquals(option2.getArgument(0), null); + } + + public void testOptionalArgLongEquals() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + // Check that optional args work woth long options + final String[] args = new String[] { "--taint=param", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals("Option count", 2, size); + + final CLOption option0 = clOptions.get(0); + assertEquals("Option Code: " + option0.getDescriptor().getId(), TAINT_OPT, option0.getDescriptor().getId()); + assertEquals("Option Arg: " + option0.getArgument(0), "param", option0.getArgument(0)); + + final CLOption option2 = clOptions.get(1); + assertEquals(option2.getDescriptor().getId(), ALL_OPT); + assertEquals(option2.getArgument(0), null); + } + + public void testShortOptArgUnenteredBeforeOtherOpt() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals("Option count", 2, size); + + final CLOption option0 = clOptions.get(0); + assertEquals("Option Code: " + option0.getDescriptor().getId(), TAINT_OPT, option0.getDescriptor().getId()); + assertEquals("Option Arg: " + option0.getArgument(0), null, option0.getArgument(0)); + + final CLOption option1 = clOptions.get(1); + assertEquals(option1.getDescriptor().getId(), ALL_OPT); + assertEquals(option1.getArgument(0), null); + } + + public void testOptionalArgsWithArgShortBeforeOtherOpt() { + // "-T3","-a" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T3", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + final CLOption option0 = clOptions.get(0); + assertEquals(option0.getDescriptor().getId(), TAINT_OPT); + assertEquals(option0.getArgument(0), "3"); + + final CLOption option1 = clOptions.get(1); + assertEquals(ALL_OPT, option1.getDescriptor().getId()); + assertEquals(null, option1.getArgument(0)); + } + + public void testOptionalArgsWithArgShortEqualsBeforeOtherOpt() { + // "-T3","-a" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T=3", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + final CLOption option0 = clOptions.get(0); + assertEquals(option0.getDescriptor().getId(), TAINT_OPT); + assertEquals(option0.getArgument(0), "3"); + + final CLOption option1 = clOptions.get(1); + assertEquals(ALL_OPT, option1.getDescriptor().getId()); + assertEquals(null, option1.getArgument(0)); + } + + public void testOptionalArgsNoArgShortBeforeOtherOpt() { + // "-T","-a" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + final CLOption option0 = clOptions.get(0); + assertEquals(TAINT_OPT, option0.getDescriptor().getId()); + assertEquals(null, option0.getArgument(0)); + + final CLOption option1 = clOptions.get(1); + assertEquals(ALL_OPT, option1.getDescriptor().getId()); + assertEquals(null, option1.getArgument(0)); + } + + public void testFullParse() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { YOU, ALL, CLEAR1, CLEAR2, CLEAR3, CLEAR5 }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST1, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 8); + assertEquals(clOptions.get(0).getDescriptor().getId(), YOU_OPT); + assertEquals(clOptions.get(1).getDescriptor().getId(), 0); + assertEquals(clOptions.get(2).getDescriptor().getId(), ALL_OPT); + assertEquals(clOptions.get(3).getDescriptor().getId(), CLEAR1_OPT); + assertEquals(clOptions.get(4).getDescriptor().getId(), CLEAR2_OPT); + assertEquals(clOptions.get(5).getDescriptor().getId(), CLEAR3_OPT); + assertEquals(clOptions.get(6).getDescriptor().getId(), CLEAR5_OPT); + assertEquals(clOptions.get(7).getDescriptor().getId(), 0); + } + + public void testDuplicateOptions() { + // "-Dstupid=idiot","are","--all","--all","here" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, ALL, CLEAR1 }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST3, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 5); + assertEquals(clOptions.get(0).getDescriptor().getId(), DEFINE_OPT); + assertEquals(clOptions.get(1).getDescriptor().getId(), 0); + assertEquals(clOptions.get(2).getDescriptor().getId(), ALL_OPT); + assertEquals(clOptions.get(3).getDescriptor().getId(), ALL_OPT); + assertEquals(clOptions.get(4).getDescriptor().getId(), 0); + } + + public void testIncompatableOptions() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, ALL, CLEAR1, BLEE }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST4, options); + + assertNotNull(parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 5); + assertEquals(clOptions.get(0).getDescriptor().getId(), DEFINE_OPT); + assertEquals(clOptions.get(1).getDescriptor().getId(), 0); + assertEquals(clOptions.get(2).getDescriptor().getId(), ALL_OPT); + assertEquals(clOptions.get(3).getDescriptor().getId(), BLEE_OPT); + assertEquals(clOptions.get(4).getDescriptor().getId(), 0); + } + + public void testSingleArg() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST5, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 1); + assertEquals(clOptions.get(0).getDescriptor().getId(), FILE_OPT); + assertEquals(clOptions.get(0).getArgument(), "myfile.txt"); + } + + public void testSingleArg2() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-f-=,=-" } // Check + // delimiters + // are + // allowed + , options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals("-=,=-", clOptions.get(0).getArgument()); + } + + public void testSingleArg3() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file=-=,-" } // Check + // delimiters + // are + // allowed + , options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals("-=,-", clOptions.get(0).getArgument()); + } + + public void testSingleArg4() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file", "myfile.txt" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals("myfile.txt", clOptions.get(0).getArgument()); + } + + public void testSingleArg5() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-f", "myfile.txt" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals("myfile.txt", clOptions.get(0).getArgument()); + } + + public void testSingleArg6() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-f", "-=-" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals("-=-", clOptions.get(0).getArgument()); + } + + public void testSingleArg7() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file=-=-" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals("-=-", clOptions.get(0).getArgument()); + } + + public void testSingleArg8() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file", "-=-" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals("-=-", clOptions.get(0).getArgument()); + } + + public void testSingleArg9() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file", "-=-" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals("-=-", clOptions.get(0).getArgument()); + } + + public void testCombinedArgs1() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { BLEE, TAINT }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-bT", "rest" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + assertEquals(3, size); + assertEquals(BLEE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals(TAINT_OPT, clOptions.get(1).getDescriptor().getId()); + assertEquals(0, clOptions.get(2).getDescriptor().getId()); + assertEquals("rest", clOptions.get(2).getArgument()); + } + + public void testCombinedArgs2() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { BLEE, TAINT, FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-bT", "-fa" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + assertEquals(3, size); + assertEquals(BLEE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals(TAINT_OPT, clOptions.get(1).getDescriptor().getId()); + assertEquals(FILE_OPT, clOptions.get(2).getDescriptor().getId()); + assertEquals("a", clOptions.get(2).getArgument()); + } + + public void testCombinedArgs3() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { BLEE, TAINT, FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-bT", "--", "-fa" }// Should + // not + // detect + // trailing + // option + , options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + assertEquals(3, size); + assertEquals(BLEE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals(TAINT_OPT, clOptions.get(1).getDescriptor().getId()); + assertEquals(0, clOptions.get(2).getDescriptor().getId()); + assertEquals("-fa", clOptions.get(2).getArgument()); + } + + public void testCombinedArgs4() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { BLEE, TAINT, FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-bT", "rest", "-fa" } // should + // detect + // trailing + // option + , options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + assertEquals(4, size); + assertEquals(BLEE_OPT, clOptions.get(0).getDescriptor().getId()); + assertEquals(TAINT_OPT, clOptions.get(1).getDescriptor().getId()); + assertEquals(0, clOptions.get(2).getDescriptor().getId()); + assertEquals("rest", clOptions.get(2).getArgument()); + assertEquals(FILE_OPT, clOptions.get(3).getDescriptor().getId()); + assertEquals("a", clOptions.get(3).getArgument()); + } + + public void test2ArgsParse() { + // "-Dstupid=idiot","are","--all","here" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, ALL, CLEAR1, CASE_CHECK }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST2, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 5); + assertEquals(clOptions.get(0).getDescriptor().getId(), DEFINE_OPT); + assertEquals(clOptions.get(1).getDescriptor().getId(), 0); + assertEquals(clOptions.get(2).getDescriptor().getId(), ALL_OPT); + assertEquals(clOptions.get(3).getDescriptor().getId(), 0); + assertEquals(clOptions.get(4).getDescriptor().getId(), CASE_CHECK_OPT); + + final CLOption option = clOptions.get(0); + assertEquals("stupid", option.getArgument(0)); + assertEquals("idiot", option.getArgument(1)); + } + + public void test2ArgsParse2() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--define", "a-b,c=d-e,f" }, // Check + // "-" + // is + // allowed + // in + // arg2 + options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(DEFINE_OPT, clOptions.get(0).getDescriptor().getId()); + + final CLOption option = clOptions.get(0); + assertEquals("a-b,c", option.getArgument(0)); + assertEquals("d-e,f", option.getArgument(1)); + } + + public void test2ArgsParse3() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-D", "A-b,c", "G-e,f" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(DEFINE_OPT, clOptions.get(0).getDescriptor().getId()); + + final CLOption option = clOptions.get(0); + assertEquals("A-b,c", option.getArgument(0)); + assertEquals("G-e,f", option.getArgument(1)); + } + + public void test2ArgsParse4() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE_MANY }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-Dval1=-1", "-D", "val2=-2", "--define=val-3=-3", + "--define", "val4-=-4" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(4, size); + for (int i = 0; i < size; i++) { + assertEquals(DEFINE_OPT, clOptions.get(i).getDescriptor().getId()); + } + + CLOption option; + option = clOptions.get(0); + assertEquals("val1", option.getArgument(0)); + assertEquals("-1", option.getArgument(1)); + + option = clOptions.get(1); + assertEquals("val2", option.getArgument(0)); + assertEquals("-2", option.getArgument(1)); + + option = clOptions.get(2); + assertEquals("val-3", option.getArgument(0)); + assertEquals("-3", option.getArgument(1)); + + option = clOptions.get(3); + assertEquals("val4-", option.getArgument(0)); + assertEquals("-4", option.getArgument(1)); + } + + public void testPartParse() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { YOU }; + + final ParserControl control = new AbstractParserControl() { + @Override + public boolean isFinished(int lastOptionCode) { + return (lastOptionCode == YOU_OPT); + } + }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST1, options, control); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 1); + assertEquals(clOptions.get(0).getDescriptor().getId(), YOU_OPT); + } + + public void test2PartParse() { + final CLOptionDescriptor[] options1 = new CLOptionDescriptor[] { YOU }; + + final CLOptionDescriptor[] options2 = new CLOptionDescriptor[] { ALL, CLEAR1, CLEAR2, CLEAR3, CLEAR5 }; + + final ParserControl control1 = new AbstractParserControl() { + @Override + public boolean isFinished(int lastOptionCode) { + return (lastOptionCode == YOU_OPT); + } + }; + + final CLArgsParser parser1 = new CLArgsParser(ARGLIST1, options1, control1); + + assertNull(parser1.getErrorString(), parser1.getErrorString()); + + final List clOptions1 = parser1.getArguments(); + final int size1 = clOptions1.size(); + + assertEquals(size1, 1); + assertEquals(clOptions1.get(0).getDescriptor().getId(), YOU_OPT); + + final CLArgsParser parser2 = new CLArgsParser(parser1.getUnparsedArgs(), options2); + + assertNull(parser2.getErrorString(), parser2.getErrorString()); + + final List clOptions2 = parser2.getArguments(); + final int size2 = clOptions2.size(); + + assertEquals(size2, 7); + assertEquals(clOptions2.get(0).getDescriptor().getId(), 0); + assertEquals(clOptions2.get(1).getDescriptor().getId(), ALL_OPT); + assertEquals(clOptions2.get(2).getDescriptor().getId(), CLEAR1_OPT); + assertEquals(clOptions2.get(3).getDescriptor().getId(), CLEAR2_OPT); + assertEquals(clOptions2.get(4).getDescriptor().getId(), CLEAR3_OPT); + assertEquals(clOptions2.get(5).getDescriptor().getId(), CLEAR5_OPT); + assertEquals(clOptions2.get(6).getDescriptor().getId(), 0); + } + + public void test2PartPartialParse() { + final CLOptionDescriptor[] options1 = new CLOptionDescriptor[] { YOU, ALL, CLEAR1 }; + + final CLOptionDescriptor[] options2 = new CLOptionDescriptor[] {}; + + final ParserControl control1 = new AbstractParserControl() { + @Override + public boolean isFinished(final int lastOptionCode) { + return (lastOptionCode == CLEAR1_OPT); + } + }; + + final CLArgsParser parser1 = new CLArgsParser(ARGLIST1, options1, control1); + + assertNull(parser1.getErrorString(), parser1.getErrorString()); + + final List clOptions1 = parser1.getArguments(); + final int size1 = clOptions1.size(); + + assertEquals(size1, 4); + assertEquals(clOptions1.get(0).getDescriptor().getId(), YOU_OPT); + assertEquals(clOptions1.get(1).getDescriptor().getId(), 0); + assertEquals(clOptions1.get(2).getDescriptor().getId(), ALL_OPT); + assertEquals(clOptions1.get(3).getDescriptor().getId(), CLEAR1_OPT); + + assertEquals("ler",parser1.getUnparsedArgs()[0]); + + final CLArgsParser parser2 = new CLArgsParser(parser1.getUnparsedArgs(), options2); + + assertNull(parser2.getErrorString(), parser2.getErrorString()); + + final List clOptions2 = parser2.getArguments(); + final int size2 = clOptions2.size(); + + assertEquals(size2, 2); + assertEquals(clOptions2.get(0).getDescriptor().getId(), 0); + assertEquals(clOptions2.get(1).getDescriptor().getId(), 0); + } + + public void testDuplicatesFail() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { YOU, ALL, CLEAR1, CLEAR2, CLEAR3, CLEAR5 }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST1, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + } + + public void testIncomplete2Args() { + // "-Dstupid=" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-Dstupid=" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 1); + final CLOption option = clOptions.get(0); + assertEquals(option.getDescriptor().getId(), DEFINE_OPT); + assertEquals(option.getArgument(0), "stupid"); + assertEquals(option.getArgument(1), ""); + } + + public void testIncomplete2ArgsMixed() { + // "-Dstupid=","-c" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, CLEAR1 }; + + final String[] args = new String[] { "-Dstupid=", "-c" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + assertEquals(clOptions.get(1).getDescriptor().getId(), CLEAR1_OPT); + final CLOption option = clOptions.get(0); + assertEquals(option.getDescriptor().getId(), DEFINE_OPT); + assertEquals(option.getArgument(0), "stupid"); + assertEquals(option.getArgument(1), ""); + } + + public void testIncomplete2ArgsMixedNoEq() { + // "-Dstupid","-c" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, CLEAR1 }; + + final String[] args = new String[] { "-DStupid", "-c" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + assertEquals(clOptions.get(1).getDescriptor().getId(), CLEAR1_OPT); + final CLOption option = clOptions.get(0); + assertEquals(option.getDescriptor().getId(), DEFINE_OPT); + assertEquals(option.getArgument(0), "Stupid"); + assertEquals(option.getArgument(1), ""); + } + + /** + * Test the getArgumentById and getArgumentByName lookup methods. + */ + public void testArgumentLookup() { + final String[] args = { "-f", "testarg" }; + final CLOptionDescriptor[] options = { FILE }; + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + CLOption optionById = parser.getArgumentById(FILE_OPT); + assertNotNull(optionById); + assertEquals(FILE_OPT, optionById.getDescriptor().getId()); + assertEquals("testarg", optionById.getArgument()); + + CLOption optionByName = parser.getArgumentByName(FILE.getName()); + assertNotNull(optionByName); + assertEquals(FILE_OPT, optionByName.getDescriptor().getId()); + assertEquals("testarg", optionByName.getArgument()); + } + + /** + * Test that you can have null long forms. + */ + public void testNullLongForm() { + final CLOptionDescriptor test = new CLOptionDescriptor(null, CLOptionDescriptor.ARGUMENT_DISALLOWED, 'n', + "test null long form"); + + final String[] args = { "-n", "testarg" }; + final CLOptionDescriptor[] options = { test }; + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final CLOption optionByID = parser.getArgumentById('n'); + assertNotNull(optionByID); + assertEquals('n', optionByID.getDescriptor().getId()); + + final CLOption optionByName = parser.getArgumentByName(FILE.getName()); + assertNull("Looking for non-existent option by name", optionByName); + } + + /** + * Test that you can have null descriptions. + */ + public void testNullDescription() { + final CLOptionDescriptor test = new CLOptionDescriptor("nulltest", CLOptionDescriptor.ARGUMENT_DISALLOWED, 'n', + null); + + final String[] args = { "-n", "testarg" }; + final CLOptionDescriptor[] options = { test }; + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final CLOption optionByID = parser.getArgumentById('n'); + assertNotNull(optionByID); + assertEquals('n', optionByID.getDescriptor().getId()); + + final StringBuilder sb = CLUtil.describeOptions(options); + final String lineSeparator = System.getProperty("line.separator"); + assertEquals("Testing display of null description", "\t-n, --nulltest" + lineSeparator, sb.toString()); + } + + public void testCombinations() throws Exception { + check(new String [] {},""); + check(new String [] {"--none", + "-0" + }, + "-0 -0"); // Canonical form + check(new String [] {"--one=a", + "--one","A", + "-1b", + "-1=c", + "-1","d" + }, + "-1=[a] -1=[A] -1=[b] -1=[c] -1=[d]"); + check(new String [] {"-2n=v", + "-2","N=V" + }, + "-2=[n, v] -2=[N, V]"); + check(new String [] {"--two=n=v", + "--two","N=V" + }, + "-2=[n, v] -2=[N, V]"); + // Test optional arguments + check(new String [] {"-?", + "A", // Separate argument + "-?=B", + "-?C", + "-?" + }, + "-? [A] -?=[B] -?=[C] -?"); + check(new String [] {"--optional=A", // OK + "--optional","B", // should treat B as separate + "--optional" // Should have no arg + }, + "-?=[A] -? [B] -?"); + } + + private void check(String args[], String canon){ + final CLArgsParser parser = new CLArgsParser(args, OPTIONS); + + assertNull(parser.getErrorString(),parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + StringBuilder sb = new StringBuilder(); + for (int i=0; i< size; i++){ + if (i>0) { + sb.append(" "); + } + sb.append(clOptions.get(i).toShortString()); + } + assertEquals("Canonical form ("+size+")",canon,sb.toString()); + } + /* + * TODO add tests to check for: - name clash - long option abbreviations + * (match shortest unique abbreviation) + */ + +} diff --git a/test/src/org/apache/jmeter/JMeterVersionTest.java b/test/src/org/apache/jmeter/JMeterVersionTest.java new file mode 100644 index 00000000000..db0a1e8cf8f --- /dev/null +++ b/test/src/org/apache/jmeter/JMeterVersionTest.java @@ -0,0 +1,238 @@ +/* + * 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; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Check the eclipse and Maven version definitions against build.properties + */ +public class JMeterVersionTest extends JMeterTestCase { + + // Convert between eclipse jar name and build.properties name + private static Map JAR_TO_BUILD_PROP = new HashMap(); + static { + JAR_TO_BUILD_PROP.put("bsf", "apache-bsf"); + JAR_TO_BUILD_PROP.put("bsh", "beanshell"); + JAR_TO_BUILD_PROP.put("geronimo-jms_1.1_spec", "jms"); + JAR_TO_BUILD_PROP.put("htmllexer", "htmlparser"); // two jars same version + JAR_TO_BUILD_PROP.put("httpmime", "httpclient"); // two jars same version + JAR_TO_BUILD_PROP.put("mail", "javamail"); + JAR_TO_BUILD_PROP.put("oro", "jakarta-oro"); + JAR_TO_BUILD_PROP.put("xercesImpl", "xerces"); + JAR_TO_BUILD_PROP.put("xpp3_min", "xpp3"); + } + + private static final File JMETER_HOME = new File(JMeterUtils.getJMeterHome()); + + public JMeterVersionTest() { + super(); + } + + public JMeterVersionTest(String arg0) { + super(arg0); + } + + private final Map versions = new HashMap(); + private final Set propNames = new HashSet(); + + private File getFileFromHome(String relativeFile) { + return new File(JMETER_HOME, relativeFile); + } + + private Properties prop; + + @Override + protected void setUp() throws Exception { + final Properties buildProp = new Properties(); + final FileInputStream bp = new FileInputStream(getFileFromHome("build.properties")); + buildProp.load(bp); + bp.close(); + for (Entry entry : buildProp.entrySet()) { + final String key = (String) entry.getKey(); + if (key.endsWith(".version")) { + final String value = (String) entry.getValue(); + final String jarprop = key.replace(".version",""); + final String old = versions.put(jarprop, value); + propNames.add(jarprop); + if (old != null) { + fail("Already have entry for "+key); + } + } + } + // remove docs-only jars + propNames.remove("velocity"); + propNames.remove("commons-lang"); + prop = buildProp; + } + + /** + * Check eclipse.classpath contains the jars declared in build.properties + * @throws Exception if something fails + */ + public void testEclipse() throws Exception { + final BufferedReader eclipse = new BufferedReader( + new FileReader(getFileFromHome("eclipse.classpath"))); // assume default charset is OK here +// +// +// + final Pattern p = Pattern.compile("\\s+"); + final Pattern versionPat = Pattern.compile("\\$\\{(.+)\\.version\\}"); + String line; + final ArrayList toRemove = new ArrayList(); + while((line=eclipse.readLine()) != null){ + final Matcher m = p.matcher(line); + if (m.matches()) { + String jar = m.group(1); + String version = m.group(2); +// System.out.println(jar + " => " + version); + if (jar.endsWith("-jdk15on")) { // special handling + jar=jar.replace("-jdk15on",""); + } else if (jar.equals("commons-jexl") && version.startsWith("2")) { // special handling + jar="commons-jexl2"; + } else { + String tmp = JAR_TO_BUILD_PROP.get(jar); + if (tmp != null) { + jar = tmp; + } + } + String expected = versions.get(jar); + if(expected == null) { + System.err.println("Didn't find version for jar name extracted by regexp, jar name extracted:"+jar+", version extracted:"+version+", current line:"+line); + fail("Didn't find version for jar name extracted by regexp, jar name extracted:"+jar+", version extracted:"+version+", current line:"+line); + } + // Process ${xxx.version} references + final Matcher mp = versionPat.matcher(expected); + if (mp.matches()) { + String key = mp.group(1); + expected = versions.get(key); + toRemove.add(key); // in case it is not itself used we remove it later + } + propNames.remove(jar); + if (expected == null) { + fail("Versions list does not contain: " + jar); + } else { + if (!version.equals(expected)) { + assertEquals(jar,version,expected); + } + } + } + } + // remove any possibly unused references + for(Object key : toRemove.toArray()) { + propNames.remove(key); + } + eclipse.close(); + if (propNames.size() > 0) { + fail("Should have no names left: "+Arrays.toString(propNames.toArray()) + ". Check eclipse.classpath"); + } + } + + public void testMaven() throws Exception { + final BufferedReader maven = new BufferedReader( + new FileReader(getFileFromHome("res/maven/ApacheJMeter_parent.pom"))); // assume default charset is OK here +// 2.4.0 + final Pattern p = Pattern.compile("\\s+<([^\\.]+)\\.version>([^<]+)<.*"); + + String line; + while((line=maven.readLine()) != null){ + final Matcher m = p.matcher(line); + if (m.matches()) { + String jar = m.group(1); + String version = m.group(2); + String expected = versions.get(jar); + propNames.remove(jar); + if (expected == null) { + fail("Versions list does not contain: " + jar); + } else { + if (!version.equals(expected)) { + assertEquals(jar,expected,version); + } + } + } + } + maven.close(); + if (propNames.size() > 0) { + fail("Should have no names left: "+Arrays.toString(propNames.toArray()) + ". Check ApacheJMeter_parent.pom"); + } + } + + public void testLicences() { + Set liceNames = new HashSet(); + for (Map.Entry me : versions.entrySet()) { + final String key = me.getKey(); + liceNames.add(key+"-"+me.getValue()+".txt"); + if (key.equals("htmlparser")) { + liceNames.add("htmllexer"+"-"+me.getValue()+".txt"); + } + } + File licencesDir = getFileFromHome("licenses/bin"); + String [] lice = licencesDir.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return ! name.equalsIgnoreCase("README.txt") + && !name.equals(".svn"); // Allow for old-style SVN workspaces + } + }); + assertTrue("Expected at least one license file",lice.length > 0); + for(String l : lice) { + if (!liceNames.remove(l)) { + fail("Mismatched version in license file " + l); + } + } + } + + /** + * Check that all downloads use Maven Central + */ + public void testMavenDownload() { + int fails = 0; + for (Entry entry : prop.entrySet()) { + final String key = (String) entry.getKey(); + if (key.endsWith(".loc")) { + final String value = (String) entry.getValue(); + if (! value.startsWith("${maven2.repo}")) { + fails++; + System.err.println("ERROR: non-Maven download detected\n" + key + "=" +value); + } + } + } + if (fails > 0) { + // TODO replace with fail() + System.err.println("ERROR: All files must be available from Maven Central; but " + fails + " use(s) a different download source"); + } + } +} diff --git a/test/src/org/apache/jmeter/assertions/MD5HexAssertionTest.java b/test/src/org/apache/jmeter/assertions/MD5HexAssertionTest.java new file mode 100644 index 00000000000..00f48ccd638 --- /dev/null +++ b/test/src/org/apache/jmeter/assertions/MD5HexAssertionTest.java @@ -0,0 +1,36 @@ +/* + * 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.assertions; + +import junit.framework.TestCase; +public class MD5HexAssertionTest extends TestCase { + + public MD5HexAssertionTest() { + super(); + } + + public MD5HexAssertionTest(String arg0) { + super(arg0); + } + + public void testMD5() throws Exception { + assertEquals("D41D8CD98F00B204E9800998ECF8427E", MD5HexAssertion.baMD5Hex(new byte[] {}).toUpperCase(java.util.Locale.ENGLISH)); + } + +} diff --git a/test/src/org/apache/jmeter/assertions/ResponseAssertionTest.java b/test/src/org/apache/jmeter/assertions/ResponseAssertionTest.java new file mode 100644 index 00000000000..c6a080b74f2 --- /dev/null +++ b/test/src/org/apache/jmeter/assertions/ResponseAssertionTest.java @@ -0,0 +1,253 @@ +/* + * 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.assertions; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import junit.framework.TestCase; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class ResponseAssertionTest extends TestCase { + + public ResponseAssertionTest() { + } + + private ResponseAssertion assertion; + private SampleResult sample; + private AssertionResult result; + + @Override + public void setUp() throws MalformedURLException { + JMeterContext jmctx = JMeterContextService.getContext(); + assertion = new ResponseAssertion(); + assertion.setThreadContext(jmctx); + sample = new SampleResult(); + JMeterVariables vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(sample); + sample.setResponseData("response Data\nline 2\n\nEOF", null); + sample.setURL(new URL("http://localhost/Sampler/Data/")); + sample.setResponseCode("401"); + sample.setResponseHeaders("X-Header: abcd"); + } + + public void testResponseAssertionEquals() throws Exception{ + assertion.unsetNotType(); + assertion.setToEqualsType(); + assertion.setTestFieldURL(); + assertion.addTestString("Sampler Label"); + assertion.addTestString("Sampler labelx"); + result = assertion.getResult(sample); + assertFailed(); + + assertion.setToNotType(); + assertion.clearTestStrings(); + assertion.addTestString("Sampler LabeL"); + assertion.addTestString("Sampler Labelx"); + result = assertion.getResult(sample); + assertPassed(); + } + + public void testResponseAssertionHeaders() throws Exception{ + assertion.unsetNotType(); + assertion.setToEqualsType(); + assertion.setTestFieldResponseHeaders(); + assertion.addTestString("X-Header: abcd"); + assertion.addTestString("X-Header: abcdx"); + result = assertion.getResult(sample); + assertFailed(); + + assertion.clearTestStrings(); + assertion.addTestString("X-Header: abcd"); + result = assertion.getResult(sample); + assertPassed(); + } + + public void testResponseAssertionContains() throws Exception{ + assertion.unsetNotType(); + assertion.setToContainsType(); + assertion.setTestFieldURL(); + assertion.addTestString("Sampler"); + assertion.addTestString("Label"); + assertion.addTestString(" x"); + + result = assertion.getResult(sample); + assertFailed(); + + assertion.setToNotType(); + + result = assertion.getResult(sample); + assertFailed(); + + assertion.clearTestStrings(); + assertion.addTestString("r l"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.unsetNotType(); + assertion.setTestFieldResponseData(); + + assertion.clearTestStrings(); + assertion.addTestString("line 2"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.clearTestStrings(); + assertion.addTestString("(?s)line \\d+.*EOF"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.setTestFieldResponseCode(); + + assertion.clearTestStrings(); + assertion.addTestString("401"); + result = assertion.getResult(sample); + assertPassed(); + + } + + // Bug 46831 - check can match dollars + public void testResponseAssertionContainsDollar() throws Exception { + sample.setResponseData("value=\"${ID}\" Group$ctl00$drpEmails", null); + assertion.unsetNotType(); + assertion.setToContainsType(); + assertion.setTestFieldResponseData(); + assertion.addTestString("value=\"\\${ID}\" Group\\$ctl00\\$drpEmails"); + + result = assertion.getResult(sample); + assertPassed(); + } + + public void testResponseAssertionSubstring() throws Exception{ + assertion.unsetNotType(); + assertion.setToSubstringType(); + assertion.setTestFieldURL(); + assertion.addTestString("Sampler"); + assertion.addTestString("Label"); + assertion.addTestString("+("); + + result = assertion.getResult(sample); + assertFailed(); + + assertion.setToNotType(); + + result = assertion.getResult(sample); + assertFailed(); + + assertion.clearTestStrings(); + assertion.addTestString("r l"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.unsetNotType(); + assertion.setTestFieldResponseData(); + + assertion.clearTestStrings(); + assertion.addTestString("line 2"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.clearTestStrings(); + assertion.addTestString("line 2\n\nEOF"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.setTestFieldResponseCode(); + + assertion.clearTestStrings(); + assertion.addTestString("401"); + result = assertion.getResult(sample); + assertPassed(); + + } + +//TODO - need a lot more tests + + private void assertPassed() throws Exception{ + assertNull(result.getFailureMessage(),result.getFailureMessage()); + assertFalse("Not expecting error: "+result.getFailureMessage(),result.isError()); + assertFalse("Not expecting error",result.isError()); + assertFalse("Not expecting failure",result.isFailure()); + } + + private void assertFailed() throws Exception{ + assertNotNull(result.getFailureMessage()); + assertFalse("Should not be: Response was null","Response was null".equals(result.getFailureMessage())); + assertFalse("Not expecting error: "+result.getFailureMessage(),result.isError()); + assertTrue("Expecting failure",result.isFailure()); + + } + private AtomicInteger failed; + + public void testThreadSafety() throws Exception { + Thread[] threads = new Thread[100]; + CountDownLatch latch = new CountDownLatch(threads.length); + for (int i = 0; i < threads.length; i++) { + threads[i] = new TestThread(latch); + } + failed = new AtomicInteger(0); + for (int i = 0; i < threads.length; i++) { + threads[i].start(); + } + latch.await(); + assertEquals(failed.get(), 0); + } + + class TestThread extends Thread { + static final String TEST_STRING = "DAbale arroz a la zorra el abad."; + + // Used to be 'dábale', but caused trouble on Gump. Reasons + // unknown. + static final String TEST_PATTERN = ".*A.*\\."; + + private CountDownLatch latch; + + public TestThread(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void run() { + try { + ResponseAssertion assertion = new ResponseAssertion(); + assertion.setTestFieldResponseData(); + assertion.setToContainsType(); + assertion.addTestString(TEST_PATTERN); + SampleResult response = new SampleResult(); + response.setResponseData(TEST_STRING, null); + for (int i = 0; i < 100; i++) { + AssertionResult result; + result = assertion.getResult(response); + if (result.isFailure() || result.isError()) { + failed.incrementAndGet(); + } + } + } finally { + latch.countDown(); + } + } + } +} diff --git a/test/src/org/apache/jmeter/assertions/SizeAssertionTest.java b/test/src/org/apache/jmeter/assertions/SizeAssertionTest.java new file mode 100644 index 00000000000..a8b580bf69a --- /dev/null +++ b/test/src/org/apache/jmeter/assertions/SizeAssertionTest.java @@ -0,0 +1,168 @@ +/* + * 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.assertions; + +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; + +public class SizeAssertionTest extends JMeterTestCase{ + + private SizeAssertion assertion; + private SampleResult sample1,sample0; + private AssertionResult result; + private final String data1 = "response Data\n" + "line 2\n\nEOF"; + private final int data1Len=data1.length(); + + @Override + public void setUp() { + JMeterContext jmctx = JMeterContextService.getContext(); + assertion = new SizeAssertion(); + assertion.setThreadContext(jmctx); + assertion.setTestFieldResponseBody(); + JMeterVariables vars = new JMeterVariables(); + jmctx.setVariables(vars); + sample0 = new SampleResult(); + sample1 = new SampleResult(); + sample1.setResponseData(data1, null); + } + + public void testSizeAssertionEquals() throws Exception{ + assertion.setCompOper(SizeAssertion.EQUAL); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertPassed(); + + assertion.setAllowedSize(data1Len); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertFailed(); + } + + public void testSizeAssertionNotEquals() throws Exception{ + assertion.setCompOper(SizeAssertion.NOTEQUAL); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertFailed(); + + assertion.setAllowedSize(data1Len); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertPassed(); + } + + public void testSizeAssertionGreaterThan() throws Exception{ + assertion.setCompOper(SizeAssertion.GREATERTHAN); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertFailed(); + + assertion.setAllowedSize(data1Len); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertFailed(); + } + + public void testSizeAssertionGreaterThanEqual() throws Exception{ + assertion.setCompOper(SizeAssertion.GREATERTHANEQUAL); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertPassed(); + + assertion.setAllowedSize(data1Len); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertFailed(); + } + + public void testSizeAssertionLessThan() throws Exception{ + assertion.setCompOper(SizeAssertion.LESSTHAN); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertFailed(); + + assertion.setAllowedSize(data1Len+1); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertPassed(); + } + + public void testSizeAssertionLessThanEqual() throws Exception{ + assertion.setCompOper(SizeAssertion.LESSTHANEQUAL); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertPassed(); + + assertion.setAllowedSize(data1Len+1); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertPassed(); + } +// TODO - need a lot more tests + + private void assertPassed() throws Exception{ + // if (null != result.getFailureMessage()){ + //System.out.println(result.getFailureMessage());// debug + //} + assertNull("Failure message should be null",result.getFailureMessage()); + assertFalse(result.isError()); + assertFalse(result.isFailure()); + } + + private void assertFailed() throws Exception{ + assertNotNull("Failure nessage should not be null",result.getFailureMessage()); + //System.out.println(result.getFailureMessage()); + assertFalse("Should not be: Response was null","Response was null".equals(result.getFailureMessage())); + assertFalse(result.isError()); + assertTrue(result.isFailure()); + + } +} diff --git a/test/src/org/apache/jmeter/assertions/XMLSchemaAssertionTest.java b/test/src/org/apache/jmeter/assertions/XMLSchemaAssertionTest.java new file mode 100644 index 00000000000..0b80b36ff45 --- /dev/null +++ b/test/src/org/apache/jmeter/assertions/XMLSchemaAssertionTest.java @@ -0,0 +1,175 @@ +/* + * 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.assertions; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; + +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.jorphan.logging.LoggingManager; + +public class XMLSchemaAssertionTest extends JMeterTestCase { + + private XMLSchemaAssertion assertion; + + private SampleResult result; + + private JMeterContext jmctx; + + public XMLSchemaAssertionTest(String arg0) { + super(arg0); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + jmctx = JMeterContextService.getContext(); + assertion = new XMLSchemaAssertion(); + assertion.setThreadContext(jmctx);// This would be done by the run + // command + result = new SampleResult(); + JMeterVariables vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + // LoggingManager.setPriority("DEBUG","jmeter"); + } + + private ByteArrayOutputStream readBA(String name) throws IOException { + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(findTestFile(name))); + ByteArrayOutputStream baos = new ByteArrayOutputStream(1000); + int len = 0; + byte[] data = new byte[512]; + while ((len = bis.read(data)) >= 0) { + baos.write(data, 0, len); + } + bis.close(); + return baos; + } + + private byte[] readFile(String name) throws IOException { + return readBA(name).toByteArray(); + } + + public void testAssertionOK() throws Exception { + result.setResponseData(readFile("testfiles/XMLSchematest.xml")); + assertion.setXsdFileName(findTestPath("testfiles/XMLSchema-pass.xsd")); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure", res.isFailure()); + } + + public void testAssertionFail() throws Exception { + result.setResponseData(readFile("testfiles/XMLSchematest.xml")); + assertion.setXsdFileName("testfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testAssertionBadXSDFile() throws Exception { + result.setResponseData(readFile("testfiles/XMLSchematest.xml")); + assertion.setXsdFileName("xtestfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertTrue(res.getFailureMessage().indexOf("Failed to read schema document") > 0); + assertTrue(res.isError());// TODO - should this be a failure? + assertFalse(res.isFailure()); + } + + public void testAssertionNoFile() throws Exception { + result.setResponseData(readFile("testfiles/XMLSchematest.xml")); + assertion.setXsdFileName(""); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertEquals(XMLSchemaAssertion.FILE_NAME_IS_REQUIRED, res.getFailureMessage()); + assertFalse(res.isError()); + assertTrue(res.isFailure()); + } + + public void testAssertionNoResult() throws Exception { + // result.setResponseData - not set + assertion.setXsdFileName("testfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertEquals(AssertionResult.RESPONSE_WAS_NULL, res.getFailureMessage()); + assertFalse(res.isError()); + assertTrue(res.isFailure()); + } + + public void testAssertionEmptyResult() throws Exception { + result.setResponseData("", null); + assertion.setXsdFileName("testfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertEquals(AssertionResult.RESPONSE_WAS_NULL, res.getFailureMessage()); + assertFalse(res.isError()); + assertTrue(res.isFailure()); + } + + public void testAssertionBlankResult() throws Exception { + result.setResponseData(" ", null); + assertion.setXsdFileName("testfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertTrue(res.getFailureMessage().indexOf("Premature end of file") > 0); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testXMLTrailingcontent() throws Exception { + ByteArrayOutputStream baos = readBA("testfiles/XMLSchematest.xml"); + baos.write("extra".getBytes()); // TODO - charset? + result.setResponseData(baos.toByteArray()); + assertion.setXsdFileName(findTestPath("testfiles/XMLSchema-pass.xsd")); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertTrue(res.getFailureMessage().indexOf("Content is not allowed in trailing section") > 0); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testXMLTrailingwhitespace() throws Exception { + ByteArrayOutputStream baos = readBA("testfiles/XMLSchematest.xml"); + baos.write(" \t\n".getBytes()); // TODO - charset? + result.setResponseData(baos.toByteArray()); + assertion.setXsdFileName(findTestPath("testfiles/XMLSchema-pass.xsd")); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("xisError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertFalse(res.isError()); + assertFalse(res.isFailure()); + } +} diff --git a/test/src/org/apache/jmeter/assertions/XPathAssertionTest.java b/test/src/org/apache/jmeter/assertions/XPathAssertionTest.java new file mode 100644 index 00000000000..2d43b95bef4 --- /dev/null +++ b/test/src/org/apache/jmeter/assertions/XPathAssertionTest.java @@ -0,0 +1,322 @@ +/* + * 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.assertions; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; + +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.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class XPathAssertionTest extends JMeterTestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private XPathAssertion assertion; + + private SampleResult result; + + private JMeterVariables vars; + + private JMeterContext jmctx; + + public XPathAssertionTest(String arg0) { + super(arg0); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + jmctx = JMeterContextService.getContext(); + assertion = new XPathAssertion(); + assertion.setThreadContext(jmctx);// This would be done by the run command + result = new SampleResult(); + result.setResponseData(readFile("testfiles/XPathAssertionTest.xml")); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + //testLog.setPriority(org.apache.log.Priority.DEBUG); + } + + private void setAlternateResponseData(){ + String data = "" + "" + "LIS_OK" + + "" + "" + + "" + "0" + + "1" + "" + + "5" + "" + + "6" + "" + + "" + ""; + result.setResponseData(data, null); + } + + private ByteArrayOutputStream readBA(String name) throws IOException { + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(findTestFile(name))); + ByteArrayOutputStream baos = new ByteArrayOutputStream(1000); + int len = 0; + byte[] data = new byte[512]; + while ((len = bis.read(data)) >= 0) { + baos.write(data, 0, len); + } + bis.close(); + return baos; + } + + private byte[] readFile(String name) throws IOException { + return readBA(name).toByteArray(); + } + + public void testAssertionOK() throws Exception { + assertion.setXPathString("/"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure", res.isFailure()); + } + + public void testAssertionFail() throws Exception { + assertion.setXPathString("//x"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionPath1() throws Exception { + assertion.setXPathString("//*[code=1]"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure",res.isFailure()); + } + + public void testAssertionPath2() throws Exception { + assertion.setXPathString("//*[code=2]"); // Not present + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionBool1() throws Exception { + assertion.setXPathString("count(//error)=2"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure",res.isFailure()); + } + + public void testAssertionBool2() throws Exception { + assertion.setXPathString("count(//*[code=1])=1"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure",res.isFailure()); + } + + public void testAssertionBool3() throws Exception { + assertion.setXPathString("count(//error)=1"); // wrong + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionBool4() throws Exception { + assertion.setXPathString("count(//*[code=2])=1"); //Wrong + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionNumber() throws Exception { + assertion.setXPathString("count(//error)");// not yet handled + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionNoResult() throws Exception { + // result.setResponseData - not set + result = new SampleResult(); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertEquals(AssertionResult.RESPONSE_WAS_NULL, res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionEmptyResult() throws Exception { + result.setResponseData("", null); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertEquals(AssertionResult.RESPONSE_WAS_NULL, res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionBlankResult() throws Exception { + result.setResponseData(" ", null); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertTrue(res.getFailureMessage().indexOf("Premature end of file") > 0); + assertTrue("Should be an error",res.isError()); + assertFalse("Should not be a failure", res.isFailure()); + } + + public void testNoTolerance() throws Exception { + String data = "testtitle" + "" + + "

invalid tag nesting


" + ""; + + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + assertion.setXPathString("/html/head/title"); + assertion.setValidating(false); + assertion.setTolerant(false); + AssertionResult res = assertion.getResult(result); + log.debug("failureMessage: " + res.getFailureMessage()); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testAssertion() throws Exception { + setAlternateResponseData(); + assertion.setXPathString("//row/value[@field = 'alias']"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + log.debug(" res " + res.isError()); + log.debug(" failure " + res.getFailureMessage()); + assertFalse(res.isError()); + assertFalse(res.isFailure()); + } + + public void testNegateAssertion() throws Exception { + setAlternateResponseData(); + assertion.setXPathString("//row/value[@field = 'noalias']"); + assertion.setNegated(true); + + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + log.debug(" res " + res.isError()); + log.debug(" failure " + res.getFailureMessage()); + assertFalse(res.isError()); + assertFalse(res.isFailure()); + } + + public void testValidationFailure() throws Exception { + setAlternateResponseData(); + assertion.setXPathString("//row/value[@field = 'alias']"); + assertion.setNegated(false); + assertion.setValidating(true); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + log.debug(res.getFailureMessage() + " error: " + res.isError() + " failure: " + res.isFailure()); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testValidationSuccess() throws Exception { + String data = "" + "" + + "" + + "" + "" + + "" + "" + + "" + "" + + "" + "" + + "" + "" + "]>" + "" + + "" + "All About Me" + "" + "" + + "
Welcome To My Book
" + "" + + "CHAPTER 1" + "" + + "

Glad you want to hear about me.

" + "

There's so much to say!

" + + "

Where should we start?

" + "

How about more about me?

" + "
" + + "
" + "
" + "
"; + + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + assertion.setXPathString("/"); + assertion.setValidating(true); + AssertionResult res = assertion.getResult(result); + assertFalse(res.isError()); + assertFalse(res.isFailure()); + } + + public void testValidationFailureWithDTD() throws Exception { + String data = "" + "" + + "" + + "" + "" + + "" + "" + + "" + "" + + "" + "" + + "" + "" + "]>" + "" + + "" + "All About Me" + "" + "" + + "
Welcome To My Book
" + "" + + "CHAPTER 1" + "" + + "

Glad you want to hear about me.

" + "

There's so much to say!

" + + "

Where should we start?

" + "

How about more about me?

" + "
" + + "
" + "not defined in dtd" + "
" + "
"; + + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + assertion.setXPathString("/"); + assertion.setValidating(true); + AssertionResult res = assertion.getResult(result); + log.debug("failureMessage: " + res.getFailureMessage()); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testTolerance() throws Exception { + String data = "testtitle" + "" + + "

invalid tag nesting


" + ""; + + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + assertion.setXPathString("/html/head/title"); + assertion.setValidating(true); + assertion.setTolerant(true); + AssertionResult res = assertion.getResult(result); + log.debug("failureMessage: " + res.getFailureMessage()); + assertFalse(res.isFailure()); + assertFalse(res.isError()); + } + +} diff --git a/test/src/org/apache/jmeter/config/TestCVSDataSet.java b/test/src/org/apache/jmeter/config/TestCVSDataSet.java new file mode 100644 index 00000000000..64313dc05ff --- /dev/null +++ b/test/src/org/apache/jmeter/config/TestCVSDataSet.java @@ -0,0 +1,235 @@ +/* + * 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 to test FileServer methods + */ + +package org.apache.jmeter.config; + +import java.io.IOException; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.util.JMeterStopThreadException; + +public class TestCVSDataSet extends JMeterTestCase { + + private JMeterVariables threadVars; + + public TestCVSDataSet(String arg0) { + super(arg0); + } + + @Override + public void setUp(){ + JMeterContext jmcx = JMeterContextService.getContext(); + jmcx.setVariables(new JMeterVariables()); + threadVars = jmcx.getVariables(); + threadVars.put("b", "value"); + } + + @Override + public void tearDown() throws IOException{ + FileServer.getFileServer().closeFiles(); + } + + public void testopen() throws Exception { + CSVDataSet csv = new CSVDataSet(); + csv.setFilename("No.such.filename"); + csv.setVariableNames("a,b,c"); + csv.setDelimiter(","); + csv.iterationStart(null); + assertEquals("",threadVars.get("a")); + assertEquals("",threadVars.get("b")); + assertEquals("",threadVars.get("c")); + + csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/testempty.csv")); + csv.setVariableNames("a,b,c"); + csv.setDelimiter(","); + + csv.iterationStart(null); + assertEquals("",threadVars.get("a")); + assertEquals("b1",threadVars.get("b")); + assertEquals("c1",threadVars.get("c")); + + csv.iterationStart(null); + assertEquals("a2",threadVars.get("a")); + assertEquals("",threadVars.get("b")); + assertEquals("c2",threadVars.get("c")); + + csv.iterationStart(null); + assertEquals("a3",threadVars.get("a")); + assertEquals("b3",threadVars.get("b")); + assertEquals("",threadVars.get("c")); + + + csv.iterationStart(null); + assertEquals("a4",threadVars.get("a")); + assertEquals("b4",threadVars.get("b")); + assertEquals("c4",threadVars.get("c")); + + csv.iterationStart(null); // Restart file + assertEquals("",threadVars.get("a")); + assertEquals("b1",threadVars.get("b")); + assertEquals("c1",threadVars.get("c")); + } + + public void testutf8() throws Exception { + + CSVDataSet csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/testutf8.csv")); + csv.setVariableNames("a,b,c,d"); + csv.setDelimiter(","); + csv.setQuotedData( true ); + csv.setFileEncoding( "UTF-8" ); + + csv.iterationStart(null); + assertEquals("a1",threadVars.get("a")); + assertEquals("b1",threadVars.get("b")); + assertEquals("\u00e71",threadVars.get("c")); + assertEquals("d1",threadVars.get("d")); + + csv.iterationStart(null); + assertEquals("a2",threadVars.get("a")); + assertEquals("b2",threadVars.get("b")); + assertEquals("\u00e72",threadVars.get("c")); + assertEquals("d2",threadVars.get("d")); + + csv.iterationStart(null); + assertEquals("a3",threadVars.get("a")); + assertEquals("b3",threadVars.get("b")); + assertEquals("\u00e73",threadVars.get("c")); + assertEquals("d3",threadVars.get("d")); + + csv.iterationStart(null); + assertEquals("a4",threadVars.get("a")); + assertEquals("b4",threadVars.get("b")); + assertEquals("\u00e74",threadVars.get("c")); + assertEquals("d4",threadVars.get("d")); + } + + // Test CSV file with a header line + public void testHeaderOpen(){ + CSVDataSet csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/testheader.csv")); + csv.setDelimiter("|"); + assertNull(csv.getVariableNames()); + csv.iterationStart(null); + assertNull(threadVars.get("a")); + assertEquals("a1",threadVars.get("A")); + assertEquals("b1",threadVars.get("B")); + assertEquals("c1",threadVars.get("C")); + assertEquals("d1",threadVars.get("D|1")); + csv.iterationStart(null); + assertNull(threadVars.get("a")); + assertEquals("a2",threadVars.get("A")); + assertEquals("b2",threadVars.get("B")); + assertEquals("c2",threadVars.get("C")); + assertEquals("d2",threadVars.get("D|1")); + } + + // Test CSV file with a header line and recycle is true + public void testHeaderOpenAndRecycle(){ + CSVDataSet csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/testheader.csv")); + csv.setDelimiter("|"); + csv.setRecycle(true); + assertNull(csv.getVariableNames()); // read 1st line + // read 5 lines + restart to file begin + csv.iterationStart(null); // line 2 + csv.iterationStart(null); // line 3 + csv.iterationStart(null); // line 4 + csv.iterationStart(null); // line 5 + csv.iterationStart(null); // return to 2nd line (first line is names) + assertEquals("a1",threadVars.get("A")); + assertEquals("b1",threadVars.get("B")); + assertEquals("c1",threadVars.get("C")); + assertEquals("d1",threadVars.get("D|1")); + } + + // Test CSV file with a header line + public void testHeaderQuotes(){ + CSVDataSet csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/testquoted.csv")); + csv.setDelimiter("|"); + csv.setQuotedData(true); + csv.setRecycle(false); + csv.setStopThread(true); + assertNull(csv.getVariableNames()); + csv.iterationStart(null); + assertNull(threadVars.get("a")); + assertEquals("a1",threadVars.get("A")); + assertEquals("b1",threadVars.get("B")); + assertEquals("c1",threadVars.get("C")); + assertEquals("d1",threadVars.get("D|1")); + csv.iterationStart(null); + assertNull(threadVars.get("a")); + assertEquals("a2",threadVars.get("A")); + assertEquals("b2",threadVars.get("B")); + assertEquals("c2",threadVars.get("C")); + assertEquals("d2",threadVars.get("D|1")); + csv.iterationStart(null); + assertNull(threadVars.get("a")); + assertEquals("a3",threadVars.get("A")); + assertEquals("b3",threadVars.get("B")); + assertEquals("c3",threadVars.get("C")); + assertEquals("d3",threadVars.get("D|1")); + try { + csv.iterationStart(null); + fail("Expected JMeterStopThreadException"); + } catch (JMeterStopThreadException expected) { + + } + } + + private CSVDataSet initCSV(){ + CSVDataSet csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/test.csv")); + csv.setVariableNames("a,b,c"); + csv.setDelimiter(","); + return csv; + } + + public void testShareMode(){ + + new CSVDataSetBeanInfo(); // needs to be initialised + CSVDataSet csv0 = initCSV(); + CSVDataSet csv1 = initCSV(); + assertNull(csv1.getShareMode()); + csv1.setShareMode("abc"); + assertEquals("abc",csv1.getShareMode()); + csv1.iterationStart(null); + assertEquals("a1",threadVars.get("a")); + csv1.iterationStart(null); + assertEquals("a2",threadVars.get("a")); + CSVDataSet csv2 = initCSV(); + csv2.setShareMode("abc"); + assertEquals("abc",csv2.getShareMode()); + csv2.iterationStart(null); + assertEquals("a3",threadVars.get("a")); + csv0.iterationStart(null); + assertEquals("a1",threadVars.get("a")); + csv1.iterationStart(null); + assertEquals("a4",threadVars.get("a")); + } +} diff --git a/test/src/org/apache/jmeter/config/gui/TestArgumentsPanel.java b/test/src/org/apache/jmeter/config/gui/TestArgumentsPanel.java new file mode 100644 index 00000000000..5ffabd33bfe --- /dev/null +++ b/test/src/org/apache/jmeter/config/gui/TestArgumentsPanel.java @@ -0,0 +1,60 @@ +/* + * 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.config.gui; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; + +/** + * A GUI panel allowing the user to enter name-value argument pairs. These + * arguments (or parameters) are usually used to provide configuration values + * for some other component. + * + */ +public class TestArgumentsPanel extends TestCase { + /** + * Create a new test. + * + * @param name + * the name of the test + */ + public TestArgumentsPanel(String name) { + super(name); + } + + /** + * Test that adding an argument to the table results in an appropriate + * TestElement being created. + * + * @throws Exception + * if an exception occurred during the test + */ + public void testArgumentCreation() throws Exception { + ArgumentsPanel gui = new ArgumentsPanel(); + gui.tableModel.addRow(new Argument()); + gui.tableModel.setValueAt("howdy", 0, 0); + gui.tableModel.addRow(new Argument()); + gui.tableModel.setValueAt("doody", 0, 1); + + assertEquals("=", ((Argument) ((Arguments) gui.createTestElement()).getArguments().get(0).getObjectValue()) + .getMetaData()); + } +} diff --git a/test/src/org/apache/jmeter/control/TestGenericController.java b/test/src/org/apache/jmeter/control/TestGenericController.java new file mode 100644 index 00000000000..9dbd67f1997 --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestGenericController.java @@ -0,0 +1,58 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +public class TestGenericController extends JMeterTestCase { + public TestGenericController(String name) { + super(name); + } + + public void testProcessing() throws Exception { + testLog.debug("Testing Generic Controller"); + GenericController controller = new GenericController(); + GenericController sub_1 = new GenericController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + GenericController sub_2 = new GenericController(); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] order = new String[] { "one", "two", "three", "four", "five", "six", "seven" }; + int counter = 7; + controller.initialize(); + for (int i = 0; i < 2; i++) { + assertEquals(7, counter); + counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals(order[counter++], sampler.getName()); + } + } + } +} diff --git a/test/src/org/apache/jmeter/control/TestIfController.java b/test/src/org/apache/jmeter/control/TestIfController.java new file mode 100644 index 00000000000..766a89beba6 --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestIfController.java @@ -0,0 +1,293 @@ +/* + * 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.control; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.modifiers.CounterConfig; +import org.apache.jmeter.sampler.DebugSampler; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestIfController extends JMeterTestCase { + public TestIfController(String name) { + super(name); + } + + /** + * See Bug 56160 + * @throws Exception if something fails + */ + public void testStackOverflow() throws Exception { + LoopController controller = new LoopController(); + controller.setLoops(1); + controller.setContinueForever(false); + + IfController ifCont = new IfController("true==false"); + ifCont.setUseExpression(false); + ifCont.setEvaluateAll(false); + WhileController whileController = new WhileController(); + whileController.setCondition("${__javaScript(\"true\" != \"false\")}"); + whileController.addTestElement(new TestSampler("Sample1")); + + + controller.addTestElement(ifCont); + ifCont.addTestElement(whileController); + + Sampler sampler = null; + int counter = 0; + controller.initialize(); + controller.setRunningVersion(true); + ifCont.setRunningVersion(true); + whileController.setRunningVersion(true); + + try { + while ((sampler = controller.next()) != null) { + sampler.sample(null); + counter++; + } + assertEquals(0, counter); + } catch(StackOverflowError e) { + fail("Stackoverflow occured in testStackOverflow"); + } + } + + /** + * See Bug 53768 + * @throws Exception if something fails + */ + public void testBug53768() throws Exception { + LoopController controller = new LoopController(); + controller.setLoops(1); + controller.setContinueForever(false); + + Arguments arguments = new Arguments(); + arguments.addArgument("VAR1", "0", "="); + + DebugSampler debugSampler1 = new DebugSampler(); + debugSampler1.setName("VAR1 = ${VAR1}"); + + IfController ifCont = new IfController("true==false"); + ifCont.setUseExpression(false); + ifCont.setEvaluateAll(false); + + IfController ifCont2 = new IfController("true==true"); + ifCont2.setUseExpression(false); + ifCont2.setEvaluateAll(false); + + CounterConfig counterConfig = new CounterConfig(); + counterConfig.setStart(1); + counterConfig.setIncrement(1); + counterConfig.setVarName("VAR1"); + + DebugSampler debugSampler2 = new DebugSampler(); + debugSampler2.setName("VAR1 = ${VAR1}"); + + controller.addTestElement(arguments); + controller.addTestElement(debugSampler1); + controller.addTestElement(ifCont); + ifCont.addTestElement(ifCont2); + ifCont2.addTestElement(counterConfig); + controller.addTestElement(debugSampler2); + + + + controller.initialize(); + controller.setRunningVersion(true); + ifCont.setRunningVersion(true); + ifCont2.setRunningVersion(true); + counterConfig.setRunningVersion(true); + arguments.setRunningVersion(true); + debugSampler1.setRunningVersion(true); + debugSampler2.setRunningVersion(true); + ifCont2.addIterationListener(counterConfig); + JMeterVariables vars = new JMeterVariables(); + JMeterContext jmctx = JMeterContextService.getContext(); + + jmctx.setVariables(vars); + vars.put("VAR1", "0"); + try { + + Sampler sampler = controller.next(); + SampleResult sampleResult1 = sampler.sample(null); + assertEquals("0", vars.get("VAR1")); + sampler = controller.next(); + SampleResult sampleResult2 = sampler.sample(null); + assertEquals("0", vars.get("VAR1")); + + + } catch(StackOverflowError e) { + fail("Stackoverflow occured in testStackOverflow"); + } + } + + public void testProcessing() throws Exception { + + GenericController controller = new GenericController(); + + controller.addTestElement(new IfController("false==false")); + controller.addTestElement(new IfController(" \"a\".equals(\"a\")")); + controller.addTestElement(new IfController("2<100")); + + //TODO enable some proper tests!! + + /* + * GenericController sub_1 = new GenericController(); + * sub_1.addTestElement(new IfController("3==3")); + * controller.addTestElement(sub_1); controller.addTestElement(new + * IfController("false==true")); + */ + + /* + * GenericController controller = new GenericController(); + * GenericController sub_1 = new GenericController(); + * sub_1.addTestElement(new IfController("10<100")); + * sub_1.addTestElement(new IfController("true==false")); + * controller.addTestElement(sub_1); controller.addTestElement(new + * IfController("false==false")); + * + * IfController sub_2 = new IfController(); sub_2.setCondition( "10<10000"); + * GenericController sub_3 = new GenericController(); + * + * sub_2.addTestElement(new IfController( " \"a\".equals(\"a\")" ) ); + * sub_3.addTestElement(new IfController("2>100")); + * sub_3.addTestElement(new IfController("false==true")); + * sub_2.addTestElement(sub_3); sub_2.addTestElement(new + * IfController("2==3")); controller.addTestElement(sub_2); + */ + + /* + * IfController controller = new IfController("12==12"); + * controller.initialize(); + */ +// TestElement sampler = null; +// while ((sampler = controller.next()) != null) { +// logger.debug(" ->>> Gonna assertTrue :" + sampler.getClass().getName() + " Property is ---->>>" +// + sampler.getName()); +// } + } + + public void testProcessingTrue() throws Exception { + LoopController controller = new LoopController(); + controller.setLoops(2); + controller.addTestElement(new TestSampler("Sample1")); + IfController ifCont = new IfController("true==true"); + ifCont.setEvaluateAll(true); + ifCont.addTestElement(new TestSampler("Sample2")); + TestSampler sample3 = new TestSampler("Sample3"); + ifCont.addTestElement(sample3); + controller.addTestElement(ifCont); + + String[] order = new String[] { "Sample1", "Sample2", "Sample3", + "Sample1", "Sample2", "Sample3" }; + int counter = 0; + controller.initialize(); + controller.setRunningVersion(true); + ifCont.setRunningVersion(true); + + Sampler sampler = null; + while ((sampler = controller.next()) != null) { + sampler.sample(null); + assertEquals(order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, 6); + } + + /** + * Test false return on sample3 (sample4 doesn't execute) + * @throws Exception if something fails + */ + public void testEvaluateAllChildrenWithoutSubController() throws Exception { + LoopController controller = new LoopController(); + controller.setLoops(2); + controller.addTestElement(new TestSampler("Sample1")); + IfController ifCont = new IfController("true==true"); + ifCont.setEvaluateAll(true); + controller.addTestElement(ifCont); + + ifCont.addTestElement(new TestSampler("Sample2")); + TestSampler sample3 = new TestSampler("Sample3"); + ifCont.addTestElement(sample3); + TestSampler sample4 = new TestSampler("Sample4"); + ifCont.addTestElement(sample4); + + String[] order = new String[] { "Sample1", "Sample2", "Sample3", + "Sample1", "Sample2", "Sample3" }; + int counter = 0; + controller.initialize(); + controller.setRunningVersion(true); + ifCont.setRunningVersion(true); + + Sampler sampler = null; + while ((sampler = controller.next()) != null) { + sampler.sample(null); + if (sampler.getName().equals("Sample3")) { + ifCont.setCondition("true==false"); + } + assertEquals(order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, 6); + } + + /** + * test 2 loops with a sub generic controller (sample4 doesn't execute) + * @throws Exception if something fails + */ + public void testEvaluateAllChildrenWithSubController() throws Exception { + LoopController controller = new LoopController(); + controller.setLoops(2); + controller.addTestElement(new TestSampler("Sample1")); + IfController ifCont = new IfController("true==true"); + ifCont.setEvaluateAll(true); + controller.addTestElement(ifCont); + ifCont.addTestElement(new TestSampler("Sample2")); + + GenericController genericCont = new GenericController(); + TestSampler sample3 = new TestSampler("Sample3"); + genericCont.addTestElement(sample3); + TestSampler sample4 = new TestSampler("Sample4"); + genericCont.addTestElement(sample4); + ifCont.addTestElement(genericCont); + + String[] order = new String[] { "Sample1", "Sample2", "Sample3", + "Sample1", "Sample2", "Sample3" }; + int counter = 0; + controller.initialize(); + controller.setRunningVersion(true); + ifCont.setRunningVersion(true); + genericCont.setRunningVersion(true); + + Sampler sampler = null; + while ((sampler = controller.next()) != null) { + sampler.sample(null); + if (sampler.getName().equals("Sample3")) { + ifCont.setCondition("true==false"); + } + assertEquals(order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, 6); + } +} diff --git a/test/src/org/apache/jmeter/control/TestInterleaveControl.java b/test/src/org/apache/jmeter/control/TestInterleaveControl.java new file mode 100644 index 00000000000..6fbcc5783a4 --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestInterleaveControl.java @@ -0,0 +1,227 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +public class TestInterleaveControl extends JMeterTestCase { + public TestInterleaveControl(String name) { + super(name); + } + + public void testProcessing() throws Exception { + testLog.debug("Testing Interleave Controller 1"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] interleaveOrder = new String[] { "one", "two" }; + String[] order = new String[] { "dummy", "three", "four", "five", "six", "seven", "four", "five", "six", + "seven", "four", "five", "six", "seven" }; + int counter = 14; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 4; i++) { + assertEquals(14, counter); + counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + if (counter == 0) { + assertEquals(interleaveOrder[i % 2], sampler.getName()); + } else { + assertEquals(order[counter], sampler.getName()); + } + counter++; + } + } + } + + public void testProcessing6() throws Exception { + testLog.debug("Testing Interleave Controller 6"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + controller.addTestElement(new TestSampler("one")); + sub_1.setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + controller.addTestElement(sub_1); + LoopController sub_2 = new LoopController(); + sub_1.addTestElement(sub_2); + sub_2.setLoops(3); + int counter = 1; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 4; i++) { + assertEquals(1, counter); + counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("one", sampler.getName()); + counter++; + } + } + } + + public void testProcessing2() throws Exception { + testLog.debug("Testing Interleave Controller 2"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + sub_1.addTestElement(sub_2); + String[] order = new String[] { "one", "three", "two", "three", "four", "three", "one", "three", "two", + "three", "five", "three", "one", "three", "two", "three", "six", "three", "one", "three" }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + while (counter < order.length) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("failed on " + counter, order[counter], sampler.getName()); + counter++; + } + } + } + + public void testProcessing3() throws Exception { + testLog.debug("Testing Interleave Controller 3"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + sub_1.addTestElement(sub_2); + String[] order = new String[] { "one", "three", "two", "three", "four", "five", "six", "seven", "four", + "five", "six", "seven", "four", "five", "six", "seven", "three", "one", "three", "two", "three" }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + while (counter < order.length) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("failed on" + counter, order[counter], sampler.getName()); + counter++; + } + } + } + + public void testProcessing4() throws Exception { + testLog.debug("Testing Interleave Controller 4"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + controller.addTestElement(sub_1); + GenericController sub_2 = new GenericController(); + sub_2.addTestElement(new TestSampler("one")); + sub_2.addTestElement(new TestSampler("two")); + sub_1.addTestElement(sub_2); + GenericController sub_3 = new GenericController(); + sub_3.addTestElement(new TestSampler("three")); + sub_3.addTestElement(new TestSampler("four")); + sub_1.addTestElement(sub_3); + String[] order = new String[] { "one", "three", "two", "four" }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + while (counter < order.length) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("failed on" + counter, order[counter], sampler.getName()); + counter++; + } + } + } + + public void testProcessing5() throws Exception { + testLog.debug("Testing Interleave Controller 5"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + controller.addTestElement(sub_1); + GenericController sub_2 = new GenericController(); + sub_2.addTestElement(new TestSampler("one")); + sub_2.addTestElement(new TestSampler("two")); + sub_1.addTestElement(sub_2); + GenericController sub_3 = new GenericController(); + sub_3.addTestElement(new TestSampler("three")); + sub_3.addTestElement(new TestSampler("four")); + sub_1.addTestElement(sub_3); + String[] order = new String[] { "one", "two", "three", "four" }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + while (counter < order.length) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("failed on" + counter, order[counter], sampler.getName()); + counter++; + } + } + } +} diff --git a/test/src/org/apache/jmeter/control/TestLoopController.java b/test/src/org/apache/jmeter/control/TestLoopController.java new file mode 100644 index 00000000000..d995643bfef --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestLoopController.java @@ -0,0 +1,116 @@ +/* + * 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.control; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.engine.util.ReplaceStringWithFunctions; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestLoopController extends JMeterTestCase { + public TestLoopController(String name) { + super(name); + } + + public void testProcessing() throws Exception { + GenericController controller = new GenericController(); + GenericController sub_1 = new GenericController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] order = new String[] { "one", "two", "three", "four", "five", "six", "seven", "four", "five", + "six", "seven", "four", "five", "six", "seven" }; + int counter = 15; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 2; i++) { + assertEquals(15, counter); + counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals(order[counter++], sampler.getName()); + } + } + } + + public void testLoopZeroTimes() throws Exception { + LoopController loop = new LoopController(); + loop.setLoops(0); + loop.addTestElement(new TestSampler("never run")); + loop.initialize(); + assertNull(loop.next()); + } + + public void testInfiniteLoop() throws Exception { + LoopController loop = new LoopController(); + loop.setLoops(LoopController.INFINITE_LOOP_COUNT); + loop.addTestElement(new TestSampler("never run")); + loop.setRunningVersion(true); + loop.initialize(); + for (int i = 0; i < 42; i++) { + assertNotNull(loop.next()); + } + } + + public void testBug54467() throws Exception { + JMeterContext jmctx = JMeterContextService.getContext(); + LoopController loop = new LoopController(); + Map variables = new HashMap(); + ReplaceStringWithFunctions transformer = new ReplaceStringWithFunctions(new CompoundVariable(), variables); + jmctx.setVariables(new JMeterVariables()); + + StringProperty prop = new StringProperty(LoopController.LOOPS,"${__Random(1,12,)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + + loop.setProperty(newProp); + loop.addTestElement(new TestSampler("random run")); + loop.setRunningVersion(true); + loop.initialize(); + int loops = loop.getLoops(); + for (int i = 0; i < loops; i++) { + Sampler s = loop.next(); + assertNotNull(s); + } + assertNull(loop.next()); + } +} diff --git a/test/src/org/apache/jmeter/control/TestOnceOnlyController.java b/test/src/org/apache/jmeter/control/TestOnceOnlyController.java new file mode 100644 index 00000000000..5a88f216825 --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestOnceOnlyController.java @@ -0,0 +1,327 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +public class TestOnceOnlyController extends JMeterTestCase { + public TestOnceOnlyController(String name) { + super(name); + } + + public void testProcessing() throws Exception { + GenericController controller = new GenericController(); + GenericController sub_1 = new OnceOnlyController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] interleaveOrder = new String[] { "one", "two" }; + String[] order = new String[] { "", "", "three", "four", "five", "six", "seven", "four", "five", "six", + "seven", "four", "five", "six", "seven" }; + int counter = 15; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 4; i++) { + assertEquals(15, counter); + counter = 0; + if (i > 0) { + counter = 2; + } + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + if (i == 0 && counter < 2) { + assertEquals(interleaveOrder[counter], sampler.getName()); + } else { + assertEquals(order[counter], sampler.getName()); + } + counter++; + } + } + } + + public void testProcessing2() throws Exception { + GenericController controller = new GenericController(); + GenericController sub_1 = new OnceOnlyController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + OnceOnlyController sub_3 = new OnceOnlyController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addIterationListener(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] interleaveOrder = new String[] { "one", "two" }; + String[] order = new String[] { "", "", "three", "four", "five", "six", "seven", "four", "seven", "four", + "seven" }; + int counter = 11; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 4; i++) { + assertEquals(11, counter); + counter = 0; + if (i > 0) { + counter = 2; + } + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + if (i == 0 && counter < 2) { + assertEquals(interleaveOrder[counter], sampler.getName()); + } else { + assertEquals(order[counter], sampler.getName()); + } + counter++; + } + } + } + + public void testInOuterLoop() throws Exception { + // Set up the test plan + LoopController controller = new LoopController(); + final int outerLoopCount = 4; + controller.setLoops(outerLoopCount); + // OnlyOnce samples + OnceOnlyController sub_1 = new OnceOnlyController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + // Outer sample + controller.addTestElement(new TestSampler("three")); + // Inner loop + LoopController sub_2 = new LoopController(); + final int innerLoopCount = 3; + sub_2.setLoops(innerLoopCount); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + // Sample in inner loop + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + + // Compute the expected sample names + String[] onlyOnceOrder = new String[] { "one", "two" }; + String[] order = new String[] { "three", "four", "five", "six", "seven", "four", "five", "six", + "seven", "four", "five", "six", "seven" }; + // Outer only once + ("three" + ("four" + "five" + "six" + "seven") * innerLoopCount) * outerLoopCount; + int expectedNoSamples = 2 + (1 + (3 + 1) * innerLoopCount) * outerLoopCount; + String[] expectedSamples = new String[expectedNoSamples]; + // The only once samples + System.arraycopy(onlyOnceOrder, 0, expectedSamples, 0, onlyOnceOrder.length); + // The outer sample and the inner loop samples + final int onceOnlySamples = onlyOnceOrder.length; + for(int i = 0; i < order.length * outerLoopCount; i++) { + expectedSamples[onceOnlySamples + i] = order[i % order.length]; + } + + // Execute the test pan + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + + int counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals(expectedSamples[counter], sampler.getPropertyAsString(TestElement.NAME)); + + counter++; + } + assertEquals(expectedNoSamples, counter); + } + + public void testInsideInnerLoop() throws Exception { + // Test plan with OnlyOnceController inside inner loop + // Set up the test plan + LoopController controller = new LoopController(); + final int outerLoopCount = 4; + controller.setLoops(outerLoopCount); + // OnlyOnce samples + OnceOnlyController sub_1 = new OnceOnlyController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + // Outer sample + controller.addTestElement(new TestSampler("three")); + // Inner loop + LoopController sub_2 = new LoopController(); + final int innerLoopCount = 3; + sub_2.setLoops(innerLoopCount); + // Sample in inner loop + sub_2.addTestElement(new TestSampler("four")); + // OnlyOnce inside inner loop + OnceOnlyController sub_3 = new OnceOnlyController(); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addIterationListener(sub_3); + // Sample in inner loop + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + + // Compute the expected sample names + String[] onlyOnceOrder = new String[] { "one", "two" }; + String[] order = new String[] { "three", "four", "five", "six", "seven", "four", "seven", "four", "seven" }; + // Outer only once + ("three" + "only once five and six" + ("four" + "seven") * innerLoopCount) * outerLoopCount; + int expectedNoSamples = 2 + (1 + 2 + (1 + 1) * innerLoopCount) * outerLoopCount; + String[] expectedSamples = new String[expectedNoSamples]; + // The only once samples + System.arraycopy(onlyOnceOrder, 0, expectedSamples, 0, onlyOnceOrder.length); + + // The outer sample and the inner loop samples + final int onceOnlySamples = onlyOnceOrder.length; + for(int i = 0; i < order.length * outerLoopCount; i++) { + expectedSamples[onceOnlySamples + i] = order[i % order.length]; + } + + // Execute the test pan + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + + int counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals(expectedSamples[counter], sampler.getPropertyAsString(TestElement.NAME)); + + counter++; + } + assertEquals(expectedNoSamples, counter); + } + + // Test skipped for now as behaviour is not yet properly defined + public void notestInsideInterleave() throws Exception { + // Test to show current problem with InterleaveController + // I am not sure if the expected order of the samples + // below are correct, because I am not sure if it is + // properly defined how the InterleaveController and + // OnlyOnceController should function. + + // Test plan with OnlyOnceController inside inner loop + // Set up the test plan + LoopController controller = new LoopController(); + final int outerLoopCount = 4; + controller.setLoops(outerLoopCount); + // OnlyOnce samples + OnceOnlyController sub_1 = new OnceOnlyController(); + sub_1.setName("outer OnlyOnce"); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + // Outer sample + controller.addTestElement(new TestSampler("three")); + // Inner loop + LoopController sub_2 = new LoopController(); + final int innerLoopCount = 5; + sub_2.setLoops(innerLoopCount); + sub_2.addTestElement(new TestSampler("four")); + // OnlyOnce inside inner loop + OnceOnlyController sub_3 = new OnceOnlyController(); + sub_3.setName("In loop OnlyOnce"); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addIterationListener(sub_3); + // InterleaveController in inner loop + InterleaveControl sub_4 = new InterleaveControl(); + sub_4.setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + // OnlyOnce inside InterleaveController + OnceOnlyController sub_5 = new OnceOnlyController(); + sub_5.addTestElement(new TestSampler("seven")); + sub_5.addTestElement(new TestSampler("eight")); + sub_5.setName("Inside InterleaveController OnlyOnce"); + sub_4.addTestElement(sub_5); + sub_4.addIterationListener(sub_5); + // Samples inside InterleaveController + sub_4.addTestElement(new TestSampler("nine")); + sub_4.addTestElement(new TestSampler("ten")); + sub_2.addTestElement(sub_4); + // Sample in inner loop + sub_2.addTestElement(new TestSampler("eleven")); + controller.addTestElement(sub_2); + + // Compute the expected sample names + String[] onlyOnceOrder = new String[] { "one", "two" }; + String[] order = new String[] { "three", "four", "five", "six", "seven", "eight", "eleven", + "four", "nine", "eleven", + "four", "ten", "eleven", + "four", "nine", "eleven", + "four", "ten", "eleven" }; + // Outer only once + ("three" + "only once five and six" + "eight in interleave only once" + ("four" + "interleave" + "eleven") * innerLoopCount) * outerLoopCount; + int expectedNoSamples = 2 + (1 + 2 + 1 + (1 + 1 + 1) * innerLoopCount) * outerLoopCount; + String[] expectedSamples = new String[expectedNoSamples]; + // The only once samples + System.arraycopy(onlyOnceOrder, 0, expectedSamples, 0, onlyOnceOrder.length); + + // The outer sample and the inner loop samples + final int onceOnlySamples = onlyOnceOrder.length; + for (int i = 0; i < order.length * outerLoopCount; i++) { + expectedSamples[onceOnlySamples + i] = order[i % order.length]; + } + + // Execute the test pan + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + sub_4.setRunningVersion(true); + sub_5.setRunningVersion(true); + controller.initialize(); + + int counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + System.out.println("ex: " + expectedSamples[counter] + " ac: " + sampler.getPropertyAsString(TestElement.NAME)); + assertEquals(expectedSamples[counter], sampler.getPropertyAsString(TestElement.NAME)); + + counter++; + } + assertEquals(expectedNoSamples, counter); + } +} diff --git a/test/src/org/apache/jmeter/control/TestRandomOrderController.java b/test/src/org/apache/jmeter/control/TestRandomOrderController.java new file mode 100644 index 00000000000..cb96ccf8ff1 --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestRandomOrderController.java @@ -0,0 +1,73 @@ +/* + * 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.control; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +public class TestRandomOrderController extends JMeterTestCase { + + public TestRandomOrderController(String name) { + super(name); + } + + public void testRandomOrder() { + testLog.debug("Testing RandomOrderController"); + RandomOrderController roc = new RandomOrderController(); + roc.addTestElement(new TestSampler("zero")); + roc.addTestElement(new TestSampler("one")); + roc.addTestElement(new TestSampler("two")); + roc.addTestElement(new TestSampler("three")); + TestElement sampler = null; + List usedSamplers = new ArrayList(); + roc.initialize(); + while ((sampler = roc.next()) != null) { + String samplerName = sampler.getName(); + if (usedSamplers.contains(samplerName)) { + assertTrue("Duplicate sampler returned from next()", false); + } + usedSamplers.add(samplerName); + } + assertEquals("All samplers were returned", 4, usedSamplers.size()); + } + + public void testRandomOrderNoElements() { + RandomOrderController roc = new RandomOrderController(); + roc.initialize(); + assertNull(roc.next()); + } + + public void testRandomOrderOneElement() { + RandomOrderController roc = new RandomOrderController(); + roc.addTestElement(new TestSampler("zero")); + TestElement sampler = null; + List usedSamplers = new ArrayList(); + roc.initialize(); + while ((sampler = roc.next()) != null) { + String samplerName = sampler.getName(); + if (usedSamplers.contains(samplerName)) { + assertTrue("Duplicate sampler returned from next()", false); + } + usedSamplers.add(samplerName); + } + assertEquals("All samplers were returned", 1, usedSamplers.size()); + } +} diff --git a/test/src/org/apache/jmeter/control/TestRunTime.java b/test/src/org/apache/jmeter/control/TestRunTime.java new file mode 100644 index 00000000000..05b27207222 --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestRunTime.java @@ -0,0 +1,70 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.samplers.Sampler; + +/** + * @version $Revision$ + */ +public class TestRunTime extends JMeterTestCase { + public TestRunTime(String name) { + super(name); + } + + public void testProcessing() throws Exception { + + RunTime controller = new RunTime(); + controller.setRuntime(10); + TestSampler samp1 = new TestSampler("Sample 1", 500); + TestSampler samp2 = new TestSampler("Sample 2", 490); + + LoopController sub1 = new LoopController(); + sub1.setLoops(2); + sub1.setContinueForever(false); + sub1.addTestElement(samp1); + + LoopController sub2 = new LoopController(); + sub2.setLoops(40); + sub2.setContinueForever(false); + sub2.addTestElement(samp2); + controller.addTestElement(sub1); + controller.addTestElement(sub2); + controller.setRunningVersion(true); + sub1.setRunningVersion(true); + sub2.setRunningVersion(true); + controller.initialize(); + Sampler sampler = null; + int loops = 0; + long now = System.currentTimeMillis(); + while ((sampler = controller.next()) != null) { + loops++; + sampler.sample(null); + } + long elapsed = System.currentTimeMillis() - now; + assertTrue("Should be at least 20 loops "+loops, loops >= 20); + assertTrue("Should be fewer than 30 loops "+loops, loops < 30); + assertTrue("Should take at least 10 seconds "+elapsed, elapsed >= 10000); + assertTrue("Should take less than 12 seconds "+elapsed, elapsed <= 12000); + assertEquals("Sampler 1 should run 2 times", 2, samp1.getSamples()); + assertTrue("Sampler 2 should run >= 18 times", samp2.getSamples() >= 18); + } +} diff --git a/test/src/org/apache/jmeter/control/TestSwitchController.java b/test/src/org/apache/jmeter/control/TestSwitchController.java new file mode 100644 index 00000000000..9b77d105b7e --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestSwitchController.java @@ -0,0 +1,298 @@ +/* + * 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.control; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.engine.util.ReplaceStringWithFunctions; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestSwitchController extends JMeterTestCase { +// static { +// LoggingManager.setPriority("DEBUG","jmeter"); +// LoggingManager.setTarget(new java.io.PrintWriter(System.out)); +// } + + public TestSwitchController(String name) { + super(name); + } + + // Get next sample and its name + private String nextName(GenericController c) { + Sampler s = c.next(); + String n; + if (s == null) { + return null; + } + n = s.getName(); + return n; + } + + public void test() throws Exception { + runSimpleTests("", "zero"); + } + + public void test0() throws Exception { + runSimpleTests("0", "zero"); + } + + public void test1() throws Exception { + runSimpleTests("1", "one"); + runSimpleTests("one", "one"); // Match by name + } + + public void test2() throws Exception { + runSimpleTests("2", "two"); + runSimpleTests("two", "two"); // Match by name + } + + public void test3() throws Exception { + runSimpleTests("3", "three"); + runSimpleTests("three", "three"); // Match by name + } + + public void test4() throws Exception { + runSimpleTests("4", "zero"); + } + + public void testX() throws Exception { + runSimpleTests("X", null); // should not run any children + runSimpleTest2("X", "one", "Default"); // should match the default entry + } + + private void runSimpleTests(String cond, String exp) throws Exception { + runSimpleTest(cond, exp); + runSimpleTest2(cond, exp, "one"); + } + + /* + * Simple test with single Selection controller + * Generic Controller + * + Sampler "before" + * + Switch Controller + * + + Sampler "zero" + * + + Sampler "one" + * + + Sampler "two" + * + + Sampler "three" + * + Sampler "after" + */ + private void runSimpleTest(String cond, String exp) throws Exception { + GenericController controller = new GenericController(); + + SwitchController switch_cont = new SwitchController(); + switch_cont.setSelection(cond); + + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(switch_cont); + + switch_cont.addTestElement(new TestSampler("zero")); + switch_cont.addTestElement(new TestSampler("one")); + switch_cont.addTestElement(new TestSampler("two")); + switch_cont.addTestElement(new TestSampler("three")); + + controller.addTestElement(new TestSampler("after")); + + controller.initialize(); + + for (int i = 1; i <= 3; i++) { + assertEquals("Loop " + i, "before", nextName(controller)); + if (exp!=null){ + assertEquals("Loop " + i, exp, nextName(controller)); + } + assertEquals("Loop " + i, "after", nextName(controller)); + assertNull(nextName(controller)); + } + } + + // Selection controller with two sub-controllers, but each has only 1 + // child + /* + * Controller + * + Before + * + Switch (cond) + * + + zero + * + + Controller sub_1 + * + + + one + * + + two + * + + Controller sub_2 + * + + + three + * + After + */ + private void runSimpleTest2(String cond, String exp, String sub1Name) throws Exception { + GenericController controller = new GenericController(); + GenericController sub_1 = new GenericController(); + GenericController sub_2 = new GenericController(); + + SwitchController switch_cont = new SwitchController(); + switch_cont.setSelection(cond); + + switch_cont.addTestElement(new TestSampler("zero")); + switch_cont.addTestElement(sub_1); + sub_1.addTestElement(new TestSampler("one")); + sub_1.setName(sub1Name); + + switch_cont.addTestElement(new TestSampler("two")); + + switch_cont.addTestElement(sub_2); + sub_2.addTestElement(new TestSampler("three")); + sub_2.setName("three"); + + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(switch_cont); + controller.addTestElement(new TestSampler("after")); + controller.initialize(); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop="+i,"before", nextName(controller)); + if (exp!=null){ + assertEquals("Loop="+i,exp, nextName(controller)); + } + assertEquals("Loop="+i,"after", nextName(controller)); + assertNull("Loop="+i,nextName(controller)); + } + } + + public void testTest2() throws Exception { + runTest2("", new String[] { "zero" }); + runTest2("0", new String[] { "zero" }); + runTest2("7", new String[] { "zero" }); + runTest2("5", new String[] { "zero" }); + runTest2("4", new String[] { "six" }); + runTest2("3", new String[] { "five" }); + runTest2("1", new String[] { "one", "two" }); + runTest2("2", new String[] { "three", "four" }); + } + + /* + * Test: + * Before + * Selection Controller + * - zero (default) + * - simple controller 1 + * - - one + * - - two + * - simple controller 2 + * - - three + * - - four + * - five + * - six + * After + * + * cond = Switch condition + * exp[] = expected results + */ + private void runTest2(String cond, String exp[]) throws Exception { + int loops = 3; + LoopController controller = new LoopController(); + controller.setLoops(loops); + controller.setContinueForever(false); + GenericController sub_1 = new GenericController(); + GenericController sub_2 = new GenericController(); + + SwitchController switch_cont = new SwitchController(); + switch_cont.setSelection(cond); + + switch_cont.addTestElement(new TestSampler("zero")); + switch_cont.addTestElement(sub_1); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + switch_cont.addTestElement(sub_2); + sub_2.addTestElement(new TestSampler("three")); + sub_2.addTestElement(new TestSampler("four")); + + switch_cont.addTestElement(new TestSampler("five")); + switch_cont.addTestElement(new TestSampler("six")); + + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(switch_cont); + controller.addTestElement(new TestSampler("after")); + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + switch_cont.setRunningVersion(true); + controller.initialize(); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop:" + i, "before", nextName(controller)); + for (int j = 0; j < exp.length; j++) { + assertEquals("Loop:" + i, exp[j], nextName(controller)); + } + assertEquals("Loop:" + i, "after", nextName(controller)); + } + assertNull("Loops:" + loops, nextName(controller)); + } + + /* + * N.B. Requires ApacheJMeter_functions.jar to be on the classpath, + * otherwise the function cannot be resolved. + */ + public void testFunction() throws Exception { + JMeterContext jmctx = JMeterContextService.getContext(); + Map variables = new HashMap(); + ReplaceStringWithFunctions transformer = new ReplaceStringWithFunctions(new CompoundVariable(), variables); + jmctx.setVariables(new JMeterVariables()); + JMeterVariables jmvars = jmctx.getVariables(); + jmvars.put("VAR", "100"); + StringProperty prop = new StringProperty(SwitchController.SWITCH_VALUE,"${__counter(TRUE,VAR)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + + GenericController controller = new GenericController(); + + SwitchController switch_cont = new SwitchController(); + switch_cont.setProperty(newProp); + + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(switch_cont); + + switch_cont.addTestElement(new TestSampler("0")); + switch_cont.addTestElement(new TestSampler("1")); + switch_cont.addTestElement(new TestSampler("2")); + switch_cont.addTestElement(new TestSampler("3")); + + controller.addTestElement(new TestSampler("after")); + + controller.initialize(); + + assertEquals("100",jmvars.get("VAR")); + + for (int i = 1; i <= 3; i++) { + assertEquals("Loop " + i, "before", nextName(controller)); + assertEquals("Loop " + i, ""+i, nextName(controller)); + assertEquals("Loop " + i, ""+i, jmvars.get("VAR")); + assertEquals("Loop " + i, "after", nextName(controller)); + assertNull(nextName(controller)); + } + int i = 4; + assertEquals("Loop " + i, "before", nextName(controller)); + assertEquals("Loop " + i, "0", nextName(controller)); + assertEquals("Loop " + i, ""+i, jmvars.get("VAR")); + assertEquals("Loop " + i, "after", nextName(controller)); + assertNull(nextName(controller)); + assertEquals("4",jmvars.get("VAR")); + } +} diff --git a/test/src/org/apache/jmeter/control/TestThroughputController.java b/test/src/org/apache/jmeter/control/TestThroughputController.java new file mode 100644 index 00000000000..a0654d2872c --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestThroughputController.java @@ -0,0 +1,216 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +/** + * This class represents a controller that can controll the number of times that + * it is executed, either by the total number of times the user wants the + * controller executed (BYNUMBER) or by the percentage of time it is called + * (BYPERCENT) + * + */ +public class TestThroughputController extends JMeterTestCase { + public TestThroughputController(String name) { + super(name); + } + + public void testByNumber() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYNUMBER); + sub_1.setMaxThroughput(2); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController loop = new LoopController(); + loop.setLoops(5); + loop.addTestElement(new TestSampler("zero")); + loop.addTestElement(sub_1); + loop.addIterationListener(sub_1); + loop.addTestElement(new TestSampler("three")); + + LoopController test = new LoopController(); + test.setLoops(2); + test.addTestElement(loop); + + String[] order = new String[] { "zero", "one", "two", "three", "zero", "one", "two", "three", "zero", + "three", "zero", "three", "zero", "three", "zero", "three", "zero", "three", "zero", "three", + "zero", "three", "zero", "three", }; + sub_1.testStarted(); + test.setRunningVersion(true); + sub_1.setRunningVersion(true); + loop.setRunningVersion(true); + test.initialize(); + for (int counter = 0; counter < order.length; counter++) { + TestElement sampler = test.next(); + assertNotNull(sampler); + assertEquals("Counter: " + counter, order[counter], sampler.getName()); + } + assertNull(test.next()); + sub_1.testEnded(); + } + + public void testByNumberZero() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYNUMBER); + sub_1.setMaxThroughput(0); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController controller = new LoopController(); + controller.setLoops(5); + controller.addTestElement(new TestSampler("zero")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + controller.addTestElement(new TestSampler("three")); + + String[] order = new String[] { "zero", "three", "zero", "three", "zero", "three", "zero", "three", "zero", + "three", }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_1.testStarted(); + controller.initialize(); + for (int i = 0; i < 3; i++) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("Counter: " + counter + ", i: " + i, order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, order.length); + counter = 0; + } + sub_1.testEnded(); + } + + public void testByPercent33() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYPERCENT); + sub_1.setPercentThroughput(33.33f); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController controller = new LoopController(); + controller.setLoops(6); + controller.addTestElement(new TestSampler("zero")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + controller.addTestElement(new TestSampler("three")); + // Expected results established using the DDA + // algorithm (see + // http://www.siggraph.org/education/materials/HyperGraph/scanline/outprims/drawline.htm): + String[] order = new String[] { "zero", // 0/1 vs. 1/1 -> 0 is + // closer to 33.33 + "three", "zero", // 0/2 vs. 1/2 -> 50.0 is closer to + // 33.33 + "one", "two", "three", "zero", // 1/3 vs. 2/3 -> 33.33 is + // closer to 33.33 + "three", "zero", // 1/4 vs. 2/4 -> 25.0 is closer to + // 33.33 + "three", "zero", // 1/5 vs. 2/5 -> 40.0 is closer to + // 33.33 + "one", "two", "three", "zero", // 2/6 vs. 3/6 -> 33.33 is + // closer to 33.33 + "three", + // etc... + }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_1.testStarted(); + controller.initialize(); + for (int i = 0; i < 3; i++) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("Counter: " + counter + ", i: " + i, order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, order.length); + counter = 0; + } + sub_1.testEnded(); + } + + public void testByPercentZero() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYPERCENT); + sub_1.setPercentThroughput(0.0f); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController controller = new LoopController(); + controller.setLoops(150); + controller.addTestElement(new TestSampler("zero")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + controller.addTestElement(new TestSampler("three")); + + String[] order = new String[] { "zero", "three", }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_1.testStarted(); + controller.initialize(); + for (int i = 0; i < 3; i++) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("Counter: " + counter + ", i: " + i, order[counter % order.length], sampler.getName()); + counter++; + } + assertEquals(counter, 150 * order.length); + counter = 0; + } + sub_1.testEnded(); + } + + public void testByPercent100() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYPERCENT); + sub_1.setPercentThroughput(100.0f); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController controller = new LoopController(); + controller.setLoops(150); + controller.addTestElement(new TestSampler("zero")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + controller.addTestElement(new TestSampler("three")); + + String[] order = new String[] { "zero", "one", "two", "three", }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_1.testStarted(); + controller.initialize(); + for (int i = 0; i < 3; i++) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("Counter: " + counter + ", i: " + i, order[counter % order.length], sampler.getName()); + counter++; + } + assertEquals(counter, 150 * order.length); + counter = 0; + } + sub_1.testEnded(); + } +} diff --git a/test/src/org/apache/jmeter/control/TestWhileController.java b/test/src/org/apache/jmeter/control/TestWhileController.java new file mode 100644 index 00000000000..0d24f51635f --- /dev/null +++ b/test/src/org/apache/jmeter/control/TestWhileController.java @@ -0,0 +1,365 @@ +/* + * 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.control; + +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterThread; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestWhileController extends JMeterTestCase { +// static { +// LoggingManager.setPriority("DEBUG","jmeter"); +// LoggingManager.setTarget(new java.io.PrintWriter(System.out)); +// } + + public TestWhileController(String name) { + super(name); + } + + private JMeterContext jmctx; + private JMeterVariables jmvars; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + jmctx.setVariables(new JMeterVariables()); + jmvars = jmctx.getVariables(); + } + + private void setLastSampleStatus(boolean status){ + jmvars.put(JMeterThread.LAST_SAMPLE_OK,Boolean.toString(status)); + } + + private void setRunning(TestElement el){ + PropertyIterator pi = el.propertyIterator(); + while(pi.hasNext()){ + pi.next().setRunningVersion(true); + } + } + + // Get next sample and its name + private String nextName(GenericController c) { + Sampler s = c.next(); + if (s == null) { + return null; + } + return s.getName(); + } + + // While (blank), previous sample OK - should loop until false + public void testBlankPrevOK() throws Exception { +// log.info("testBlankPrevOK"); + runtestPrevOK(""); + } + + // While (LAST), previous sample OK - should loop until false + public void testLastPrevOK() throws Exception { +// log.info("testLASTPrevOK"); + runtestPrevOK("LAST"); + } + + private static final String OTHER = "X"; // Dummy for testing functions + + // While (LAST), previous sample OK - should loop until false + public void testOtherPrevOK() throws Exception { +// log.info("testOtherPrevOK"); + runtestPrevOK(OTHER); + } + + private void runtestPrevOK(String type) throws Exception { + GenericController controller = new GenericController(); + WhileController while_cont = new WhileController(); + setLastSampleStatus(true); + while_cont.setCondition(type); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + while_cont.addTestElement(new TestSampler("three")); + controller.addTestElement(while_cont); + controller.addTestElement(new TestSampler("four")); + controller.initialize(); + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + assertEquals("one", nextName(controller)); + setLastSampleStatus(false); + if (type.equals(OTHER)){ + while_cont.setCondition("false"); + } + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + setLastSampleStatus(true); + if (type.equals(OTHER)) { + while_cont.setCondition(OTHER); + } + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + setLastSampleStatus(false); + if (type.equals(OTHER)) { + while_cont.setCondition("false"); + } + assertEquals("four", nextName(controller)); + assertNull(nextName(controller)); + setLastSampleStatus(true); + if (type.equals(OTHER)) { + while_cont.setCondition(OTHER); + } + assertEquals("one", nextName(controller)); + } + + // While (blank), previous sample failed - should run once + public void testBlankPrevFailed() throws Exception { +// log.info("testBlankPrevFailed"); + GenericController controller = new GenericController(); + controller.setRunningVersion(true); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition(""); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + controller.addTestElement(while_cont); + controller.addTestElement(new TestSampler("three")); + controller.initialize(); + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + assertNull(nextName(controller)); + // Run entire test again + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + assertNull(nextName(controller)); + } + + /* + * Generic Controller + * - before + * - While Controller ${VAR} + * - - one + * - - two + * - - Simple Controller + * - - - three + * - - - four + * - after + */ + public void testVariable1() throws Exception { + GenericController controller = new GenericController(); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition("${VAR}"); + jmvars.put("VAR", ""); + ValueReplacer vr = new ValueReplacer(); + vr.replaceValues(while_cont); + setRunning(while_cont); + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(while_cont); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + GenericController simple = new GenericController(); + while_cont.addTestElement(simple); + simple.addTestElement(new TestSampler("three")); + simple.addTestElement(new TestSampler("four")); + controller.addTestElement(new TestSampler("after")); + controller.initialize(); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + assertEquals("Loop: "+i,"one", nextName(controller)); + assertEquals("Loop: "+i,"two", nextName(controller)); + assertEquals("Loop: "+i,"three", nextName(controller)); + assertEquals("Loop: "+i,"four", nextName(controller)); + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + jmvars.put("VAR", "LAST"); // Should not enter the loop + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + jmvars.put("VAR", ""); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + if (i==1) { + assertEquals("Loop: "+i,"one", nextName(controller)); + assertEquals("Loop: "+i,"two", nextName(controller)); + assertEquals("Loop: "+i,"three", nextName(controller)); + jmvars.put("VAR", "LAST"); // Should not enter the loop next time + assertEquals("Loop: "+i,"four", nextName(controller)); + } + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + } + + // Test with SimpleController as first item + public void testVariable2() throws Exception { + GenericController controller = new GenericController(); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition("${VAR}"); + jmvars.put("VAR", ""); + ValueReplacer vr = new ValueReplacer(); + vr.replaceValues(while_cont); + setRunning(while_cont); + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(while_cont); + GenericController simple = new GenericController(); + while_cont.addTestElement(simple); + simple.addTestElement(new TestSampler("one")); + simple.addTestElement(new TestSampler("two")); + while_cont.addTestElement(new TestSampler("three")); + while_cont.addTestElement(new TestSampler("four")); + controller.addTestElement(new TestSampler("after")); + controller.initialize(); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + assertEquals("Loop: "+i,"one", nextName(controller)); + assertEquals("Loop: "+i,"two", nextName(controller)); + assertEquals("Loop: "+i,"three", nextName(controller)); + assertEquals("Loop: "+i,"four", nextName(controller)); + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + jmvars.put("VAR", "LAST"); // Should not enter the loop + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + jmvars.put("VAR", ""); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + if (i==1){ + assertEquals("Loop: "+i,"one", nextName(controller)); + assertEquals("Loop: "+i,"two", nextName(controller)); + jmvars.put("VAR", "LAST"); // Should not enter the loop next time + // But should continue to the end of the loop + assertEquals("Loop: "+i,"three", nextName(controller)); + assertEquals("Loop: "+i,"four", nextName(controller)); + } + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + } + + // While LAST, previous sample failed - should not run + public void testLASTPrevFailed() throws Exception { +// log.info("testLastPrevFailed"); + runTestPrevFailed("LAST"); + } + + // While False, previous sample failed - should not run + public void testfalsePrevFailed() throws Exception { +// log.info("testFalsePrevFailed"); + runTestPrevFailed("False"); + } + + private void runTestPrevFailed(String s) throws Exception { + GenericController controller = new GenericController(); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition(s); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + controller.addTestElement(while_cont); + controller.addTestElement(new TestSampler("three")); + controller.initialize(); + assertEquals("three", nextName(controller)); + assertNull(nextName(controller)); + assertEquals("three", nextName(controller)); + assertNull(nextName(controller)); + } + + public void testLastFailedBlank() throws Exception{ + runTestLastFailed(""); + } + + public void testLastFailedLast() throws Exception{ + runTestLastFailed("LAST"); + } + + // Should behave the same for blank and LAST because success on input + private void runTestLastFailed(String s) throws Exception { + GenericController controller = new GenericController(); + controller.addTestElement(new TestSampler("1")); + WhileController while_cont = new WhileController(); + controller.addTestElement(while_cont); + while_cont.setCondition(s); + GenericController sub = new GenericController(); + while_cont.addTestElement(sub); + sub.addTestElement(new TestSampler("2")); + sub.addTestElement(new TestSampler("3")); + + controller.addTestElement(new TestSampler("4")); + + setLastSampleStatus(true); + controller.initialize(); + assertEquals("1", nextName(controller)); + assertEquals("2", nextName(controller)); + setLastSampleStatus(false); + assertEquals("3", nextName(controller)); + assertEquals("4", nextName(controller)); + assertNull(nextName(controller)); + } + + // Tests for Stack Overflow (bug 33954) + public void testAlwaysFailOK() throws Exception { + runTestAlwaysFail(true); // Should be OK + } + + public void testAlwaysFailBAD() throws Exception { + runTestAlwaysFail(false); + } + + private void runTestAlwaysFail(boolean other) { + LoopController controller = new LoopController(); + controller.setContinueForever(true); + controller.setLoops(-1); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition("false"); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + controller.addTestElement(while_cont); + if (other) { + controller.addTestElement(new TestSampler("three")); + } + controller.initialize(); + try { + if (other) { + assertEquals("three", nextName(controller)); + } else { + assertNull(nextName(controller)); + } + } catch (StackOverflowError e) { + // e.printStackTrace(); + fail(e.toString()); + } + } +} diff --git a/test/src/org/apache/jmeter/engine/DistributedRunnerTest.java b/test/src/org/apache/jmeter/engine/DistributedRunnerTest.java new file mode 100644 index 00000000000..37848182190 --- /dev/null +++ b/test/src/org/apache/jmeter/engine/DistributedRunnerTest.java @@ -0,0 +1,180 @@ +/* + * 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.engine; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.net.MalformedURLException; +import java.rmi.NotBoundException; +import java.rmi.RemoteException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Properties; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class DistributedRunnerTest extends junit.framework.TestCase { + + public static void createJmeterEnv() throws IOException { + File propsFile; + try { + propsFile = File.createTempFile("jmeter", ".properties"); + propsFile.deleteOnExit(); + JMeterUtils.loadJMeterProperties(propsFile.getAbsolutePath()); + } catch (IOException ex) { + ex.printStackTrace(System.err); + } + JMeterUtils.setLocale(new Locale("ignoreResources")); + } + + public void testSuccess() throws Exception { + createJmeterEnv(); + JMeterUtils.setProperty(DistributedRunner.RETRIES_NUMBER, "1"); + JMeterUtils.setProperty(DistributedRunner.CONTINUE_ON_FAIL, "false"); + DistributedRunnerEmul obj = new DistributedRunnerEmul(); + obj.engines.add(new EmulatorEngine()); + obj.engines.add(new EmulatorEngine()); + List hosts = Arrays.asList("test1", "test2"); + obj.init(hosts, new HashTree()); + obj.start(); + obj.shutdown(hosts); + obj.stop(hosts); + obj.exit(hosts); + } + + public void testFailure1() throws Exception { + createJmeterEnv(); + JMeterUtils.setProperty(DistributedRunner.RETRIES_NUMBER, "2"); + JMeterUtils.setProperty(DistributedRunner.RETRIES_DELAY, "1"); + JMeterUtils.setProperty(DistributedRunner.CONTINUE_ON_FAIL, "true"); + DistributedRunnerEmul obj = new DistributedRunnerEmul(); + List hosts = Arrays.asList("test1", "test2"); + initRunner(obj, hosts); + obj.start(); + obj.shutdown(hosts); + obj.stop(hosts); + obj.exit(hosts); + } + + private void initRunner(DistributedRunnerEmul runner, List hosts) { + PrintStream origSystemOut = System.out; + ByteArrayOutputStream catchingOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(catchingOut)); + try { + runner.init(hosts, new HashTree()); + fail(); + } catch (RuntimeException ignored) { + } + System.setOut(origSystemOut); + } + + public void testFailure2() throws Exception { + createJmeterEnv(); + JMeterUtils.setProperty(DistributedRunner.RETRIES_NUMBER, "1"); + JMeterUtils.setProperty(DistributedRunner.RETRIES_DELAY, "1"); + JMeterUtils.setProperty(DistributedRunner.CONTINUE_ON_FAIL, "false"); + DistributedRunnerEmul obj = new DistributedRunnerEmul(); + List hosts = Arrays.asList("test1", "test2"); + initRunner(obj, hosts); + } + + public void testFailure3() throws Exception { + createJmeterEnv(); + JMeterUtils.setProperty(DistributedRunner.RETRIES_NUMBER, "1"); + JMeterUtils.setProperty(DistributedRunner.RETRIES_DELAY, "1"); + JMeterUtils.setProperty(DistributedRunner.CONTINUE_ON_FAIL, "true"); + DistributedRunnerEmul obj = new DistributedRunnerEmul(); + List hosts = Arrays.asList("test1", "test2"); + initRunner(obj, hosts); + obj.start(hosts); + obj.shutdown(hosts); + obj.stop(hosts); + obj.exit(hosts); + } + + private static class DistributedRunnerEmul extends DistributedRunner { + public List engines = new LinkedList(); + + @Override + protected JMeterEngine createEngine(String address) throws RemoteException, NotBoundException, MalformedURLException { + if(engines.size()==0) { + throw new IllegalArgumentException("Throwing on Engine creation to simulate failure"); + } + EmulatorEngine engine = engines.remove(0); + engine.setHost(address); + return engine; + } + } + + private static class EmulatorEngine implements JMeterEngine { + private static final Logger log = LoggingManager.getLoggerForClass(); + private String host; + + public EmulatorEngine() { + log.debug("Creating emulator " + host); + } + + @Override + public void configure(HashTree testPlan) { + log.debug("Configuring " + host); + } + + @Override + public void runTest() throws JMeterEngineException { + log.debug("Running " + host); + } + + @Override + public void stopTest(boolean now) { + log.debug("Stopping " + host); + } + + @Override + public void reset() { + log.debug("Resetting " + host); + } + + @Override + public void setProperties(Properties p) { + log.debug("Set properties " + host); + } + + @Override + public void exit() { + log.debug("Exitting " + host); + } + + @Override + public boolean isActive() { + log.debug("Check if active " + host); + return false; + } + + public void setHost(String host) { + this.host = host; + } + } +} \ No newline at end of file diff --git a/test/src/org/apache/jmeter/engine/TestTreeCloner.java b/test/src/org/apache/jmeter/engine/TestTreeCloner.java new file mode 100644 index 00000000000..96c9a83fd4a --- /dev/null +++ b/test/src/org/apache/jmeter/engine/TestTreeCloner.java @@ -0,0 +1,81 @@ +/* + * 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.engine; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.control.GenericController; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jorphan.collections.ListedHashTree; + +public class TestTreeCloner extends junit.framework.TestCase { + public TestTreeCloner(String name) { + super(name); + } + + public void testCloning() throws Exception { + ListedHashTree original = new ListedHashTree(); + GenericController controller = new GenericController(); + controller.setName("controller"); + Arguments args = new Arguments(); + args.setName("args"); + TestPlan plan = new TestPlan(); + plan.addParameter("server", "jakarta"); + original.add(controller, args); + original.add(plan); + ResultCollector listener = new ResultCollector(); + listener.setName("Collector"); + original.add(controller, listener); + TreeCloner cloner = new TreeCloner(); + original.traverse(cloner); + ListedHashTree newTree = cloner.getClonedTree(); + assertTrue(original != newTree); + assertEquals(original.size(), newTree.size()); + assertEquals(original.getTree(original.getArray()[0]).size(), newTree.getTree(newTree.getArray()[0]).size()); + assertTrue(original.getArray()[0] != newTree.getArray()[0]); + assertEquals(((GenericController) original.getArray()[0]).getName(), ((GenericController) newTree + .getArray()[0]).getName()); + assertSame(original.getTree(original.getArray()[0]).getArray()[1], newTree.getTree(newTree.getArray()[0]) + .getArray()[1]); + TestPlan clonedTestPlan = (TestPlan) newTree.getArray()[1]; + clonedTestPlan.setRunningVersion(true); + clonedTestPlan.recoverRunningVersion(); + assertTrue(!plan.getUserDefinedVariablesAsProperty().isRunningVersion()); + assertTrue(clonedTestPlan.getUserDefinedVariablesAsProperty().isRunningVersion()); + Arguments vars = (Arguments) plan.getUserDefinedVariablesAsProperty().getObjectValue(); + PropertyIterator iter = ((CollectionProperty) vars.getProperty(Arguments.ARGUMENTS)).iterator(); + while (iter.hasNext()) { + JMeterProperty argProp = iter.next(); + assertTrue(!argProp.isRunningVersion()); + assertTrue(argProp.getObjectValue() instanceof Argument); + Argument arg = (Argument) argProp.getObjectValue(); + arg.setValue("yahoo"); + assertEquals("yahoo", arg.getValue()); + } + vars = (Arguments) clonedTestPlan.getUserDefinedVariablesAsProperty().getObjectValue(); + iter = vars.propertyIterator(); + while (iter.hasNext()) { + assertTrue(iter.next().isRunningVersion()); + } + } +} diff --git a/test/src/org/apache/jmeter/engine/util/PackageTest.java b/test/src/org/apache/jmeter/engine/util/PackageTest.java new file mode 100644 index 00000000000..0406714049b --- /dev/null +++ b/test/src/org/apache/jmeter/engine/util/PackageTest.java @@ -0,0 +1,214 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 25, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +/* + * To run this test stand-alone, ensure that ApacheJMeter_functions.jar is on the classpath, + * as it is needed to resolve the functions. + */ +public class PackageTest extends JMeterTestCase { + private ReplaceStringWithFunctions transformer; + + public PackageTest(String arg0) { + super(arg0); + } + + private JMeterContext jmctx = null; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + Map variables = new HashMap(); + variables.put("my_regex", ".*"); + variables.put("server", "jakarta.apache.org"); + SampleResult result = new SampleResult(); + result.setResponseData("hello world costs: $3.47,$5.67", null); + transformer = new ReplaceStringWithFunctions(new CompoundVariable(), variables); + jmctx.setVariables(new JMeterVariables()); + jmctx.setSamplingStarted(true); + jmctx.setPreviousResult(result); + jmctx.getVariables().put("server", "jakarta.apache.org"); + jmctx.getVariables().put("my_regex", ".*"); + } + + public void testFunctionParse1() throws Exception { + StringProperty prop = new StringProperty("date", "${__javaScript((new Date().getDate() / 100).toString()." + + "substr(${__javaScript(1+1,d\\,ay)}\\,2),heute)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + newProp.recoverRunningVersion(null); + assertTrue(Integer.parseInt(newProp.getStringValue()) > -1); + assertEquals("2", jmctx.getVariables().getObject("d,ay")); + } + + public void testParseExample1() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((.*),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("hello world", newProp.getStringValue()); + } + + public void testParseExample2() throws Exception { + StringProperty prop = new StringProperty("html", "It should say:\\${${__regexFunction((.*),$1$)}}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("It should say:${hello world}", newProp.getStringValue()); + } + + public void testParseExample3() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((.*),$1$)}" + + "${__regexFunction((.*o)(.*o)(.*)," + "$1$$3$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("hello worldhellorld", newProp.getStringValue()); + } + + public void testParseExample4() throws Exception { + StringProperty prop = new StringProperty("html", "${non-existing function}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("${non-existing function}", newProp.getStringValue()); + } + + public void testParseExample6() throws Exception { + StringProperty prop = new StringProperty("html", "${server}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("jakarta.apache.org", newProp.getStringValue()); + } + + public void testParseExample5() throws Exception { + StringProperty prop = new StringProperty("html", ""); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.StringProperty", newProp.getClass().getName()); + assertEquals("", newProp.getStringValue()); + } + + public void testParseExample7() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction(\\<([a-z]*)\\>,$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("html", newProp.getStringValue()); + } + + public void testParseExample8() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((\\\\$\\d+\\.\\d+),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("$3.47", newProp.getStringValue()); + } + + public void testParseExample9() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction(([$]\\d+\\.\\d+),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("$3.47", newProp.getStringValue()); + } + + public void testParseExample10() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction(\\ " + + "(\\\\\\$\\d+\\.\\d+\\,\\\\$\\d+\\.\\d+),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("$3.47,$5.67", newProp.getStringValue()); + } + + // Escaped dollar commma and backslash with no variable reference + public void testParseExample11() throws Exception { + StringProperty prop = new StringProperty("html", "\\$a \\, \\\\ \\x \\ jakarta.apache.org"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.StringProperty", newProp.getClass().getName()); + assertEquals("\\$a \\, \\\\ \\x \\ jakarta.apache.org", newProp.getStringValue()); + } + + // N.B. See Bug 46831 which wanted to changed the behaviour of \$ + // It's too late now, as this would invalidate some existing test plans, + // so document the current behaviour with some more tests. + + // Escaped dollar commma and backslash with variable reference + public void testParseExample12() throws Exception { + StringProperty prop = new StringProperty("html", "\\$a \\, \\\\ \\x \\ ${server} \\$b \\, \\\\ cd"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + // N.B. Backslashes are removed before dollar, comma and backslash + assertEquals("$a , \\ \\x \\ jakarta.apache.org $b , \\ cd", newProp.getStringValue()); + } + + // Escaped dollar commma and backslash with missing variable reference + public void testParseExample13() throws Exception { + StringProperty prop = new StringProperty("html", "\\$a \\, \\\\ \\x \\ ${missing} \\$b \\, \\\\ cd"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + // N.B. Backslashes are removed before dollar, comma and backslash + assertEquals("$a , \\ \\x \\ ${missing} $b , \\ cd", newProp.getStringValue()); + } + + // Escaped dollar commma and backslash with missing function reference + public void testParseExample14() throws Exception { + StringProperty prop = new StringProperty("html", "\\$a \\, \\\\ \\x \\ ${__missing(a)} \\$b \\, \\\\ cd"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + // N.B. Backslashes are removed before dollar, comma and backslash + assertEquals("$a , \\ \\x \\ ${__missing(a)} $b , \\ cd", newProp.getStringValue()); + } + + public void testNestedExample1() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((${my_regex})," + + "$1$)}${__regexFunction((.*o)(.*o)(.*)" + ",$1$$3$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("hello worldhellorld", newProp.getStringValue()); + } + + public void testNestedExample2() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((${my_regex}),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("hello world", newProp.getStringValue()); + } + +} diff --git a/test/src/org/apache/jmeter/engine/util/TestValueReplacer.java b/test/src/org/apache/jmeter/engine/util/TestValueReplacer.java new file mode 100644 index 00000000000..fe39ec4600e --- /dev/null +++ b/test/src/org/apache/jmeter/engine/util/TestValueReplacer.java @@ -0,0 +1,154 @@ +/* + * 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.engine.util; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestValueReplacer extends JMeterTestCase { + private TestPlan variables; + + public TestValueReplacer(String name) { + super(name); + } + + /** {@inheritDoc} */ + @Override + public void setUp() { + variables = new TestPlan(); + variables.addParameter("server", "jakarta.apache.org"); + variables.addParameter("username", "jack"); + // The following used to be jacks_password, but the Arguments class uses + // HashMap for which the order is not defined. + variables.addParameter("password", "his_password"); + variables.addParameter("normal_regex", "Hello .*"); + variables.addParameter("bounded_regex", "(<.*>)"); + JMeterVariables vars = new JMeterVariables(); + vars.put("server", "jakarta.apache.org"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + } + + public void testReverseReplacement() throws Exception { + ValueReplacer replacer = new ValueReplacer(variables); + assertTrue(variables.getUserDefinedVariables().containsKey("server")); + assertTrue(replacer.containsKey("server")); + TestElement element = new TestPlan(); + element.setProperty(new StringProperty("domain", "jakarta.apache.org")); + List argsin = new ArrayList(); + argsin.add("username is jack"); + argsin.add("his_password"); + element.setProperty(new CollectionProperty("args", argsin)); + replacer.reverseReplace(element); + assertEquals("${server}", element.getPropertyAsString("domain")); + @SuppressWarnings("unchecked") + List args = (List) element.getProperty("args").getObjectValue(); + assertEquals("username is ${username}", args.get(0).getStringValue()); + assertEquals("${password}", args.get(1).getStringValue()); + } + + public void testReverseReplacementXml() throws Exception { + ValueReplacer replacer = new ValueReplacer(variables); + assertTrue(variables.getUserDefinedVariables().containsKey("bounded_regex")); + assertTrue(variables.getUserDefinedVariables().containsKey("normal_regex")); + assertTrue(replacer.containsKey("bounded_regex")); + assertTrue(replacer.containsKey("normal_regex")); + TestElement element = new TestPlan(); + element.setProperty(new StringProperty("domain", "xml")); + List argsin = new ArrayList(); + argsin.add("xml"); + argsin.add("And I say: Hello World."); + element.setProperty(new CollectionProperty("args", argsin)); + replacer.reverseReplace(element, true); + @SuppressWarnings("unchecked") + List args = (List) element.getProperty("args").getObjectValue(); + assertEquals("${bounded_regex}", element.getPropertyAsString("domain")); + assertEquals("${bounded_regex}", args.get(0).getStringValue()); + } + + public void testOverlappingMatches() throws Exception { + TestPlan plan = new TestPlan(); + plan.addParameter("longMatch", "servername"); + plan.addParameter("shortMatch", ".*"); + ValueReplacer replacer = new ValueReplacer(plan); + TestElement element = new TestPlan(); + element.setProperty(new StringProperty("domain", "servername.domain")); + replacer.reverseReplace(element, true); + String replacedDomain = element.getPropertyAsString("domain"); + assertEquals("${${shortMatch}", replacedDomain); + } + + public void testReplace() throws Exception { + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(variables.getUserDefinedVariables()); + TestElement element = new ConfigTestElement(); + element.setProperty(new StringProperty("domain", "${server}")); + replacer.replaceValues(element); + //log.debug("domain property = " + element.getProperty("domain")); + element.setRunningVersion(true); + assertEquals("jakarta.apache.org", element.getPropertyAsString("domain")); + } + + public void testReplaceStringWithBackslash() throws Exception { + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(variables.getUserDefinedVariables()); + TestElement element = new ConfigTestElement(); + String input = "\\${server} \\ \\\\ \\\\\\ \\, "; + element.setProperty(new StringProperty("domain", input)); + replacer.replaceValues(element); + //log.debug("domain property = " + element.getProperty("domain")); + element.setRunningVersion(true); + assertEquals(input, element.getPropertyAsString("domain")); + } + + /* + * This test should be compared with the one above. + * Here, the string contains a valid variable reference, so all + * backslashes are also processed. + * + * See https://bz.apache.org/bugzilla/show_bug.cgi?id=53534 + */ + public void testReplaceFunctionWithBackslash() throws Exception { + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(variables.getUserDefinedVariables()); + TestElement element = new ConfigTestElement(); + String input = "${server} \\ \\\\ \\\\\\ \\, "; + element.setProperty(new StringProperty("domain", input)); + replacer.replaceValues(element); + //log.debug("domain property = " + element.getProperty("domain")); + element.setRunningVersion(true); + assertEquals("jakarta.apache.org \\ \\ \\\\ , ", element.getPropertyAsString("domain")); + } + + /** {@inheritDoc} */ + @Override + protected void tearDown() throws Exception { + JMeterContextService.getContext().setSamplingStarted(false); + } +} diff --git a/test/src/org/apache/jmeter/extractor/TestRegexExtractor.java b/test/src/org/apache/jmeter/extractor/TestRegexExtractor.java new file mode 100644 index 00000000000..2af29d0d126 --- /dev/null +++ b/test/src/org/apache/jmeter/extractor/TestRegexExtractor.java @@ -0,0 +1,412 @@ +/* + * 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.extractor; + + +import java.net.URL; + +import junit.framework.TestCase; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestRegexExtractor extends TestCase { + private RegexExtractor extractor; + + private SampleResult result; + + private JMeterVariables vars; + + public TestRegexExtractor(String name) { + super(name); + } + + private JMeterContext jmctx; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + extractor = new RegexExtractor(); + extractor.setThreadContext(jmctx);// This would be done by the run + // command + extractor.setRefName("regVal"); + result = new SampleResult(); + String data = "" + "" + "LIS_OK" + + "" + "" + + "" + "0" + + "1" + "" + + "5" + "" + + "6" + "" + + "" + ""; + result.setResponseData(data, null); + result.setResponseHeaders("Header1: Value1\nHeader2: Value2"); + result.setResponseCode("abcd"); + result.setResponseMessage("The quick brown fox"); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + } + + public void testVariableExtraction0() throws Exception { + extractor.setRegex("<(value) field=\""); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(0); + extractor.process(); + assertEquals("value", vars.get("regVal")); + } + + public void testVariableExtraction() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(2); + extractor.process(); + assertEquals("5", vars.get("regVal")); + assertEquals("pinposition2", vars.get("regVal_g1")); + assertEquals("5", vars.get("regVal_g2")); + assertEquals("5", vars.get("regVal_g0")); + assertNull(vars.get("regVal_g3")); + assertEquals("2",vars.get("regVal_g")); + } + + private static void templateSetup(RegexExtractor rex, String tmp) { + rex.setRegex(""); + rex.setMatchNumber(1); + rex.setTemplate(tmp); + rex.process(); + } + + public void testTemplate1() throws Exception { + templateSetup(extractor, ""); + assertEquals("", vars.get("regVal_g0")); + assertEquals("xmlext", vars.get("regVal_g1")); + assertEquals("query", vars.get("regVal_g2")); + assertEquals("ret", vars.get("regVal_g3")); + assertEquals("", vars.get("regVal")); + assertEquals("3",vars.get("regVal_g")); + } + + public void testTemplate2() throws Exception { + templateSetup(extractor, "ABC"); + assertEquals("ABC", vars.get("regVal")); + } + + public void testTemplate3() throws Exception { + templateSetup(extractor, "$2$"); + assertEquals("query", vars.get("regVal")); + } + + public void testTemplate4() throws Exception { + templateSetup(extractor, "PRE$2$"); + assertEquals("PREquery", vars.get("regVal")); + } + + public void testTemplate5() throws Exception { + templateSetup(extractor, "$2$POST"); + assertEquals("queryPOST", vars.get("regVal")); + } + + public void testTemplate6() throws Exception { + templateSetup(extractor, "$2$$1$"); + assertEquals("queryxmlext", vars.get("regVal")); + } + + public void testTemplate7() throws Exception { + templateSetup(extractor, "$2$MID$1$"); + assertEquals("queryMIDxmlext", vars.get("regVal")); + } + + public void testVariableExtraction2() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(3); + extractor.process(); + assertEquals("pinposition3", vars.get("regVal")); + } + + public void testVariableExtraction6() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(4); + extractor.setDefaultValue("default"); + extractor.process(); + assertEquals("default", vars.get("regVal")); + } + + public void testVariableExtraction3() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("_$1$"); + extractor.setMatchNumber(2); + extractor.process(); + assertEquals("_pinposition2", vars.get("regVal")); + } + + public void testVariableExtraction5() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1);// Set up the non-wild variables + extractor.process(); + assertNotNull(vars.get("regVal")); + assertEquals("2",vars.get("regVal_g")); + assertNotNull(vars.get("regVal_g0")); + assertNotNull(vars.get("regVal_g1")); + assertNotNull(vars.get("regVal_g2")); + + extractor.setMatchNumber(-1); + extractor.process(); + assertNotNull(vars.get("regVal"));// Should not clear this? + assertNull(vars.get("regVal_g")); + assertNull(vars.get("regVal_g1")); + assertNull(vars.get("regVal_g2")); + assertEquals("3", vars.get("regVal_matchNr")); + assertEquals("pinposition1", vars.get("regVal_1")); + assertEquals("pinposition2", vars.get("regVal_2")); + assertEquals("pinposition3", vars.get("regVal_3")); + assertEquals("2", vars.get("regVal_1_g")); + assertEquals("pinposition1", vars.get("regVal_1_g1")); + assertEquals("1", vars.get("regVal_1_g2")); + assertEquals("6", vars.get("regVal_3_g2")); + assertEquals("1", vars.get("regVal_1_g0")); + assertNull(vars.get("regVal_4")); + + // Check old values don't hang around: + extractor.setRegex("(\\w+)count"); // fewer matches + extractor.process(); + assertEquals("2", vars.get("regVal_matchNr")); + assertEquals("position", vars.get("regVal_1")); + assertEquals("1", vars.get("regVal_1_g")); + assertEquals("position", vars.get("regVal_1_g1")); + assertNull("Unused variables should be null", vars.get("regVal_1_g2")); + assertEquals("invalidpin", vars.get("regVal_2")); + assertEquals("1", vars.get("regVal_2_g")); + assertEquals("invalidpin", vars.get("regVal_2_g1")); + assertNull("Unused variables should be null", vars.get("regVal_2_g2")); + assertEquals("1", vars.get("regVal_1_g")); + assertNull("Unused variables should be null", vars.get("regVal_3")); + assertNull("Unused variables should be null", vars.get("regVal_3_g")); + assertNull("Unused variables should be null", vars.get("regVal_3_g0")); + assertNull("Unused variables should be null", vars.get("regVal_3_g1")); + assertNull("Unused variables should be null", vars.get("regVal_3_g2")); + + // Check when match fails + extractor.setRegex("xxxx(.)(.)"); + extractor.process(); + assertEquals("0", vars.get("regVal_matchNr")); + assertNull("Unused variables should be null", vars.get("regVal_1")); + assertNull("Unused variables should be null", vars.get("regVal_1_g0")); + assertNull("Unused variables should be null", vars.get("regVal_1_g1")); + assertNull("Unused variables should be null", vars.get("regVal_1_g2")); + } + + public void testVariableExtraction7() throws Exception { + extractor.setRegex("Header1: (\\S+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + assertTrue("useBody should be true", extractor.useBody()); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useURL should be false", extractor.useUrl()); + extractor.setUseField(RegexExtractor.USE_BODY); + assertTrue("useBody should be true", extractor.useBody()); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useURL should be false", extractor.useUrl()); + extractor.setUseField(RegexExtractor.USE_HDRS); + assertTrue("useHdrs should be true", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertFalse("useURL should be false", extractor.useUrl()); + extractor.process(); + assertEquals("Value1", vars.get("regVal")); + extractor.setUseField(RegexExtractor.USE_URL); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertTrue("useURL should be true", extractor.useUrl()); + } + + public void testVariableExtraction8() throws Exception { + extractor.setRegex("http://jakarta\\.apache\\.org/(\\w+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + extractor.setUseField(RegexExtractor.USE_URL); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertTrue("useURL should be true", extractor.useUrl()); + extractor.process(); + assertNull(vars.get("regVal")); + result.setURL(new URL("http://jakarta.apache.org/index.html?abcd")); + extractor.process(); + assertEquals("index",vars.get("regVal")); + } + + public void testVariableExtraction9() throws Exception { + extractor.setRegex("(\\w+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + extractor.setUseField(RegexExtractor.USE_CODE); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertFalse("useURL should be false", extractor.useUrl()); + assertFalse("useMessage should be false", extractor.useMessage()); + assertTrue("useCode should be true", extractor.useCode()); + extractor.process(); + assertEquals("abcd",vars.get("regVal")); + extractor.setUseField(RegexExtractor.USE_MESSAGE); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertFalse("useURL should be false", extractor.useUrl()); + assertTrue("useMessage should be true", extractor.useMessage()); + assertFalse("useCode should be falsee", extractor.useCode()); + extractor.setMatchNumber(3); + extractor.process(); + assertEquals("brown",vars.get("regVal")); + } + + public void testNoDefault() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(4); + //extractor.setDefaultValue("default"); + vars.put("regVal", "initial"); + assertEquals("initial", vars.get("regVal")); + extractor.process(); + assertEquals("initial", vars.get("regVal")); + } + + public void testDefault() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(999); + extractor.setDefaultValue("default"); + vars.put("regVal", "initial"); + assertEquals("initial", vars.get("regVal")); + extractor.process(); + assertEquals("default", vars.get("regVal")); + assertNull(vars.get("regVal_g0")); + assertNull(vars.get("regVal_g1")); + } + + public void testStaleVariables() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(1); + extractor.setDefaultValue("default"); + extractor.process(); + assertEquals("1", vars.get("regVal")); + assertEquals("1", vars.get("regVal_g2")); + assertEquals("2", vars.get("regVal_g")); + assertNotNull(vars.get("regVal_g0")); + assertNotNull(vars.get("regVal_g1")); + // Now rerun with match fail + extractor.setMatchNumber(10); + extractor.process(); + assertEquals("default", vars.get("regVal")); + assertNull(vars.get("regVal_g0")); + assertNull(vars.get("regVal_g1")); + assertNull(vars.get("regVal_g")); + } + + public void testScope1() throws Exception { + result.setResponseData("ONE", "ISO-8859-1"); + extractor.setScopeParent(); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + extractor.setRegex("([^<]+)<"); + extractor.setDefaultValue("NOTFOUND"); + extractor.process(); + assertEquals("ONE", vars.get("regVal")); + extractor.setScopeAll(); + extractor.process(); + assertEquals("ONE", vars.get("regVal")); + extractor.setScopeChildren(); + extractor.process(); + assertEquals("NOTFOUND", vars.get("regVal")); + } + + public void testScope2() throws Exception { + result.sampleStart(); + result.setResponseData("<title>PARENT", "ISO-8859-1"); + result.sampleEnd(); + SampleResult child1 = new SampleResult(); + child1.sampleStart(); + child1.setResponseData("ONE", "ISO-8859-1"); + child1.sampleEnd(); + result.addSubResult(child1); + SampleResult child2 = new SampleResult(); + child2.sampleStart(); + child2.setResponseData("TWO", "ISO-8859-1"); + child2.sampleEnd(); + result.addSubResult(child2); + SampleResult child3 = new SampleResult(); + child3.sampleStart(); + child3.setResponseData("THREE", "ISO-8859-1"); + child3.sampleEnd(); + result.addSubResult(child3); + extractor.setScopeParent(); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + extractor.setRegex("([^<]+)<"); + extractor.setDefaultValue("NOTFOUND"); + extractor.process(); + assertEquals("PARENT", vars.get("regVal")); + extractor.setScopeAll(); + extractor.setMatchNumber(3); + extractor.process(); + assertEquals("TWO", vars.get("regVal")); + extractor.setScopeChildren(); + extractor.process(); + assertEquals("THREE", vars.get("regVal")); + extractor.setRegex(">(...)<"); + extractor.setScopeAll(); + extractor.setMatchNumber(2); + extractor.process(); + assertEquals("TWO", vars.get("regVal")); + + // Match all + extractor.setRegex("<title>([^<]+)<"); + extractor.setMatchNumber(-1); + + extractor.setScopeParent(); + extractor.process(); + assertEquals("1", vars.get("regVal_matchNr")); + extractor.setScopeAll(); + extractor.process(); + assertEquals("4", vars.get("regVal_matchNr")); + extractor.setScopeChildren(); + extractor.process(); + assertEquals("3", vars.get("regVal_matchNr")); + + // Check random number + extractor.setMatchNumber(0); + extractor.setScopeParent(); + extractor.process(); + assertEquals("PARENT", vars.get("regVal")); + extractor.setRegex("(<title>)"); + extractor.setScopeAll(); + extractor.process(); + assertEquals("<title>", vars.get("regVal")); + extractor.setScopeChildren(); + extractor.process(); + assertEquals("<title>", vars.get("regVal")); + extractor.setRegex("<title>(...)<"); + extractor.setScopeAll(); + extractor.process(); + final String found = vars.get("regVal"); + assertTrue(found.equals("ONE") || found.equals("TWO")); + } + +} diff --git a/test/src/org/apache/jmeter/extractor/TestXPathExtractor.java b/test/src/org/apache/jmeter/extractor/TestXPathExtractor.java new file mode 100644 index 00000000000..1bad69a23c0 --- /dev/null +++ b/test/src/org/apache/jmeter/extractor/TestXPathExtractor.java @@ -0,0 +1,221 @@ +/* + * 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.extractor; + + +import java.io.UnsupportedEncodingException; + +import junit.framework.TestCase; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestXPathExtractor extends TestCase { + private XPathExtractor extractor; + + private SampleResult result; + + private String data; + + private JMeterVariables vars; + + public TestXPathExtractor(String name) { + super(name); + } + + private JMeterContext jmctx; + + private static final String VAL_NAME = "value"; + private static final String VAL_NAME_NR = "value_matchNr"; + @Override + public void setUp() throws UnsupportedEncodingException { + jmctx = JMeterContextService.getContext(); + extractor = new XPathExtractor(); + extractor.setThreadContext(jmctx);// This would be done by the run command + extractor.setRefName(VAL_NAME); + extractor.setDefaultValue("Default"); + result = new SampleResult(); + data = "<book><preface title='Intro'>zero</preface><page>one</page><page>two</page><empty></empty><a><b></b></a></book>"; + result.setResponseData(data.getBytes("UTF-8")); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + } + + public void testAttributeExtraction() throws Exception { + extractor.setXPathQuery("/book/preface/@title"); + extractor.process(); + assertEquals("Intro", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("Intro", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setXPathQuery("/book/preface[@title]"); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setXPathQuery("/book/preface[@title='Intro']"); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setXPathQuery("/book/preface[@title='xyz']"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + } + + public void testVariableExtraction() throws Exception { + extractor.setXPathQuery("/book/preface"); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setXPathQuery("/book/page"); + extractor.process(); + assertEquals("one", vars.get(VAL_NAME)); + assertEquals("2", vars.get(VAL_NAME_NR)); + assertEquals("one", vars.get(VAL_NAME+"_1")); + assertEquals("two", vars.get(VAL_NAME+"_2")); + assertNull(vars.get(VAL_NAME+"_3")); + + extractor.setXPathQuery("/book/page[2]"); + extractor.process(); + assertEquals("two", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("two", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + assertNull(vars.get(VAL_NAME+"_3")); + + extractor.setXPathQuery("/book/index"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + + // Has child, but child is empty + extractor.setXPathQuery("/book/a"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + + // Has no child + extractor.setXPathQuery("/book/empty"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + + // No text + extractor.setXPathQuery("//a"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + + // Test fragment + extractor.setXPathQuery("/book/page[2]"); + extractor.setFragment(true); + extractor.process(); + assertEquals("<page>two</page>", vars.get(VAL_NAME)); + // Now get its text + extractor.setXPathQuery("/book/page[2]/text()"); + extractor.process(); + assertEquals("two", vars.get(VAL_NAME)); + + // No text, but using fragment mode + extractor.setXPathQuery("//a"); + extractor.process(); + assertEquals("<a><b/></a>", vars.get(VAL_NAME)); + } + + public void testScope(){ + extractor.setXPathQuery("/book/preface"); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setScopeChildren(); // There aren't any + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + + extractor.setScopeAll(); // same as Parent + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + // Try to get data from subresult + result.sampleStart(); // Needed for addSubResult() + result.sampleEnd(); + SampleResult subResult = new SampleResult(); + subResult.sampleStart(); + subResult.setResponseData(result.getResponseData()); + subResult.sampleEnd(); + result.addSubResult(subResult); + + + // Get data from both + extractor.setScopeAll(); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("2", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertEquals("zero", vars.get(VAL_NAME+"_2")); + assertNull(vars.get(VAL_NAME+"_3")); + + // get data from child + extractor.setScopeChildren(); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + } + + public void testInvalidXpath() throws Exception { + extractor.setXPathQuery("<"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + } + + public void testInvalidDocument() throws Exception { + result.setResponseData("<z>", null); + extractor.setXPathQuery("<"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + } +} diff --git a/test/src/org/apache/jmeter/functions/PackageTest.java b/test/src/org/apache/jmeter/functions/PackageTest.java new file mode 100644 index 00000000000..58cb465f18c --- /dev/null +++ b/test/src/org/apache/jmeter/functions/PackageTest.java @@ -0,0 +1,982 @@ +/* + * 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 to test functions + * + * Functions are created and parameters set up in one thread. + * + * They are then tested in another thread, or two threads running in parallel + * + */ +package org.apache.jmeter.functions; + +import java.io.FileNotFoundException; +import java.util.Collection; +import java.util.LinkedList; + +import junit.extensions.ActiveTestSuite; +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.log.Logger; + +/** + * Test cases for Functions + */ +public class PackageTest extends JMeterTestCase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + +// static { +// LoggingManager.setPriority("DEBUG","jmeter"); +// LoggingManager.setTarget(new java.io.PrintWriter(System.out)); +// } + + public PackageTest(String arg0) { + super(arg0); + } + + // Create the CSVRead function and set its parameters. + private static CSVRead setCSVReadParams(String p1, String p2) throws Exception { + CSVRead cr = new CSVRead(); + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + if (p1 != null) { + parms.add(new CompoundVariable(p1)); + } + if (p2 != null) { + parms.add(new CompoundVariable(p2)); + } + cr.setParameters(parms); + return cr; + } + + // Create the StringFromFile function and set its parameters. + private static StringFromFile SFFParams(String p1, String p2, String p3, String p4) throws Exception { + StringFromFile sff = new StringFromFile(); + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + if (p1 != null) { + parms.add(new CompoundVariable(p1)); + } + if (p2 != null) { + parms.add(new CompoundVariable(p2)); + } + if (p3 != null) { + parms.add(new CompoundVariable(p3)); + } + if (p4 != null) { + parms.add(new CompoundVariable(p4)); + } + sff.setParameters(parms); + return sff; + } + + // Create the SplitFile function and set its parameters. + private static SplitFunction splitParams(String p1, String p2, String p3) throws Exception { + SplitFunction split = new SplitFunction(); + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + parms.add(new CompoundVariable(p1)); + if (p2 != null) { + parms.add(new CompoundVariable(p2)); + } + if (p3 != null) { + parms.add(new CompoundVariable(p3)); + } + split.setParameters(parms); + return split; + } + + // Create the BeanShell function and set its parameters. + private static BeanShell BSHFParams(String p1, String p2, String p3) throws Exception { + BeanShell bsh = new BeanShell(); + bsh.setParameters(makeParams(p1, p2, p3)); + return bsh; + } + + private static Collection<CompoundVariable> makeParams(String p1, String p2, String p3) { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + if (p1 != null) { + parms.add(new CompoundVariable(p1)); + } + if (p2 != null) { + parms.add(new CompoundVariable(p2)); + } + if (p3 != null) { + parms.add(new CompoundVariable(p3)); + } + return parms; + } + + public static Test suite() throws Exception { + TestSuite allsuites = new TestSuite("Function PackageTest"); + + if (!BeanShellInterpreter.isInterpreterPresent()){ + final String msg = "BeanShell jar not present, tests ignored"; + log.warn(msg); + } else { + TestSuite bsh = new TestSuite("BeanShell"); + bsh.addTest(new PackageTest("BSH1")); + allsuites.addTest(bsh); + } + + TestSuite suite = new TestSuite("SingleThreaded"); + suite.addTest(new PackageTest("CSVParams")); + suite.addTest(new PackageTest("CSVNoFile")); + suite.addTest(new PackageTest("CSVSetup")); + suite.addTest(new PackageTest("CSVRun")); + + suite.addTest(new PackageTest("CSValias")); + suite.addTest(new PackageTest("CSVBlankLine")); + allsuites.addTest(suite); + + // Reset files + suite.addTest(new PackageTest("CSVSetup")); + TestSuite par = new ActiveTestSuite("Parallel"); + par.addTest(new PackageTest("CSVThread1")); + par.addTest(new PackageTest("CSVThread2")); + allsuites.addTest(par); + + TestSuite sff = new TestSuite("StringFromFile"); + sff.addTest(new PackageTest("SFFTest1")); + sff.addTest(new PackageTest("SFFTest2")); + sff.addTest(new PackageTest("SFFTest3")); + sff.addTest(new PackageTest("SFFTest4")); + sff.addTest(new PackageTest("SFFTest5")); + allsuites.addTest(sff); + + TestSuite split = new TestSuite("SplitFunction"); + split.addTest(new PackageTest("splitTest1")); + allsuites.addTest(split); + + TestSuite xpath = new TestSuite("XPath"); + xpath.addTest(new PackageTest("XPathtestColumns")); + xpath.addTest(new PackageTest("XPathtestDefault")); + xpath.addTest(new PackageTest("XPathtestNull")); + xpath.addTest(new PackageTest("XPathtestrowNum")); + xpath.addTest(new PackageTest("XPathEmpty")); + xpath.addTest(new PackageTest("XPathFile1")); + xpath.addTest(new PackageTest("XPathFile2")); + xpath.addTest(new PackageTest("XPathNoFile")); + + allsuites.addTest(xpath); + + TestSuite random = new TestSuite("Random"); + random.addTest(new PackageTest("randomTest1")); + allsuites.addTest(random); + + allsuites.addTest(new PackageTest("XPathSetup1")); + TestSuite par2 = new ActiveTestSuite("ParallelXPath1"); + par2.addTest(new PackageTest("XPathThread1")); + par2.addTest(new PackageTest("XPathThread2")); + allsuites.addTest(par2); + + allsuites.addTest(new PackageTest("XPathSetup2")); + TestSuite par3 = new ActiveTestSuite("ParallelXPath2"); + par3.addTest(new PackageTest("XPathThread1")); + par3.addTest(new PackageTest("XPathThread2")); + allsuites.addTest(par3); + + TestSuite variable = new TestSuite("Variable"); + variable.addTest(new PackageTest("variableTest1")); + allsuites.addTest(variable); + + TestSuite eval = new TestSuite("Eval"); + eval.addTest(new PackageTest("evalTest1")); + eval.addTest(new PackageTest("evalTest2")); + allsuites.addTest(eval); + + TestSuite intSum = new TestSuite("Sums"); + intSum.addTest(new PackageTest("sumTest")); + allsuites.addTest(intSum); + + return allsuites; + } + + private JMeterContext jmctx = null; + + private JMeterVariables vars = null; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + jmctx.setVariables(new JMeterVariables()); + vars = jmctx.getVariables(); + } + + public void BSH1() throws Exception { + String fn = "testfiles/BeanShellTest.bsh"; + try { + BSHFParams(null, null, null); + fail("Expected InvalidVariableException"); + } catch (InvalidVariableException e) { + } + + try { + BSHFParams("", "", ""); + fail("Expected InvalidVariableException"); + } catch (InvalidVariableException e) { + } + + BeanShell bsh; + try { + bsh = BSHFParams("", "", null); + assertEquals("", bsh.execute()); + } catch (InvalidVariableException e) { + fail("BeanShell not present"); + } + + bsh = BSHFParams("1", null, null); + assertEquals("1", bsh.execute()); + + bsh = BSHFParams("1+1", "VAR", null); + assertEquals("2", bsh.execute()); + assertEquals("2", vars.get("VAR")); + + // Check some initial variables + bsh = BSHFParams("return threadName", null, null); + assertEquals(Thread.currentThread().getName(), bsh.execute()); + bsh = BSHFParams("return log.getClass().getName()", null, null); + assertEquals(log.getClass().getName(), bsh.execute()); + + // Check source works + bsh = BSHFParams("source (\"testfiles/BeanShellTest.bsh\")", null, null); + assertEquals("9876", bsh.execute()); + + // Check persistence + bsh = BSHFParams("${SCR1}", null, null); + + vars.put("SCR1", "var1=11"); + assertEquals("11", bsh.execute()); + + vars.put("SCR1", "var2=22"); + assertEquals("22", bsh.execute()); + + vars.put("SCR1", "x=var1"); + assertEquals("11", bsh.execute()); + + vars.put("SCR1", "++x"); + assertEquals("12", bsh.execute()); + + vars.put("VAR1", "test"); + vars.put("SCR1", "vars.get(\"VAR1\")"); + assertEquals("test", bsh.execute()); + + // Check init file functioning + JMeterUtils.getJMeterProperties().setProperty(BeanShell.INIT_FILE, fn); + bsh = BSHFParams("${SCR2}", null, null); + vars.put("SCR2", "getprop(\"" + BeanShell.INIT_FILE + "\")"); + assertEquals(fn, bsh.execute());// Check that bsh has read the file + vars.put("SCR2", "getprop(\"avavaav\",\"default\")"); + assertEquals("default", bsh.execute()); + vars.put("SCR2", "++i"); + assertEquals("1", bsh.execute()); + vars.put("SCR2", "++i"); + assertEquals("2", bsh.execute()); + + } + + public void splitTest1() throws Exception { + String src = ""; + + try { + splitParams("a,b,c", null, null); + fail("Expected InvalidVariableException (wrong number of parameters)"); + } catch (InvalidVariableException e) { + // OK + } + src = "a,b,c"; + SplitFunction split; + split = splitParams(src, "VAR1", null); + assertEquals(src, split.execute()); + assertEquals(src, vars.get("VAR1")); + assertEquals("3", vars.get("VAR1_n")); + assertEquals("a", vars.get("VAR1_1")); + assertEquals("b", vars.get("VAR1_2")); + assertEquals("c", vars.get("VAR1_3")); + assertNull(vars.get("VAR1_4")); + + split = splitParams(src, "VAR2", ","); + assertEquals(src, split.execute()); + assertEquals(src, vars.get("VAR2")); + assertEquals("3", vars.get("VAR2_n")); + assertEquals("a", vars.get("VAR2_1")); + assertEquals("b", vars.get("VAR2_2")); + assertEquals("c", vars.get("VAR2_3")); + assertNull(vars.get("VAR2_4")); + + src = "a|b|c"; + split = splitParams(src, "VAR3", "|"); + assertEquals(src, split.execute()); + assertEquals(src, vars.get("VAR3")); + assertEquals("3", vars.get("VAR3_n")); + assertEquals("a", vars.get("VAR3_1")); + assertEquals("b", vars.get("VAR3_2")); + assertEquals("c", vars.get("VAR3_3")); + assertNull(vars.get("VAR3_4")); + + src = "a|b||"; + split = splitParams(src, "VAR4", "|"); + assertEquals(src, split.execute()); + assertEquals(src, vars.get("VAR4")); + assertEquals("4", vars.get("VAR4_n")); + assertEquals("a", vars.get("VAR4_1")); + assertEquals("b", vars.get("VAR4_2")); + assertEquals("?", vars.get("VAR4_3")); + assertNull(vars.get("VAR4_5")); + + src = "a,,c"; + vars.put("VAR", src); + split = splitParams("${VAR}", "VAR", null); + assertEquals(src, split.execute()); + assertEquals("3", vars.get("VAR_n")); + assertEquals("a", vars.get("VAR_1")); + assertEquals("?", vars.get("VAR_2")); + assertEquals("c", vars.get("VAR_3")); + assertNull(vars.get("VAR_4")); + + src = "a,b"; + vars.put("VAR", src); + split = splitParams("${VAR}", "VAR", null); + assertEquals(src, split.execute()); + assertEquals("2", vars.get("VAR_n")); + assertEquals("a", vars.get("VAR_1")); + assertEquals("b", vars.get("VAR_2")); + assertNull(vars.get("VAR_3")); + + src = "a,,c,"; + vars.put("VAR", src); + split = splitParams("${VAR}", "VAR5", null); + assertEquals(src, split.execute()); + assertEquals("4", vars.get("VAR5_n")); + assertEquals("a", vars.get("VAR5_1")); + assertEquals("?", vars.get("VAR5_2")); + assertEquals("c", vars.get("VAR5_3")); + assertEquals("?", vars.get("VAR5_4")); + assertNull(vars.get("VAR5_5")); + +} + + public void SFFTest1() throws Exception { + StringFromFile sff1 = SFFParams("testfiles/SFFTest#'.'txt", "", "1", "3"); + assertEquals("uno", sff1.execute()); + assertEquals("dos", sff1.execute()); + assertEquals("tres", sff1.execute()); + assertEquals("cuatro", sff1.execute()); + assertEquals("cinco", sff1.execute()); + assertEquals("one", sff1.execute()); + assertEquals("two", sff1.execute()); + sff1.execute(); + sff1.execute(); + assertEquals("five", sff1.execute()); + assertEquals("eins", sff1.execute()); + sff1.execute(); + sff1.execute(); + sff1.execute(); + assertEquals("fuenf", sff1.execute()); + try { + sff1.execute(); + fail("Should have thrown JMeterStopThreadException"); + } catch (JMeterStopThreadException e) { + // expected + } + } + + public void SFFTest2() throws Exception { + StringFromFile sff = SFFParams("testfiles/SFFTest1.txt", "", null, null); + assertEquals("uno", sff.execute()); + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + assertEquals("uno", sff.execute()); // Restarts + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + } + + public void SFFTest3() throws Exception { + StringFromFile sff = SFFParams("testfiles/SFFTest1.txt", "", "", ""); + assertEquals("uno", sff.execute()); + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + assertEquals("uno", sff.execute()); // Restarts + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + } + + public void SFFTest4() throws Exception { + StringFromFile sff = SFFParams("xxtestfiles/SFFTest1.txt", "", "", ""); + assertEquals(StringFromFile.ERR_IND, sff.execute()); + assertEquals(StringFromFile.ERR_IND, sff.execute()); + } + + // Test that only loops twice + public void SFFTest5() throws Exception { + StringFromFile sff = SFFParams("testfiles/SFFTest1.txt", "", "", "2"); + assertEquals("uno", sff.execute()); + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + assertEquals("uno", sff.execute()); + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + try { + sff.execute(); + fail("Should have thrown JMeterStopThreadException"); + } catch (JMeterStopThreadException e) { + // expected + } + } + + // Function objects to be tested + private static CSVRead cr1, cr2, cr3, cr4, cr5, cr6; + + // Helper class used to implement co-routine between two threads + private static class Baton { + void pass() { + done(); + try { + // System.out.println(">wait:"+Thread.currentThread().getName()); + wait(1000); + } catch (InterruptedException e) { + System.out.println(e); + } + // System.out.println("<wait:"+Thread.currentThread().getName()); + + } + + void done() { + // System.out.println(">done:"+Thread.currentThread().getName()); + notifyAll(); + } + + } + + private static final Baton baton = new Baton(); + + public void CSVThread1() throws Exception { + Thread.currentThread().setName("One"); + synchronized (baton) { + + assertEquals("b1", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + assertEquals("b2", cr1.execute(null, null)); + + baton.pass(); + + assertEquals("", cr4.execute(null, null)); + + assertEquals("b4", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + baton.pass(); + + assertEquals("b3", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + baton.done(); + } + } + + public void CSVThread2() throws Exception { + Thread.currentThread().setName("Two"); + Thread.sleep(500);// Allow other thread to start + synchronized (baton) { + + assertEquals("b3", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + baton.pass(); + + assertEquals("b1", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + assertEquals("b2", cr1.execute(null, null)); + + baton.pass(); + + assertEquals("", cr4.execute(null, null)); + + assertEquals("b4", cr1.execute(null, null)); + + baton.done(); + } + } + + public void CSVRun() throws Exception { + assertEquals("b1", cr1.execute(null, null)); + assertEquals("c1", cr2.execute(null, null)); + assertEquals("d1", cr3.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + assertEquals("b2", cr1.execute(null, null)); + assertEquals("c2", cr2.execute(null, null)); + assertEquals("d2", cr3.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + assertEquals("b3", cr1.execute(null, null)); + assertEquals("c3", cr2.execute(null, null)); + assertEquals("d3", cr3.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + assertEquals("b4", cr1.execute(null, null)); + assertEquals("c4", cr2.execute(null, null)); + assertEquals("d4", cr3.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + assertEquals("b1", cr1.execute(null, null)); + assertEquals("c1", cr2.execute(null, null)); + assertEquals("d1", cr3.execute(null, null)); + + assertEquals("a1", cr5.execute(null, null)); + assertEquals("", cr6.execute(null, null)); + assertEquals("a2", cr5.execute(null, null)); + + } + + public void CSVParams() throws Exception { + try { + setCSVReadParams(null, null); + fail("Should have failed"); + } catch (InvalidVariableException e) { + } + try { + setCSVReadParams(null, ""); + fail("Should have failed"); + } catch (InvalidVariableException e) { + } + try { + setCSVReadParams("", null); + fail("Should have failed"); + } catch (InvalidVariableException e) { + } + } + + public void CSVSetup() throws Exception { + cr1 = setCSVReadParams("testfiles/test.csv", "1"); + cr2 = setCSVReadParams("testfiles/test.csv", "2"); + cr3 = setCSVReadParams("testfiles/test.csv", "3"); + cr4 = setCSVReadParams("testfiles/test.csv", "next"); + cr5 = setCSVReadParams("", "0"); + cr6 = setCSVReadParams("", "next"); + } + + public void CSValias() throws Exception { + cr1 = setCSVReadParams("testfiles/test.csv", "*A"); + cr2 = setCSVReadParams("*A", "1"); + cr3 = setCSVReadParams("*A", "next"); + + cr4 = setCSVReadParams("testfiles/test.csv", "*B"); + cr5 = setCSVReadParams("*B", "2"); + cr6 = setCSVReadParams("*B", "next"); + + String s; + + s = cr1.execute(null, null); // open as *A + assertEquals("", s); + s = cr2.execute(null, null); // col 1, line 1, *A + assertEquals("b1", s); + + s = cr4.execute(null, null);// open as *B + assertEquals("", s); + s = cr5.execute(null, null);// col2 line 1 + assertEquals("c1", s); + + s = cr3.execute(null, null);// *A next + assertEquals("", s); + s = cr2.execute(null, null);// col 1, line 2, *A + assertEquals("b2", s); + + s = cr5.execute(null, null);// col2, line 1, *B + assertEquals("c1", s); + + s = cr6.execute(null, null);// *B next + assertEquals("", s); + + s = cr5.execute(null, null);// col2, line 2, *B + assertEquals("c2", s); + + } + + public void CSVNoFile() throws Exception { + String s; + + cr1 = setCSVReadParams("xtestfiles/test.csv", "1"); + log.info("Expecting file not found"); + s = cr1.execute(null, null); + assertEquals("", s); + + cr2 = setCSVReadParams("xtestfiles/test.csv", "next"); + log.info("Expecting no entry for file"); + s = cr2.execute(null, null); + assertEquals("", s); + + cr3 = setCSVReadParams("xtestfiles/test.csv", "*ABC"); + log.info("Expecting file not found"); + s = cr3.execute(null, null); + assertEquals("", s); + + cr4 = setCSVReadParams("*ABC", "1"); + log.info("Expecting cannot open file"); + s = cr4.execute(null, null); + assertEquals("", s); + } + + // Check blank lines are treated as EOF + public void CSVBlankLine() throws Exception { + CSVRead csv1 = setCSVReadParams("testfiles/testblank.csv", "1"); + CSVRead csv2 = setCSVReadParams("testfiles/testblank.csv", "next"); + + String s; + + for (int i = 1; i <= 2; i++) { + s = csv1.execute(null, null); + assertEquals("b1", s); + + s = csv2.execute(null, null); + assertEquals("", s); + + s = csv1.execute(null, null); + assertEquals("b2", s); + + s = csv2.execute(null, null); + assertEquals("", s); + } + + } + + // XPathFileContainer tests + + public void XPathtestNull() throws Exception { + try { + new XPathFileContainer("nosuch.xml", "/"); + fail("Should not find the file"); + } catch (FileNotFoundException e) { + } + } + + public void XPathtestrowNum() throws Exception { + XPathFileContainer f = new XPathFileContainer("../build.xml", "/project/target/@name"); + assertNotNull(f); + // assertEquals("Expected 4 lines",4,f.size()); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals(1, f.getNextRow()); + + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals(2, f.getNextRow()); + + myRow = f.nextRow(); + assertEquals(2, myRow); + assertEquals(3, f.getNextRow()); + + // myRow = f.nextRow(); + // assertEquals(3,myRow); + // assertEquals(0,f.getNextRow()); + + // myRow = f.nextRow(); + // assertEquals(0,myRow); + // assertEquals(1,f.getNextRow()); + + } + + public void XPathtestColumns() throws Exception { + XPathFileContainer f = new XPathFileContainer("../build.xml", "/project/target/@name"); + assertNotNull(f); + assertTrue("Not empty", f.size() > 0); + int last = 0; + for (int i = 0; i < f.size(); i++) { + last = f.nextRow(); + log.debug("found [" + i + "]" + f.getXPathString(last)); + } + assertEquals(last + 1, f.size()); + + } + + public void XPathtestDefault() throws Exception { + XPathFileContainer f = new XPathFileContainer("../build.xml", "/project/@default"); + assertNotNull(f); + assertTrue("Not empty", f.size() > 0); + assertEquals("install", f.getXPathString(0)); + + } + + public void XPathEmpty() throws Exception{ + XPath xp = setupXPath("",""); + String val=xp.execute(); + assertEquals("",val); + val=xp.execute(); + assertEquals("",val); + val=xp.execute(); + assertEquals("",val); + } + + public void XPathNoFile() throws Exception{ + XPath xp = setupXPath("no-such-file",""); + String val=xp.execute(); + assertEquals("",val); // TODO - should check that error has been logged... + } + + public void XPathFile1() throws Exception{ + XPath xp = setupXPath("testfiles/XPathTest.xml","//user/@username"); + assertEquals("u1",xp.execute()); + assertEquals("u2",xp.execute()); + assertEquals("u3",xp.execute()); + assertEquals("u4",xp.execute()); + assertEquals("u5",xp.execute()); + assertEquals("u1",xp.execute()); + } + + public void XPathFile2() throws Exception{ + XPath xp1 = setupXPath("testfiles/XPathTest.xml","//user/@username"); + XPath xp1a = setupXPath("testfiles/XPathTest.xml","//user/@username"); + XPath xp2 = setupXPath("testfiles/XPathTest.xml","//user/@password"); + XPath xp2a = setupXPath("testfiles/XPathTest.xml","//user/@password"); + assertEquals("u1",xp1.execute()); + assertEquals("p1",xp2.execute()); + assertEquals("p2",xp2.execute()); + assertEquals("u2",xp1a.execute()); + assertEquals("u3",xp1.execute()); + assertEquals("u4",xp1.execute()); + assertEquals("p3",xp2a.execute()); + + } + + private static XPath sxp1,sxp2; + // Use same XPath for both threads + public void XPathSetup1() throws Exception{ + sxp1 = setupXPath("testfiles/XPathTest.xml","//user/@username"); + sxp2=sxp1; + } + + // Use different XPath for both threads + public void XPathSetup2() throws Exception{ + sxp1 = setupXPath("testfiles/XPathTest.xml","//user/@username"); + sxp2 = setupXPath("testfiles/XPathTest.xml","//user/@username"); + } + + public void XPathThread1() throws Exception { + Thread.currentThread().setName("XPathOne"); + synchronized (baton) { + assertEquals("u1",sxp1.execute()); + assertEquals("u2",sxp1.execute()); + baton.pass(); + assertEquals("u5",sxp1.execute()); + baton.pass(); + assertEquals("u2",sxp1.execute()); + baton.done(); + } + } + + public void XPathThread2() throws Exception { + Thread.currentThread().setName("XPathTwo"); + Thread.sleep(500); + synchronized (baton) { + assertEquals("u3",sxp2.execute()); + assertEquals("u4",sxp2.execute()); + baton.pass(); + assertEquals("u1",sxp2.execute()); + baton.pass(); + assertEquals("u3",sxp2.execute()); + baton.done(); + } + } + + private XPath setupXPath(String file, String expr) throws Exception{ + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + parms.add(new CompoundVariable(file)); + parms.add(new CompoundVariable(expr)); + XPath xp = new XPath(); + xp.setParameters(parms); + return xp; + } + + + + public void randomTest1() throws Exception { + Random r = new Random(); + Collection<CompoundVariable> parms = makeParams("0","10000000000","VAR"); + r.setParameters(parms); + String s = + r.execute(null,null); + long l = Long.parseLong(s); + assertTrue(l>=0 && l<=10000000000L); + + + parms = makeParams("1","1","VAR"); + r.setParameters(parms); + s = + r.execute(null,null); + l = Long.parseLong(s); + assertTrue(l==1); + + } + + public void variableTest1() throws Exception { + Variable r = new Variable(); + vars.put("A_1","a1"); + vars.put("A_2","a2"); + vars.put("one","1"); + vars.put("two","2"); + vars.put("V","A"); + Collection<CompoundVariable> parms; + String s; + + parms = makeParams("V",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("A",s); + + parms = makeParams("X",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("X",s); + + parms = makeParams("A${X}",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("A${X}",s); + + parms = makeParams("A_1",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("a1",s); + + parms = makeParams("A_2",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("a2",s); + + parms = makeParams("A_${two}",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("a2",s); + + parms = makeParams("${V}_${one}",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("a1",s); + } + + public void evalTest1() throws Exception { + EvalFunction eval = new EvalFunction(); + vars.put("query","select ${column} from ${table}"); + vars.put("column","name"); + vars.put("table","customers"); + Collection<CompoundVariable> parms; + String s; + + parms = makeParams("${query}",null,null); + eval.setParameters(parms); + s = eval.execute(null,null); + assertEquals("select name from customers",s); + + } + + public void evalTest2() throws Exception { + EvalVarFunction evalVar = new EvalVarFunction(); + vars.put("query","select ${column} from ${table}"); + vars.put("column","name"); + vars.put("table","customers"); + Collection<CompoundVariable> parms; + String s; + + parms = makeParams("query",null,null); + evalVar.setParameters(parms); + s = evalVar.execute(null,null); + assertEquals("select name from customers",s); + } + + public void sumTest() throws Exception { + String maxIntVal = Integer.toString(Integer.MAX_VALUE); + String minIntVal = Integer.toString(Integer.MIN_VALUE); + + { // prevent accidental use of is below + IntSum is = new IntSum(); + checkInvalidParameterCounts(is,2); + checkSum(is,"3", new String[]{"1","2"}); + checkSumNoVar(is,"3", new String[]{"1","2"}); + checkSum(is,"1", new String[]{"-1","1","1","1","-2","1"}); + checkSumNoVar(is,"1", new String[]{"-1","1","1","1","-2","1"}); + checkSumNoVar(is,"-1", new String[]{"-1","1","1","1","-2","-1"}); + checkSum(is,maxIntVal, new String[]{maxIntVal,"0"}); + checkSum(is,minIntVal, new String[]{maxIntVal,"1"}); // wrap-round check + } + + LongSum ls = new LongSum(); + checkInvalidParameterCounts(ls,2); + checkSum(ls,"3", new String[]{"1","2"}); + checkSum(ls,"1", new String[]{"-1","1","1","1","-1","0"}); + checkSumNoVar(ls,"3", new String[]{"1","2"}); + checkSumNoVar(ls,"1", new String[]{"-1","1","1","1","-1","0"}); + checkSumNoVar(ls,"0", new String[]{"-1","1","1","1","-1","-1"}); + String maxIntVal_1 = Long.toString(1+(long)Integer.MAX_VALUE); + checkSum(ls,maxIntVal, new String[]{maxIntVal,"0"}); + checkSum(ls,maxIntVal_1, new String[]{maxIntVal,"1"}); // no wrap-round check + String maxLongVal = Long.toString(Long.MAX_VALUE); + String minLongVal = Long.toString(Long.MIN_VALUE); + checkSum(ls,maxLongVal, new String[]{maxLongVal,"0"}); + checkSum(ls,minLongVal, new String[]{maxLongVal,"1"}); // wrap-round check + } + + // Perform a sum and check the results + private void checkSum(AbstractFunction func, String value, String [] addends) throws Exception { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + for (int i=0; i< addends.length; i++){ + parms.add(new CompoundVariable(addends[i])); + } + parms.add(new CompoundVariable("Result")); + func.setParameters(parms); + assertEquals(value,func.execute(null,null)); + assertEquals(value,vars.getObject("Result")); + } + // Perform a sum and check the results + private void checkSumNoVar(AbstractFunction func, String value, String [] addends) throws Exception { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + for (int i=0; i< addends.length; i++){ + parms.add(new CompoundVariable(addends[i])); + } + func.setParameters(parms); + assertEquals(value,func.execute(null,null)); + } +} diff --git a/test/src/org/apache/jmeter/functions/TestFileRowColContainer.java b/test/src/org/apache/jmeter/functions/TestFileRowColContainer.java new file mode 100644 index 00000000000..f9978811e29 --- /dev/null +++ b/test/src/org/apache/jmeter/functions/TestFileRowColContainer.java @@ -0,0 +1,143 @@ +/* + * 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.FileNotFoundException; + +import org.apache.jmeter.junit.JMeterTestCase; + +/** + * File data container for CSV (and similar delimited) files Data is accessible + * via row and column number + * + * @version $Revision$ + */ +public class TestFileRowColContainer extends JMeterTestCase { + + public void testNull() throws Exception { + try { + new FileRowColContainer(findTestPath("testfiles/xyzxyz")); + fail("Should not find the file"); + } catch (FileNotFoundException e) { + } + } + + public void testrowNum() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/test.csv")); + assertNotNull(f); + assertEquals("Expected 4 lines", 4, f.getSize()); + + assertEquals(0, f.nextRow()); + assertEquals(1, f.nextRow()); + assertEquals(2, f.nextRow()); + assertEquals(3, f.nextRow()); + assertEquals(0, f.nextRow()); + + } + + public void testColumns() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/test.csv")); + assertNotNull(f); + assertTrue("Not empty", f.getSize() > 0); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals("a1", f.getColumn(myRow, 0)); + assertEquals("d1", f.getColumn(myRow, 3)); + + try { + f.getColumn(myRow, 4); + fail("Expected out of bounds"); + } catch (IndexOutOfBoundsException e) { + } + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals("b2", f.getColumn(myRow, 1)); + assertEquals("c2", f.getColumn(myRow, 2)); + } + + public void testColumnsComma() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/test.csv"), ","); + assertNotNull(f); + assertTrue("Not empty", f.getSize() > 0); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals("a1", f.getColumn(myRow, 0)); + assertEquals("d1", f.getColumn(myRow, 3)); + + try { + f.getColumn(myRow, 4); + fail("Expected out of bounds"); + } catch (IndexOutOfBoundsException e) { + } + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals("b2", f.getColumn(myRow, 1)); + assertEquals("c2", f.getColumn(myRow, 2)); + } + + public void testColumnsTab() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/test.tsv"), "\t"); + assertNotNull(f); + assertTrue("Not empty", f.getSize() > 0); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals("a1", f.getColumn(myRow, 0)); + assertEquals("d1", f.getColumn(myRow, 3)); + + try { + f.getColumn(myRow, 4); + fail("Expected out of bounds"); + } catch (IndexOutOfBoundsException e) { + } + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals("b2", f.getColumn(myRow, 1)); + assertEquals("c2", f.getColumn(myRow, 2)); + } + + public void testEmptyCols() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/testempty.csv")); + assertNotNull(f); + assertEquals("Expected 4 lines", 4, f.getSize()); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals("", f.getColumn(myRow, 0)); + assertEquals("d1", f.getColumn(myRow, 3)); + + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals("", f.getColumn(myRow, 1)); + assertEquals("c2", f.getColumn(myRow, 2)); + + myRow = f.nextRow(); + assertEquals(2, myRow); + assertEquals("b3", f.getColumn(myRow, 1)); + assertEquals("", f.getColumn(myRow, 2)); + + myRow = f.nextRow(); + assertEquals(3, myRow); + assertEquals("b4", f.getColumn(myRow, 1)); + assertEquals("c4", f.getColumn(myRow, 2)); + assertEquals("", f.getColumn(myRow, 3)); + } +} diff --git a/test/src/org/apache/jmeter/functions/TestJexlFunction.java b/test/src/org/apache/jmeter/functions/TestJexlFunction.java new file mode 100644 index 00000000000..4fac1f99257 --- /dev/null +++ b/test/src/org/apache/jmeter/functions/TestJexlFunction.java @@ -0,0 +1,96 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; + +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; + +public class TestJexlFunction extends JMeterTestCase { + private JexlFunction function; + + private SampleResult result; + + private Collection<CompoundVariable> params; + + private JMeterVariables vars; + + private JMeterContext jmctx; + + public TestJexlFunction(String name) { + super(name); + } + + @Override + public void setUp() { + function = new JexlFunction(); + result = new SampleResult(); + jmctx = JMeterContextService.getContext(); + String data = "The quick brown fox"; + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + params = new LinkedList<CompoundVariable>(); + } + + public void testParameterCount() throws Exception { + checkInvalidParameterCounts(function, 1, 2); + } + + public void testSum() throws Exception { + params.add(new CompoundVariable("1+2+3")); + function.setParameters(params); + String ret = function.execute(result, null); + assertEquals("6", ret); + } + + public void testSumVar() throws Exception { + params.add(new CompoundVariable("1+2+3")); + params.add(new CompoundVariable("TOTAL")); + function.setParameters(params); + String ret = function.execute(result, null); + assertEquals("6", ret); + assertEquals("6", vars.get("TOTAL")); + } + + public void testReplace1() throws Exception { + params.add(new CompoundVariable( + "sampleResult.getResponseDataAsString().replaceAll('T','t')")); + function.setParameters(params); + String ret = function.execute(result, null); + assertEquals("the quick brown fox", ret); + } + + public void testReplace2() throws Exception { + vars.put("URL", "/query.cgi?s1=1&s2=2&s3=3"); + params.add(new CompoundVariable("vars.get('URL').replaceAll('&','&')")); + params.add(new CompoundVariable("URL")); + function.setParameters(params); + String ret = function.execute(result, null); + assertEquals("/query.cgi?s1=1&s2=2&s3=3", ret); + assertEquals(ret,vars.getObject("URL")); + } +} diff --git a/test/src/org/apache/jmeter/functions/TestRegexFunction.java b/test/src/org/apache/jmeter/functions/TestRegexFunction.java new file mode 100644 index 00000000000..17adc5169ad --- /dev/null +++ b/test/src/org/apache/jmeter/functions/TestRegexFunction.java @@ -0,0 +1,357 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; + +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; + +public class TestRegexFunction extends JMeterTestCase { + private static final String INPUT_VARIABLE_NAME = "INVAR"; + + private RegexFunction variable; + + private SampleResult result; + + private Collection<CompoundVariable> params; + + private JMeterVariables vars; + + private JMeterContext jmctx; + + public TestRegexFunction(String name) { + super(name); + } + + @Override + public void setUp() { + variable = new RegexFunction(); + result = new SampleResult(); + jmctx = JMeterContextService.getContext(); + String data = "<company-xmlext-query-ret><row>" + "<value field=\"RetCode\">" + "LIS_OK</value><value" + + " field=\"RetCodeExtension\"></value>" + "<value field=\"alias\"></value><value" + + " field=\"positioncount\"></value>" + "<value field=\"invalidpincount\">0</value><value" + + " field=\"pinposition1\">1</value><value" + " field=\"pinpositionvalue1\"></value><value" + + " field=\"pinposition2\">5</value><value" + " field=\"pinpositionvalue2\"></value><value" + + " field=\"pinposition3\">6</value><value" + " field=\"pinpositionvalue3\"></value>" + + "</row></company-xmlext-query-ret>"; + result.setResponseData(data, null); + vars = new JMeterVariables(); + String data2 = "The quick brown fox jumped over the lazy dog 123 times"; + vars.put(INPUT_VARIABLE_NAME, data2); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + } + + public void testVariableExtraction() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$")); + params.add(new CompoundVariable("2")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("5", match); + } + + // Test with output variable name + public void testVariableExtraction1a() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$")); // template + params.add(new CompoundVariable("2")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("3", vars.getObject("OUTVAR_matchNr")); + assertEquals("5", match); + assertEquals("5", vars.getObject("OUTVAR")); + assertEquals("<value field=\"pinposition2\">5</value>", vars.getObject("OUTVAR_g0")); + assertEquals("pinposition2", vars.getObject("OUTVAR_g1")); + assertEquals("5", vars.getObject("OUTVAR_g2")); + } + + // Test with empty output variable name + public void testVariableExtraction1b() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$")); // template + params.add(new CompoundVariable("2")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("5", match); + assertNull(vars.getObject("OUTVAR")); + } + + public void testVariableExtractionFromVariable() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("times", match); + assertEquals("times", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable2() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$1$$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("123times", match); + assertEquals("123times", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable3() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("pre$2$post")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("pretimespost", match); + assertEquals("pretimespost", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable4() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("pre$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("pretimes", match); + assertEquals("pretimes", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable5() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$2$post")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("timespost", match); + assertEquals("timespost", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable6() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$2$$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("timestimes", match); + assertEquals("timestimes", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable7() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("pre$1$mid$2$post")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("pre123midtimespost", match); + assertEquals("pre123midtimespost", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable8() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("pre$1$mid$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("pre123midtimes", match); + assertEquals("pre123midtimes", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable9() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$1$mid$2$post")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("123midtimespost", match); + assertEquals("123midtimespost", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtraction2() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$1$")); + params.add(new CompoundVariable("3")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("pinposition3", match); + } + + public void testVariableExtraction5() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$1$")); + params.add(new CompoundVariable("ALL")); + params.add(new CompoundVariable("_")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("pinposition1_pinposition2_pinposition3", match); + } + + public void testVariableExtraction6() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$")); + params.add(new CompoundVariable("4")); + params.add(new CompoundVariable("")); + params.add(new CompoundVariable("default")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("default", match); + } + + public void testComma() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value,? field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$1$")); + params.add(new CompoundVariable("3")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("pinposition3", match); + } + + public void testVariableExtraction3() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("_$1$")); + params.add(new CompoundVariable("2")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("_pinposition2", match); + } + + public void testVariableExtraction4() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$, ")); + params.add(new CompoundVariable(".333")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1, ", match); + } + + public void testDefaultValue() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value,, field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$, ")); + params.add(new CompoundVariable(".333")); + params.add(new CompoundVariable("")); + params.add(new CompoundVariable("No Value Found")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("No Value Found", match); + } +} diff --git a/test/src/org/apache/jmeter/functions/TestTimeFunction.java b/test/src/org/apache/jmeter/functions/TestTimeFunction.java new file mode 100644 index 00000000000..33f986d0163 --- /dev/null +++ b/test/src/org/apache/jmeter/functions/TestTimeFunction.java @@ -0,0 +1,188 @@ +/* + * 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.util.Collection; +import java.util.LinkedList; +import java.util.Locale; + +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; + +public class TestTimeFunction extends JMeterTestCase { + private Function variable; + + private SampleResult result; + + private Collection<CompoundVariable> params; + + private JMeterVariables vars; + + private JMeterContext jmctx = null; + + private String value; + + public TestTimeFunction(String name) { + super(name); + } + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + params = new LinkedList<CompoundVariable>(); + result = new SampleResult(); + variable = new TimeFunction(); + } + + public void testDefault() throws Exception { + variable.setParameters(params); + long before = System.currentTimeMillis(); + value = variable.execute(result, null); + long now= Long.parseLong(value); + long after = System.currentTimeMillis(); + assertTrue(now >= before && now <= after); + } + + public void testDefault1() throws Exception { + params.add(new CompoundVariable()); + variable.setParameters(params); + long before = System.currentTimeMillis(); + value = variable.execute(result, null); + long now= Long.parseLong(value); + long after = System.currentTimeMillis(); + assertTrue(now >= before && now <= after); + } + + public void testDefault2() throws Exception { + params.add(new CompoundVariable()); + params.add(new CompoundVariable()); + variable.setParameters(params); + long before = System.currentTimeMillis(); + value = variable.execute(result, null); + long now= Long.parseLong(value); + long after = System.currentTimeMillis(); + assertTrue(now >= before && now <= after); + } + + public void testDefaultNone() throws Exception { + long before = System.currentTimeMillis(); + value = variable.execute(result, null); + long now= Long.parseLong(value); + long after = System.currentTimeMillis(); + assertTrue(now >= before && now <= after); + } + + public void testTooMany() throws Exception { + params.add(new CompoundVariable("YMD")); + params.add(new CompoundVariable("NAME")); + params.add(new CompoundVariable("YMD")); + try { + variable.setParameters(params); + fail("Should have raised InvalidVariableException"); + } catch (InvalidVariableException ignored){ + } + } + + public void testYMD() throws Exception { + params.add(new CompoundVariable("YMD")); + params.add(new CompoundVariable("NAME")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(8,value.length()); + assertEquals(value,vars.get("NAME")); + } + + public void testYMDnoV() throws Exception { + params.add(new CompoundVariable("YMD")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(8,value.length()); + assertNull(vars.get("NAME")); + } + + public void testHMS() throws Exception { + params.add(new CompoundVariable("HMS")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(6,value.length()); + } + + public void testYMDHMS() throws Exception { + params.add(new CompoundVariable("YMDHMS")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(15,value.length()); + } + + public void testUSER1() throws Exception { + params.add(new CompoundVariable("USER1")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(0,value.length()); + } + + public void testUSER2() throws Exception { + params.add(new CompoundVariable("USER2")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(0,value.length()); + } + + public void testFixed() throws Exception { + params.add(new CompoundVariable("'Fixed text'")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals("Fixed text",value); + } + + public void testMixed() throws Exception { + params.add(new CompoundVariable("G")); + variable.setParameters(params); + Locale locale = Locale.getDefault(); + Locale.setDefault(Locale.ENGLISH); + value = variable.execute(result, null); + Locale.setDefault(locale); + assertEquals("AD",value); + } + + public void testDivisor() throws Exception { + params.add(new CompoundVariable("/1000")); + variable.setParameters(params); + long before = System.currentTimeMillis()/1000; + value = variable.execute(result, null); + long now= Long.parseLong(value); + long after = System.currentTimeMillis()/1000; + assertTrue(now >= before && now <= after); + } + + public void testDivisorNoMatch() throws Exception { + params.add(new CompoundVariable("/1000 ")); // trailing space + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals("/1000 ", value); + } + +} diff --git a/test/src/org/apache/jmeter/gui/action/PackageTest.java b/test/src/org/apache/jmeter/gui/action/PackageTest.java new file mode 100644 index 00000000000..d877c18b35f --- /dev/null +++ b/test/src/org/apache/jmeter/gui/action/PackageTest.java @@ -0,0 +1,37 @@ +/* + * 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 junit.framework.TestCase; + +public class PackageTest extends TestCase { + + public PackageTest(String arg0) { + super(arg0); + } + + //TODO add tests for SaveGraphics + public void testSaveGraphics() throws Exception { + } + + //TODO add tests for ReportSaveGraphics + public void testReportSaveGraphics() throws Exception { + } + +} diff --git a/test/src/org/apache/jmeter/gui/action/TestLoad.java b/test/src/org/apache/jmeter/gui/action/TestLoad.java new file mode 100644 index 00000000000..cbb66134f68 --- /dev/null +++ b/test/src/org/apache/jmeter/gui/action/TestLoad.java @@ -0,0 +1,112 @@ +/* + * 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.io.File; +import java.io.FilenameFilter; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestSuite; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.save.SaveService; +import org.apache.jorphan.collections.HashTree; + +/** + * + * Test JMX files to check that they can be loaded OK. + */ +public class TestLoad extends JMeterTestCase { + + private static final String basedir = new File(System.getProperty("user.dir")).getParent(); + private static final File testfiledir = new File(basedir,"bin/testfiles"); + private static final File demofiledir = new File(basedir,"xdocs/demos"); + + private static final Set<String> notTestPlan = new HashSet<String>();// not full test plans + + static{ + notTestPlan.add("load_bug_list.jmx");// used by TestAnchorModifier + notTestPlan.add("Load_JMeter_Page.jmx");// used by TestAnchorModifier + notTestPlan.add("ProxyServerTestPlan.jmx");// used by TestSaveService + } + + private static final FilenameFilter jmxFilter = new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".jmx"); + } + }; + + private final File testFile; + private final String parent; + + public TestLoad(String name) { + super(name); + testFile=null; + parent=null; + } + + public TestLoad(String name, File file, String dir) { + super(name); + testFile=file; + parent=dir; + } + + public static TestSuite suite(){ + TestSuite suite=new TestSuite("Load Test"); + //suite.addTest(new TestLoad("checkGuiPackage")); + scanFiles(suite,testfiledir); + scanFiles(suite,demofiledir); + return suite; + } + + private static void scanFiles(TestSuite suite, File parent) { + File testFiles[]=parent.listFiles(jmxFilter); + String dir = parent.getName(); + for (int i=0; i<testFiles.length; i++){ + suite.addTest(new TestLoad("checkTestFile",testFiles[i],dir)); + } + } + + public void checkTestFile() throws Exception{ + HashTree tree = null; + try { + tree =getTree(testFile); + } catch (Exception e) { + fail(parent+": "+ testFile.getName()+" caused "+e); + } + assertTree(tree); + } + + private void assertTree(HashTree tree) throws Exception { + assertNotNull(parent+": "+ testFile.getName()+" caused null tree: ",tree); + final Object object = tree.getArray()[0]; + final String name = testFile.getName(); + + if (! (object instanceof org.apache.jmeter.testelement.TestPlan) && !notTestPlan.contains(name)){ + fail(parent+ ": " +name+" tree should be TestPlan, but is "+object.getClass().getName()); + } + } + + private HashTree getTree(File f) throws Exception { + HashTree tree = SaveService.loadTree(f); + return tree; + } +} diff --git a/test/src/org/apache/jmeter/gui/action/TestSave.java b/test/src/org/apache/jmeter/gui/action/TestSave.java new file mode 100644 index 00000000000..3e4cd53f522 --- /dev/null +++ b/test/src/org/apache/jmeter/gui/action/TestSave.java @@ -0,0 +1,50 @@ +/* + * 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 org.apache.jmeter.config.Arguments; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; + +public class TestSave extends junit.framework.TestCase { + private Save save; + + public TestSave(String name) { + super(name); + } + + @Override + public void setUp() { + save = new Save(); + } + + public void testTreeConversion() throws Exception { + HashTree tree = new ListedHashTree(); + JMeterTreeNode root = new JMeterTreeNode(new Arguments(), null); + tree.add(root, root); + tree.getTree(root).add(root, root); + save.convertSubTree(tree); + assertEquals(tree.getArray()[0].getClass().getName(), root.getTestElement().getClass().getName()); + tree = tree.getTree(tree.getArray()[0]); + assertEquals(tree.getArray()[0].getClass().getName(), root.getTestElement().getClass().getName()); + assertEquals(tree.getTree(tree.getArray()[0]).getArray()[0].getClass().getName(), root.getTestElement() + .getClass().getName()); + } +} diff --git a/test/src/org/apache/jmeter/gui/util/JSyntaxTextAreaTest.java b/test/src/org/apache/jmeter/gui/util/JSyntaxTextAreaTest.java new file mode 100644 index 00000000000..0051e027a67 --- /dev/null +++ b/test/src/org/apache/jmeter/gui/util/JSyntaxTextAreaTest.java @@ -0,0 +1,74 @@ +/* + * 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.util; + +import java.awt.HeadlessException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Properties; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.util.JMeterUtils; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; + +public class JSyntaxTextAreaTest extends JMeterTestCase { + + public void testSetLanguage() { + try { + JSyntaxTextArea textArea = new JSyntaxTextArea(30, 50, false); + textArea.setLanguage(null); + assertEquals(SyntaxConstants.SYNTAX_STYLE_NONE, textArea.getSyntaxEditingStyle()); + } catch (HeadlessException he) { + // Does not work in headless mode + } + } + + public void testSyntaxNames() throws IllegalArgumentException, + IllegalAccessException { + HashSet<String> values = new HashSet<String>(); + for (Field field : SyntaxConstants.class.getFields()) { + int modifiers = field.getModifiers(); + if (field.getType().equals(String.class) + && Modifier.isStatic(modifiers) + && Modifier.isPublic(modifiers)) { + values.add((String) field.get(null)); + } + } + final Properties languageProperties = JMeterUtils + .loadProperties("org/apache/jmeter/gui/util/textarea.properties"); //$NON-NLS-1$; + for (Object s : languageProperties.values()) { + if (!values.contains(s)) { + fail("Invalid property value: " + s); + } + } + // Show unused entries +// for (Object s : languageProperties.values()) { +// values.remove(s); +// } +// if (values.size() > 0) { +// System.out.print("Unused JSyntaxAreaTypes:"); +// for (String value : values) { +// System.out.print(" "); +// System.out.print(value); +// } +// System.out.println(); +// } + } +} diff --git a/test/src/org/apache/jmeter/gui/util/TestMenuFactory.java b/test/src/org/apache/jmeter/gui/util/TestMenuFactory.java new file mode 100644 index 00000000000..1de0355a357 --- /dev/null +++ b/test/src/org/apache/jmeter/gui/util/TestMenuFactory.java @@ -0,0 +1,53 @@ +/* + * 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.util; + +import org.apache.jmeter.junit.JMeterTestCase; + +public final class TestMenuFactory extends JMeterTestCase { + + public TestMenuFactory() { + super(); + } + + public TestMenuFactory(String name) { + super(name); + } + + private static void check(String s, int i) throws Exception { + assertFalse("The number of " + s + " should not be 0", 0 == i); + } + + public void testMenu() throws Exception { + check("menumap", MenuFactory.menuMap_size()); + + check("assertions", MenuFactory.assertions_size()); + check("configElements", MenuFactory.configElements_size()); + check("controllers", MenuFactory.controllers_size()); + check("listeners", MenuFactory.listeners_size()); + check("nonTestElements", MenuFactory.nonTestElements_size()); + check("postProcessors", MenuFactory.postProcessors_size()); + check("preProcessors", MenuFactory.preProcessors_size()); + check("samplers", MenuFactory.samplers_size()); + check("timers", MenuFactory.timers_size()); + + check("elementstoskip", MenuFactory.elementsToSkip_size()); + + } +} diff --git a/test/src/org/apache/jmeter/gui/util/TristateCheckBoxTest.java b/test/src/org/apache/jmeter/gui/util/TristateCheckBoxTest.java new file mode 100644 index 00000000000..b8ade11ba04 --- /dev/null +++ b/test/src/org/apache/jmeter/gui/util/TristateCheckBoxTest.java @@ -0,0 +1,117 @@ +/* + * 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.util; + +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.UIManager; + +//derived from: http://www.javaspecialists.eu/archive/Issue145.html + +public class TristateCheckBoxTest { + public static void main(String args[]) throws Exception { + JFrame frame = new JFrame("TristateCheckBoxTest"); + frame.setLayout(new GridLayout(0, 1, 15, 15)); + UIManager.LookAndFeelInfo[] lfs = + UIManager.getInstalledLookAndFeels(); + for (UIManager.LookAndFeelInfo lf : lfs) { + System.out.println("Look&Feel " + lf.getName()); + UIManager.setLookAndFeel(lf.getClassName()); + frame.add(makePanel(lf.getName())); + } + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.pack(); + frame.setVisible(true); + } + + private static JPanel makePanel(String name) { + final TristateCheckBox tristateBox = new TristateCheckBox("Tristate checkbox (icon)", false); + createTristate(tristateBox); + final TristateCheckBox tristateBoxorig = new TristateCheckBox("Tristate checkbox (original)", true); + createTristate(tristateBoxorig); + final JCheckBox normalBox = new JCheckBox("Normal checkbox"); + normalBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + System.out.println(e); + } + }); + + final JCheckBox enabledBox = new JCheckBox("Enable", true); + enabledBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + tristateBox.setEnabled(enabledBox.isSelected()); + normalBox.setEnabled(enabledBox.isSelected()); + } + }); + + JPanel panel = new JPanel(new GridLayout(0, 1, 5, 5)); + panel.add(new JLabel(name)); + panel.add(tristateBox); + panel.add(tristateBoxorig); + panel.add(normalBox); + panel.add(enabledBox); + return panel; + } + + private static void createTristate(final TristateCheckBox tristateBox) { + tristateBox.setIndeterminate(); // start in new state + tristateBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + System.out.println(e); + switch(tristateBox.getState()) { + case SELECTED: + System.out.println("Selected"); break; + case DESELECTED: + System.out.println("Not Selected"); break; + case INDETERMINATE: + System.out.println("Tristate Selected"); break; + default: + System.err.println("Unexpected state: " + tristateBox.getState()); break; + } + } + }); + tristateBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + System.out.println(e); + switch(tristateBox.getState()) { + case SELECTED: + System.out.println("Selected"); break; + case DESELECTED: + System.out.println("Not Selected"); break; + case INDETERMINATE: + System.out.println("Tristate Selected"); break; + default: + System.err.println("Unexpected state: " + tristateBox.getState()); break; + } + } + }); + } +} diff --git a/test/src/org/apache/jmeter/junit/JMeterTest.java b/test/src/org/apache/jmeter/junit/JMeterTest.java new file mode 100644 index 00000000000..6acabfd968d --- /dev/null +++ b/test/src/org/apache/jmeter/junit/JMeterTest.java @@ -0,0 +1,657 @@ +/* + * 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.junit; + +import java.awt.Component; +import java.awt.HeadlessException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.rmi.RemoteException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.jmeter.config.gui.ObsoleteGui; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.functions.Function; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TestBeanGUI; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.input.SAXBuilder; + +public class JMeterTest extends JMeterTestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static Map<String, Boolean> guiTitles; + + private static Map<String, Boolean> guiTags; + + private static Map<String, Boolean> funcTitles; + + private static Properties nameMap; + + private static final Locale TEST_LOCALE = Locale.ENGLISH; + + private static final Locale DEFAULT_LOCALE = Locale.getDefault(); + + public JMeterTest(String name) { + super(name); + } + + /* + * The suite() method creates separate test suites for each of the types of + * test. The suitexxx() methods create a list of items to be tested, and + * create a new test instance for each. + * + * Each test type has its own constructor, which saves the item to be tested + * + * Note that the suite() method must be static, and the methods to run the + * tests must be instance methods so that they can pick up the item value + * which was saved by the constructor. + * + */ + // Constructor for TestElement tests + private TestElement testItem; + + public JMeterTest(String testName, TestElement te) { + super(testName);// Save the method name + testItem = te; + } + + // Constructor for Serializable tests + private Serializable serObj; + + public JMeterTest(String testName, Serializable ser) { + super(testName);// Save the method name + serObj = ser; + } + + // Constructor for GUI tests + private JMeterGUIComponent guiItem; + + public JMeterTest(String testName, JMeterGUIComponent gc) { + super(testName);// Save the method name + guiItem = gc; + } + + // Constructor for Function tests + private Function funcItem; + + private static volatile boolean classPathShown = false;// Only show classpath once + + public JMeterTest(String testName, Function fi) { + super(testName);// Save the method name + funcItem = fi; + } + + /* + * Use a suite to allow the tests to be generated at run-time + */ + public static Test suite() throws Exception { + // The Locale used to instantiate the GUI objects + JMeterUtils.setLocale(TEST_LOCALE); + Locale.setDefault(TEST_LOCALE); + // Needs to be done before any GUI classes are instantiated + + TestSuite suite = new TestSuite("JMeterTest"); + suite.addTest(new JMeterTest("readAliases")); + suite.addTest(new JMeterTest("createTitleSet")); + suite.addTest(new JMeterTest("createTagSet")); + suite.addTest(suiteGUIComponents()); + suite.addTest(suiteSerializableElements()); + suite.addTest(suiteTestElements()); + suite.addTest(suiteBeanComponents()); + suite.addTest(new JMeterTest("createFunctionSet")); + suite.addTest(suiteFunctions()); + suite.addTest(new JMeterTest("checkGuiSet")); + suite.addTest(new JMeterTest("checkFunctionSet")); + + suite.addTest(new JMeterTest("resetLocale")); // revert + return suite; + } + + // Restore the original Locale + public void resetLocale(){ + JMeterUtils.setLocale(DEFAULT_LOCALE); + Locale.setDefault(DEFAULT_LOCALE); + } + + /* + * Extract titles from component_reference.xml + */ + public void createTitleSet() throws Exception { + guiTitles = new HashMap<String, Boolean>(90); + + String compref = "../xdocs/usermanual/component_reference.xml"; + SAXBuilder bldr = new SAXBuilder(); + Document doc; + doc = bldr.build(compref); + Element root = doc.getRootElement(); + Element body = root.getChild("body"); + @SuppressWarnings("unchecked") + List<Element> sections = body.getChildren("section"); + for (int i = 0; i < sections.size(); i++) { + @SuppressWarnings("unchecked") + List<Element> components = sections.get(i).getChildren("component"); + for (int j = 0; j < components.size(); j++) { + Element comp = components.get(j); + String nm=comp.getAttributeValue("name"); + if (!nm.equals("SSL Manager")){// Not a true GUI component + guiTitles.put(nm.replace(' ','_'), Boolean.FALSE); + } + } + } + // Add titles that don't need to be documented + //guiTitles.put("Root", Boolean.FALSE); + guiTitles.put("Example Sampler", Boolean.FALSE); + } + + /* + * Extract titles from component_reference.xml + */ + public void createTagSet() throws Exception { + guiTags = new HashMap<String, Boolean>(90); + + String compref = "../xdocs/usermanual/component_reference.xml"; + SAXBuilder bldr = new SAXBuilder(); + Document doc; + doc = bldr.build(compref); + Element root = doc.getRootElement(); + Element body = root.getChild("body"); + @SuppressWarnings("unchecked") + List<Element> sections = body.getChildren("section"); + for (int i = 0; i < sections.size(); i++) { + @SuppressWarnings("unchecked") + List<Element> components = sections.get(i).getChildren("component"); + for (int j = 0; j < components.size(); j++) { + Element comp = components.get(j); + guiTags.put(comp.getAttributeValue("tag"), Boolean.FALSE); + } + } + } + + /* + * Extract titles from functions.xml + */ + public void createFunctionSet() throws Exception { + funcTitles = new HashMap<String, Boolean>(20); + + String compref = "../xdocs/usermanual/functions.xml"; + SAXBuilder bldr = new SAXBuilder(); + Document doc; + doc = bldr.build(compref); + Element root = doc.getRootElement(); + Element body = root.getChild("body"); + Element section = body.getChild("section"); + @SuppressWarnings("unchecked") + List<Element> sections = section.getChildren("subsection"); + for (int i = 0; i < sections.size(); i++) { + @SuppressWarnings("unchecked") + List<Element> components = sections.get(i).getChildren("component"); + for (int j = 0; j < components.size(); j++) { + Element comp = components.get(j); + funcTitles.put(comp.getAttributeValue("name"), Boolean.FALSE); + String tag = comp.getAttributeValue("tag"); + if (tag != null){ + funcTitles.put(tag, Boolean.FALSE); + } + } + } + } + + private int scanprintMap(Map<String, Boolean> m, String t) { + Set<String> s = m.keySet(); + int unseen = 0; + if (s.size() == 0) { + return 0; + } + Iterator<String> i = s.iterator(); + while (i.hasNext()) { + String key = i.next(); + if (!m.get(key).equals(Boolean.TRUE)) { + if (unseen == 0)// first time + { + System.out.println("\nNames remaining in " + t + " Map:"); + } + unseen++; + System.out.println(key); + } + } + return unseen; + } + + public void checkGuiSet() throws Exception { + guiTitles.remove("Example Sampler");// We don't mind if this is left over + guiTitles.remove("Sample_Result_Save_Configuration");// Ditto, not a sampler + assertEquals("Should not have any names left over, check name of components in EN (default) Locale, which must match name attribute of component", 0, scanprintMap(guiTitles, "GUI")); + } + + public void checkFunctionSet() throws Exception { + assertEquals("Should not have any names left over", 0, scanprintMap(funcTitles, "Function")); + } + + /* + * Test GUI elements - create the suite of tests + */ + private static Test suiteGUIComponents() throws Exception { + TestSuite suite = new TestSuite("GuiComponents"); + Iterator<Object> iter = getObjects(JMeterGUIComponent.class).iterator(); + while (iter.hasNext()) { + JMeterGUIComponent item = (JMeterGUIComponent) iter.next(); + if (item instanceof JMeterTreeNode) { + System.out.println("o.a.j.junit.JMeterTest INFO: JMeterGUIComponent: skipping all tests " + item.getClass().getName()); + continue; + } + if (item instanceof ObsoleteGui){ + continue; + } + TestSuite ts = new TestSuite(item.getClass().getName()); + ts.addTest(new JMeterTest("GUIComponents1", item)); + if (item instanceof TestBeanGUI) { + System.out.println("o.a.j.junit.JMeterTest INFO: JMeterGUIComponent: skipping some tests " + item.getClass().getName()); + } else { + ts.addTest(new JMeterTest("GUIComponents2", item)); + ts.addTest(new JMeterTest("runGUITitle", item)); + } + suite.addTest(ts); + } + return suite; + } + + /* + * Test Functions - create the suite of tests + */ + private static Test suiteFunctions() throws Exception { + TestSuite suite = new TestSuite("Functions"); + Iterator<Object> iter = getObjects(Function.class).iterator(); + while (iter.hasNext()) { + Object item = iter.next(); + if (item.getClass().equals(CompoundVariable.class)) { + continue; + } + TestSuite ts = new TestSuite(item.getClass().getName()); + ts.addTest(new JMeterTest("runFunction", (Function) item)); + ts.addTest(new JMeterTest("runFunction2", (Function) item)); + suite.addTest(ts); + } + return suite; + } + + /* + * Test GUI elements - create the suite of tests + */ + private static Test suiteBeanComponents() throws Exception { + TestSuite suite = new TestSuite("BeanComponents"); + Iterator<Object> iter = getObjects(TestBean.class).iterator(); + while (iter.hasNext()) { + Class<? extends Object> c = iter.next().getClass(); + try { + JMeterGUIComponent item = new TestBeanGUI(c); + // JMeterGUIComponent item = (JMeterGUIComponent) iter.next(); + TestSuite ts = new TestSuite(item.getClass().getName()); + ts.addTest(new JMeterTest("GUIComponents2", item)); + ts.addTest(new JMeterTest("runGUITitle", item)); + suite.addTest(ts); + } catch (IllegalArgumentException e) { + System.out.println("o.a.j.junit.JMeterTest Cannot create test for " + c.getName() + " " + e); + e.printStackTrace(System.out); + } + } + return suite; + } + + /* + * Test GUI elements - run the test + */ + public void runGUITitle() throws Exception { + if (guiTitles.size() > 0) { + String title = guiItem.getDocAnchor(); + boolean ct = guiTitles.containsKey(title); + if (ct) { + guiTitles.put(title, Boolean.TRUE);// So we can detect extra entries + } + String name = guiItem.getClass().getName(); + if (// Is this a work in progress or an internal GUI component? + (title != null && title.length() > 0) // Will be "" for internal components + && (title.toUpperCase(java.util.Locale.ENGLISH).indexOf("(ALPHA") == -1) + && (title.toUpperCase(java.util.Locale.ENGLISH).indexOf("(BETA") == -1) + && (!title.matches("Example\\d+")) // Skip the example samplers ... + && (!name.startsWith("org.apache.jmeter.examples.")) + && (!name.startsWith("org.apache.jmeter.report.")) // Skip report packages as implementation is incomplete + && (!name.equals("org.apache.jmeter.control.gui.ReportGui"))) // Skip report GUI as implementation is incomplete + {// No, not a work in progress ... + String s = "component_reference.xml needs '" + title + "' anchor for " + name; + if (!ct) { + log.warn(s); // Record in log as well + } + assertTrue(s, ct); + } + } + } + + /* + * run the function test + */ + public void runFunction() throws Exception { + if (funcTitles.size() > 0) { + String title = funcItem.getReferenceKey(); + boolean ct = funcTitles.containsKey(title); + if (ct) { + funcTitles.put(title, Boolean.TRUE);// For detecting extra entries + } + if (// Is this a work in progress ? + title.indexOf("(ALPHA") == -1 && title.indexOf("(EXPERIMENTAL") == -1) {// No, + // not + // a + // work + // in + // progress + // ... + String s = "function.xml needs '" + title + "' entry for " + funcItem.getClass().getName(); + if (!ct) { + log.warn(s); // Record in log as well + } + assertTrue(s, ct); + } + } + } + + + /* + * Check that function descriptions are OK + */ + public void runFunction2() throws Exception { + Iterator<?> i = funcItem.getArgumentDesc().iterator(); + while (i.hasNext()) { + Object o = i.next(); + assertTrue("Description must be a String", o instanceof String); + assertFalse("Description must not start with [refkey", ((String) o).startsWith("[refkey")); + } + } + + /* + * Test GUI elements - run for all components + */ + public void GUIComponents1() throws Exception { + String name = guiItem.getClass().getName(); + + assertEquals("Name should be same as static label for " + name, guiItem.getStaticLabel(), guiItem.getName()); + if (name.startsWith("org.apache.jmeter.examples.")){ + return; + } + if (!name.endsWith("TestBeanGUI")) { + try { + String label = guiItem.getLabelResource(); + assertNotNull("Label should not be null for "+name, label); + assertTrue("Label should not be empty for "+name, label.length() > 0); + assertFalse("'" + label + "' should be in resource file for " + name, JMeterUtils.getResString( + label).startsWith(JMeterUtils.RES_KEY_PFX)); + } catch (UnsupportedOperationException uoe) { + log.warn("Class has not yet implemented getLabelResource " + name); + } + } + checkElementAlias(guiItem); + } + + /* + * Test GUI elements - not run for TestBeanGui items + */ + public void GUIComponents2() throws Exception { + String name = guiItem.getClass().getName(); + + // TODO these assertions should be separate tests + + TestElement el = guiItem.createTestElement(); + assertNotNull(name + ".createTestElement should be non-null ", el); + assertEquals("GUI-CLASS: Failed on " + name, name, el.getPropertyAsString(TestElement.GUI_CLASS)); + + assertEquals("NAME: Failed on " + name, guiItem.getName(), el.getName()); + assertEquals("TEST-CLASS: Failed on " + name, el.getClass().getName(), el + .getPropertyAsString(TestElement.TEST_CLASS)); + TestElement el2 = guiItem.createTestElement(); + el.setName("hey, new name!:"); + el.setProperty("NOT", "Shouldn't be here"); + if (!(guiItem instanceof UnsharedComponent)) { + assertEquals("SHARED: Failed on " + name, "", el2.getPropertyAsString("NOT")); + } + log.debug("Saving element: " + el.getClass()); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + SaveService.saveElement(el, bos); + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + bos.close(); + el = (TestElement) SaveService.loadElement(bis); + bis.close(); + assertNotNull("Load element failed on: "+name,el); + guiItem.configure(el); + assertEquals("CONFIGURE-TEST: Failed on " + name, el.getName(), guiItem.getName()); + guiItem.modifyTestElement(el2); + assertEquals("Modify Test: Failed on " + name, "hey, new name!:", el2.getName()); + } + + /* + * Test serializable elements - create the suite of tests + */ + private static Test suiteSerializableElements() throws Exception { + TestSuite suite = new TestSuite("SerializableElements"); + Iterator<Object> iter = getObjects(Serializable.class).iterator(); + while (iter.hasNext()) { + Serializable serObj = (Serializable) iter.next(); + if (serObj.getClass().getName().endsWith("_Stub")) { + continue; + } + TestSuite ts = new TestSuite(serObj.getClass().getName()); + ts.addTest(new JMeterTest("runSerialTest", serObj)); + suite.addTest(ts); + } + return suite; + } + + /* + * Test serializable elements - test the object + */ + public void runSerialTest() throws Exception { + if (!(serObj instanceof Component)) {// + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bytes); + out.writeObject(serObj); + out.close(); + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray())); + Object readObject = in.readObject(); + in.close(); + assertEquals("deserializing class: " + serObj.getClass().getName(), serObj.getClass(), readObject + .getClass()); + } catch (Exception e) { + fail("serialization of " + serObj.getClass().getName() + " failed: " + e); + } + } + } + + /* + * Test TestElements - create the suite + */ + private static Test suiteTestElements() throws Exception { + TestSuite suite = new TestSuite("TestElements"); + Iterator<Object> iter = getObjects(TestElement.class).iterator(); + while (iter.hasNext()) { + TestElement item = (TestElement) iter.next(); + TestSuite ts = new TestSuite(item.getClass().getName()); + ts.addTest(new JMeterTest("runTestElement", item)); + suite.addTest(ts); + } + return suite; + } + + /* + * Test TestElements - implement the test case + */ + public void runTestElement() throws Exception { + checkElementCloning(testItem); + String name = testItem.getClass().getName(); + assertTrue(name + " must implement Serializable", testItem instanceof Serializable); + if (name.startsWith("org.apache.jmeter.examples.")){ + return; + } + if (name.equals("org.apache.jmeter.control.TransactionSampler")){ + return; // Not a real sampler + } + + checkElementAlias(testItem); + } + + public void readAliases() throws Exception { + nameMap = SaveService.loadProperties(); + assertNotNull("SaveService nameMap (saveservice.properties) should not be null",nameMap); + } + + private void checkElementAlias(Object item) { + String name=item.getClass().getName(); + boolean contains = nameMap.values().contains(name); + if (!contains){ + //System.out.println(name.substring(name.lastIndexOf('.')+1)+"="+name); + fail("SaveService nameMap (saveservice.properties) should contain "+name); + } + } + + private static Collection<Object> getObjects(Class<?> extendsClass) throws Exception { + String exName = extendsClass.getName(); + Object myThis = ""; + Iterator<String> classes = ClassFinder + .findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { extendsClass }).iterator(); + List<Object> objects = new LinkedList<Object>(); + String n = ""; + boolean caughtError = true; + Throwable caught = null; + try { + while (classes.hasNext()) { + n = classes.next(); + // TODO - improve this check + if (n.endsWith("RemoteJMeterEngineImpl")) { + continue; // Don't try to instantiate remote server + } + Class<?> c = null; + try { + c = Class.forName(n); + try { + // Try with a parameter-less constructor first + objects.add(c.newInstance()); + } catch (InstantiationException e) { + caught = e; + // System.out.println(e.toString()); + try { + // Events often have this constructor + objects.add(c.getConstructor(new Class[] { Object.class }).newInstance( + new Object[] { myThis })); + } catch (NoSuchMethodException f) { + // no luck. Ignore this class + System.out.println("o.a.j.junit.JMeterTest WARN: " + exName + ": NoSuchMethodException " + n + ", missing empty Constructor or Constructor with Object parameter"); + } + } + } catch (NoClassDefFoundError e) { + // no luck. Ignore this class + System.out.println("o.a.j.junit.JMeterTest WARN: " + exName + ": NoClassDefFoundError " + n); + } catch (IllegalAccessException e) { + caught = e; + System.out.println("o.a.j.junit.JMeterTest WARN: " + exName + ": IllegalAccessException " + n); + // We won't test restricted-access classes. + } catch (HeadlessException e) { + caught = e; + System.out.println("o.a.j.junit.JMeterTest Error creating "+n+" "+e.toString()); + } catch (Exception e) { + caught = e; + if (e instanceof RemoteException) { // not thrown, so need to check here + System.out.println("o.a.j.junit.JMeterTest WARN: " + "Error creating " + n + " " + e.toString()); + } else { + throw new Exception("Error creating " + n, e); + } + } + } + caughtError = false; + } finally { + if (caughtError) { + System.out.println("Last class=" + n); + System.out.println("objects.size=" + objects.size()); + System.out.println("Last error=" + caught); + } + } + + if (objects.size() == 0) { + System.out.println("No classes found that extend " + exName + ". Check the following:"); + System.out.println("Search paths are:"); + String ss[] = JMeterUtils.getSearchPaths(); + for (int i = 0; i < ss.length; i++) { + System.out.println(ss[i]); + } + if (!classPathShown) {// Only dump it once + System.out.println("Class path is:"); + String cp = System.getProperty("java.class.path"); + String cpe[] = JOrphanUtils.split(cp, java.io.File.pathSeparator); + for (int i = 0; i < cpe.length; i++) { + System.out.println(cpe[i]); + } + classPathShown = true; + } + } + return objects; + } + + private static void cloneTesting(TestElement item, TestElement clonedItem) { + assertTrue(item != clonedItem); + assertEquals("CLONE-SAME-CLASS: testing " + item.getClass().getName(), item.getClass().getName(), clonedItem + .getClass().getName()); + } + + private static void checkElementCloning(TestElement item) { + TestElement clonedItem = (TestElement) item.clone(); + cloneTesting(item, clonedItem); + PropertyIterator iter2 = item.propertyIterator(); + while (iter2.hasNext()) { + JMeterProperty item2 = iter2.next(); + // [sebb] assertEquals(item2, + // clonedItem.getProperty(item2.getName())); + assertEquals(item2.getStringValue(), clonedItem.getProperty(item2.getName()).getStringValue()); + assertTrue(item2 != clonedItem.getProperty(item2.getName())); + } + } +} diff --git a/test/src/org/apache/jmeter/junit/JMeterTestCase.java b/test/src/org/apache/jmeter/junit/JMeterTestCase.java new file mode 100644 index 00000000000..8d1ea41d14a --- /dev/null +++ b/test/src/org/apache/jmeter/junit/JMeterTestCase.java @@ -0,0 +1,177 @@ +/* + * 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.junit; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Locale; +import java.util.MissingResourceException; + +import junit.framework.TestCase; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.functions.AbstractFunction; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/* + * Extend JUnit TestCase to provide common setup + */ +public abstract class JMeterTestCase extends TestCase { + // Used by findTestFile + private static final String filePrefix; + + public JMeterTestCase() { + super(); + } + + public JMeterTestCase(String name) { + super(name); + } + + /* + * If not running under AllTests.java, make sure that the properties (and + * log file) are set up correctly. + * + * N.B. In order for this to work correctly, the JUnit test must be started + * in the bin directory, and all the JMeter jars (plus any others needed at + * run-time) need to be on the classpath. + * + */ + static { + if (JMeterUtils.getJMeterProperties() == null) { + String file = "testfiles/jmetertest.properties"; + File f = new File(file); + if (!f.canRead()) { + System.out.println("Can't find " + file + " - trying bin directory"); + file = "bin/" + file;// JMeterUtils assumes Unix-style separators + filePrefix = "bin/"; + } else { + filePrefix = ""; + } + // Used to be done in initializeProperties + String home=new File(System.getProperty("user.dir"),filePrefix).getParent(); + System.out.println("Setting JMeterHome: "+home); + JMeterUtils.setJMeterHome(home); + System.setProperty("jmeter.home", home); // needed for scripts + JMeterUtils jmu = new JMeterUtils(); + try { + jmu.initializeProperties(file); + } catch (MissingResourceException e) { + System.out.println("** Can't find resources - continuing anyway **"); + } + System.out.println("JMeterVersion="+JMeterUtils.getJMeterVersion()); + logprop("java.version"); + logprop("java.vm.name"); + logprop("java.vendor"); + logprop("java.home"); + logprop("file.encoding"); + // Display actual encoding used (will differ if file.encoding is not recognised) + System.out.println("default encoding="+Charset.defaultCharset()); + logprop("user.home"); + logprop("user.dir"); + logprop("user.language"); + logprop("user.region"); + logprop("user.country"); + logprop("user.variant"); + System.out.println("Locale="+Locale.getDefault().toString()); + logprop("java.class.version"); + logprop("os.name"); + logprop("os.version"); + logprop("os.arch"); + logprop("java.class.path"); + // String cp = System.getProperty("java.class.path"); + // String cpe[]= JOrphanUtils.split(cp,File.pathSeparator); + // System.out.println("java.class.path="); + // for (int i=0;i<cpe.length;i++){ + // System.out.println(cpe[i]); + // } + } else { + filePrefix = ""; + } + } + + private static void logprop(String prop) { + System.out.println(prop + "=" + System.getProperty(prop)); + } + + // Helper method to find a file + protected static File findTestFile(String file) { + File f = new File(file); + if (filePrefix.length() > 0 && !f.isAbsolute()) { + f = new File(filePrefix, file);// Add the offset + } + return f; + } + + // Helper method to find a test path + protected static String findTestPath(String file) { + File f = new File(file); + if (filePrefix.length() > 0 && !f.isAbsolute()) { + return filePrefix + file;// Add the offset + } + return file; + } + + protected static final Logger testLog = LoggingManager.getLoggerForClass(); + + protected void checkInvalidParameterCounts(AbstractFunction func, int minimum) + throws Exception { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + for (int c = 0; c < minimum; c++) { + try { + func.setParameters(parms); + fail("Should have generated InvalidVariableException for " + parms.size() + + " parameters"); + } catch (InvalidVariableException ignored) { + } + parms.add(new CompoundVariable()); + } + func.setParameters(parms); + } + + protected void checkInvalidParameterCounts(AbstractFunction func, int min, + int max) throws Exception { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + for (int count = 0; count < min; count++) { + try { + func.setParameters(parms); + fail("Should have generated InvalidVariableException for " + parms.size() + + " parameters"); + } catch (InvalidVariableException ignored) { + } + parms.add(new CompoundVariable()); + } + for (int count = min; count <= max; count++) { + func.setParameters(parms); + parms.add(new CompoundVariable()); + } + parms.add(new CompoundVariable()); + try { + func.setParameters(parms); + fail("Should have generated InvalidVariableException for " + parms.size() + + " parameters"); + } catch (InvalidVariableException ignored) { + } + } +} diff --git a/test/src/org/apache/jmeter/junit/stubs/TestSampler.java b/test/src/org/apache/jmeter/junit/stubs/TestSampler.java new file mode 100644 index 00000000000..cd767514528 --- /dev/null +++ b/test/src/org/apache/jmeter/junit/stubs/TestSampler.java @@ -0,0 +1,69 @@ +/* + * 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.junit.stubs; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; + +public class TestSampler extends AbstractSampler { + + private static final long serialVersionUID = 240L; + + private long wait = 0; + + private long samples = 0; // number of samples taken + + /** + * {@inheritDoc} + */ + @Override + public SampleResult sample(Entry e) { + if (wait > 0) { + try { + Thread.sleep(wait); + } catch (InterruptedException e1) { + // ignore + } + } + samples++; + return null; + } + + public TestSampler(String name, long wait) { + setName(name); + this.wait = wait; + } + + public TestSampler(String name) { + setName(name); + } + + public TestSampler() { + } + + @Override + public String toString() { + return getName(); + } + + public long getSamples() { + return samples; + } +} diff --git a/test/src/org/apache/jmeter/monitor/model/TestObjectFactory.java b/test/src/org/apache/jmeter/monitor/model/TestObjectFactory.java new file mode 100644 index 00000000000..512300eadc6 --- /dev/null +++ b/test/src/org/apache/jmeter/monitor/model/TestObjectFactory.java @@ -0,0 +1,73 @@ +/* + * 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.monitor.model; + +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.jmeter.junit.JMeterTestCase; + +public class TestObjectFactory extends JMeterTestCase { + + private ObjectFactory of; + + private Status status; + + @Override + public void setUp(){ + of = ObjectFactory.getInstance(); + } + + + public void testStatus() throws Exception { + status = of.parseString("<status></status>"); + assertNotNull(status); + } + + public void testNoStatus() throws Exception { + status = of.parseString("<a></a>"); + assertNull(status); + } + + public void testFileData() throws Exception { + byte[] bytes= FileUtils.readFileToByteArray(findTestFile("testfiles/monitorStatus.xml")); + status = of.parseBytes(bytes); + checkResult(); + } + + public void testStringData() throws Exception { + String content = FileUtils.readFileToString(findTestFile("testfiles/monitorStatus.xml")); + status = of.parseString(content); + checkResult(); + } + + private void checkResult(){ + assertNotNull(status); + final Jvm jvm = status.getJvm(); + assertNotNull(jvm); + final Memory memory = jvm.getMemory(); + assertNotNull(memory); + assertEquals(10807352, memory.getFree()); + assertEquals(16318464, memory.getTotal()); + assertEquals(259522560, memory.getMax()); + final List<Connector> connector = status.getConnector(); + assertNotNull(connector); + assertEquals(2, connector.size()); + Connector conn = connector.get(0); + assertEquals(200, conn.getThreadInfo().getMaxThreads()); + } +} diff --git a/test/src/org/apache/jmeter/monitor/model/benchmark/ParseBenchmark.java b/test/src/org/apache/jmeter/monitor/model/benchmark/ParseBenchmark.java new file mode 100644 index 00000000000..b1cf59e6d7f --- /dev/null +++ b/test/src/org/apache/jmeter/monitor/model/benchmark/ParseBenchmark.java @@ -0,0 +1,104 @@ +/* + * 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.monitor.model.benchmark; + +import org.apache.commons.io.IOUtils; + +public class ParseBenchmark { + + /** + * + */ + public ParseBenchmark() { + super(); + } + + public static void main(String[] args) { + if (args.length == 3) { + int parser = 0; + String file = null; + int loops = 1000; + if (args[0] != null) { + if (!args[0].equals("jaxb")) { + parser = 1; + } + } + if (args[1] != null) { + file = args[1]; + } + if (args[2] != null) { + loops = Integer.parseInt(args[2]); + } + java.io.File infile = new java.io.File(file); + java.io.FileInputStream fis = null; + java.io.InputStreamReader isr = null; + java.io.BufferedReader br = null; + StringBuilder buf = new StringBuilder(); + try { + fis = new java.io.FileInputStream(infile); + isr = new java.io.InputStreamReader(fis); + br = new java.io.BufferedReader(isr); + String line = null; + while ((line = br.readLine()) != null) { + buf.append(line); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(br); + IOUtils.closeQuietly(isr); + IOUtils.closeQuietly(fis); + } + long start = 0; + long end = 0; + String contents = buf.toString().trim(); + System.out.println("start test: " + loops + " iterations"); + System.out.println("content:"); + System.out.println(contents); + + if (parser == 0) { + /** + * try { JAXBContext jxbc = new + * org.apache.jorphan.tomcat.manager.ObjectFactory(); + * Unmarshaller mar = jxbc.createUnmarshaller(); + * + * start = System.currentTimeMillis(); for (int idx=0; idx < + * loops; idx++){ StreamSource ss = new StreamSource( new + * ByteArrayInputStream(contents.getBytes())); Object ld = + * mar.unmarshal(ss); } end = System.currentTimeMillis(); + * System.out.println("elapsed Time: " + (end - start)); } catch + * (JAXBException e){ } + */ + } else { + org.apache.jmeter.monitor.model.ObjectFactory of = org.apache.jmeter.monitor.model.ObjectFactory + .getInstance(); + start = System.currentTimeMillis(); + for (int idx = 0; idx < loops; idx++) { + // NOTUSED org.apache.jmeter.monitor.model.Status st = + of.parseBytes(contents.getBytes()); // TODO - charset? + } + end = System.currentTimeMillis(); + System.out.println("elapsed Time: " + (end - start)); + } + + } else { + System.out.println("missing paramters:"); + System.out.println("parser file iterations"); + System.out.println("example: jaxb status.xml 1000"); + } + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/config/MultipartUrlConfigTest.java b/test/src/org/apache/jmeter/protocol/http/config/MultipartUrlConfigTest.java new file mode 100644 index 00000000000..3d68fa087d1 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/config/MultipartUrlConfigTest.java @@ -0,0 +1,141 @@ +/* + * 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.protocol.http.config; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.protocol.http.util.HTTPFileArgs; + +public class MultipartUrlConfigTest extends TestCase { + + public MultipartUrlConfigTest(String name) { + super(name); + } + + @SuppressWarnings("deprecation") + public void testConstructors() { + MultipartUrlConfig muc = new MultipartUrlConfig(); + assertEquals(0, muc.getArguments().getArgumentCount()); + assertEquals(0, muc.getHTTPFileArgs().getHTTPFileArgCount()); + muc = new MultipartUrlConfig("boundary"); + assertEquals(0, muc.getArguments().getArgumentCount()); + assertEquals(0, muc.getHTTPFileArgs().getHTTPFileArgCount()); + assertEquals("boundary", muc.getBoundary()); + } + + // TODO - should LF-only EOL be allowed? + public void testParseArgumentsLF() { + String queryString + = "Content-Disposition: form-data; name=\"aa\"\n" + + "Content-Type: text/plain; charset=ISO-8859-1\n" + + "Content-Transfer-Encoding: 8bit\n" + + "\n" + + "bb\n" + + "--7d159c1302d0y0\n" + + "Content-Disposition: form-data; name=\"xx\"\n" + + "Content-Type: text/plain; charset=ISO-8859-1\n" + + "Content-Transfer-Encoding: 8bit\n" + + "\n" + + "yy\n" + + "--7d159c1302d0y0\n" + + "Content-Disposition: form-data; name=\"abc\"\n" + + "Content-Type: text/plain; charset=ISO-8859-1\n" + + "Content-Transfer-Encoding: 8bit\n" + + "\n" + + "xyz \n" + + "xyz \n" + + "--7d159c1302d0y0\n" + + "Content-Disposition: form-data; name=\"param1\"; filename=\"file1\"\n" + + "Content-Type: text/plain\n" + + "Content-Transfer-Encoding: binary\n" + + "\n" + + "file content\n" + + "\n"; + MultipartUrlConfig muc = new MultipartUrlConfig("7d159c1302d0y0"); + muc.parseArguments(queryString); + HTTPFileArgs files = muc.getHTTPFileArgs(); + assertEquals(1, files.getHTTPFileArgCount()); + HTTPFileArg file = (HTTPFileArg) files.iterator().next().getObjectValue(); + assertEquals("file1", file.getPath()); + assertEquals("param1", file.getParamName()); + assertEquals("text/plain", file.getMimeType()); + Arguments args = muc.getArguments(); + assertEquals(3, args.getArgumentCount()); + Argument arg = args.getArgument(0); + assertEquals("aa", arg.getName()); + assertEquals("bb", arg.getValue()); + arg = args.getArgument(1); + assertEquals("xx", arg.getName()); + assertEquals("yy", arg.getValue()); + arg = args.getArgument(2); + assertEquals("abc", arg.getName()); + assertEquals("xyz \nxyz ", arg.getValue()); + } + + public void testParseArgumentsCRLF() { + String queryString + = "Content-Disposition: form-data; name=\"aa\"\r\n" + + "Content-Type: text/plain; charset=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "bb\r\n" + + "--7d159c1302d0y0\r\n" + + "Content-Disposition: form-data; name=\"xx\"\r\n" + + "Content-Type: text/plain; charset=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "yy\r\n" + + "--7d159c1302d0y0\r\n" + + "Content-Disposition: form-data; name=\"abc\"\r\n" + + "Content-Type: text/plain; charset=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "xyz \r\n" + + "xyz \r\n" + + "--7d159c1302d0y0\r\n" + + "Content-Disposition: form-data; name=\"param1\"; filename=\"file1\"\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "file content\r\n" + + "\r\n"; + MultipartUrlConfig muc = new MultipartUrlConfig("7d159c1302d0y0"); + muc.parseArguments(queryString); + HTTPFileArgs files = muc.getHTTPFileArgs(); + assertEquals(1, files.getHTTPFileArgCount()); + HTTPFileArg file = (HTTPFileArg) files.iterator().next().getObjectValue(); + assertEquals("file1", file.getPath()); + assertEquals("param1", file.getParamName()); + assertEquals("text/plain", file.getMimeType()); + Arguments args = muc.getArguments(); + assertEquals(3, args.getArgumentCount()); + Argument arg = args.getArgument(0); + assertEquals("aa", arg.getName()); + assertEquals("bb", arg.getValue()); + arg = args.getArgument(1); + assertEquals("xx", arg.getName()); + assertEquals("yy", arg.getValue()); + arg = args.getArgument(2); + assertEquals("abc", arg.getName()); + assertEquals("xyz \r\nxyz ", arg.getValue()); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/config/UrlConfigTest.java b/test/src/org/apache/jmeter/protocol/http/config/UrlConfigTest.java new file mode 100644 index 00000000000..f145ea661fd --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/config/UrlConfigTest.java @@ -0,0 +1,75 @@ +/* + * 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.protocol.http.config; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; + +public class UrlConfigTest extends JMeterTestCase { + private HTTPSamplerBase config; + + private HTTPSamplerBase defaultConfig; + + private HTTPSamplerBase partialConfig; + + public UrlConfigTest(String name) { + super(name); + } + + @Override + protected void setUp() { + Arguments args = new Arguments(); + args.addArgument("username", "mstover"); + args.addArgument("password", "pass"); + args.addArgument("action", "login"); + config = new HTTPNullSampler(); + config.setName("Full Config"); + config.setProperty(HTTPSamplerBase.DOMAIN, "www.lazer.com"); + config.setProperty(HTTPSamplerBase.PATH, "login.jsp"); + config.setProperty(HTTPSamplerBase.METHOD, HTTPConstants.POST); + config.setProperty(new TestElementProperty(HTTPSamplerBase.ARGUMENTS, args)); + defaultConfig = new HTTPNullSampler(); + defaultConfig.setName("default"); + defaultConfig.setProperty(HTTPSamplerBase.DOMAIN, "www.xerox.com"); + defaultConfig.setProperty(HTTPSamplerBase.PATH, "default.html"); + partialConfig = new HTTPNullSampler(); + partialConfig.setProperty(HTTPSamplerBase.PATH, "main.jsp"); + partialConfig.setProperty(HTTPSamplerBase.METHOD, HTTPConstants.GET); + } + + public void testSimpleConfig() { + assertEquals("Full Config", config.getName()); + assertEquals("www.lazer.com", config.getDomain()); + } + + public void testOverRide() { + JMeterProperty jmp = partialConfig.getProperty(HTTPSamplerBase.DOMAIN); + assertTrue(jmp instanceof NullProperty); + assertEquals(jmp, new NullProperty(HTTPSamplerBase.DOMAIN)); + partialConfig.addTestElement(defaultConfig); + assertEquals(partialConfig.getPropertyAsString(HTTPSamplerBase.DOMAIN), "www.xerox.com"); + assertEquals(partialConfig.getPropertyAsString(HTTPSamplerBase.PATH), "main.jsp"); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/control/TestAuthManager.java b/test/src/org/apache/jmeter/protocol/http/control/TestAuthManager.java new file mode 100644 index 00000000000..debeac60dbc --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/control/TestAuthManager.java @@ -0,0 +1,80 @@ +/* + * 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.protocol.http.control; + +import java.net.URL; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.testelement.property.CollectionProperty; + +public class TestAuthManager extends JMeterTestCase { + public TestAuthManager(String name) { + super(name); + } + + public void testHttp() throws Exception { + assertTrue(AuthManager.isSupportedProtocol(new URL("http:"))); + } + + public void testHttps() throws Exception { + assertTrue(AuthManager.isSupportedProtocol(new URL("https:"))); + } + + public void testFile() throws Exception { + AuthManager am = new AuthManager(); + CollectionProperty ao = am.getAuthObjects(); + assertEquals(0, ao.size()); + am.addFile(findTestPath("testfiles/TestAuth.txt")); + assertEquals(9, ao.size()); + Authorization at; + at = am.getAuthForURL(new URL("http://a.b.c/")); + assertEquals("login", at.getUser()); + assertEquals("password", at.getPass()); + at = am.getAuthForURL(new URL("http://a.b.c:80/")); // same as above + assertEquals("login", at.getUser()); + assertEquals("password", at.getPass()); + at = am.getAuthForURL(new URL("http://a.b.c:443/"));// not same + assertNull(at); + at = am.getAuthForURL(new URL("http://a.b.c/1")); + assertEquals("login1", at.getUser()); + assertEquals("password1", at.getPass()); + assertEquals("", at.getDomain()); + assertEquals("", at.getRealm()); + at = am.getAuthForURL(new URL("http://d.e.f/")); + assertEquals("user", at.getUser()); + assertEquals("pass", at.getPass()); + assertEquals("domain", at.getDomain()); + assertEquals("realm", at.getRealm()); + at = am.getAuthForURL(new URL("https://j.k.l/")); + assertEquals("jkl", at.getUser()); + assertEquals("pass", at.getPass()); + at = am.getAuthForURL(new URL("https://j.k.l:443/")); + assertEquals("jkl", at.getUser()); + assertEquals("pass", at.getPass()); + at = am.getAuthForURL(new URL("https://l.m.n/")); + assertEquals("lmn443", at.getUser()); + assertEquals("pass", at.getPass()); + at = am.getAuthForURL(new URL("https://l.m.n:443/")); + assertEquals("lmn443", at.getUser()); + assertEquals("pass", at.getPass()); + at = am.getAuthForURL(new URL("https://l.m.n:8443/")); + assertEquals("lmn8443", at.getUser()); + assertEquals("pass", at.getPass()); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/control/TestCacheManager.java b/test/src/org/apache/jmeter/protocol/http/control/TestCacheManager.java new file mode 100644 index 00000000000..2b27d9ba378 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/control/TestCacheManager.java @@ -0,0 +1,486 @@ +/* + * 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.protocol.http.control; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLConnection; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.URI; +import org.apache.commons.httpclient.URIException; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.util.HttpURLConnection; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.control.CacheManager.CacheEntry; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.protocol.http.util.HTTPConstants; + +public class TestCacheManager extends JMeterTestCase { + + private class URLConnectionStub extends URLConnection { + + protected URLConnectionStub(URL url) { + super(url); + } + + private URLConnectionStub(URLConnection urlConnection) { + super(urlConnection.getURL()); + } + + @Override + public void connect() throws IOException { + } + + private String expires = null; + private String cacheControl = null; + + @Override + public String getHeaderField(String name) { + if (HTTPConstants.LAST_MODIFIED.equals(name)) { + return currentTimeInGMT; + } else if (HTTPConstants.ETAG.equals(name)) { + return EXPECTED_ETAG; + } else if (HTTPConstants.EXPIRES.equals(name)){ + return expires; + } else if (HTTPConstants.CACHE_CONTROL.equals(name)){ + return cacheControl; + } else if (HTTPConstants.DATE.equals(name)){ + return currentTimeInGMT; + } + return super.getHeaderField(name); + } + @Override + public URL getURL() { + return url; + } + } + + private class HttpMethodStub extends PostMethod { + private Header lastModifiedHeader; + private Header etagHeader; + private String expires; + private String cacheControl; + private Header dateHeader; + + HttpMethodStub() { + this.lastModifiedHeader = new Header(HTTPConstants.LAST_MODIFIED, currentTimeInGMT); + this.dateHeader = new Header(HTTPConstants.DATE, currentTimeInGMT); + this.etagHeader = new Header(HTTPConstants.ETAG, EXPECTED_ETAG); + } + + @Override + public Header getResponseHeader(String headerName) { + if (HTTPConstants.LAST_MODIFIED.equals(headerName)) { + return this.lastModifiedHeader; + } else if (HTTPConstants.ETAG.equals(headerName)) { + return this.etagHeader; + } else if (HTTPConstants.EXPIRES.equals(headerName)) { + return expires == null ? null : new Header(HTTPConstants.EXPIRES, expires); + } else if (HTTPConstants.CACHE_CONTROL.equals(headerName)) { + return cacheControl == null ? null : new Header(HTTPConstants.CACHE_CONTROL, cacheControl); + } if (HTTPConstants.DATE.equals(headerName)) { + return this.dateHeader; + } + return null; + } + + @Override + public URI getURI() throws URIException { + return uri; + } + } + + private static class HttpURLConnectionStub extends HttpURLConnection { + private Map<String, List<String>> properties; + + public HttpURLConnectionStub(HttpMethod method, URL url) { + super(method, url); + this.properties = new HashMap<String, List<String>>(); + } + + @Override + public void addRequestProperty(String key, String value) { + List<String> list = new ArrayList<String>(); + list.add(value); + this.properties.put(key, list); + } + + @Override + public Map<String, List<String>> getRequestProperties() { + return this.properties; + } + + } + + private static final String LOCAL_HOST = "http://localhost/"; + private static final String EXPECTED_ETAG = "0xCAFEBABEDEADBEEF"; + private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + private CacheManager cacheManager; + private String currentTimeInGMT; + private URL url; + private URI uri; + private URLConnection urlConnection; + private HttpMethod httpMethod; + private HttpURLConnection httpUrlConnection; + private HTTPSampleResult sampleResultOK; + + public TestCacheManager(String name) { + super(name); + } + + private String makeDate(Date d){ + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); + simpleDateFormat.setTimeZone(GMT); + return simpleDateFormat.format(d); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + this.cacheManager = new CacheManager(); + this.currentTimeInGMT = makeDate(new Date()); + this.uri = new URI(LOCAL_HOST, false); + this.url = new URL(LOCAL_HOST); + this.urlConnection = new URLConnectionStub(this.url.openConnection()); + this.httpMethod = new HttpMethodStub(); + this.httpUrlConnection = new HttpURLConnectionStub(this.httpMethod, this.url); + this.sampleResultOK = getSampleResultWithSpecifiedResponseCode("200"); + } + + @Override + protected void tearDown() throws Exception { + this.httpUrlConnection = null; + this.httpMethod = null; + this.urlConnection = null; + this.url = null; + this.uri = null; + this.cacheManager = null; + this.currentTimeInGMT = null; + this.sampleResultOK = null; + super.tearDown(); + } + + public void testExpiresJava() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((URLConnectionStub)urlConnection).expires=makeDate(new Date(System.currentTimeMillis()+2000)); + this.cacheManager.saveDetails(this.urlConnection, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + Thread.sleep(2010); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + + } + + public void testNoExpiresJava() throws Exception{ + this.cacheManager.setUseExpires(false); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((URLConnectionStub)urlConnection).expires=makeDate(new Date(System.currentTimeMillis()+2000)); + this.cacheManager.saveDetails(this.urlConnection, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testCacheJava() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((URLConnectionStub)urlConnection).expires=makeDate(new Date(System.currentTimeMillis())); + ((URLConnectionStub)urlConnection).cacheControl="public, max-age=5"; + this.cacheManager.saveDetails(this.urlConnection, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + Thread.sleep(5010); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testExpiresHttpClient() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).expires=makeDate(new Date(System.currentTimeMillis()+2000)); + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + Thread.sleep(2010); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + + public void testCacheHttpClient() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).expires=makeDate(new Date(System.currentTimeMillis())); + ((HttpMethodStub)httpMethod).cacheControl="public, max-age=5"; + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + Thread.sleep(5010); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testCacheHttpClientHEAD() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).expires=makeDate(new Date(System.currentTimeMillis())); + ((HttpMethodStub)httpMethod).cacheControl="public, max-age=5"; + HTTPSampleResult sampleResultHEAD=getSampleResultWithSpecifiedResponseCode("200"); + sampleResultHEAD.setHTTPMethod("HEAD"); + this.cacheManager.saveDetails(httpMethod, sampleResultHEAD); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testPrivateCacheHttpClient() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).expires=makeDate(new Date(System.currentTimeMillis())); + ((HttpMethodStub)httpMethod).cacheControl="private, max-age=5"; + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + Thread.sleep(5010); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testPrivateCacheNoMaxAgeNoExpireHttpClient() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).cacheControl="private"; + ((HttpMethodStub)httpMethod).lastModifiedHeader=new Header(HTTPConstants.LAST_MODIFIED, + makeDate(new Date(System.currentTimeMillis()-(10*5*1000)))); + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + Thread.sleep(5010); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testPrivateCacheExpireNoMaxAgeHttpClient() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).expires=makeDate(new Date(System.currentTimeMillis()+2000)); + ((HttpMethodStub)httpMethod).cacheControl="private"; + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + Thread.sleep(2010); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testNoCacheHttpClient() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).cacheControl="no-cache"; + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testNoStoreHttpClient() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).cacheControl="no-store"; + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testCacheHttpClientBug51932() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).expires=makeDate(new Date(System.currentTimeMillis())); + ((HttpMethodStub)httpMethod).cacheControl="public, max-age=5, no-transform"; + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + Thread.sleep(5010); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testGetClearEachIteration() throws Exception { + assertFalse("Should default not to clear after each iteration.", this.cacheManager.getClearEachIteration()); + this.cacheManager.setClearEachIteration(true); + assertTrue("Should be settable to clear after each iteration.", this.cacheManager.getClearEachIteration()); + this.cacheManager.setClearEachIteration(false); + assertFalse("Should be settable not to clear after each iteration.", this.cacheManager.getClearEachIteration()); + } + + public void testSaveDetailsWithEmptySampleResultGivesNoCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode(""); + assertTrue("Saving details with empty SampleResult should not make cache entry.", getThreadCache().isEmpty()); + } + + public void testSaveDetailsURLConnectionWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode("200"); + CacheManager.CacheEntry cacheEntry = getThreadCacheEntry(this.url.toString()); + assertNotNull("Saving details with SampleResult & connection with 200 response should make cache entry.", cacheEntry); + assertEquals("Saving details with SampleResult & connection with 200 response should make cache entry with an etag.", EXPECTED_ETAG, cacheEntry.getEtag()); + assertEquals("Saving details with SampleResult & connection with 200 response should make cache entry with last modified date.", this.currentTimeInGMT, cacheEntry.getLastModified()); + } + + public void testSaveDetailsHttpMethodWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("200"); + CacheManager.CacheEntry cacheEntry = getThreadCacheEntry(this.httpMethod.getURI().toString()); + assertNotNull("Saving SampleResult with HttpMethod & 200 response should make cache entry.", cacheEntry); + assertEquals("Saving details with SampleResult & HttpMethod with 200 response should make cache entry with no etag.", EXPECTED_ETAG, cacheEntry.getEtag()); + assertEquals("Saving details with SampleResult & HttpMethod with 200 response should make cache entry with no last modified date.", this.currentTimeInGMT, cacheEntry.getLastModified()); + } + + public void testSaveDetailsURLConnectionWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode("404"); + assertNull("Saving details with SampleResult & connection with 404 response should not make cache entry.", getThreadCacheEntry(url.toString())); + } + + public void testSaveDetailsHttpMethodWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("404"); + assertNull("Saving SampleResult with HttpMethod & 404 response should not make cache entry.", getThreadCacheEntry(this.httpMethod.getPath())); + } + + public void testSetHeadersHttpMethodWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { + this.httpMethod.setURI(this.uri); + this.httpMethod.addRequestHeader(new Header(HTTPConstants.IF_MODIFIED_SINCE, this.currentTimeInGMT, false)); + this.httpMethod.addRequestHeader(new Header(HTTPConstants.ETAG, EXPECTED_ETAG, false)); + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("200"); + setHeadersWithUrlAndHttpMethod(); + checkRequestHeader(HTTPConstants.IF_NONE_MATCH, EXPECTED_ETAG); + checkRequestHeader(HTTPConstants.IF_MODIFIED_SINCE, this.currentTimeInGMT); + } + + public void testSetHeadersHttpMethodWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { + this.httpMethod.setURI(this.uri); + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("404"); + setHeadersWithUrlAndHttpMethod(); + assertNull("Saving SampleResult with HttpMethod & 404 response should not make cache entry.", getThreadCacheEntry(this.httpMethod.getPath())); + } + + public void testSetHeadersHttpURLConnectionWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode("200"); + setHeadersWithHttpUrlConnectionAndUrl(); + Map<String, List<String>> properties = this.httpUrlConnection.getRequestProperties(); + checkProperty(properties, HTTPConstants.IF_NONE_MATCH, EXPECTED_ETAG); + checkProperty(properties, HTTPConstants.IF_MODIFIED_SINCE, this.currentTimeInGMT); + } + + public void testSetHeadersHttpURLConnectionWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode("404"); + setHeadersWithHttpUrlConnectionAndUrl(); + assertNull("Saving SampleResult with HttpMethod & 404 response should not make cache entry.", getThreadCacheEntry(this.url.toString())); + } + + public void testClearCache() throws Exception { + assertTrue("ThreadCache should be empty initially.", getThreadCache().isEmpty()); + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("200"); + assertFalse("ThreadCache should be populated after saving details for HttpMethod with SampleResult with response code 200.", getThreadCache().isEmpty()); + this.cacheManager.clear(); + assertTrue("ThreadCache should be emptied by call to clear.", getThreadCache().isEmpty()); + } + + private void checkRequestHeader(String requestHeader, String expectedValue) { + Header header = this.httpMethod.getRequestHeader(requestHeader); + assertEquals("Wrong name in header for " + requestHeader, requestHeader, header.getName()); + assertEquals("Wrong value for header " + header, expectedValue, header.getValue()); + } + + private static void checkProperty(Map<String, List<String>> properties, String property, String expectedPropertyValue) { + assertNotNull("Properties should not be null. Expected to find within it property = " + property + " with expected value = " + expectedPropertyValue, properties); + List<String> listOfPropertyValues = properties.get(property); + assertNotNull("No property entry found for property " + property, listOfPropertyValues); + assertEquals("Did not find single property for property " + property, 1, listOfPropertyValues.size()); + assertEquals("Unexpected value for property " + property, expectedPropertyValue, listOfPropertyValues.get(0)); + } + + private HTTPSampleResult getSampleResultWithSpecifiedResponseCode(String code) { + HTTPSampleResult sampleResult = new HTTPSampleResult(); + sampleResult.setResponseCode(code); + sampleResult.setHTTPMethod("GET"); + return sampleResult; + } + + private Map<String, CacheManager.CacheEntry> getThreadCache() throws Exception { + Field threadLocalfield = CacheManager.class.getDeclaredField("threadCache"); + threadLocalfield.setAccessible(true); + @SuppressWarnings("unchecked") + ThreadLocal<Map<String, CacheEntry>> threadLocal = (ThreadLocal<Map<String, CacheManager.CacheEntry>>) threadLocalfield.get(this.cacheManager); + return threadLocal.get(); + } + + private CacheManager.CacheEntry getThreadCacheEntry(String url) throws Exception { + return getThreadCache().get(url); + } + + private void saveDetailsWithHttpMethodAndSampleResultWithResponseCode(String responseCode) throws Exception { + HTTPSampleResult sampleResult = getSampleResultWithSpecifiedResponseCode(responseCode); + this.cacheManager.saveDetails(this.httpMethod, sampleResult); + } + + private void saveDetailsWithConnectionAndSampleResultWithResponseCode(String responseCode) { + HTTPSampleResult sampleResult = getSampleResultWithSpecifiedResponseCode(responseCode); + this.cacheManager.saveDetails(this.urlConnection, sampleResult); + } + + private void setHeadersWithHttpUrlConnectionAndUrl() { + this.cacheManager.setHeaders(this.httpUrlConnection, this.url); + } + + private void setHeadersWithUrlAndHttpMethod() { + this.cacheManager.setHeaders(this.url, this.httpMethod); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/control/TestCookieManager.java b/test/src/org/apache/jmeter/protocol/http/control/TestCookieManager.java new file mode 100644 index 00000000000..ae6720a4658 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/control/TestCookieManager.java @@ -0,0 +1,423 @@ +/* + * 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.protocol.http.control; + +import java.net.URL; + +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; + +public class TestCookieManager extends JMeterTestCase { + private CookieManager man = null; + + public TestCookieManager(String name) { + super(name); + } + + private JMeterContext jmctx = null; + + @Override + public void setUp() throws Exception { + super.setUp(); + jmctx = JMeterContextService.getContext(); + man = new CookieManager(); + man.setThreadContext(jmctx); + man.testStarted();// This is needed in order to set up the cookie policy + } + + public void testRemoveCookie() throws Exception { + man.setThreadContext(jmctx); + Cookie c = new Cookie("id", "me", "127.0.0.1", "/", false, 0); + man.add(c); + assertEquals(1, man.getCookieCount()); + // This should be ignored, as there is no value + Cookie d = new Cookie("id", "", "127.0.0.1", "/", false, 0); + man.add(d); + assertEquals(0, man.getCookieCount()); + man.add(c); + man.add(c); + assertEquals(1, man.getCookieCount()); + Cookie e = new Cookie("id", "me2", "127.0.0.1", "/", false, 0); + man.add(e); + assertEquals(1, man.getCookieCount()); + } + + public void testSendCookie() throws Exception { + man.add(new Cookie("id", "value", "jakarta.apache.org", "/", false, 9999999999L)); + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setDomain("jakarta.apache.org"); + sampler.setPath("/index.html"); + sampler.setMethod(HTTPConstants.GET); + assertNotNull(man.getCookieHeaderForURL(sampler.getUrl())); + } + + public void testSendCookie2() throws Exception { + man.add(new Cookie("id", "value", ".apache.org", "/", false, 9999999999L)); + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setDomain("jakarta.apache.org"); + sampler.setPath("/index.html"); + sampler.setMethod(HTTPConstants.GET); + assertNotNull(man.getCookieHeaderForURL(sampler.getUrl())); + } + + /** + * Test that the cookie domain field is actually handled as browsers do + * (i.e.: host X matches domain .X): + * + * @throws Exception if something fails + */ + public void testDomainHandling() throws Exception { + URL url = new URL("http://jakarta.apache.org/"); + man.addCookieFromHeader("test=1;domain=.jakarta.apache.org", url); + assertNotNull(man.getCookieHeaderForURL(url)); + } + + public void testCrossDomainHandling() throws Exception { + URL url = new URL("http://jakarta.apache.org/"); + assertEquals(0,man.getCookieCount()); // starts empty + man.addCookieFromHeader("test=2;domain=.hc.apache.org", url); + assertEquals(0,man.getCookieCount()); // should not be stored + man.addCookieFromHeader("test=1;domain=.jakarta.apache.org", url); + assertEquals(1,man.getCookieCount()); // OK + } + + /** + * Test that we won't be tricked by similar host names (this was a past + * bug, although it never got reported in the bug database): + * + * @throws Exception if something fails + */ + public void testSimilarHostNames() throws Exception { + URL url = new URL("http://ache.org/"); + man.addCookieFromHeader("test=1", url); + url = new URL("http://jakarta.apache.org/"); + assertNull(man.getCookieHeaderForURL(url)); + } + + // Test session cookie is returned + public void testSessionCookie() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=1", s); + } + + // Bug 2063 + public void testCookieWithEquals() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("NSCP_USER_LOGIN1_NEW=SHA=xxxxx", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("NSCP_USER_LOGIN1_NEW=SHA=xxxxx", s); + Cookie c=man.get(0); + assertEquals("NSCP_USER_LOGIN1_NEW",c.getName()); + assertEquals("SHA=xxxxx",c.getValue()); + } + + // Test Old cookie is not returned + public void testOldCookie() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1; expires=Mon, 01-Jan-1990 00:00:00 GMT", url); + String s = man.getCookieHeaderForURL(url); + assertNull(s); + } + + // Test New cookie is returned + public void testNewCookie() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1; expires=Mon, 01-Jan-2990 00:00:00 GMT", url); + assertEquals(1,man.getCookieCount()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=1", s); + } + + // Test multi-cookie header handling + public void testCookies1() throws Exception { + URL url = new URL("http://a.b.c.d/testCookies1"); + man.addCookieFromHeader("test1=1; comment=\"how,now\", test2=2; version=1", url); + assertEquals(2,man.getCookieCount()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test1=1; test2=2", s); + } + + public void testCookies2() throws Exception { + URL url = new URL("https://a.b.c.d/testCookies2"); + man.addCookieFromHeader("test1=1;secure, test2=2;secure", url); + assertEquals(2,man.getCookieCount()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test1=1; test2=2", s); + } + + // Test duplicate cookie handling + public void testDuplicateCookie() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=1", s); + man.addCookieFromHeader("test=2", url); + s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=2", s); + } + public void testDuplicateCookie2() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1", url); + man.addCookieFromHeader("test2=a", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=1; test2=a", s); // Assumes some kind of list is used + man.addCookieFromHeader("test=2", url); + man.addCookieFromHeader("test3=b", url); + s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test2=a; test=2; test3=b", s);// Assumes some kind of list is use + // If not using a list that retains the order, then the asserts would need to change + } + + + /** Tests missing cookie path for a trivial URL fetch from the domain + * Note that this fails prior to a fix for BUG 38256 + * + * @throws Exception if something fails + */ + public void testMissingPath0() throws Exception { + URL url = new URL("http://d.e.f/goo.html"); + man.addCookieFromHeader("test=moo", url); + String s = man.getCookieHeaderForURL(new URL("http://d.e.f/")); + assertNotNull(s); + assertEquals("test=moo", s); + } + + /** Tests missing cookie path for a non-trivial URL fetch from the + * domain. Note that this fails prior to a fix for BUG 38256 + * + * @throws Exception if something fails + */ + public void testMissingPath1() throws Exception { + URL url = new URL("http://d.e.f/moo.html"); + man.addCookieFromHeader("test=moo", url); + String s = man.getCookieHeaderForURL(new URL("http://d.e.f/goo.html")); + assertNotNull(s); + assertEquals("test=moo", s); + } + + /** Tests explicit root path with a trivial URL fetch from the domain + * + * @throws Exception if something fails + */ + public void testRootPath0() throws Exception { + URL url = new URL("http://d.e.f/goo.html"); + man.addCookieFromHeader("test=moo;path=/", url); + String s = man.getCookieHeaderForURL(new URL("http://d.e.f/")); + assertNotNull(s); + assertEquals("test=moo", s); + } + + /** Tests explicit root path with a non-trivial URL fetch from the domain + * + * @throws Exception if something fails + */ + public void testRootPath1() throws Exception { + URL url = new URL("http://d.e.f/moo.html"); + man.addCookieFromHeader("test=moo;path=/", url); + String s = man.getCookieHeaderForURL(new URL("http://d.e.f/goo.html")); + assertNotNull(s); + assertEquals("test=moo", s); + } + + // Test cookie matching + public void testCookieMatching() throws Exception { + URL url = new URL("http://a.b.c:8080/TopDir/fred.jsp"); + man.addCookieFromHeader("ID=abcd; Path=/TopDir", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("ID=abcd", s); + + url = new URL("http://a.b.c:8080/other.jsp"); + s=man.getCookieHeaderForURL(url); + assertNull(s); + + url = new URL("http://a.b.c:8080/TopDir/suub/another.jsp"); + s=man.getCookieHeaderForURL(url); + assertNotNull(s); + + url = new URL("http://a.b.c:8080/TopDir"); + s=man.getCookieHeaderForURL(url); + assertNotNull(s); + + url = new URL("http://a.b.d/"); + s=man.getCookieHeaderForURL(url); + assertNull(s); + } + + public void testCookieOrdering1() throws Exception { + URL url = new URL("http://order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;path=/", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(3,man.getCookieCount()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test2=moo2; test1=moo1; test2=moo3", s); + } + + public void testCookieOrdering2() throws Exception { + URL url = new URL("http://order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(3,man.getCookieCount()); + assertEquals("/sub1",man.get(0).getPath()); // Defaults to caller URL + assertEquals("/sub1",man.get(1).getPath()); + assertEquals("/",man.get(2).getPath()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + HC3CookieHandler hc3CookieHandler = (HC3CookieHandler) man.getCookieHandler(); + org.apache.commons.httpclient.Cookie[] c = + hc3CookieHandler.getCookiesForUrl(man.getCookies(), url, + CookieManager.ALLOW_VARIABLE_COOKIES); + assertEquals("/sub1",c[0].getPath()); + assertFalse(c[0].isPathAttributeSpecified()); + assertEquals("/sub1",c[1].getPath()); + assertTrue(c[1].isPathAttributeSpecified()); + assertEquals("/",c[2].getPath()); + assertEquals("test1=moo1; test2=moo2; test2=moo3", s); + } + + public void testCookiePolicy2109() throws Exception { + man.setCookiePolicy(CookiePolicy.RFC_2109); + man.testStarted(); // ensure policy is picked up + URL url = new URL("http://order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(3,man.getCookieCount()); + //assertEquals("/",man.get(0).getPath()); + assertEquals("/sub1",man.get(1).getPath()); + assertEquals("/",man.get(2).getPath()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + HC3CookieHandler hc3CookieHandler = (HC3CookieHandler) man.getCookieHandler(); + org.apache.commons.httpclient.Cookie[] c = + hc3CookieHandler.getCookiesForUrl(man.getCookies(), url, + CookieManager.ALLOW_VARIABLE_COOKIES); + assertEquals("/sub1",c[0].getPath()); + assertFalse(c[0].isPathAttributeSpecified()); + assertEquals("/sub1",c[1].getPath()); + assertTrue(c[1].isPathAttributeSpecified()); + assertEquals("/",c[2].getPath()); + assertTrue(c[2].isPathAttributeSpecified()); + assertEquals("$Version=0; test1=moo1; test2=moo2; $Path=/sub1; test2=moo3; $Path=/", s); + } + + public void testCookiePolicyNetscape() throws Exception { + man.setCookiePolicy(CookiePolicy.NETSCAPE); + man.testStarted(); // ensure policy is picked up + URL url = new URL("http://www.order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(3,man.getCookieCount()); + assertEquals("/sub1",man.get(0).getPath()); + assertEquals("/sub1",man.get(1).getPath()); + assertEquals("/",man.get(2).getPath()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + HC3CookieHandler hc3CookieHandler = (HC3CookieHandler) man.getCookieHandler(); + + org.apache.commons.httpclient.Cookie[] c = + hc3CookieHandler.getCookiesForUrl(man.getCookies(), url, + CookieManager.ALLOW_VARIABLE_COOKIES); + assertEquals("/sub1",c[0].getPath()); + assertFalse(c[0].isPathAttributeSpecified()); + assertEquals("/sub1",c[1].getPath()); + assertTrue(c[1].isPathAttributeSpecified()); + assertEquals("/",c[2].getPath()); + assertTrue(c[2].isPathAttributeSpecified()); + assertEquals("test1=moo1; test2=moo2; test2=moo3", s); + } + + public void testCookiePolicyIgnore() throws Exception { + man.setCookiePolicy(CookiePolicy.IGNORE_COOKIES); + man.testStarted(); // ensure policy is picked up + URL url = new URL("http://order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(0,man.getCookieCount());// Cookies are ignored + Cookie cc; + cc=new Cookie("test1","moo1",null,"/sub1",false,0,false,false); + man.add(cc); + cc=new Cookie("test2","moo2",null,"/sub1",false,0,true,false); + man.add(cc); + cc=new Cookie("test3","moo3",null,"/",false,0,false,false); + man.add(cc); + assertEquals(3,man.getCookieCount()); + assertEquals("/sub1",man.get(0).getPath()); + assertEquals("/sub1",man.get(1).getPath()); + assertEquals("/",man.get(2).getPath()); + String s = man.getCookieHeaderForURL(url); + assertNull(s); + HC3CookieHandler hc3CookieHandler = (HC3CookieHandler) man.getCookieHandler(); + org.apache.commons.httpclient.Cookie[] c = + hc3CookieHandler.getCookiesForUrl(man.getCookies(), url, + CookieManager.ALLOW_VARIABLE_COOKIES); + assertEquals(0,c.length); // Cookies again ignored + } + + public void testLoad() throws Exception{ + assertEquals(0,man.getCookieCount()); + man.addFile(findTestPath("testfiles/cookies.txt")); + assertEquals(3,man.getCookieCount()); + + int num = 0; + assertEquals("name",man.get(num).getName()); + assertEquals("value",man.get(num).getValue()); + assertEquals("path",man.get(num).getPath()); + assertEquals("domain",man.get(num).getDomain()); + assertTrue(man.get(num).getSecure()); + assertEquals(num,man.get(num).getExpires()); + + num++; + assertEquals("name2",man.get(num).getName()); + assertEquals("value2",man.get(num).getValue()); + assertEquals("/",man.get(num).getPath()); + assertEquals("",man.get(num).getDomain()); + assertFalse(man.get(num).getSecure()); + assertEquals(0,man.get(num).getExpires()); + + num++; + assertEquals("a",man.get(num).getName()); + assertEquals("b",man.get(num).getValue()); + assertEquals("d",man.get(num).getPath()); + assertEquals("c",man.get(num).getDomain()); + assertTrue(man.get(num).getSecure()); + assertEquals(0,man.get(num).getExpires()); // Show that maxlong now saved as 0 + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/control/TestDNSCacheManager.java b/test/src/org/apache/jmeter/protocol/http/control/TestDNSCacheManager.java new file mode 100644 index 00000000000..865b9acd2ec --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/control/TestDNSCacheManager.java @@ -0,0 +1,42 @@ +/* + * 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.protocol.http.control; + +import java.net.UnknownHostException; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.junit.Test; + +public class TestDNSCacheManager extends JMeterTestCase { + + @Test + public void testCloneWithCustomResolverAndInvalidNameserver() throws UnknownHostException { + DNSCacheManager original = new DNSCacheManager(); + original.setCustomResolver(true); + original.addServer("127.0.0.99"); + DNSCacheManager clone = (DNSCacheManager) original.clone(); + try { + clone.resolve("jmeter.apache.org"); + fail(); + } catch (UnknownHostException e) { + // OK + } + } + +} diff --git a/test/src/org/apache/jmeter/protocol/http/control/TestHTTPMirrorThread.java b/test/src/org/apache/jmeter/protocol/http/control/TestHTTPMirrorThread.java new file mode 100644 index 00000000000..e17a5cef02e --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/control/TestHTTPMirrorThread.java @@ -0,0 +1,478 @@ +/* + * 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.protocol.http.control; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.Socket; +import java.net.URI; +import java.net.URL; + +import org.apache.commons.io.IOUtils; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import junit.extensions.TestSetup; + +/** + * Class for testing the HTTPMirrorThread, which is handling the + * incoming requests for the HTTPMirrorServer + */ +public class TestHTTPMirrorThread extends TestCase { + /** The encodings used for http headers and control information */ + private static final String ISO_8859_1 = "ISO-8859-1"; // $NON-NLS-1$ + private static final String UTF_8 = "UTF-8"; // $NON-NLS-1$ + + private static final byte[] CRLF = { 0x0d, 0x0a }; + private static final int HTTP_SERVER_PORT = 8181; + + public TestHTTPMirrorThread(String arg0) { + super(arg0); + } + + // We need to use a suite in order to preserve the server across test cases + // With JUnit4 we could use before/after class annotations + public static Test suite(){ + TestSetup setup = new TestSetup(new TestSuite(TestHTTPMirrorThread.class)){ + private HttpMirrorServer httpServer; + + @Override + protected void setUp() throws Exception { + httpServer = startHttpMirror(HTTP_SERVER_PORT); + } + + @Override + protected void tearDown() throws Exception { + // Shutdown the http server + httpServer.stopServer(); + httpServer = null; + } + }; + return setup; + } + + /** + * Utility method to handle starting the HttpMirrorServer for testing. Also + * used by TestHTTPSamplersAgainstHttpMirrorServer + * + * @param port + * port on which the mirror should be started + * @return newly created http mirror server + * @throws Exception + * if something fails + */ + public static HttpMirrorServer startHttpMirror(int port) throws Exception { + HttpMirrorServer server = null; + server = new HttpMirrorServer(port); + server.start(); + Exception e = null; + for (int i=0; i < 10; i++) {// Wait up to 1 second + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + e = server.getException(); + if (e != null) {// Already failed + throw new Exception("Could not start mirror server on port: "+port+". "+e); + } + if (server.isAlive()) { + break; // succeeded + } + } + + if (!server.isAlive()){ + throw new Exception("Could not start mirror server on port: "+port); + } + return server; + } + + public void testGetRequest() throws Exception { + // Connect to the http server, and do a simple http get + Socket clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + OutputStream outputStream = clientSocket.getOutputStream(); + InputStream inputStream = clientSocket.getInputStream(); + + // Write to the socket + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.close(); + outputStream.write(bos.toByteArray()); + + // Read the response + ByteArrayOutputStream response = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length = 0; + while(( length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + byte[] mirroredResponse = getMirroredResponse(response.toByteArray()); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + outputStream.close(); + inputStream.close(); + clientSocket.close(); + + // Connect to the http server, and do a simple http get, with + // a pause in the middle of transmitting the header + clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + outputStream = clientSocket.getOutputStream(); + inputStream = clientSocket.getInputStream(); + + // Write to the socket + bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + // Write the start of the headers, and then sleep, so that the mirror + // thread will have to block to wait for more data to appear + bos.close(); + byte[] firstChunk = bos.toByteArray(); + outputStream.write(firstChunk); + Thread.sleep(300); + // Write the rest of the headers + bos = new ByteArrayOutputStream(); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.close(); + byte[] secondChunk = bos.toByteArray(); + outputStream.write(secondChunk); + // Read the response + response = new ByteArrayOutputStream(); + buffer = new byte[1024]; + length = 0; + while((length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + mirroredResponse = getMirroredResponse(response.toByteArray()); + // The content sent + bos = new ByteArrayOutputStream(); + bos.write(firstChunk); + bos.write(secondChunk); + bos.close(); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + outputStream.close(); + inputStream.close(); + clientSocket.close(); + } + + public void testPostRequest() throws Exception { + // Connect to the http server, and do a simple http post + Socket clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + OutputStream outputStream = clientSocket.getOutputStream(); + InputStream inputStream = clientSocket.getInputStream(); + // Construct body + StringBuilder postBodyBuffer = new StringBuilder(); + for(int i = 0; i < 1000; i++) { + postBodyBuffer.append("abc"); + } + byte[] postBody = postBodyBuffer.toString().getBytes(ISO_8859_1); + + // Write to the socket + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-type: text/plain; charset=" + ISO_8859_1).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-length: " + postBody.length).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.write(postBody); + bos.close(); + // Write the headers and body + outputStream.write(bos.toByteArray()); + // Read the response + ByteArrayOutputStream response = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length = 0; + while((length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + byte[] mirroredResponse = getMirroredResponse(response.toByteArray()); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + outputStream.close(); + inputStream.close(); + clientSocket.close(); + + // Connect to the http server, and do a simple http post, with + // a pause after transmitting the headers + clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + outputStream = clientSocket.getOutputStream(); + inputStream = clientSocket.getInputStream(); + + // Write to the socket + bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-type: text/plain; charset=" + ISO_8859_1).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-length: " + postBody.length).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.close(); + // Write the headers, and then sleep + bos.close(); + byte[] firstChunk = bos.toByteArray(); + outputStream.write(firstChunk); + Thread.sleep(300); + + // Write the body + byte[] secondChunk = postBody; + outputStream.write(secondChunk); + // Read the response + response = new ByteArrayOutputStream(); + buffer = new byte[1024]; + length = 0; + while((length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + mirroredResponse = getMirroredResponse(response.toByteArray()); + // The content sent + bos = new ByteArrayOutputStream(); + bos.write(firstChunk); + bos.write(secondChunk); + bos.close(); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + outputStream.close(); + inputStream.close(); + clientSocket.close(); + + // Connect to the http server, and do a simple http post with utf-8 + // encoding of the body, which caused problems when reader/writer + // classes were used in the HttpMirrorThread + clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + outputStream = clientSocket.getOutputStream(); + inputStream = clientSocket.getInputStream(); + // Construct body + postBodyBuffer = new StringBuilder(); + for(int i = 0; i < 1000; i++) { + postBodyBuffer.append("\u0364\u00c5\u2052"); + } + postBody = postBodyBuffer.toString().getBytes(UTF_8); + + // Write to the socket + bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-type: text/plain; charset=" + UTF_8).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-length: " + postBody.length).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.close(); + // Write the headers, and then sleep + bos.close(); + firstChunk = bos.toByteArray(); + outputStream.write(firstChunk); + Thread.sleep(300); + + // Write the body + secondChunk = postBody; + outputStream.write(secondChunk); + // Read the response + response = new ByteArrayOutputStream(); + buffer = new byte[1024]; + length = 0; + while((length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + mirroredResponse = getMirroredResponse(response.toByteArray()); + // The content sent + bos = new ByteArrayOutputStream(); + bos.write(firstChunk); + bos.write(secondChunk); + bos.close(); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + outputStream.close(); + inputStream.close(); + clientSocket.close(); + } + +/* + public void testPostRequestChunked() throws Exception { + // TODO - implement testing of chunked post request + } +*/ + + public void testStatus() throws Exception { + URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.addRequestProperty("X-ResponseStatus", "302 Temporary Redirect"); + conn.connect(); + assertEquals(302, conn.getResponseCode()); + assertEquals("Temporary Redirect", conn.getResponseMessage()); + } + + public void testQueryStatus() throws Exception { + URL url = new URI("http",null,"localhost",HTTP_SERVER_PORT,"/path","status=303 See Other",null).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.connect(); + assertEquals(303, conn.getResponseCode()); + assertEquals("See Other", conn.getResponseMessage()); + } + + public void testQueryRedirect() throws Exception { + URL url = new URI("http",null,"localhost",HTTP_SERVER_PORT,"/path","redirect=/a/b/c/d?q",null).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setInstanceFollowRedirects(false); + conn.connect(); + assertEquals(302, conn.getResponseCode()); + assertEquals("Temporary Redirect", conn.getResponseMessage()); + assertEquals("/a/b/c/d?q",conn.getHeaderField("Location")); + } + + public void testHeaders() throws Exception { + URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.addRequestProperty("X-SetHeaders", "Location: /abcd|X-Dummy: none"); + conn.connect(); + assertEquals(200, conn.getResponseCode()); + assertEquals("OK", conn.getResponseMessage()); + assertEquals("/abcd",conn.getHeaderField("Location")); + assertEquals("none",conn.getHeaderField("X-Dummy")); + } + + public void testResponseLength() throws Exception { + URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.addRequestProperty("X-ResponseLength", "10"); + conn.connect(); + final InputStream inputStream = conn.getInputStream(); + assertEquals(10, IOUtils.toByteArray(inputStream).length); + inputStream.close(); + } + + public void testCookie() throws Exception { + URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.addRequestProperty("X-SetCookie", "four=2*2"); + conn.connect(); + assertEquals("four=2*2",conn.getHeaderField("Set-Cookie")); + } + + public void testSleep() throws Exception { + URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.addRequestProperty("X-Sleep", "1000"); + conn.connect(); + // use nanoTime to do timing measurement or calculation + // See https://blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks + long now = System.nanoTime(); + final InputStream inputStream = conn.getInputStream(); + while(inputStream.read() != -1) {} + inputStream.close(); + final long elapsed = (System.nanoTime() - now)/1000000L; + assertTrue("Expected > 1000 " + elapsed, elapsed >= 1000); + } + + /** + * Check that the the two byte arrays have identical content + * + * @param expected + * @param actual + * @throws UnsupportedEncodingException + */ + private void checkArraysHaveSameContent(byte[] expected, byte[] actual) throws UnsupportedEncodingException { + if(expected != null && actual != null) { + if(expected.length != actual.length) { + System.out.println(">>>>>>>>>>>>>>>>>>>> (expected) : length " + expected.length); + System.out.println(new String(expected,"UTF-8")); + System.out.println("==================== (actual) : length " + actual.length); + System.out.println(new String(actual,"UTF-8")); + System.out.println("<<<<<<<<<<<<<<<<<<<<"); + fail("arrays have different length, expected is " + expected.length + ", actual is " + actual.length); + } + else { + for(int i = 0; i < expected.length; i++) { + if(expected[i] != actual[i]) { + System.out.println(">>>>>>>>>>>>>>>>>>>> (expected) : length " + expected.length); + System.out.println(new String(expected,0,i+1, ISO_8859_1)); + System.out.println("==================== (actual) : length " + actual.length); + System.out.println(new String(actual,0,i+1, ISO_8859_1)); + System.out.println("<<<<<<<<<<<<<<<<<<<<"); +/* + // Useful to when debugging + for(int j = 0; j < expected.length; j++) { + System.out.print(expected[j] + " "); + } + System.out.println(); + for(int j = 0; j < actual.length; j++) { + System.out.print(actual[j] + " "); + } + System.out.println(); +*/ + fail("byte at position " + i + " is different, expected is " + expected[i] + ", actual is " + actual[i]); + } + } + } + } + else { + fail("expected or actual byte arrays were null"); + } + } + + private byte[] getMirroredResponse(byte[] allResponse) { + // The response includes the headers from the mirror server, + // we want to skip those, to only keep the content mirrored. + // Look for the first CRLFCRLF section + int startOfMirrorResponse = 0; + for(int i = 0; i < allResponse.length; i++) { + // TODO : This is a bit fragile + if(allResponse[i] == 0x0d && allResponse[i+1] == 0x0a && allResponse[i+2] == 0x0d && allResponse[i+3] == 0x0a) { + startOfMirrorResponse = i + 4; + break; + } + } + byte[] mirrorResponse = new byte[allResponse.length - startOfMirrorResponse]; + System.arraycopy(allResponse, startOfMirrorResponse, mirrorResponse, 0, mirrorResponse.length); + return mirrorResponse; + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/control/gui/TestHttpTestSampleGui.java b/test/src/org/apache/jmeter/protocol/http/control/gui/TestHttpTestSampleGui.java new file mode 100644 index 00000000000..81f258bf96f --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/control/gui/TestHttpTestSampleGui.java @@ -0,0 +1,62 @@ +/* + * 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.protocol.http.control.gui; + +import java.awt.GraphicsEnvironment; + +import junit.framework.TestCase; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class TestHttpTestSampleGui extends TestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private HttpTestSampleGui gui; + + public TestHttpTestSampleGui(String name) { + super(name); + } + + @Override + public void setUp() { + if(GraphicsEnvironment.isHeadless()) { + System.out.println("Skipping test:"+getClass().getName()+", cannot run in Headless mode"); + log.warn("Skipping test:"+getClass().getName()+", cannot run in Headless mode"); + return; + } + gui = new HttpTestSampleGui(); + } + + public void testCloneSampler() throws Exception { + if(GraphicsEnvironment.isHeadless()) { + System.out.println("Skipping test:"+getClass().getName()+"#testCloneSampler"+", cannot run in Headless mode"); + log.warn("Skipping test:"+getClass().getName()+"#testCloneSampler"+", cannot run in Headless mode"); + return; + } + HTTPSamplerBase sampler = (HTTPSamplerBase) gui.createTestElement(); + sampler.addArgument("param", "value"); + HTTPSamplerBase clonedSampler = (HTTPSamplerBase) sampler.clone(); + clonedSampler.setRunningVersion(true); + sampler.getArguments().getArgument(0).setValue("new value"); + assertEquals("Sampler didn't clone correctly", "new value", sampler.getArguments().getArgument(0) + .getValue()); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/modifier/TestAnchorModifier.java b/test/src/org/apache/jmeter/protocol/http/modifier/TestAnchorModifier.java new file mode 100644 index 00000000000..cc5d03a7b77 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/modifier/TestAnchorModifier.java @@ -0,0 +1,343 @@ +/* + * 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.protocol.http.modifier; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.io.TextFile; + +public class TestAnchorModifier extends JMeterTestCase { + private AnchorModifier parser = new AnchorModifier(); + public TestAnchorModifier(String name) { + super(name); + } + + private JMeterContext jmctx = null; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + parser.setThreadContext(jmctx); + } + + public void testProcessingHTMLFile(String HTMLFileName) throws Exception { + File file = new File(System.getProperty("user.dir") + "/testfiles/load_bug_list.jmx"); + HTTPSamplerBase config = (HTTPSamplerBase) SaveService.loadTree(file).getArray()[0]; + config.setRunningVersion(true); + HTTPSampleResult result = new HTTPSampleResult(); + file = new File(System.getProperty("user.dir") + "/testfiles/Load_JMeter_Page.jmx"); + HTTPSamplerBase context = (HTTPSamplerBase) SaveService.loadTree(file).getArray()[0]; + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + result.setResponseData(new TextFile(System.getProperty("user.dir") + HTMLFileName).getText(), null); + result.setSampleLabel(context.toString()); + result.setSamplerData(context.toString()); + result.setURL(new URL("http://bz.apache.org/fakepage.html")); + jmctx.setPreviousResult(result); + AnchorModifier modifier = new AnchorModifier(); + modifier.setThreadContext(jmctx); + modifier.process(); + assertEquals("http://bz.apache.org/bugzilla/buglist.cgi?" + + "bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED" + + "&email1=&emailtype1=substring&emailassigned_to1=1" + + "&email2=&emailtype2=substring&emailreporter2=1" + "&bugidtype=include&bug_id=&changedin=&votes=" + + "&chfieldfrom=&chfieldto=Now&chfieldvalue=" + + "&product=JMeter&short_desc=&short_desc_type=substring" + + "&long_desc=&long_desc_type=substring&bug_file_loc=" + "&bug_file_loc_type=substring&keywords=" + + "&keywords_type=anywords" + "&field0-0-0=noop&type0-0-0=noop&value0-0-0=" + + "&cmdtype=doit&order=Reuse+same+sort+as+last+time", config.toString()); + config.recoverRunningVersion(); + assertEquals("http://bz.apache.org/bugzilla/buglist.cgi?" + + "bug_status=.*&bug_status=.*&bug_status=.*&email1=" + + "&emailtype1=substring&emailassigned_to1=1&email2=" + "&emailtype2=substring&emailreporter2=1" + + "&bugidtype=include&bug_id=&changedin=&votes=" + "&chfieldfrom=&chfieldto=Now&chfieldvalue=" + + "&product=JMeter&short_desc=&short_desc_type=substring" + + "&long_desc=&long_desc_type=substring&bug_file_loc=" + "&bug_file_loc_type=substring&keywords=" + + "&keywords_type=anywords&field0-0-0=noop" + "&type0-0-0=noop&value0-0-0=&cmdtype=doit" + + "&order=Reuse+same+sort+as+last+time", config.toString()); + } + + public void testModifySampler() throws Exception { + testProcessingHTMLFile("/testfiles/jmeter_home_page.html"); + } + + public void testModifySamplerWithRelativeLink() throws Exception { + testProcessingHTMLFile("/testfiles/jmeter_home_page_with_relative_links.html"); + } + + public void testModifySamplerWithBaseHRef() throws Exception { + testProcessingHTMLFile("/testfiles/jmeter_home_page_with_base_href.html"); + } + + public void testSimpleParse() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*/index\\.html"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "<html><head><title>Test page" + + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setSamplerData(context.toString()); + result.setURL(context.getUrl()); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("http://www.apache.org/subdir/index.html", config.getUrl().toString()); + } + + // Test https works too + public void testSimpleParse1() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*/index\\.html"); + config.setProtocol(HTTPConstants.PROTOCOL_HTTPS); + config.setPort(HTTPConstants.DEFAULT_HTTPS_PORT); + HTTPSamplerBase context = makeContext("https://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setSamplerData(context.toString()); + result.setURL(context.getUrl()); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("https://www.apache.org/subdir/index.html", config.getUrl().toString()); + } + + public void testSimpleParse2() throws Exception { + HTTPSamplerBase config = makeUrlConfig("/index\\.html"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "Goto index page" + "hfdfjiudfjdfjkjfkdjf" + + "bold textlower" + ""; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertTrue("http://www.apache.org/index.html".equals(newUrl) + || "http://www.apache.org/subdir/lowerdir/index.html".equals(newUrl)); + } + + public void testSimpleParse3() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*index.*"); + config.getArguments().addArgument("param1", "value1"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "" + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertEquals("http://www.apache.org/home/index.html?param1=value1", newUrl); + } + + public void testSimpleParse4() throws Exception { + HTTPSamplerBase config = makeUrlConfig("/subdir/index\\..*"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertEquals("http://www.apache.org/subdir/index.html", newUrl); + } + + public void testSimpleParse5() throws Exception { + HTTPSamplerBase config = makeUrlConfig("/subdir/index\\.h.*"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/one/previous.html"); + String responseText = "Test page" + + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertEquals("http://www.apache.org/subdir/index.html", newUrl); + } + + public void testFailSimpleParse1() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*index.*?param2=.+1"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "" + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + String newUrl = config.getUrl().toString(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals(newUrl, config.getUrl().toString()); + } + + public void testFailSimpleParse3() throws Exception { + HTTPSamplerBase config = makeUrlConfig("/home/index.html"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "" + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + String newUrl = config.getUrl().toString(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals(newUrl + "?param1=value1", config.getUrl().toString()); + } + + public void testFailSimpleParse2() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*login\\.html"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "" + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertTrue(!"http://www.apache.org/home/index.html?param1=value1".equals(newUrl)); + assertEquals(config.getUrl().toString(), newUrl); + } + + public void testSimpleFormParse() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*index.html"); + config.addArgument("test", "g.*"); + config.setMethod(HTTPConstants.POST); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "
" + "Goto index page
"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("http://www.apache.org/subdir/index.html", config.getUrl().toString()); + assertEquals("test=goto", config.getQueryString()); + } + + public void testBadCharParse() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*index.html"); + config.addArgument("te$st", "g.*"); + config.setMethod(HTTPConstants.POST); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "
" + "Goto index page
"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("http://www.apache.org/subdir/index.html", config.getUrl().toString()); + assertEquals("te%24st=goto", config.getQueryString()); + } + + public void testSpecialCharParse() throws Exception { + String specialChars = "-_.!~*'()%25";// These are some of the special characters + String htmlEncodedFixture = URLEncoder.encode(specialChars, "UTF-8"); + + HTTPSamplerBase config = makeUrlConfig(".*index.html"); + config.addArgument("test", ".*"); + config.setMethod(HTTPConstants.POST); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "
" + "Goto index page
"; + + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("http://www.apache.org/subdir/index.html", config.getUrl().toString()); + assertEquals("test=" + htmlEncodedFixture, config.getQueryString()); + } + + + private HTTPSamplerBase makeContext(String url) throws MalformedURLException { + URL u = new URL(url); + HTTPSamplerBase context = new HTTPNullSampler(); + context.setDomain(u.getHost()); + context.setPath(u.getPath()); + context.setPort(u.getPort()); + context.setProtocol(u.getProtocol()); + context.parseArguments(u.getQuery()); + return context; + } + + private HTTPSamplerBase makeUrlConfig(String path) { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setDomain("www.apache.org"); + config.setMethod(HTTPConstants.GET); + config.setPath(path); + config.setPort(HTTPConstants.DEFAULT_HTTP_PORT); + config.setProtocol(HTTPConstants.PROTOCOL_HTTP); + return config; + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/modifier/TestURLRewritingModifier.java b/test/src/org/apache/jmeter/protocol/http/modifier/TestURLRewritingModifier.java new file mode 100644 index 00000000000..a0d5a5a1be9 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/modifier/TestURLRewritingModifier.java @@ -0,0 +1,303 @@ +/* + * 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.protocol.http.modifier; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.NullSampler; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; + +public class TestURLRewritingModifier extends JMeterTestCase { + private SampleResult response = null; + + private JMeterContext context = null; + + private URLRewritingModifier mod = null; + + public TestURLRewritingModifier(String name) { + super(name); + } + + @Override + public void setUp() { + context = JMeterContextService.getContext(); + mod = new URLRewritingModifier(); + mod.setThreadContext(context); + } + + public void testNonHTTPSampler() throws Exception { + Sampler sampler = new NullSampler(); + response = new SampleResult(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + } + + public void testGrabSessionId() throws Exception { + String html = "location: http://server.com/index.html" + "?session_id=jfdkjdkf%20jddkfdfjkdjfdf%22;"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + HTTPSamplerBase sampler = createSampler(); + sampler.addArgument("session_id", "adfasdfdsafasdfasd"); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkf jddkfdfjkdjfdf\"", ((Argument) args.getArguments().get(0).getObjectValue()) + .getValue()); + assertEquals("http://server.com/index.html?" + "session_id=jfdkjdkf+jddkfdfjkdjfdf%22", sampler.toString()); + } + + public void testGrabSessionId2() throws Exception { + String html = ""; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkfjddkfdfjkdjfdf", ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + + private HTTPSamplerBase createSampler() { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setDomain("server.com"); + sampler.setPath("index.html"); + sampler.setMethod(HTTPConstants.GET); + sampler.setProtocol("http"); + return sampler; + } + + public void testGrabSessionId3() throws Exception { + String html = "href='index.html?session_id=jfdkjdkfjddkfdfjkdjfdf'"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkfjddkfdfjkdjfdf", ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + + public void testGrabSessionId6() throws Exception { + String html = "location: http://server.com/index.html" + "?session_id=bonjour+monsieur"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + HTTPSamplerBase sampler = createSampler(); + sampler.addArgument("session_id", "adfasdfdsafasdfasd"); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("bonjour monsieur", ((Argument) args.getArguments().get(0).getObjectValue()) + .getValue()); + assertEquals("http://server.com/index.html?" + "session_id=bonjour+monsieur", sampler.toString()); + } + + public void testGrabSessionId7() throws Exception { + String html = "location: http://server.com/index.html" + "?session_id=bonjour+monsieur"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + mod.setEncode(true); + HTTPSamplerBase sampler = createSampler(); + sampler.addArgument("session_id", "adfasdfdsafasdfasd"); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + // System.out.println(((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + // System.out.println(sampler.toString()); + assertEquals("bonjour+monsieur", ((Argument) args.getArguments().get(0).getObjectValue()) + .getValue()); + assertEquals("http://server.com/index.html?" + "session_id=bonjour%2Bmonsieur", sampler.toString()); + } + + public void testGrabSessionIdFromXMLNonPatExtension() throws Exception { // Bug 50286 + String html = "/some/path;jsessionid=123456789"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("jsessionid"); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("123456789", ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + + public void testGrabSessionIdFromXMLPatExtension() throws Exception { // Bug 50286 + String html = "/some/path;jsessionid=123456789"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("jsessionid"); + mod.setPathExtension(true); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + assertEquals("index.html;jsessionid=123456789",sampler.getPath()); + } + + public void testGrabSessionIdEndedInTab() throws Exception { + String html = "href='index.html?session_id=jfdkjdkfjddkfdfjkdjfdf\t"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkfjddkfdfjkdjfdf", ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + + public void testGrabSessionId4() throws Exception { + String html = "href='index.html;%24sid%24KQNq3AAADQZoEQAxlkX8uQV5bjqVBPbT'"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("%24sid%24"); // $sid$ + mod.setPathExtension(true); + mod.setPathExtensionNoEquals(true); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + // Arguments args = sampler.getArguments(); + assertEquals("index.html;%24sid%24KQNq3AAADQZoEQAxlkX8uQV5bjqVBPbT", sampler.getPath()); + } + + public void testGrabSessionId5() throws Exception { + String html = "location: http://server.com/index.html" + "?session[33]=jfdkjdkf%20jddkfdfjkdjfdf%22;"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session[33]"); + HTTPSamplerBase sampler = createSampler(); + sampler.addArgument("session[33]", "adfasdfdsafasdfasd"); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkf jddkfdfjkdjfdf\"", ((Argument) args.getArguments().get(0).getObjectValue()) + .getValue()); + assertEquals("http://server.com/index.html?session%5B33%5D=jfdkjdkf+jddkfdfjkdjfdf%22", sampler.toString()); + } + + + public void testGrabSessionIdFromForm() throws Exception { + String[] html = new String[] { + "", + "", + "", + "", + "", + "", + "", + }; + for (int i = 0; i < html.length; i++) { + response = new SampleResult(); + response.setResponseData(html[i], null); + URLRewritingModifier newMod = new URLRewritingModifier(); + newMod.setThreadContext(context); + newMod.setArgumentName("sid"); + newMod.setPathExtension(false); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + newMod.process(); + Arguments args = sampler.getArguments(); + assertEquals("For case i=" + i, "myId", + ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + } + + public void testGrabSessionIdURLinJSON() throws Exception { + String html = + "", + "", // No entry; check it is still present + }; + URLRewritingModifier newMod = new URLRewritingModifier(); + newMod.setShouldCache(true); + newMod.setThreadContext(context); + newMod.setArgumentName("sid"); + newMod.setPathExtension(false); + for (int i = 0; i < html.length; i++) { + response = new SampleResult(); + response.setResponseData(html[i], null); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + newMod.process(); + Arguments args = sampler.getArguments(); + assertEquals("For case i=" + i, "myId", + ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + } + public void testNoCache() throws Exception { + String[] html = new String[] { + "", "myId", + "", "", + }; + URLRewritingModifier newMod = new URLRewritingModifier(); + newMod.setThreadContext(context); + newMod.setArgumentName("sid"); + newMod.setPathExtension(false); + newMod.setShouldCache(false); + for (int i = 0; i < html.length/2; i++) { + response = new SampleResult(); + response.setResponseData(html[i*2], null); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + newMod.process(); + Arguments args = sampler.getArguments(); + assertEquals("For case i=" + i, html[i*2+1], + ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/parser/TestHTMLParser.java b/test/src/org/apache/jmeter/protocol/http/parser/TestHTMLParser.java new file mode 100644 index 00000000000..de20d9e3055 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/parser/TestHTMLParser.java @@ -0,0 +1,444 @@ +/* + * 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.protocol.http.parser; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.TreeSet; +import java.util.Vector; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import junit.framework.TestSuite; + +public class TestHTMLParser extends JMeterTestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String DEFAULT_UA = "Apache-HttpClient/4.2.6"; + private static final String UA_FF = "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"; + private static final String UA_IE55 = "Mozilla/4.0 (compatible;MSIE 5.5; Windows 98)"; + private static final String UA_IE6 = "Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)"; + private static final String UA_IE7 = "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)"; + private static final String UA_IE8 = "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)"; + private static final String UA_IE9 = "Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))"; + private static final String UA_IE10 = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; + + public TestHTMLParser(String arg0) { + super(arg0); + } + private String parserName; + + private int testNumber = 0; + + + public TestHTMLParser(String name, int test) { + super(name); + testNumber = test; + } + + public TestHTMLParser(String name, String parser, int test) { + super(name); + testNumber = test; + parserName = parser; + } + + private static class StaticTestClass // Can't instantiate + { + private StaticTestClass() { + } + } + + private class TestClass // Can't instantiate + { + private TestClass() { + } + } + + private static class TestData { + private String fileName; + + private String baseURL; + + private String expectedSet; + + private String expectedList; + + public String userAgent; + + /** + * + * @param htmlFileName HTML File with content + * @param baseUrl Base URL + * @param expectedSet Set of expected URLs + * @param expectedList List of expected URLs + * @param userAgent User Agent + */ + private TestData(String htmlFileName, String baseUrl, String expectedSet, String expectedList) { + this(htmlFileName, baseUrl, expectedList, expectedList, DEFAULT_UA); + } + /** + * + * @param htmlFileName HTML File with content + * @param baseUrl Base URL + * @param expectedSet Set of expected URLs + * @param expectedList List of expected URLs + * @param userAgent User Agent + */ + private TestData(String htmlFileName, String baseUrl, String expectedSet, String expectedList, String userAgent) { + this.fileName = htmlFileName; + this.baseURL = baseUrl; + this.expectedSet = expectedSet; + this.expectedList = expectedList; + this.userAgent = userAgent; + } + +// private TestData(String f, String b, String s) { +// this(f, b, s, null); +// } + } + + private static final String DEFAULT_JMETER_PARSER = + "org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser"; + + // List of parsers to test. Should probably be derived automatically + private static final String[] PARSERS = { + "org.apache.jmeter.protocol.http.parser.HtmlParserHTMLParser", + "org.apache.jmeter.protocol.http.parser.JTidyHTMLParser", + "org.apache.jmeter.protocol.http.parser.RegexpHTMLParser", + DEFAULT_JMETER_PARSER, + "org.apache.jmeter.protocol.http.parser.JsoupBasedHtmlParser" + }; + + private static final TestData[] TESTS = new TestData[] { + new TestData("testfiles/HTMLParserTestCase.html", + "http://localhost/mydir/myfile.html", + "testfiles/HTMLParserTestCase.set", + "testfiles/HTMLParserTestCase.all"), + new TestData("testfiles/HTMLParserTestCaseWithBaseHRef.html", + "http://localhost/mydir/myfile.html", + "testfiles/HTMLParserTestCaseBase.set", + "testfiles/HTMLParserTestCaseBase.all"), + new TestData("testfiles/HTMLParserTestCaseWithBaseHRef2.html", + "http://localhost/mydir/myfile.html", + "testfiles/HTMLParserTestCaseBase.set", + "testfiles/HTMLParserTestCaseBase.all"), + new TestData("testfiles/HTMLParserTestCaseWithMissingBaseHRef.html", + "http://localhost/mydir/images/myfile.html", + "testfiles/HTMLParserTestCaseBase.set", + "testfiles/HTMLParserTestCaseBase.all"), + new TestData("testfiles/HTMLParserTestCase2.html", + "http:", "", ""), // Dummy as the file has no entries + new TestData("testfiles/HTMLParserTestCase3.html", + "http:", "", ""), // Dummy as the file has no entries + new TestData("testfiles/HTMLParserTestCaseWithComments.html", + "http://localhost/mydir/myfile.html", + "testfiles/HTMLParserTestCaseBase.set", + "testfiles/HTMLParserTestCaseBase.all"), + new TestData("testfiles/HTMLScript.html", + "http://localhost/", + "testfiles/HTMLScript.set", + "testfiles/HTMLScript.all"), + new TestData("testfiles/HTMLParserTestFrames.html", + "http://localhost/", + "testfiles/HTMLParserTestFrames.all", + "testfiles/HTMLParserTestFrames.all"), + // Relative filenames + new TestData("testfiles/HTMLParserTestFile_2.html", + "file:HTMLParserTestFile_2.html", + "testfiles/HTMLParserTestFile_2.all", + "testfiles/HTMLParserTestFile_2.all"), + }; + + + private static final TestData[] SPECIFIC_PARSER_TESTS = new TestData[] { + new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional1_FF.all", + UA_FF), + new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional1_IE6.all", + UA_IE6), + new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional1_IE7.all", + UA_IE7), + new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional1_IE8.all", + UA_IE8), + new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional1_IE8.all", + UA_IE8), + + // FF gets mixed up by nested comments + new TestData("testfiles/HTMLParserTestCaseWithConditional2.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional2_FF.all", + UA_FF), + + new TestData("testfiles/HTMLParserTestCaseWithConditional2.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional2_IE7.all", + UA_IE7), + new TestData("testfiles/HTMLParserTestCaseWithConditional2.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional2_IE8.all", + UA_IE8), + new TestData("testfiles/HTMLParserTestCaseWithConditional2.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional2_IE9.all", + UA_IE9), + new TestData("testfiles/HTMLParserTestCaseWithConditional3.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional3_FF.all", + UA_FF), + new TestData("testfiles/HTMLParserTestCaseWithConditional3.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional3_IE10.all", + UA_IE10), + new TestData("testfiles/HTMLParserTestCaseWithConditional3.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional3_IE55.all", + UA_IE55), + new TestData("testfiles/HTMLParserTestCaseWithConditional3.html", + "http://localhost/mydir/myfile.html", + null, + "testfiles/HTMLParserTestCaseWithConditional3_IE6.all", + UA_IE6) + }; + + public static junit.framework.Test suite() { + TestSuite suite = new TestSuite("TestHTMLParser"); + suite.addTest(new TestHTMLParser("testDefaultParser")); + suite.addTest(new TestHTMLParser("testParserDefault")); + suite.addTest(new TestHTMLParser("testParserMissing")); + suite.addTest(new TestHTMLParser("testNotParser")); + suite.addTest(new TestHTMLParser("testNotCreatable")); + suite.addTest(new TestHTMLParser("testNotCreatableStatic")); + for (int i = 0; i < PARSERS.length; i++) { + TestSuite ps = new TestSuite(PARSERS[i]);// Identify subtests + ps.addTest(new TestHTMLParser("testParserProperty", PARSERS[i], 0)); + for (int j = 0; j < TESTS.length; j++) { + TestSuite ts = new TestSuite(TESTS[j].fileName); + ts.addTest(new TestHTMLParser("testParserSet", PARSERS[i], j)); + ts.addTest(new TestHTMLParser("testParserList", PARSERS[i], j)); + ps.addTest(ts); + } + suite.addTest(ps); + } + + TestSuite ps = new TestSuite(DEFAULT_JMETER_PARSER+"_conditional_comments");// Identify subtests + for (int j = 0; j < SPECIFIC_PARSER_TESTS.length; j++) { + TestSuite ts = new TestSuite(SPECIFIC_PARSER_TESTS[j].fileName); + ts.addTest(new TestHTMLParser("testSpecificParserList", DEFAULT_JMETER_PARSER, j)); + ps.addTest(ts); + } + suite.addTest(ps); + return suite; + } + + // Test if can instantiate parser using property name + public void testParserProperty() throws Exception { + Properties p = JMeterUtils.getJMeterProperties(); + if (p == null) { + p = JMeterUtils.getProperties("jmeter.properties"); + } + p.setProperty(HTMLParser.PARSER_CLASSNAME, parserName); + HTMLParser.getParser(); + } + + public void testDefaultParser() throws Exception { + HTMLParser.getParser(); + } + + public void testParserDefault() throws Exception { + HTMLParser.getParser(HTMLParser.DEFAULT_PARSER); + } + + public void testParserMissing() throws Exception { + try { + HTMLParser.getParser("no.such.parser"); + fail("Should not have been able to create the parser"); + } catch (HTMLParseError e) { + if (e.getCause() instanceof ClassNotFoundException) { + // This is OK + } else { + throw e; + } + } + } + + public void testNotParser() throws Exception { + try { + HTMLParser.getParser("java.lang.String"); + fail("Should not have been able to create the parser"); + } catch (HTMLParseError e) { + if (e.getCause() instanceof ClassCastException) { + return; + } + throw e; + } + } + + public void testNotCreatable() throws Exception { + try { + HTMLParser.getParser(TestClass.class.getName()); + fail("Should not have been able to create the parser"); + } catch (HTMLParseError e) { + if (e.getCause() instanceof InstantiationException) { + return; + } + throw e; + } + } + + public void testNotCreatableStatic() throws Exception { + try { + HTMLParser.getParser(StaticTestClass.class.getName()); + fail("Should not have been able to create the parser"); + } catch (HTMLParseError e) { + if (e.getCause() instanceof ClassCastException) { + return; + } + if (e.getCause() instanceof IllegalAccessException) { + return; + } + throw e; + } + } + + public void testParserSet() throws Exception { + HTMLParser p = HTMLParser.getParser(parserName); + filetest(p, TESTS[testNumber].fileName, TESTS[testNumber].baseURL, TESTS[testNumber].expectedSet, null, + false, TESTS[testNumber].userAgent); + } + + public void testParserList() throws Exception { + HTMLParser p = HTMLParser.getParser(parserName); + filetest(p, TESTS[testNumber].fileName, TESTS[testNumber].baseURL, TESTS[testNumber].expectedList, + new Vector(), true, TESTS[testNumber].userAgent); + } + + public void testSpecificParserList() throws Exception { + HTMLParser p = HTMLParser.getParser(parserName); + filetest(p, SPECIFIC_PARSER_TESTS[testNumber].fileName, SPECIFIC_PARSER_TESTS[testNumber].baseURL, SPECIFIC_PARSER_TESTS[testNumber].expectedList, + new ArrayList(), true, SPECIFIC_PARSER_TESTS[testNumber].userAgent); + } + + + private static void filetest(HTMLParser p, String file, String url, String resultFile, Collection c, + boolean orderMatters, // Does the order matter? + String userAgent) + throws Exception { + String parserName = p.getClass().getName().substring("org.apache.jmeter.protocol.http.parser.".length()); + String fname = file.substring(file.indexOf('/')+1); + log.debug("file " + file); + File f = findTestFile(file); + byte[] buffer = new byte[(int) f.length()]; + InputStream is = null; + try { + is = new FileInputStream(f); + int len = is.read(buffer); + assertEquals(len, buffer.length); + } finally { + IOUtils.closeQuietly(is); + } + Iterator result; + if (c == null) { + result = p.getEmbeddedResourceURLs(userAgent, buffer, new URL(url), System.getProperty("file.encoding")); + } else { + result = p.getEmbeddedResourceURLs(userAgent, buffer, new URL(url), c,System.getProperty("file.encoding")); + } + /* + * TODO: Exact ordering is only required for some tests; change the + * comparison to do a set compare where necessary. + */ + Iterator expected; + if (orderMatters) { + expected = getFile(resultFile).iterator(); + } else { + // Convert both to Sets + expected = new TreeSet(getFile(resultFile)).iterator(); + TreeSet temp = new TreeSet(new Comparator() { + @Override + public int compare(Object o1, Object o2) { + return (o1.toString().compareTo(o2.toString())); + } + }); + while (result.hasNext()) { + temp.add(result.next()); + } + result = temp.iterator(); + } + + while (expected.hasNext()) { + Object next = expected.next(); + assertTrue(userAgent+"::"+fname+"::"+parserName + "::Expecting another result " + next, result.hasNext()); + try { + assertEquals(userAgent+"::"+fname+"::"+parserName + "(next)", next, result.next().toString()); + } catch (ClassCastException e) { + fail(userAgent+"::"+fname+"::"+parserName + "::Expected URL, but got " + e.toString()); + } + } + assertFalse(userAgent+"::"+fname+"::"+parserName + "::Should have reached the end of the results", result.hasNext()); + } + + // Get expected results as a List + private static List getFile(String file) throws Exception { + ArrayList al = new ArrayList(); + if (file != null && file.length() > 0) { + BufferedReader br = new BufferedReader(new FileReader(findTestFile(file))); + String line = br.readLine(); + while (line != null) { + al.add(line); + line = br.readLine(); + } + br.close(); + } + return al; + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/parser/TestHtmlParsingUtils.java b/test/src/org/apache/jmeter/protocol/http/parser/TestHtmlParsingUtils.java new file mode 100644 index 00000000000..895836fa6dc --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/parser/TestHtmlParsingUtils.java @@ -0,0 +1,107 @@ +/* + * 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.protocol.http.parser; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; + +// TODO: need more tests +public final class TestHtmlParsingUtils extends JMeterTestCase { + + public TestHtmlParsingUtils(String name) { + super(name); + } + + @Override + protected void setUp() { + } + + public void testGetParser() throws Exception { + HtmlParsingUtils.getParser(); + } + + public void testGetDom() throws Exception { + HtmlParsingUtils.getDOM(""); + HtmlParsingUtils.getDOM(""); + } + + public void testIsArgumentMatched() throws Exception { + Argument arg = new Argument(); + Argument argp = new Argument(); + assertTrue(HtmlParsingUtils.isArgumentMatched(arg, argp)); + + arg = new Argument("test", "abcd"); + argp = new Argument("test", "a.*d"); + assertTrue(HtmlParsingUtils.isArgumentMatched(arg, argp)); + + arg = new Argument("test", "abcd"); + argp = new Argument("test", "a.*e"); + assertFalse(HtmlParsingUtils.isArgumentMatched(arg, argp)); + } + + public void testIsAnchorMatched() throws Exception { + HTTPSamplerBase target=new HTTPNullSampler(); + HTTPSamplerBase pattern=new HTTPNullSampler(); + + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.setProtocol("http:"); + assertFalse(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + pattern.setProtocol(".*"); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.setDomain("a.b.c"); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + pattern.setDomain(".*"); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.setPath("/abc"); + assertFalse(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + pattern.setPath(".*"); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.addArgument("param2", "value2", "="); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + pattern.addArgument("param1", ".*", "="); + assertFalse(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.addArgument("param1", "value1", "="); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + } + + public void testisEqualOrMatches() throws Exception { + assertTrue(HtmlParsingUtils.isEqualOrMatches("http:","http:")); + assertFalse(HtmlParsingUtils.isEqualOrMatches("http:","htTp:")); + assertTrue(HtmlParsingUtils.isEqualOrMatches("http:","ht+p:")); + assertFalse(HtmlParsingUtils.isEqualOrMatches("ht+p:","http:")); + } + + public void testisEqualOrMatchesCaseBlind() throws Exception { + assertTrue(HtmlParsingUtils.isEqualOrMatchesCaseBlind("http:","http:")); + assertTrue(HtmlParsingUtils.isEqualOrMatchesCaseBlind("http:","htTp:")); + assertTrue(HtmlParsingUtils.isEqualOrMatches("http:","ht+p:")); + assertFalse(HtmlParsingUtils.isEqualOrMatches("ht+p:","http:")); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/proxy/TestHttpRequestHdr.java b/test/src/org/apache/jmeter/protocol/http/proxy/TestHttpRequestHdr.java new file mode 100644 index 00000000000..e62849bc17e --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/proxy/TestHttpRequestHdr.java @@ -0,0 +1,662 @@ +/* + * 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.protocol.http.proxy; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; + +public class TestHttpRequestHdr extends JMeterTestCase { + public TestHttpRequestHdr(String name) { + super(name); + } + + public void testRepeatedArguments() throws Exception { + String url = "http://localhost/matrix.html"; + // A HTTP GET request + String contentEncoding = "UTF-8"; + String testGetRequest = + "GET " + url + + "?update=yes&d=1&d=2&d=&d=&d=&d=&d=&d=1&d=2&d=1&d=&d= " + + "HTTP/1.0\r\n\r\n"; + HTTPSamplerBase s = getSamplerForRequest(url, testGetRequest, contentEncoding); + assertEquals(HTTPConstants.GET, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(13, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "update", "yes", "yes", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(2), "d", "2", "2", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(3), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(4), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(5), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(6), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(7), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(8), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(9), "d", "2", "2", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(10), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(11), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(12), "d", "", "", contentEncoding, false); + + // A HTTP POST request + contentEncoding = "UTF-8"; + String postBody = "update=yes&d=1&d=2&d=&d=&d=&d=&d=&d=1&d=2&d=1&d=&d="; + String testPostRequest = "POST " + url + " HTTP/1.0\n" + + "Content-type: " + + HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertFalse(s.getDoMultipartPost()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(13, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "update", "yes", "yes", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(2), "d", "2", "2", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(3), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(4), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(5), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(6), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(7), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(8), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(9), "d", "2", "2", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(10), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(11), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(12), "d", "", "", contentEncoding, false); + + // A HTTP POST request, with content-type text/plain + contentEncoding = "UTF-8"; + postBody = "update=yes&d=1&d=2&d=&d=&d=&d=&d=&d=1&d=2&d=1&d=\uc385&d="; + testPostRequest = "POST " + url + " HTTP/1.1\r\n" + + "Content-type: text/plain\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertFalse(s.getDoMultipartPost()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + // We should have one argument, with the value equal to the post body + arguments = s.getArguments(); + assertEquals(1, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "", postBody, postBody, contentEncoding, false); + + // A HTTP POST request, with content-type text/plain; charset=UTF-8 + // The encoding should be picked up from the header we send with the request + contentEncoding = "UTF-8"; + postBody = "update=yes&d=1&d=2&d=&d=&d=&d=&d=&d=1&d=2&d=1&d=\uc385&d="; + testPostRequest = "POST " + url + " HTTP/1.1\r\n" + + "Content-type: text/plain; charset=" + contentEncoding + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + // Use null for url to simulate that HttpRequestHdr do not + // know the encoding for the page. Specify contentEncoding, so the + // request is "sent" using that encoding + s = getSamplerForRequest(null, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertFalse(s.getDoMultipartPost()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + // We should have one argument, with the value equal to the post body + arguments = s.getArguments(); + assertEquals(1, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "", postBody, postBody, contentEncoding, false); + + // A HTTP POST request, with content-type text/plain; charset=UTF-8 + // The encoding should be picked up from the header we send with the request + contentEncoding = "UTF-8"; + url = "http://vmdal-hqqa9/retalixhq/GG_Implementation/ScreenEntity/ScreenEntityHTTP.aspx?Action=Save&ET=Vendor&TT=Single&Sid=1347280336092"; + postBody = ""; + testPostRequest = "POST " + url + " HTTP/1.1\r\n" + + "x-requested-with: XMLHttpRequest" + "\r\n" + + "Accept-Language: en-us" + "\r\n" + + "Referer: http://vmdal-hqqa9/retalixhq/GG_Implementation/ScreenEntity/ScreenEntityPage.aspx?ET=Vendor&TT=Single&WM=2&UID=9292&Sid=1347280331908&UITH=Blue&MUID=window_0" + "\r\n" + + "Accept: */*" + "\r\n" + + "Content-Type: application/x-www-form-urlencoded" + "\r\n" + + "Accept-Encoding: gzip, deflate" + "\r\n" + + "User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; Tablet PC 2.0)" + "\r\n" + + "Host: vmdal-hqqa9" + "\r\n" + + "Content-Length: "+ getBodyLength(postBody, contentEncoding) + "\r\n" + + "Proxy-Connection: Keep-Alive" + "\r\n" + + "Pragma: no-cache" + "\r\n" + + "Cookie: RHQ=sid=5aaeb66c-e174-4f4c-9928-83cffcc62150" + "\r\n" + + "\r\n" + + postBody; + // Use null for url to simulate that HttpRequestHdr do not + // know the encoding for the page. Specify contentEncoding, so the + // request is "sent" using that encoding + s = getSamplerForRequest(null, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertFalse(s.getDoMultipartPost()); + // TODO Should this be OK ? + //assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + // We should have one argument, with the value equal to the post body + arguments = s.getArguments(); + assertEquals(1, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "", postBody, postBody, contentEncoding, false); + } + + public void testEncodedArguments() throws Exception { + String url = "http://localhost/matrix.html"; + testEncodedArguments(url); + } + + + public void testEncodedArgumentsIPv6() throws Exception { + String url = "http://[::1]:8080/matrix.html"; + testEncodedArguments(url); + } + + public void testEncodedArguments(String url) throws Exception { + // A HTTP GET request, with encoding not known + String contentEncoding = ""; + String queryString = "abc%3FSPACE=a+b&space=a%20b&query=What%3F"; + String testGetRequest = "GET " + url + + "?" + queryString + + " HTTP/1.1\r\n\r\n"; + // Use null for url and contentEncoding, to simulate that HttpRequestHdr do not + // know the encoding for the page + HTTPSamplerBase s = getSamplerForRequest(null, testGetRequest, null); + assertEquals(HTTPConstants.GET, s.getMethod()); + assertEquals(queryString, s.getQueryString()); + assertEquals(contentEncoding, s.getContentEncoding()); + + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(3, arguments.getArgumentCount()); + // When the encoding is not known, the argument will get the encoded value, and the "encode?" set to false + checkArgument((HTTPArgument)arguments.getArgument(0), "abc%3FSPACE", "a+b", "a+b", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "space", "a%20b", "a%20b", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(2), "query", "What%3F", "What%3F", contentEncoding, false); + + // A HTTP GET request, with UTF-8 encoding + contentEncoding = "UTF-8"; + queryString = "abc%3FSPACE=a+b&space=a%20b&query=What%3F"; + testGetRequest = "GET " + url + + "?" + queryString + + " HTTP/1.1\r\n\r\n"; + s = getSamplerForRequest(url, testGetRequest, contentEncoding); + assertEquals(HTTPConstants.GET, s.getMethod()); + String expectedQueryString = "abc%3FSPACE=a+b&space=a+b&query=What%3F"; + assertEquals(expectedQueryString, s.getQueryString()); + assertEquals(contentEncoding, s.getContentEncoding()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(3, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "abc?SPACE", "a b", "a+b", contentEncoding, true); + checkArgument((HTTPArgument)arguments.getArgument(1), "space", "a b", "a+b", contentEncoding, true); + checkArgument((HTTPArgument)arguments.getArgument(2), "query", "What?", "What%3F", contentEncoding, true); + + // A HTTP POST request, with unknown encoding + contentEncoding = ""; + String postBody = "abc%3FSPACE=a+b&space=a%20b&query=What%3F"; + String testPostRequest = "POST " + url + " HTTP/1.1\r\n" + + "Content-type: " + + HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + // Use null for url and contentEncoding, to simulate that HttpRequestHdr do not + // know the encoding for the page + s = getSamplerForRequest(null, testPostRequest, null); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertEquals(queryString, s.getQueryString()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertFalse(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(3, arguments.getArgumentCount()); + // When the encoding is not known, the argument will get the encoded value, and the "encode?" set to false + checkArgument((HTTPArgument)arguments.getArgument(0), "abc%3FSPACE", "a+b", "a+b", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "space", "a%20b", "a%20b", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(2), "query", "What%3F", "What%3F", contentEncoding, false); + + // A HTTP POST request, with UTF-8 encoding + contentEncoding = "UTF-8"; + postBody = "abc?SPACE=a+b&space=a%20b&query=What?"; + testPostRequest = "POST " + url + " HTTP/1.1\n" + + "Content-type: " + + HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + expectedQueryString = "abc%3FSPACE=a+b&space=a+b&query=What%3F"; + assertEquals(expectedQueryString, s.getQueryString()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertFalse(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(3, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "abc?SPACE", "a b", "a+b", contentEncoding, true); + checkArgument((HTTPArgument)arguments.getArgument(1), "space", "a b", "a+b", contentEncoding, true); + checkArgument((HTTPArgument)arguments.getArgument(2), "query", "What?", "What%3F", contentEncoding, true); + } + + public void testGetRequestEncodings() throws Exception { + testGetRequestEncodings("http://localhost/matrix.html"); + } + + public void testGetRequestEncodingsIPv6() throws Exception { + testGetRequestEncodings("http://[::1]:8080/matrix.html"); + } + + public void testGetRequestEncodings(String url) throws Exception { + // A HTTP GET request, with encoding not known + String contentEncoding = ""; + String param1Value = "yes"; + String param2Value = "0+5 -\u00c5\uc385%C3%85"; + String param2ValueEncoded = URLEncoder.encode(param2Value,"UTF-8"); + String testGetRequest = + "GET " + url + + "?param1=" + param1Value + "¶m2=" + param2ValueEncoded + " " + + "HTTP/1.1\r\n\r\n"; + // Use null for url and contentEncoding, to simulate that HttpRequestHdr do not + // know the encoding for the page + HTTPSamplerBase s = getSamplerForRequest(null, testGetRequest, null); + assertEquals(HTTPConstants.GET, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + // When the encoding is not known, the argument will get the encoded value, and the "encode?" set to false + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2ValueEncoded, param2ValueEncoded, contentEncoding, false); + + // A HTTP GET request, with UTF-8 encoding + contentEncoding = "UTF-8"; + param1Value = "yes"; + param2Value = "0+5 -\u007c\u2aa1\u266a\u0153\u20a1\u0115\u0364\u00c5\u2052\uc385%C3%85"; + param2ValueEncoded = URLEncoder.encode(param2Value, contentEncoding); + testGetRequest = + "GET " + url + + "?param1=" + param1Value + "¶m2=" + param2ValueEncoded + " " + + "HTTP/1.1\r\n\r\n"; + s = getSamplerForRequest(url, testGetRequest, contentEncoding); + assertEquals(HTTPConstants.GET, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2Value, param2ValueEncoded, contentEncoding, true); + + // A HTTP GET request, with ISO-8859-1 encoding + contentEncoding = "ISO-8859-1"; + param1Value = "yes"; + param2Value = "0+5 -\u00c5%C3%85"; + param2ValueEncoded = URLEncoder.encode(param2Value, contentEncoding); + testGetRequest = + "GET " + url + + "?param1=" + param1Value + "¶m2=" + param2ValueEncoded + " " + + "HTTP/1.1\r\n\r\n"; + s = getSamplerForRequest(url, testGetRequest, contentEncoding); + assertEquals(HTTPConstants.GET, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2Value, param2ValueEncoded, contentEncoding, true); + } + + public void testPostRequestEncodings() throws Exception { + String url = "http://localhost/matrix.html"; + // A HTTP POST request, with encoding not known + String contentEncoding = ""; + String param1Value = "yes"; + String param2Value = "0+5 -\u00c5%C3%85"; + String param2ValueEncoded = URLEncoder.encode(param2Value,"UTF-8"); + String postBody = "param1=" + param1Value + "¶m2=" + param2ValueEncoded + "\r\n"; + String testPostRequest = + "POST " + url + " HTTP/1.1\r\n" + + "Content-type: " + + HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + + // Use null for url and contentEncoding, to simulate that HttpRequestHdr do not + // know the encoding for the page + HTTPSamplerBase s = getSamplerForRequest(null, testPostRequest, null); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + // When the encoding is not known, the argument will get the encoded value, and the "encode?" set to false + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2ValueEncoded, param2ValueEncoded, contentEncoding, false); + + // A HTTP POST request, with UTF-8 encoding + contentEncoding = "UTF-8"; + param1Value = "yes"; + param2Value = "0+5 -\u007c\u2aa1\u266a\u0153\u20a1\u0115\u0364\u00c5\u2052\uc385%C3%85"; + param2ValueEncoded = URLEncoder.encode(param2Value, contentEncoding); + postBody = "param1=" + param1Value + "¶m2=" + param2ValueEncoded + "\r\n"; + testPostRequest = + "POST " + url + " HTTP/1.1\r\n" + + "Content-type: " + + HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2Value, param2ValueEncoded, contentEncoding, true); + + // A HTTP POST request, with ISO-8859-1 encoding + contentEncoding = "ISO-8859-1"; + param1Value = "yes"; + param2Value = "0+5 -\u00c5%C3%85"; + param2ValueEncoded = URLEncoder.encode(param2Value, contentEncoding); + postBody = "param1=" + param1Value + "¶m2=" + param2ValueEncoded + "\r\n"; + testPostRequest = + "POST " + url + " HTTP/1.1\r\n" + + "Content-type: " + + HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2Value, param2ValueEncoded, contentEncoding, true); + } + + public void testPostMultipartFormData() throws Exception { + String url = "http://localhost/matrix.html"; + // A HTTP POST request, multipart/form-data, simple values, + String contentEncoding = "UTF-8"; + String boundary = "xf8SqlDNvmn6mFYwrioJaeUR2_Z4cLRXOSmB"; + String endOfLine = "\r\n"; + String titleValue = "mytitle"; + String descriptionValue = "mydescription"; + String postBody = createMultipartFormBody(titleValue, descriptionValue, contentEncoding, true, boundary, endOfLine); + String testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + HTTPSamplerBase s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "title", titleValue, titleValue, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "description", descriptionValue, descriptionValue, contentEncoding, false); + + // A HTTP POST request, multipart/form-data, simple values, + // with \r\n as end of line, which is according to spec, + // and with more headers in each multipart + endOfLine = "\r\n"; + titleValue = "mytitle"; + descriptionValue = "mydescription"; + postBody = createMultipartFormBody(titleValue, descriptionValue, contentEncoding, true, boundary, endOfLine); + testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "title", titleValue, titleValue, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "description", descriptionValue, descriptionValue, contentEncoding, false); + + // A HTTP POST request, multipart/form-data, simple values, + // with \n as end of line, which should also be handled, + // and with more headers in each multipart + endOfLine = "\n"; + titleValue = "mytitle"; + descriptionValue = "mydescription"; + postBody = createMultipartFormBody(titleValue, descriptionValue, contentEncoding, true, boundary, endOfLine); + testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "title", titleValue, titleValue, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "description", descriptionValue, descriptionValue, contentEncoding, false); + + // A HTTP POST request, multipart/form-data, with value that will change + // if they are url encoded + // Values are similar to __VIEWSTATE parameter that .net uses + endOfLine = "\r\n"; + titleValue = "/wEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ/rA+8DZ2dnZ2dnZ2d/GNDar6OshPwdJc="; + descriptionValue = "mydescription"; + postBody = createMultipartFormBody(titleValue, descriptionValue, contentEncoding, true, boundary, endOfLine); + testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "title", titleValue, titleValue, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "description", descriptionValue, descriptionValue, contentEncoding, false); + } + + public void testParse1() throws Exception {// no space after : + HttpRequestHdr req = new HttpRequestHdr(); + ByteArrayInputStream bis = null; + bis = new ByteArrayInputStream("GET xxx HTTP/1.0\r\nname:value \r\n".getBytes("ISO-8859-1")); + req.parse(bis); + bis.close(); + HeaderManager mgr = req.getHeaderManager(); + Header header; + mgr.getHeaders(); + header = mgr.getHeader(0); + assertEquals("name",header.getName()); + assertEquals("value",header.getValue()); + } + + + public void testParse2() throws Exception {// spaces after : + HttpRequestHdr req = new HttpRequestHdr(); + ByteArrayInputStream bis = null; + bis = new ByteArrayInputStream("GET xxx HTTP/1.0\r\nname: value \r\n".getBytes("ISO-8859-1")); + req.parse(bis); + bis.close(); + HeaderManager mgr = req.getHeaderManager(); + Header header; + mgr.getHeaders(); + header = mgr.getHeader(0); + assertEquals("name",header.getName()); + assertEquals("value",header.getValue()); + } + + public void testPostMultipartFileUpload() throws Exception { + String url = "http://localhost/matrix.html"; + // A HTTP POST request, multipart/form-data, simple values, + String contentEncoding = "UTF-8"; + String boundary = "xf8SqlDNvmn6mFYwrioJaeUR2_Z4cLRXOSmB"; + String endOfLine = "\r\n"; + String fileFieldValue = "test_file"; + String fileName = "somefilename.txt"; + String mimeType = "text/plain"; + String fileContent = "somedummycontent\n\ndfgdfg\r\nfgdgdg\nContent-type:dfsfsfds"; + String postBody = createMultipartFileUploadBody(fileFieldValue, fileName, mimeType, fileContent, boundary, endOfLine); + String testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + HTTPSamplerBase s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPConstants.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertEquals("", s.getQueryString()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(0, arguments.getArgumentCount()); + HTTPFileArg hfa = s.getHTTPFiles()[0]; // Assume there's at least one file + assertEquals(fileFieldValue, hfa.getParamName()); + assertEquals(fileName, hfa.getPath()); + assertEquals(mimeType, hfa.getMimeType()); + } + + private String createMultipartFormBody(String titleValue, String descriptionValue, String contentEncoding, boolean includeExtraHeaders, String boundary, String endOfLine) { + // Title multipart + String postBody = "--" + boundary + endOfLine + + "Content-Disposition: form-data; name=\"title\"" + endOfLine; + if(includeExtraHeaders) { + postBody += "Content-Type: text/plain; charset=" + contentEncoding + endOfLine + + "Content-Transfer-Encoding: 8bit" + endOfLine; + } + postBody += endOfLine + + titleValue + endOfLine + + "--" + boundary + endOfLine; + // Description multipart + postBody += "Content-Disposition: form-data; name=\"description\"" + endOfLine; + if(includeExtraHeaders) { + postBody += "Content-Type: text/plain; charset=" + contentEncoding + endOfLine + + "Content-Transfer-Encoding: 8bit" + endOfLine; + } + postBody += endOfLine + + descriptionValue + endOfLine + + "--" + boundary + "--" + endOfLine; + + return postBody; + } + + private String createMultipartFileUploadBody(String fileField, String fileName, String fileMimeType, String fileContent, String boundary, String endOfLine) { + // File upload multipart + String postBody = "--" + boundary + endOfLine + + "Content-Disposition: form-data; name=\"" + fileField + "\" filename=\"" + fileName + "\"" + endOfLine + + "Content-Type: " + fileMimeType + endOfLine + + "Content-Transfer-Encoding: binary" + endOfLine + + endOfLine + + fileContent + endOfLine + + "--" + boundary + "--" + endOfLine; + return postBody; + } + + private String createMultipartFormRequest(String url, String postBody, String contentEncoding, String boundary, String endOfLine) + throws IOException { + String postRequest = "POST " + url + " HTTP/1.1" + endOfLine + + "Content-type: " + + HTTPConstants.MULTIPART_FORM_DATA + + "; boundary=" + boundary + endOfLine + + "Content-length: " + getBodyLength(postBody, contentEncoding) + endOfLine + + endOfLine + + postBody; + return postRequest; + } + + private HTTPSamplerBase getSamplerForRequest(String url, String request, String contentEncoding) + throws Exception { + HttpRequestHdr req = new HttpRequestHdr(); + ByteArrayInputStream bis = null; + if(contentEncoding != null) { + bis = new ByteArrayInputStream(request.getBytes(contentEncoding)); + + } + else { + // Most browsers use ISO-8859-1 as default encoding, even if spec says UTF-8 + bis = new ByteArrayInputStream(request.getBytes("ISO-8859-1")); + } + req.parse(bis); + bis.close(); + Map pageEncodings = Collections.synchronizedMap(new HashMap()); + Map formEncodings = Collections.synchronizedMap(new HashMap()); + if(url != null && contentEncoding != null) { + pageEncodings.put(url, contentEncoding); + } + SamplerCreatorFactory creatorFactory = new SamplerCreatorFactory(); + SamplerCreator creator = creatorFactory.getSamplerCreator(req, pageEncodings, formEncodings); + HTTPSamplerBase sampler = creator.createSampler(req, pageEncodings, formEncodings); + creator.populateSampler(sampler, req, pageEncodings, formEncodings); + return sampler; + } + + private void checkArgument( + HTTPArgument arg, + String expectedName, + String expectedValue, + String expectedEncodedValue, + String contentEncoding, + boolean expectedEncoded) throws IOException { + assertEquals(expectedName, arg.getName()); +// System.out.println("expect " + URLEncoder.encode(expectedValue, "UTF-8")); +// System.out.println("actual " + URLEncoder.encode(arg.getValue(), "UTF-8")); + assertEquals(expectedValue, arg.getValue()); + if(contentEncoding != null && contentEncoding.length() > 0) { + assertEquals(expectedEncodedValue, arg.getEncodedValue(contentEncoding)); + } + else { + // Most browsers use ISO-8859-1 as default encoding, even if spec says UTF-8 + assertEquals(expectedEncodedValue, arg.getEncodedValue("ISO-8859-1")); + } + assertEquals(expectedEncoded, arg.isAlwaysEncoded()); + } + + private int getBodyLength(String postBody, String contentEncoding) throws IOException { + if(contentEncoding != null && contentEncoding.length() > 0) { + return postBody.getBytes(contentEncoding).length; + } + else { + // Most browsers use ISO-8859-1 as default encoding, even if spec says UTF-8 + return postBody.getBytes().length; // TODO - charset? + } + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/proxy/TestProxyControl.java b/test/src/org/apache/jmeter/protocol/http/proxy/TestProxyControl.java new file mode 100644 index 00000000000..1d7844f5c79 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/proxy/TestProxyControl.java @@ -0,0 +1,150 @@ +/* + * 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.protocol.http.proxy; + +import junit.framework.TestCase; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; + +public class TestProxyControl extends TestCase { + private HTTPSamplerBase sampler; + + private ProxyControl control; + + public TestProxyControl(String name) { + super(name); + } + + @Override + public void setUp() { + control = new ProxyControl(); + control.addIncludedPattern(".*\\.jsp"); + control.addExcludedPattern(".*apache.org.*"); + sampler = new HTTPNullSampler(); + } + + public void testFilter1() throws Exception { + sampler.setDomain("jakarta.org"); + sampler.setPath("index.jsp"); + assertTrue("Should find jakarta.org/index.jsp", control.filterUrl(sampler)); + } + + public void testFilter2() throws Exception { + sampler.setPath("index.jsp"); + sampler.setDomain("www.apache.org"); + assertFalse("Should not match www.apache.org", control.filterUrl(sampler)); + } + + public void testFilter3() throws Exception { + sampler.setPath("header.gif"); + sampler.setDomain("jakarta.org"); + assertFalse("Should not match header.gif", control.filterUrl(sampler)); + } + + public void testContentTypeNoFilters() throws Exception { + SampleResult result = new SampleResult(); + // No filters + control.setContentTypeInclude(null); + control.setContentTypeExclude(null); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("image/png"); + assertTrue("Should allow image/png", control.filterContentType(result)); + + // Empty filters + control.setContentTypeInclude(""); + control.setContentTypeExclude(""); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("image/png"); + assertTrue("Should allow image/png", control.filterContentType(result)); + + // Non empty filters + control.setContentTypeInclude(" "); + control.setContentTypeExclude(" "); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertFalse("Should not allow text/html", control.filterContentType(result)); + result.setContentType("image/png"); + assertFalse("Should not allow image/png", control.filterContentType(result)); + } + + public void testContentTypeInclude() throws Exception { + SampleResult result = new SampleResult(); + control.setContentTypeInclude("text/html|text/ascii"); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("text/css"); + assertFalse("Should not allow text/css", control.filterContentType(result)); + } + + public void testContentTypeExclude() throws Exception { + SampleResult result = new SampleResult(); + control.setContentTypeExclude("text/css"); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("text/css"); + assertFalse("Should not allow text/css", control.filterContentType(result)); + } + + public void testContentTypeIncludeAndExclude() throws Exception { + SampleResult result = new SampleResult(); + // Simple inclusion and exclusion filter + control.setContentTypeInclude("text/html|text/ascii"); + control.setContentTypeExclude("text/css"); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("text/css"); + assertFalse("Should not allow text/css", control.filterContentType(result)); + result.setContentType("image/png"); + assertFalse("Should not allow image/png", control.filterContentType(result)); + + // Allow all but images + control.setContentTypeInclude(null); + control.setContentTypeExclude("image/.*"); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("text/css"); + assertTrue("Should allow text/css", control.filterContentType(result)); + result.setContentType("image/png"); + assertFalse("Should not allow image/png", control.filterContentType(result)); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/sampler/HTTPNullSampler.java b/test/src/org/apache/jmeter/protocol/http/sampler/HTTPNullSampler.java new file mode 100644 index 00000000000..b1fa5b4ecf2 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/sampler/HTTPNullSampler.java @@ -0,0 +1,46 @@ +/* + * 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.protocol.http.sampler; + +import java.net.URL; + +/** + * Dummy HTTPSampler class for use by classes that need an HTTPSampler, but that + * don't need an actual sampler, e.g. for Parsing testing. + */ +public final class HTTPNullSampler extends HTTPSamplerBase { + + private static final long serialVersionUID = 240L; + + /** + * Returns a sample Result with the request fields filled in. + * + * {@inheritDoc} + */ + @Override + protected HTTPSampleResult sample(URL u, String method, boolean areFollowingRedirec, int depth) { + HTTPSampleResult res = new HTTPSampleResult(); + res.sampleStart(); + res.setURL(u); + res.sampleEnd(); + return res; +// throw new UnsupportedOperationException("For test purposes only"); + } + +} diff --git a/test/src/org/apache/jmeter/protocol/http/sampler/HTTPSampler3.java b/test/src/org/apache/jmeter/protocol/http/sampler/HTTPSampler3.java new file mode 100644 index 00000000000..faa3b8e08f9 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/sampler/HTTPSampler3.java @@ -0,0 +1,55 @@ +/* + * 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.protocol.http.sampler; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.samplers.Interruptible; + +/** + * A sampler which understands all the parts necessary to read statistics about + * HTTP requests, including cookies and authentication. + * This sampler uses the Apache HttpClient implementation + */ +class HTTPSampler3 extends HTTPSamplerBase implements Interruptible { + + private static final long serialVersionUID = 241L; + + private final transient HTTPHC4Impl hc; + + public HTTPSampler3(){ + hc = new HTTPHC4Impl(this); + } + + @Override + public boolean interrupt() { + return hc.interrupt(); + } + + @Override + protected HTTPSampleResult sample(java.net.URL u, String method, + boolean areFollowingRedirect, int depth) { + return hc.sample(u, method, areFollowingRedirect, depth); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent) + */ + @Override + public void testIterationStart(LoopIterationEvent event) { + hc.notifyFirstSampleAfterLoopRestart(); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/sampler/NullURLConnection.java b/test/src/org/apache/jmeter/protocol/http/sampler/NullURLConnection.java new file mode 100644 index 00000000000..7e854f721b5 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/sampler/NullURLConnection.java @@ -0,0 +1,56 @@ +/* + * 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.protocol.http.sampler; + +import java.net.URL; +import java.net.URLConnection; +import java.net.MalformedURLException; +import java.util.Properties; + +/** + * Dummy URLConnection class for use by classes that need an + * URLConnection for junit tests. + * + */ +public final class NullURLConnection extends URLConnection { + + private final Properties data = new Properties(); + + public NullURLConnection() throws MalformedURLException { + this(new URL("http://localhost")); + } + + public NullURLConnection(URL url) { + super(url); + } + + @Override + public void connect() { + } + + @Override + public void setRequestProperty(String name, String value) { + data.put(name, value); + } + + @Override + public String getRequestProperty(String name) { + return (String) data.get(name); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/sampler/PackageTest.java b/test/src/org/apache/jmeter/protocol/http/sampler/PackageTest.java new file mode 100644 index 00000000000..5945b7400c8 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/sampler/PackageTest.java @@ -0,0 +1,69 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 16, 2003 + */ +package org.apache.jmeter.protocol.http.sampler; + +import java.awt.HeadlessException; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui; +import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class PackageTest extends TestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public PackageTest(String arg0) { + super(arg0); + } + + public void testConfiguring() throws Exception { + try { + HTTPSamplerBase sampler = (HTTPSamplerBase) new HttpTestSampleGui().createTestElement(); + configure(sampler); + } catch (HeadlessException e) { + System.out.println("o.a.j.junit.JMeterTest Error running testConfiguring due to Headless mode, "+e.toString()); + log.warn("o.a.j.junit.JMeterTest Error running testConfiguring due to Headless mode, "+e.toString()); + } + } + + private void configure(HTTPSamplerBase sampler) throws Exception { + sampler.addArgument("arg1", "val1"); + ConfigTestElement config = (ConfigTestElement) new HttpDefaultsGui().createTestElement(); + ((Arguments) config.getProperty(HTTPSamplerBase.ARGUMENTS).getObjectValue()).addArgument(new HTTPArgument( + "config1", "configValue")); + config.setRunningVersion(true); + sampler.setRunningVersion(true); + sampler.setRunningVersion(true); + sampler.addTestElement(config); + assertEquals("config1=configValue", sampler.getArguments().getArgument(1).toString()); + sampler.recoverRunningVersion(); + config.recoverRunningVersion(); + assertEquals(1, sampler.getArguments().getArgumentCount()); + sampler.addTestElement(config); + assertEquals("config1=configValue", sampler.getArguments().getArgument(1).toString()); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/sampler/PostWriterTest.java b/test/src/org/apache/jmeter/protocol/http/sampler/PostWriterTest.java new file mode 100644 index 00000000000..a135c3369eb --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/sampler/PostWriterTest.java @@ -0,0 +1,933 @@ +/* + * 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.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class PostWriterTest extends TestCase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String UTF_8 = "UTF-8"; + private static final String HTTP_ENCODING = "ISO-8859-1"; + private static final byte[] CRLF = { 0x0d, 0x0A }; + private static byte[] TEST_FILE_CONTENT; + + private StubURLConnection connection; + private HTTPSampler sampler; + private File temporaryFile; + + private PostWriter postWriter; + @Override + protected void setUp() throws Exception { + establishConnection(); + sampler = new HTTPSampler();// This must be the original (Java) HTTP sampler + postWriter=new PostWriter(); + + // Create the test file content + TEST_FILE_CONTENT = "foo content &?=01234+56789-\u007c\u2aa1\u266a\u0153\u20a1\u0115\u0364\u00c5\u2052".getBytes(UTF_8); + + // create a temporary file to make sure we always have a file to give to the PostWriter + // Whereever we are or Whatever the current path is. + temporaryFile = File.createTempFile("foo", "txt"); + OutputStream output = null; + try { + output = new FileOutputStream(temporaryFile); + output.write(TEST_FILE_CONTENT); + output.flush(); + } finally { + JOrphanUtils.closeQuietly(output); + } + } + + @Override + protected void tearDown() throws Exception { + // delete temporay file + if(!temporaryFile.delete()) { + fail("Could not delete file:"+temporaryFile.getAbsolutePath()); + } + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending a request which contains both formdata and file content + */ + public void testSendPostData() throws IOException { + sampler.setMethod(HTTPConstants.POST); + setupFilepart(sampler); + String titleValue = "mytitle"; + String descriptionValue = "mydescription"; + setupFormData(sampler, titleValue, descriptionValue); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + byte[] expectedFormBody = createExpectedOutput(PostWriter.BOUNDARY, null, titleValue, descriptionValue, TEST_FILE_CONTENT); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedOutput(PostWriter.BOUNDARY, contentEncoding, titleValue, descriptionValue, TEST_FILE_CONTENT); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedOutput(PostWriter.BOUNDARY, contentEncoding, titleValue, descriptionValue, TEST_FILE_CONTENT); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending UTF-8 data with ISO-8859-1 content encoding + establishConnection(); + contentEncoding = UTF_8; + sampler.setContentEncoding("ISO-8859-1"); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedOutput(PostWriter.BOUNDARY, contentEncoding, titleValue, descriptionValue, TEST_FILE_CONTENT); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveDifferentContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending a HTTPSampler with form parameters, and only + * the filename of a file. + */ + public void testSendPostData_NoFilename() throws IOException { + setupNoFilename(sampler); + String titleValue = "mytitle"; + String descriptionValue = "mydescription"; + setupFormData(sampler, titleValue, descriptionValue); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + byte[] expectedUrl = "title=mytitle&description=mydescription".getBytes(); // TODO - charset? + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + expectedUrl = "title=mytitle&description=mydescription".getBytes(UTF_8); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + expectedUrl = "title=mytitle&description=mydescription".getBytes(contentEncoding); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + expectedUrl = "title=mytitle&description=mydescription".getBytes(UTF_8); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending file content as the only content of the post body + */ + public void testSendPostData_FileAsBody() throws IOException { + setupFilepart(sampler, "", temporaryFile, ""); + + // Check using default encoding + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentLength(connection, TEST_FILE_CONTENT.length); + checkArraysHaveSameContent(TEST_FILE_CONTENT, connection.getOutputStreamContent()); + connection.disconnect(); + + // Check using a different encoding + + String otherEncoding; + final String fileEncoding = System.getProperty( "file.encoding");// $NON-NLS-1$ + log.info("file.encoding: "+fileEncoding); + if (UTF_8.equalsIgnoreCase(fileEncoding) || "UTF8".equalsIgnoreCase(fileEncoding)){// $NON-NLS-1$ + otherEncoding="ISO-8859-1"; // $NON-NLS-1$ + } else { + otherEncoding=UTF_8; + } + log.info("Using other encoding: "+otherEncoding); + establishConnection(); + sampler.setContentEncoding(otherEncoding); + // File content is sent as binary, so the content encoding should not change the file data + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentLength(connection, TEST_FILE_CONTENT.length); + checkArraysHaveSameContent(TEST_FILE_CONTENT, connection.getOutputStreamContent()); + // Check that other encoding is not the current encoding + checkArraysHaveDifferentContent(new String(TEST_FILE_CONTENT) // TODO - charset? + .getBytes(otherEncoding), connection.getOutputStreamContent()); + + // If we have both file as body, and form data, then only form data will be sent + setupFormData(sampler); + establishConnection(); + sampler.setContentEncoding(""); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + byte[] expectedUrl = "title=mytitle&description=mydescription".getBytes(); // TODO - charset? + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending only a file multipart. + */ + public void testSendFileData_Multipart() throws IOException { + sampler.setMethod(HTTPConstants.POST); + String fileField = "upload"; + String mimeType = "text/plain"; + File file = temporaryFile; + byte[] fileContent = TEST_FILE_CONTENT; + setupFilepart(sampler, fileField, file, mimeType); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + byte[] expectedFormBody = createExpectedFilepartOutput(PostWriter.BOUNDARY, fileField, file, mimeType, fileContent, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFilepartOutput(PostWriter.BOUNDARY, fileField, file, mimeType, fileContent, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + fileField = "some_file_field"; + mimeType = "image/png"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFilepart(sampler, fileField, file, mimeType); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFilepartOutput(PostWriter.BOUNDARY, fileField, file, mimeType, fileContent, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending only a formdata, as a multipart/form-data request. + */ + public void testSendFormData_Multipart() throws IOException { + sampler.setMethod(HTTPConstants.POST); + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + setupFormData(sampler, titleValue, descriptionValue); + // Tell sampler to do multipart, even if we have no files to upload + sampler.setDoMultipartPost(true); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + byte[] expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, null, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1, with values that need to be urlencoded + establishConnection(); + titleValue = "mytitle+123 456&yes"; + descriptionValue = "mydescription and some spaces"; + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8, with values that would have been urlencoded + // if it was not sent as multipart + establishConnection(); + titleValue = "mytitle\u0153+\u20a1 \u0115&yes\u00c5"; + descriptionValue = "mydescription \u0153 \u20a1 \u0115 \u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending only a formdata, as urlencoded data + */ + public void testSendFormData_Urlencoded() throws IOException { + String titleValue = "mytitle"; + String descriptionValue = "mydescription"; + setupFormData(sampler, titleValue, descriptionValue); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + byte[] expectedUrl = ("title=" + titleValue + "&description=" + descriptionValue).getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), "ISO-8859-1"), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), "ISO-8859-1")); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + expectedUrl = new StringBuilder("title=").append(titleValue).append("&description=") + .append(descriptionValue).toString().getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + connection.disconnect(); + + // Test sending data as ISO-8859-1, with values that need to be urlencoded + establishConnection(); + titleValue = "mytitle+123 456&yes"; + descriptionValue = "mydescription and some spaces"; + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + String expectedString = "title=" + URLEncoder.encode(titleValue, contentEncoding) + "&description=" + URLEncoder.encode(descriptionValue, contentEncoding); + expectedUrl = expectedString.getBytes(contentEncoding); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + String unencodedString = "title=" + titleValue + "&description=" + descriptionValue; + byte[] unexpectedUrl = unencodedString.getBytes(UTF_8); + checkArraysHaveDifferentContent(unexpectedUrl, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + expectedString = "title=" + URLEncoder.encode(titleValue, contentEncoding) + "&description=" + URLEncoder.encode(descriptionValue, contentEncoding); + expectedUrl = expectedString.getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + connection.disconnect(); + + // Test sending data as UTF-8, with values that needs to be urlencoded + establishConnection(); + titleValue = "mytitle\u0153+\u20a1 \u0115&yes\u00c5"; + descriptionValue = "mydescription \u0153 \u20a1 \u0115 \u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + expectedString = "title=" + URLEncoder.encode(titleValue, UTF_8) + "&description=" + URLEncoder.encode(descriptionValue, UTF_8); + expectedUrl = expectedString.getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + unencodedString = "title=" + titleValue + "&description=" + descriptionValue; + unexpectedUrl = unencodedString.getBytes("US-ASCII"); + checkArraysHaveDifferentContent(unexpectedUrl, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending parameters which are urlencoded beforehand + // The values must be URL encoded with UTF-8 encoding, because that + // is what the HTTPArgument assumes + // %C3%85 in UTF-8 is the same as %C5 in ISO-8859-1, which is the same as Å + titleValue = "mytitle%20and%20space%2Ftest%C3%85"; + descriptionValue = "mydescription+and+plus+as+space%2Ftest%C3%85"; + setupFormData(sampler, true, titleValue, descriptionValue); + + // Test sending data with default encoding + establishConnection(); + contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + StringBuilder sb = new StringBuilder(); + expectedUrl = (sb.append("title=").append(titleValue.replaceAll("%20", "+").replaceAll("%C3%85", "%C5")) + .append("&description=").append(descriptionValue.replaceAll("%C3%85", "%C5"))).toString().getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), "ISO-8859-1"), // HTTPSampler uses ISO-8859-1 as default encoding + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), "ISO-8859-1")); // HTTPSampler uses ISO-8859-1 as default encoding + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + sb = new StringBuilder(); + expectedUrl = (sb.append("title=").append(titleValue.replaceAll("%20", "+").replaceAll("%C3%85", "%C5")) + .append("&description=").append(descriptionValue.replaceAll("%C3%85", "%C5"))).toString().getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + sb = new StringBuilder(); + expectedUrl = (sb.append("title=").append(titleValue.replaceAll("%20", "+")).append("&description=").append(descriptionValue)).toString().getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.setHeaders(URLConnection, HTTPSampler)' + */ + public void testSetHeaders() throws IOException { + sampler.setMethod(HTTPConstants.POST); + setupFilepart(sampler); + setupFormData(sampler); + + postWriter.setHeaders(connection, sampler); + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.setHeaders(URLConnection, HTTPSampler)' + */ + public void testSetHeaders_NoFilename() throws IOException { + setupNoFilename(sampler); + setupFormData(sampler); + + postWriter.setHeaders(connection, sampler); + checkContentTypeUrlEncoded(connection); + checkContentLength(connection, "title=mytitle&description=mydescription".length()); + } + + /** + * setup commons parts of HTTPSampler with a no filename. + * + * @param httpSampler + * @throws IOException + */ + private void setupNoFilename(HTTPSampler httpSampler) { + setupFilepart(sampler, "upload", null, "application/octet-stream"); + } + + /** + * Setup the filepart with default values + * + * @param httpSampler + */ + private void setupFilepart(HTTPSampler httpSampler) { + setupFilepart(sampler, "upload", temporaryFile, "text/plain"); + } + + /** + * Setup the filepart with specified values + * + * @param httpSampler + */ + private void setupFilepart(HTTPSampler httpSampler, String fileField, File file, String mimeType) { + HTTPFileArg[] hfa = {new HTTPFileArg(file == null ? "" : file.getAbsolutePath(), fileField, mimeType)}; + httpSampler.setHTTPFiles(hfa); + } + + /** + * Setup the form data with default values + * + * @param httpSampler + */ + private void setupFormData(HTTPSampler httpSampler) { + setupFormData(httpSampler, "mytitle", "mydescription"); + } + + /** + * Setup the form data with specified values + * + * @param httpSampler + */ + private void setupFormData(HTTPSampler httpSampler, String titleValue, String descriptionValue) { + setupFormData(sampler, false, titleValue, descriptionValue); + } + + /** + * Setup the form data with specified values + * + * @param httpSampler + */ + private void setupFormData(HTTPSampler httpSampler, boolean isEncoded, String titleValue, String descriptionValue) { + Arguments args = new Arguments(); + HTTPArgument argument1 = new HTTPArgument("title", titleValue, isEncoded); + HTTPArgument argument2 = new HTTPArgument("description", descriptionValue, isEncoded); + args.addArgument(argument1); + args.addArgument(argument2); + httpSampler.setArguments(args); + } + + private void establishConnection() throws MalformedURLException { + connection = new StubURLConnection("http://fake_url/test"); + } + + /** + * Create the expected output post body for form data and file multiparts + * with default values for field names + */ + private byte[] createExpectedOutput( + String boundaryString, + String contentEncoding, + String titleValue, + String descriptionValue, + byte[] fileContent) throws IOException { + return createExpectedOutput(boundaryString, contentEncoding, "title", titleValue, "description", descriptionValue, "upload", fileContent); + } + + /** + * Create the expected output post body for form data and file multiparts + * with specified values + */ + private byte[] createExpectedOutput( + String boundaryString, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + String fileField, + byte[] fileContent) throws IOException { + // Create the multiparts + byte[] formdataMultipart = createExpectedFormdataOutput(boundaryString, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, false); + byte[] fileMultipart = createExpectedFilepartOutput(boundaryString, fileField, temporaryFile, "text/plain", fileContent, false, true); + + // Join the two multiparts + ByteArrayOutputStream output = new ByteArrayOutputStream(); + output.write(formdataMultipart); + output.write(fileMultipart); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Create the expected output multipart/form-data, with only form data, + * and no file multipart + * + * @param lastMultipart true if this is the last multipart in the request + */ + private byte[] createExpectedFormdataOutput( + String boundaryString, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + boolean firstMultipart, + boolean lastMultipart) throws IOException { + final byte[] DASH_DASH = "--".getBytes(HTTP_ENCODING); + // All form parameter always have text/plain as mime type + final String mimeType="text/plain";//TODO make this a parameter? + + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + if(firstMultipart) { + output.write(DASH_DASH); + output.write(boundaryString.getBytes(HTTP_ENCODING)); + output.write(CRLF); + } + output.write("Content-Disposition: form-data; name=\"".getBytes(HTTP_ENCODING)); + output.write(titleField.getBytes(HTTP_ENCODING)); + output.write("\"".getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Type: ".getBytes(HTTP_ENCODING)); + output.write(mimeType.getBytes(HTTP_ENCODING)); + output.write("; charset=".getBytes(HTTP_ENCODING)); + output.write((contentEncoding==null ? PostWriter.ENCODING : contentEncoding).getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Transfer-Encoding: 8bit".getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write(CRLF); + if(contentEncoding != null) { + output.write(titleValue.getBytes(contentEncoding)); + } + else { + output.write(titleValue.getBytes()); // TODO - charset? + } + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Disposition: form-data; name=\"".getBytes(HTTP_ENCODING)); + output.write(descriptionField.getBytes(HTTP_ENCODING)); + output.write("\"".getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Type: ".getBytes(HTTP_ENCODING)); + output.write(mimeType.getBytes(HTTP_ENCODING)); + output.write("; charset=".getBytes(HTTP_ENCODING)); + output.write((contentEncoding==null ? PostWriter.ENCODING : contentEncoding).getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Transfer-Encoding: 8bit".getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write(CRLF); + if(contentEncoding != null) { + output.write(descriptionValue.getBytes(contentEncoding)); + } + else { + output.write(descriptionValue.getBytes()); // TODO - charset? + } + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(HTTP_ENCODING)); + if(lastMultipart) { + output.write(DASH_DASH); + } + output.write(CRLF); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Create the expected file multipart + * + * @param lastMultipart true if this is the last multipart in the request + */ + private byte[] createExpectedFilepartOutput( + String boundaryString, + String fileField, + File file, + String mimeType, + byte[] fileContent, + boolean firstMultipart, + boolean lastMultipart) throws IOException { + // The encoding used for http headers and control information + final String httpEncoding = "ISO-8859-1"; + final byte[] DASH_DASH = "--".getBytes(httpEncoding); + + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + if(firstMultipart) { + output.write(DASH_DASH); + output.write(boundaryString.getBytes(httpEncoding)); + output.write(CRLF); + } + // replace all backslash with double backslash + String filename = file.getName(); + output.write("Content-Disposition: form-data; name=\"".getBytes(httpEncoding)); + output.write(fileField.getBytes(httpEncoding)); + output.write(("\"; filename=\"" + filename + "\"").getBytes(httpEncoding)); + output.write(CRLF); + output.write("Content-Type: ".getBytes(httpEncoding)); + output.write(mimeType.getBytes(httpEncoding)); + output.write(CRLF); + output.write("Content-Transfer-Encoding: binary".getBytes(httpEncoding)); + output.write(CRLF); + output.write(CRLF); + output.write(fileContent); + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(httpEncoding)); + if(lastMultipart) { + output.write(DASH_DASH); + } + output.write(CRLF); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Check that the the two byte arrays have identical content + * + * @param expected + * @param actual + * @throws UnsupportedEncodingException + */ + private void checkArraysHaveSameContent(byte[] expected, byte[] actual) throws UnsupportedEncodingException { + if(expected != null && actual != null) { + if(expected.length != actual.length) { + System.out.println(new String(expected,UTF_8)); + System.out.println("--------------------"); + System.out.println(new String(actual,UTF_8)); + System.out.println("===================="); + fail("arrays have different length, expected is " + expected.length + ", actual is " + actual.length); + } + else { + for(int i = 0; i < expected.length; i++) { + if(expected[i] != actual[i]) { + System.out.println(new String(expected,0,i+1, UTF_8)); + System.out.println("--------------------"); + System.out.println(new String(actual,0,i+1, UTF_8)); + System.out.println("===================="); + fail("byte at position " + i + " is different, expected is " + expected[i] + ", actual is " + actual[i]); + } + } + } + } + else { + fail("expected or actual byte arrays were null"); + } + } + + /** + * Check that the the two byte arrays different content + * + * @param expected + * @param actual + */ + private void checkArraysHaveDifferentContent(byte[] expected, byte[] actual) { + if(expected != null && actual != null) { + if(expected.length == actual.length) { + boolean allSame = true; + for(int i = 0; i < expected.length; i++) { + if(expected[i] != actual[i]) { + allSame = false; + break; + } + } + if(allSame) { + fail("all bytes were equal"); + } + } + } + else { + fail("expected or actual byte arrays were null"); + } + } + + private void checkContentTypeMultipart(HttpURLConnection conn, String boundaryString) { + assertEquals("multipart/form-data; boundary=" + boundaryString, conn.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE)); + } + + private void checkContentTypeUrlEncoded(HttpURLConnection conn) { + assertEquals(HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED, conn.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE)); + } + + private void checkContentLength(HttpURLConnection conn, int length) { + assertEquals(Integer.toString(length), conn.getRequestProperty(HTTPConstants.HEADER_CONTENT_LENGTH)); + } + + /** + * Mock an HttpURLConnection. + * extends HttpURLConnection instead of just URLConnection because there is a cast in PostWriter. + */ + private static class StubURLConnection extends HttpURLConnection { + private ByteArrayOutputStream output = new ByteArrayOutputStream(); + private Map properties = new HashMap(); + + public StubURLConnection(String url) throws MalformedURLException { + super(new URL(url)); + } + + @Override + public void connect() throws IOException { + } + + @Override + public OutputStream getOutputStream() throws IOException { + return output; + } + + @Override + public void disconnect() { + } + + @Override + public boolean usingProxy() { + return false; + } + + @Override + public String getRequestProperty(String key) { + return properties.get(key); + } + + @Override + public void setRequestProperty(String key, String value) { + properties.put(key, value); + } + + public byte[] getOutputStreamContent() { + return output.toByteArray(); + } + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/sampler/PutWriterTest.java b/test/src/org/apache/jmeter/protocol/http/sampler/PutWriterTest.java new file mode 100644 index 00000000000..1fcfa0e0b22 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/sampler/PutWriterTest.java @@ -0,0 +1,45 @@ +/* + * 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.protocol.http.sampler; + +import java.net.URLConnection; +import junit.framework.TestCase; + +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.protocol.http.util.HTTPConstants; + +public class PutWriterTest extends TestCase { + + public PutWriterTest(String name) { + super(name); + } + + public void testSetHeaders() throws Exception { + URLConnection uc = new NullURLConnection(); + HTTPSampler sampler = new HTTPSampler(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("file1", "", "mime1")}); + PutWriter pw = new PutWriter(); + pw.setHeaders(uc, sampler); + assertEquals("mime1", uc.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE)); + uc = new NullURLConnection(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("file2", "param2", "mime2")}); + pw.setHeaders(uc, sampler); + assertEquals("mime2", uc.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE)); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplers.java b/test/src/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplers.java new file mode 100644 index 00000000000..d2dcec8e565 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplers.java @@ -0,0 +1,349 @@ +/* + * 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.protocol.http.sampler; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import junit.framework.TestCase; + +public class TestHTTPSamplers extends TestCase { + + public TestHTTPSamplers(String arg0) { + super(arg0); + } + + // Parse arguments singly + public void testParseArguments(){ + HTTPSamplerBase sampler = new HTTPNullSampler(); + Arguments args; + Argument arg; + + args = sampler.getArguments(); + assertEquals(0,args.getArgumentCount()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments(""); + args = sampler.getArguments(); + assertEquals(0,args.getArgumentCount()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments("name1"); + args = sampler.getArguments(); + assertEquals(1,args.getArgumentCount()); + arg=args.getArgument(0); + assertEquals("name1",arg.getName()); + assertEquals("",arg.getMetaData()); + assertEquals("",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments("name2="); + args = sampler.getArguments(); + assertEquals(2,args.getArgumentCount()); + arg=args.getArgument(1); + assertEquals("name2",arg.getName()); + assertEquals("=",arg.getMetaData()); + assertEquals("",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments("name3=value3"); + args = sampler.getArguments(); + assertEquals(3,args.getArgumentCount()); + arg=args.getArgument(2); + assertEquals("name3",arg.getName()); + assertEquals("=",arg.getMetaData()); + assertEquals("value3",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + } + + // Parse arguments all at once + public void testParseArguments2(){ + HTTPSamplerBase sampler = new HTTPNullSampler(); + Arguments args; + Argument arg; + + args = sampler.getArguments(); + assertEquals(0,args.getArgumentCount()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments("&name1&name2=&name3=value3"); + args = sampler.getArguments(); + assertEquals(3,args.getArgumentCount()); + assertEquals(0,sampler.getHTTPFileCount()); + + arg=args.getArgument(0); + assertEquals("name1",arg.getName()); + assertEquals("",arg.getMetaData()); + assertEquals("",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + + arg=args.getArgument(1); + assertEquals("name2",arg.getName()); + assertEquals("=",arg.getMetaData()); + assertEquals("",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + + arg=args.getArgument(2); + assertEquals("name3",arg.getName()); + assertEquals("=",arg.getMetaData()); + assertEquals("value3",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + } + + public void testArgumentWithoutEquals() throws Exception { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setProtocol("http"); + sampler.setMethod(HTTPConstants.GET); + sampler.setPath("/index.html?pear"); + sampler.setDomain("www.apache.org"); + assertEquals("http://www.apache.org/index.html?pear", sampler.getUrl().toString()); + } + + public void testMakingUrl() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.addArgument("param1", "value1"); + config.setPath("/index.html"); + config.setDomain("www.apache.org"); + assertEquals("http://www.apache.org/index.html?param1=value1", config.getUrl().toString()); + } + + public void testRedirect() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.setDomain("192.168.0.1"); + HTTPSampleResult res = new HTTPSampleResult(); + res.sampleStart(); + res.setURL(config.getUrl()); + res.setResponseCode("301"); + res.sampleEnd(); + + res.setRedirectLocation("./"); + config.followRedirects(res , 0); + assertEquals("http://192.168.0.1/", config.getUrl().toString()); + + res.setRedirectLocation("."); + config.followRedirects(res , 0); + assertEquals("http://192.168.0.1/", config.getUrl().toString()); + + res.setRedirectLocation("../"); + config.followRedirects(res , 0); + assertEquals("http://192.168.0.1/", config.getUrl().toString()); + } + + public void testMakingUrl2() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.addArgument("param1", "value1"); + config.setPath("/index.html?p1=p2"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=value1&p1=p2", config.getUrl().toString()); + } + + public void testMakingUrl3() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.POST); + config.addArgument("param1", "value1"); + config.setPath("/index.html?p1=p2"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?p1=p2", config.getUrl().toString()); + } + + // test cases for making Url, and exercise method + // addArgument(String name,String value,String metadata) + + public void testMakingUrl4() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.addArgument("param1", "value1", "="); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=value1", config.getUrl().toString()); + } + + public void testMakingUrl5() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.addArgument("param1", "", "="); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=", config.getUrl().toString()); + } + + public void testMakingUrl6() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.addArgument("param1", "", ""); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1", config.getUrl().toString()); + } + + // test cases for making Url, and exercise method + // parseArguments(String queryString) + + public void testMakingUrl7() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.parseArguments("param1=value1"); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=value1", config.getUrl().toString()); + } + + public void testMakingUrl8() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.parseArguments("param1="); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=", config.getUrl().toString()); + } + + public void testMakingUrl9() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.parseArguments("param1"); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1", config.getUrl().toString()); + } + + public void testMakingUrl10() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPConstants.GET); + config.parseArguments(""); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html", config.getUrl().toString()); + } + + public void testFileList(){ + HTTPSamplerBase config = new HTTPNullSampler(); + HTTPFileArg[] arg; + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(0,arg.length); + + config.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","","")}); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(0,arg.length); + + config.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","","text/plain")}); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(1,arg.length); + assertEquals("text/plain",arg[0].getMimeType()); + assertEquals("",arg[0].getPath()); + assertEquals("",arg[0].getParamName()); + + config.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("/tmp/test123.tmp","test123.tmp","text/plain")}); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(1,arg.length); + assertEquals("text/plain",arg[0].getMimeType()); + assertEquals("/tmp/test123.tmp",arg[0].getPath()); + assertEquals("test123.tmp",arg[0].getParamName()); + + HTTPFileArg[] files = {}; + + // Ignore empty file specs + config.setHTTPFiles(files); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(0,arg.length); + files = new HTTPFileArg[]{ + new HTTPFileArg(), + new HTTPFileArg(), + }; + config.setHTTPFiles(files); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(0,arg.length); + + // Ignore trailing empty spec + files = new HTTPFileArg[]{ + new HTTPFileArg("file"), + new HTTPFileArg(), + }; + config.setHTTPFiles(files); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(1,arg.length); + + // Ignore leading empty spec + files = new HTTPFileArg[]{ + new HTTPFileArg(), + new HTTPFileArg("file1"), + new HTTPFileArg(), + new HTTPFileArg("file2"), + new HTTPFileArg(), + }; + config.setHTTPFiles(files); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(2,arg.length); + } + + public void testSetAndGetFileField() { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","param","")}); + HTTPFileArg file = sampler.getHTTPFiles()[0]; + assertEquals("param", file.getParamName()); + + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","param2","")}); + file = sampler.getHTTPFiles()[0]; + assertEquals("param2", file.getParamName()); +} + + public void testSetAndGetFilename() { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("name","","")}); + HTTPFileArg file = sampler.getHTTPFiles()[0]; + assertEquals("name", file.getPath()); + + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("name2","","")}); + file = sampler.getHTTPFiles()[0]; + assertEquals("name2", file.getPath()); + } + + public void testSetAndGetMimetype() { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","","mime")}); + HTTPFileArg file = sampler.getHTTPFiles()[0]; + assertEquals("mime", file.getMimeType()); + + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","","mime2")}); + file = sampler.getHTTPFiles()[0]; + assertEquals("mime2", file.getMimeType()); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplersAgainstHttpMirrorServer.java b/test/src/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplersAgainstHttpMirrorServer.java new file mode 100644 index 00000000000..fae2ea7a399 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplersAgainstHttpMirrorServer.java @@ -0,0 +1,1449 @@ +/* + * 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.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.net.URL; +import java.util.Locale; + +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.control.HttpMirrorServer; +import org.apache.jmeter.protocol.http.control.TestHTTPMirrorThread; +import org.apache.jmeter.protocol.http.util.EncoderCache; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.junit.Assert; + +import junit.framework.Test; +import junit.framework.TestSuite; +import junit.extensions.TestSetup; + +/** + * Class for performing actual samples for HTTPSampler and HTTPSampler2. + * The samples are executed against the HttpMirrorServer, which is + * started when the unit tests are executed. + */ +public class TestHTTPSamplersAgainstHttpMirrorServer extends JMeterTestCase { + private static final int HTTP_SAMPLER = 0; + private static final int HTTP_SAMPLER2 = 1; + private static final int HTTP_SAMPLER3 = 2; + + /** The encodings used for http headers and control information */ + private static final String ISO_8859_1 = "ISO-8859-1"; // $NON-NLS-1$ + private static final String US_ASCII = "US-ASCII"; // $NON-NLS-1$ + + private static final byte[] CRLF = { 0x0d, 0x0A }; + private static final int MIRROR_PORT = 8182; // Different from TestHTTPMirrorThread port and standard mirror server + private static byte[] TEST_FILE_CONTENT; + + private static File temporaryFile; + + private final int item; + + public TestHTTPSamplersAgainstHttpMirrorServer(String arg0) { + super(arg0); + this.item = -1; + } + + // additional ctor for processing tests which use int parameters + public TestHTTPSamplersAgainstHttpMirrorServer(String arg0, int item) { + super(arg0); + this.item = item; + } + + // This is used to emulate @before class and @after class + public static Test suite(){ + final TestSuite testSuite = new TestSuite(TestHTTPSamplersAgainstHttpMirrorServer.class); + // Add parameterised tests. For simplicity we assune each has cases 0-10 + for(int i=0; i<11; i++) { + testSuite.addTest(new TestHTTPSamplersAgainstHttpMirrorServer("itemised_testGetRequest_Parameters", i)); + testSuite.addTest(new TestHTTPSamplersAgainstHttpMirrorServer("itemised_testGetRequest_Parameters2", i)); + testSuite.addTest(new TestHTTPSamplersAgainstHttpMirrorServer("itemised_testGetRequest_Parameters3", i)); + + testSuite.addTest(new TestHTTPSamplersAgainstHttpMirrorServer("itemised_testPostRequest_UrlEncoded", i)); + testSuite.addTest(new TestHTTPSamplersAgainstHttpMirrorServer("itemised_testPostRequest_UrlEncoded2", i)); + testSuite.addTest(new TestHTTPSamplersAgainstHttpMirrorServer("itemised_testPostRequest_UrlEncoded3", i)); + } + + TestSetup setup = new TestSetup(testSuite){ + private HttpMirrorServer httpServer; + @Override + protected void setUp() throws Exception { + httpServer = TestHTTPMirrorThread.startHttpMirror(MIRROR_PORT); + // Create the test file content + TEST_FILE_CONTENT = "some foo content &?=01234+56789-\u007c\u2aa1\u266a\u0153\u20a1\u0115\u0364\u00c5\u2052\uc385%C3%85".getBytes("UTF-8"); + + // create a temporary file to make sure we always have a file to give to the PostWriter + // Whereever we are or Whatever the current path is. + temporaryFile = File.createTempFile("TestHTTPSamplersAgainstHttpMirrorServer", "tmp"); + OutputStream output = new FileOutputStream(temporaryFile); + output.write(TEST_FILE_CONTENT); + output.flush(); + output.close(); + } + + @Override + protected void tearDown() throws Exception { + // Shutdown mirror server + httpServer.stopServer(); + httpServer = null; + // delete temporay file + if(!temporaryFile.delete()) { + Assert.fail("Could not delete file:"+temporaryFile.getAbsolutePath()); + } + } + }; + return setup; + } + + public void itemised_testPostRequest_UrlEncoded() throws Exception { + testPostRequest_UrlEncoded(HTTP_SAMPLER, ISO_8859_1, item); + } + + public void itemised_testPostRequest_UrlEncoded2() throws Exception { + testPostRequest_UrlEncoded(HTTP_SAMPLER2, US_ASCII, item); + } + + public void itemised_testPostRequest_UrlEncoded3() throws Exception { + testPostRequest_UrlEncoded(HTTP_SAMPLER3, US_ASCII, item); + } + + public void testPostRequest_FormMultipart_0() throws Exception { + testPostRequest_FormMultipart(HTTP_SAMPLER, ISO_8859_1); + } + + public void testPostRequest_FormMultipart2() throws Exception { + testPostRequest_FormMultipart(HTTP_SAMPLER2, US_ASCII); + } + + public void testPostRequest_FormMultipart3() throws Exception { + testPostRequest_FormMultipart(HTTP_SAMPLER3, US_ASCII); + } + + public void testPostRequest_FileUpload() throws Exception { + testPostRequest_FileUpload(HTTP_SAMPLER, ISO_8859_1); + } + + public void testPostRequest_FileUpload2() throws Exception { + testPostRequest_FileUpload(HTTP_SAMPLER2, US_ASCII); + } + + public void testPostRequest_FileUpload3() throws Exception { + testPostRequest_FileUpload(HTTP_SAMPLER3, US_ASCII); + } + + public void testPostRequest_BodyFromParameterValues() throws Exception { + testPostRequest_BodyFromParameterValues(HTTP_SAMPLER, ISO_8859_1); + } + + public void testPostRequest_BodyFromParameterValues2() throws Exception { + testPostRequest_BodyFromParameterValues(HTTP_SAMPLER2, US_ASCII); + } + + public void testPostRequest_BodyFromParameterValues3() throws Exception { + testPostRequest_BodyFromParameterValues(HTTP_SAMPLER3, US_ASCII); + } + + public void testGetRequest() throws Exception { + testGetRequest(HTTP_SAMPLER); + } + + public void testGetRequest2() throws Exception { + testGetRequest(HTTP_SAMPLER2); + } + + public void testGetRequest3() throws Exception { + testGetRequest(HTTP_SAMPLER3); + } + + public void itemised_testGetRequest_Parameters() throws Exception { + testGetRequest_Parameters(HTTP_SAMPLER, item); + } + + public void itemised_testGetRequest_Parameters2() throws Exception { + testGetRequest_Parameters(HTTP_SAMPLER2, item); + } + + public void itemised_testGetRequest_Parameters3() throws Exception { + testGetRequest_Parameters(HTTP_SAMPLER3, item); + } + + private void testPostRequest_UrlEncoded(int samplerType, String samplerDefaultEncoding, int test) throws Exception { + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + HTTPSamplerBase sampler = createHttpSampler(samplerType); + HTTPSampleResult res; + String contentEncoding; + + switch(test) { + case 0: + // Test sending data with default encoding + contentEncoding = ""; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + break; + case 1: + // Test sending data as ISO-8859-1 + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + break; + case 2: + // Test sending data as UTF-8 + contentEncoding = "UTF-8"; + titleValue = "mytitle2\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription2\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + break; + case 3: + // Test sending data as UTF-8, with values that will change when urlencoded + contentEncoding = "UTF-8"; + titleValue = "mytitle3/="; + descriptionValue = "mydescription3 /\\"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + break; + case 4: + // Test sending data as UTF-8, with values that have been urlencoded + contentEncoding = "UTF-8"; + titleValue = "mytitle4%2F%3D"; + descriptionValue = "mydescription4+++%2F%5C"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true); + break; + case 5: + // Test sending data as UTF-8, with values similar to __VIEWSTATE parameter that .net uses + contentEncoding = "UTF-8"; + titleValue = "/wEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ/rA+8DZ2dnZ2dnZ2d/GNDar6OshPwdJc="; + descriptionValue = "mydescription5"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + break; + case 6: + // Test sending data as UTF-8, with values similar to __VIEWSTATE parameter that .net uses, + // with values urlencoded, but the always encode set to false for the arguments + // This is how the HTTP Proxy server adds arguments to the sampler + contentEncoding = "UTF-8"; + titleValue = "%2FwEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ%2FrA%2B8DZ2dnZ2dnZ2d%2FGNDar6OshPwdJc%3D"; + descriptionValue = "mydescription6"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + assertFalse(((HTTPArgument)sampler.getArguments().getArgument(0)).isAlwaysEncoded()); + assertFalse(((HTTPArgument)sampler.getArguments().getArgument(1)).isAlwaysEncoded()); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true); + break; + case 7: + // Test sending data as UTF-8, where user defined variables are used + // to set the value for form data + JMeterUtils.setLocale(Locale.ENGLISH); + TestPlan testPlan = new TestPlan(); + JMeterVariables vars = new JMeterVariables(); + vars.put("title_prefix", "a test\u00c5"); + vars.put("description_suffix", "the_end"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(testPlan.getUserDefinedVariables()); + + contentEncoding = "UTF-8"; + titleValue = "${title_prefix}mytitle7\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription7\u0153\u20a1\u0115\u00c5${description_suffix}"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + // Replace the variables in the sampler + replacer.replaceValues(sampler); + res = executeSampler(sampler); + String expectedTitleValue = "a test\u00c5mytitle7\u0153\u20a1\u0115\u00c5"; + String expectedDescriptionValue = "mydescription7\u0153\u20a1\u0115\u00c5the_end"; + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, expectedTitleValue, descriptionField, expectedDescriptionValue, false); + break; + case 8: + break; + case 9: + break; + case 10: + break; + default: + fail("Unexpected switch value: "+test); + } + + + + + + + + } + + private void testPostRequest_FormMultipart(int samplerType, String samplerDefaultEncoding) throws Exception { + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + HTTPSampleResult res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as ISO-8859-1 + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as UTF-8 + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as UTF-8, with values that would have been urlencoded + // if it was not sent as multipart + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle/="; + descriptionValue = "mydescription /\\"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as UTF-8, with values that have been urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle%2F%3D"; + descriptionValue = "mydescription+++%2F%5C"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + String expectedTitleValue = "mytitle/="; + String expectedDescriptionValue = "mydescription /\\"; + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, expectedTitleValue, descriptionField, expectedDescriptionValue); + + // Test sending data as UTF-8, with values similar to __VIEWSTATE parameter that .net uses + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "/wEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ/rA+8DZ2dnZ2dnZ2d/GNDar6OshPwdJc="; + descriptionValue = "mydescription"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as UTF-8, where user defined variables are used + // to set the value for form data + JMeterUtils.setLocale(Locale.ENGLISH); + TestPlan testPlan = new TestPlan(); + JMeterVariables vars = new JMeterVariables(); + vars.put("title_prefix", "a test\u00c5"); + vars.put("description_suffix", "the_end"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(testPlan.getUserDefinedVariables()); + + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "${title_prefix}mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5${description_suffix}"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + // Replace the variables in the sampler + replacer.replaceValues(sampler); + res = executeSampler(sampler); + expectedTitleValue = "a test\u00c5mytitle\u0153\u20a1\u0115\u00c5"; + expectedDescriptionValue = "mydescription\u0153\u20a1\u0115\u00c5the_end"; + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, expectedTitleValue, descriptionField, expectedDescriptionValue); + } + + private void testPostRequest_FileUpload(int samplerType, String samplerDefaultEncoding) throws Exception { + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + String fileField = "file1"; + String fileMimeType = "text/plain"; + + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + setupFileUploadData(sampler, false, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType); + HTTPSampleResult res = executeSampler(sampler); + checkPostRequestFileUpload(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType, TEST_FILE_CONTENT); + + // Test sending data as ISO-8859-1 + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + setupFileUploadData(sampler, false, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType); + res = executeSampler(sampler); + checkPostRequestFileUpload(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType, TEST_FILE_CONTENT); + + // Test sending data as UTF-8 + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + setupFileUploadData(sampler, false, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType); + res = executeSampler(sampler); + checkPostRequestFileUpload(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType, TEST_FILE_CONTENT); + } + + private void testPostRequest_BodyFromParameterValues(int samplerType, String samplerDefaultEncoding) throws Exception { + final String titleField = ""; // ensure only values are used + String titleValue = "mytitle"; + final String descriptionField = ""; // ensure only values are used + String descriptionValue = "mydescription"; + + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + HTTPSampleResult res = executeSampler(sampler); + String expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as ISO-8859-1 + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8 + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values that will change when urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle/="; + descriptionValue = "mydescription /\\"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values that will change when urlencoded, and where + // we tell the sampler to urlencode the parameter value + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle/="; + descriptionValue = "mydescription /\\"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + expectedPostBody = URLEncoder.encode(titleValue + descriptionValue, contentEncoding); + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values that have been urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle%2F%3D"; + descriptionValue = "mydescription+++%2F%5C"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values that have been urlencoded, and + // where we tell the sampler to urlencode the parameter values + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle%2F%3D"; + descriptionValue = "mydescription+++%2F%5C"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values similar to __VIEWSTATE parameter that .net uses + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "/wEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ/rA+8DZ2dnZ2dnZ2d/GNDar6OshPwdJc="; + descriptionValue = "mydescription"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with + as part of the value, + // where the value is set in sampler as not urluencoded, but the + // isalwaysencoded flag of the argument is set to false. + // This mimics the HTTPConstants.addNonEncodedArgument, which the + // Proxy server calls in some cases + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle++"; + descriptionValue = "mydescription+"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, where user defined variables are used + // to set the value for form data + JMeterUtils.setLocale(Locale.ENGLISH); + TestPlan testPlan = new TestPlan(); + JMeterVariables vars = new JMeterVariables(); + vars.put("title_prefix", "a test\u00c5"); + vars.put("description_suffix", "the_end"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(testPlan.getUserDefinedVariables()); + + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "${title_prefix}mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5${description_suffix}"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + // Replace the variables in the sampler + replacer.replaceValues(sampler); + res = executeSampler(sampler); + String expectedTitleValue = "a test\u00c5mytitle\u0153\u20a1\u0115\u00c5"; + String expectedDescriptionValue = "mydescription\u0153\u20a1\u0115\u00c5the_end"; + expectedPostBody = expectedTitleValue+ expectedDescriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + } + + private void testGetRequest(int samplerType) throws Exception { + // Test sending simple HTTP get + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPConstants.GET); + HTTPSampleResult res = executeSampler(sampler); + checkGetRequest(sampler, res); + + // Test sending data with ISO-8859-1 encoding + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPConstants.GET); + res = executeSampler(sampler); + checkGetRequest(sampler, res); + + // Test sending data with UTF-8 encoding + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPConstants.GET); + res = executeSampler(sampler); + checkGetRequest(sampler, res); + } + + private void testGetRequest_Parameters(int samplerType, int test) throws Exception { + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding; + HTTPSampleResult res; + URL executedUrl; + + switch(test) { + case 0: + // Test sending simple HTTP get + // Test sending data with default encoding + contentEncoding = ""; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPConstants.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, false); + break; + case 1: + // Test sending data with ISO-8859-1 encoding + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + titleValue = "mytitle1\uc385"; + descriptionValue = "mydescription1\uc385"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPConstants.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, false); + break; + case 2: + // Test sending data with UTF-8 encoding + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle2\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription2\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPConstants.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, false); + break; + case 3: + // Test sending data as UTF-8, with values that changes when urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle3\u0153+\u20a1 \u0115&yes\u00c5"; + descriptionValue = "mydescription3 \u0153 \u20a1 \u0115 \u00c5"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPConstants.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, false); + break; + case 4: + // Test sending data as UTF-8, with values that have been urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle4%2F%3D"; + descriptionValue = "mydescription4+++%2F%5C"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPConstants.GET); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, true); + break; + case 5: + // Test sending data as UTF-8, where user defined variables are used + // to set the value for form data + JMeterUtils.setLocale(Locale.ENGLISH); + TestPlan testPlan = new TestPlan(); + JMeterVariables vars = new JMeterVariables(); + vars.put("title_prefix", "a test\u00c5"); + vars.put("description_suffix", "the_end"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(testPlan.getUserDefinedVariables()); + + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "${title_prefix}mytitle5\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription5\u0153\u20a1\u0115\u00c5${description_suffix}"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPConstants.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + // Replace the variables in the sampler + replacer.replaceValues(sampler); + res = executeSampler(sampler); + String expectedTitleValue = "a test\u00c5mytitle5\u0153\u20a1\u0115\u00c5"; + String expectedDescriptionValue = "mydescription5\u0153\u20a1\u0115\u00c5the_end"; + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, expectedTitleValue, descriptionField, expectedDescriptionValue, false); + break; + case 6: + break; + case 7: + break; + case 8: + break; + case 9: + break; + case 10: + break; + default: + fail("Unexpected switch value: "+test); + } + } + + private HTTPSampleResult executeSampler(HTTPSamplerBase sampler) { + sampler.setRunningVersion(true); + sampler.threadStarted(); + HTTPSampleResult res = (HTTPSampleResult) sampler.sample(); + sampler.threadFinished(); + sampler.setRunningVersion(false); + return res; + } + + private void checkPostRequestUrlEncoded( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String samplerDefaultEncoding, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + boolean valuesAlreadyUrlEncoded) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = samplerDefaultEncoding; + } + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + String expectedPostBody = null; + if(!valuesAlreadyUrlEncoded) { + String expectedTitle = URLEncoder.encode(titleValue, contentEncoding); + String expectedDescription = URLEncoder.encode(descriptionValue, contentEncoding); + expectedPostBody = titleField + "=" + expectedTitle + "&" + descriptionField + "=" + expectedDescription; + } + else { + expectedPostBody = titleField + "=" + titleValue + "&" + descriptionField + "=" + descriptionValue; + } + // Check the request + checkPostRequestBody( + sampler, + res, + samplerDefaultEncoding, + contentEncoding, + expectedPostBody + ); + } + + private void checkPostRequestFormMultipart( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String samplerDefaultEncoding, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = samplerDefaultEncoding; + } + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + String boundaryString = getBoundaryStringFromContentType(res.getRequestHeaders()); + assertNotNull(boundaryString); + byte[] expectedPostBody = createExpectedFormdataOutput(boundaryString, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + // Check request headers + checkHeaderTypeLength(res.getRequestHeaders(), "multipart/form-data" + "; boundary=" + boundaryString, expectedPostBody.length); + // Check post body from the result query string + checkArraysHaveSameContent(expectedPostBody, res.getQueryString().getBytes(contentEncoding), contentEncoding, res); + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String dataSentToMirrorServer = new String(res.getResponseData(), contentEncoding); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + String bodySent = ""; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + // Skip the blank line with crlf dividing headers and body + bodySent = dataSentToMirrorServer.substring(posDividerHeadersAndBody+2); + } + else { + fail("No header and body section found"); + } + // Check response headers + checkHeaderTypeLength(headersSent, "multipart/form-data" + "; boundary=" + boundaryString, expectedPostBody.length); + // Check post body which was sent to the mirror server, and + // sent back by the mirror server + checkArraysHaveSameContent(expectedPostBody, bodySent.getBytes(contentEncoding), contentEncoding, res); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), (String) null, res); + } + + private void checkPostRequestFileUpload( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String samplerDefaultEncoding, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + String fileField, + File fileValue, + String fileMimeType, + byte[] fileContent) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = samplerDefaultEncoding; + } + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + String boundaryString = getBoundaryStringFromContentType(res.getRequestHeaders()); + assertNotNull(boundaryString); + byte[] expectedPostBody = createExpectedFormAndUploadOutput(boundaryString, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, fileField, fileValue, fileMimeType, fileContent); + // Check request headers + checkHeaderTypeLength(res.getRequestHeaders(), "multipart/form-data" + "; boundary=" + boundaryString, expectedPostBody.length); + // We cannot check post body from the result query string, since that will not contain + // the actual file content, but placeholder text for file content + //checkArraysHaveSameContent(expectedPostBody, res.getQueryString().getBytes(contentEncoding)); + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String headersSent = getHeadersSent(res.getResponseData()); + if(headersSent == null) { + fail("No header and body section found"); + } + // Check response headers + checkHeaderTypeLength(headersSent, "multipart/form-data" + "; boundary=" + boundaryString, expectedPostBody.length); + byte[] bodySent = getBodySent(res.getResponseData()); + assertNotNull("Sent body should not be null", bodySent); + // Check post body which was sent to the mirror server, and + // sent back by the mirror server + checkArraysHaveSameContent(expectedPostBody, bodySent, contentEncoding, res); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), (String) null, res); + } + + private void checkPostRequestBody( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String samplerDefaultEncoding, + String contentEncoding, + String expectedPostBody) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = samplerDefaultEncoding; + } + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + // Check request headers + checkHeaderTypeLength(res.getRequestHeaders(), HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED, expectedPostBody.getBytes(contentEncoding).length); + // Check post body from the result query string + checkArraysHaveSameContent(expectedPostBody.getBytes(contentEncoding), res.getQueryString().getBytes(contentEncoding), contentEncoding, res); + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String dataSentToMirrorServer = new String(res.getResponseData(), contentEncoding); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + String bodySent = ""; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + // Skip the blank line with crlf dividing headers and body + bodySent = dataSentToMirrorServer.substring(posDividerHeadersAndBody+2); + } + else { + fail("No header and body section found"); + } + // Check response headers + checkHeaderTypeLength(headersSent, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED, expectedPostBody.getBytes(contentEncoding).length); + // Check post body which was sent to the mirror server, and + // sent back by the mirror server + checkArraysHaveSameContent(expectedPostBody.getBytes(contentEncoding), bodySent.getBytes(contentEncoding), contentEncoding, res); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), (String) null, res); + } + + private void checkGetRequest( + HTTPSamplerBase sampler, + HTTPSampleResult res + ) throws IOException { + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + // Check method + assertEquals(sampler.getMethod(), res.getHTTPMethod()); + // Check that the query string is empty + assertEquals(0, res.getQueryString().length()); + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String dataSentToMirrorServer = new String(res.getResponseData(), EncoderCache.URL_ARGUMENT_ENCODING); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + String bodySent = ""; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + // Skip the blank line with crlf dividing headers and body + bodySent = dataSentToMirrorServer.substring(posDividerHeadersAndBody+2); + } + else { + fail("No header and body section found"); + } + // No body should have been sent + assertEquals(bodySent.length(), 0); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), (String) null, res); + } + + private void checkGetRequest_Parameters( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String contentEncoding, + URL executedUrl, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + boolean valuesAlreadyUrlEncoded) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = EncoderCache.URL_ARGUMENT_ENCODING; + } + // Check URL + assertEquals(executedUrl, res.getURL()); + // Check method + assertEquals(sampler.getMethod(), res.getHTTPMethod()); + // Cannot check the query string of the result, because the mirror server + // replies without including query string in URL + + String expectedQueryString = null; + if(!valuesAlreadyUrlEncoded) { + String expectedTitle = URLEncoder.encode(titleValue, contentEncoding); + String expectedDescription = URLEncoder.encode(descriptionValue, contentEncoding); + expectedQueryString = titleField + "=" + expectedTitle + "&" + descriptionField + "=" + expectedDescription; + } + else { + expectedQueryString = titleField + "=" + titleValue + "&" + descriptionField + "=" + descriptionValue; + } + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String dataSentToMirrorServer = new String(res.getResponseData(), EncoderCache.URL_ARGUMENT_ENCODING); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + String bodySent = ""; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + // Skip the blank line with crlf dividing headers and body + bodySent = dataSentToMirrorServer.substring(posDividerHeadersAndBody+2); + } + else { + fail("No header and body section found in: ["+dataSentToMirrorServer+"]"); + } + // No body should have been sent + assertEquals(bodySent.length(), 0); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), expectedQueryString, res); + } + + private void checkMethodPathQuery( + String headersSent, + String expectedMethod, + String expectedPath, + String expectedQueryString, + HTTPSampleResult res + ) + throws IOException { + // Check the Request URI sent to the mirror server, and + // sent back by the mirror server + int indexFirstSpace = headersSent.indexOf(' '); + int indexSecondSpace = headersSent.indexOf(' ', headersSent.length() > indexFirstSpace ? indexFirstSpace + 1 : indexFirstSpace); + if(indexFirstSpace <= 0 && indexSecondSpace <= 0 || indexFirstSpace == indexSecondSpace) { + fail("Could not find method and URI sent"); + } + String methodSent = headersSent.substring(0, indexFirstSpace); + assertEquals(expectedMethod, methodSent); + String uriSent = headersSent.substring(indexFirstSpace + 1, indexSecondSpace); + int indexQueryStart = uriSent.indexOf('?'); + if(expectedQueryString != null && expectedQueryString.length() > 0) { + // We should have a query string part + if(indexQueryStart <= 0 || (indexQueryStart == uriSent.length() - 1)) { + fail("Could not find query string in URI"); + } + } + else { + if(indexQueryStart > 0) { + // We should not have a query string part + fail("Query string present in URI"); + } + else { + indexQueryStart = uriSent.length(); + } + } + // Check path + String pathSent = uriSent.substring(0, indexQueryStart); + assertEquals(expectedPath, pathSent); + // Check query + if(expectedQueryString != null && expectedQueryString.length() > 0) { + String queryStringSent = uriSent.substring(indexQueryStart + 1); + // Is it only the parameter values which are encoded in the specified + // content encoding, the rest of the query is encoded in UTF-8 + // Therefore we compare the whole query using UTF-8 + checkArraysHaveSameContent(expectedQueryString.getBytes(EncoderCache.URL_ARGUMENT_ENCODING), queryStringSent.getBytes(EncoderCache.URL_ARGUMENT_ENCODING), EncoderCache.URL_ARGUMENT_ENCODING, res); + } + } + + private String getHeadersSent(byte[] responseData) throws IOException { + // Find the data sent to the mirror server, which the mirror server is sending back to us + // We assume the headers are in ISO_8859_1, and the body can be in any content encoding. + String dataSentToMirrorServer = new String(responseData, ISO_8859_1); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + } + return headersSent; + } + + private byte[] getBodySent(byte[] responseData) throws IOException { + // Find the data sent to the mirror server, which the mirror server is sending back to us + // We assume the headers are in ISO_8859_1, and the body can be in any content encoding. + // Therefore we get the data sent in ISO_8859_1, to be able to determine the end of the + // header part, and then we just construct a byte array to hold the body part, not taking + // encoding of the body into consideration, because it can contain file data, which is + // sent as raw byte data + byte[] bodySent = null; + String headersSent = getHeadersSent(responseData); + if(headersSent != null) { + // Get the content length, it tells us how much data to read + // TODO : Maybe support chunked encoding, then we cannot rely on content length + String contentLengthValue = getSentRequestHeaderValue(headersSent, HTTPConstants.HEADER_CONTENT_LENGTH); + int contentLength = -1; + if(contentLengthValue != null) { + contentLength = Integer.parseInt(contentLengthValue); + } + else { + fail("Did not receive any content-length header"); + } + bodySent = new byte[contentLength]; + System.arraycopy(responseData, responseData.length - contentLength, bodySent, 0, contentLength); + } + return bodySent; + } + + private boolean isInRequestHeaders(String requestHeaders, String headerName, String headerValue) { + return checkRegularExpression(requestHeaders, headerName + ": " + headerValue); + } + + // Java 1.6.0_22+ no longer allows Content-Length to be set, so don't check it. + // See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6996110 + // TODO any point in checking the other headers? + private void checkHeaderTypeLength(String requestHeaders, String contentType, int contentLen) { + boolean typeOK = isInRequestHeaders(requestHeaders, HTTPConstants.HEADER_CONTENT_TYPE, contentType); +// boolean lengOK = isInRequestHeaders(requestHeaders, HTTPConstants.HEADER_CONTENT_LENGTH, Integer.toString(contentLen)); + if (!typeOK){ + fail("Expected type:" + contentType + " in:\n"+ requestHeaders); + } +// if (!lengOK){ +// fail("Expected & length: " +contentLen + " in:\n"+requestHeaders); +// } + } + + private String getSentRequestHeaderValue(String requestHeaders, String headerName) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + String expression = ".*" + headerName + ": (\\d*).*"; + Pattern pattern = JMeterUtils.getPattern(expression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.SINGLELINE_MASK); + if(localMatcher.matches(requestHeaders, pattern)) { + // The value is in the first group, group 0 is the whole match + return localMatcher.getMatch().group(1); + } + return null; + } + + private boolean checkRegularExpression(String stringToCheck, String regularExpression) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.SINGLELINE_MASK); + return localMatcher.contains(stringToCheck, pattern); + } + + private int getPositionOfBody(String stringToCheck) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + // The headers and body are divided by a blank line + String regularExpression = "^.$"; + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + + PatternMatcherInput input = new PatternMatcherInput(stringToCheck); + while(localMatcher.contains(input, pattern)) { + MatchResult match = localMatcher.getMatch(); + return match.beginOffset(0); + } + // No divider was found + return -1; + } + + private String getBoundaryStringFromContentType(String requestHeaders) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + String regularExpression = "^" + HTTPConstants.HEADER_CONTENT_TYPE + ": multipart/form-data; boundary=(.+)$"; + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + if(localMatcher.contains(requestHeaders, pattern)) { + MatchResult match = localMatcher.getMatch(); + String matchString = match.group(1); + // Header may contain ;charset= , regexp extracts it so computed boundary is wrong + int indexOf = matchString.indexOf(';'); + if(indexOf>=0) { + return matchString.substring(0, indexOf); + } else { + return matchString; + } + } + else { + return null; + } + } + + private void setupUrl(HTTPSamplerBase sampler, String contentEncoding) { + String protocol = "http"; + // String domain = "localhost"; + String domain = "localhost"; + String path = "/test/somescript.jsp"; + sampler.setProtocol(protocol); + sampler.setMethod(HTTPConstants.POST); + sampler.setPath(path); + sampler.setDomain(domain); + sampler.setPort(MIRROR_PORT); + sampler.setContentEncoding(contentEncoding); + } + + /** + * Setup the form data with specified values + * + * @param httpSampler + */ + private void setupFormData(HTTPSamplerBase httpSampler, boolean isEncoded, String titleField, String titleValue, String descriptionField, String descriptionValue) { + if(isEncoded) { + httpSampler.addEncodedArgument(titleField, titleValue); + httpSampler.addEncodedArgument(descriptionField, descriptionValue); + } + else { + httpSampler.addArgument(titleField, titleValue); + httpSampler.addArgument(descriptionField, descriptionValue); + } + } + + /** + * Setup the form data with specified values, and file to upload + * + * @param httpSampler + */ + private void setupFileUploadData( + HTTPSamplerBase httpSampler, + boolean isEncoded, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + String fileField, + File fileValue, + String fileMimeType) { + // Set the form data + setupFormData(httpSampler, isEncoded, titleField, titleValue, descriptionField, descriptionValue); + // Set the file upload data + HTTPFileArg[] hfa = {new HTTPFileArg(fileValue == null ? "" : fileValue.getAbsolutePath(), fileField, fileMimeType)}; + httpSampler.setHTTPFiles(hfa); + + } + + /** + * Check that the the two byte arrays have identical content + * + * @param expected + * @param actual + * @throws UnsupportedEncodingException + */ + private void checkArraysHaveSameContent(byte[] expected, byte[] actual, String encoding, HTTPSampleResult res) throws UnsupportedEncodingException { + if(expected != null && actual != null) { + if(expected.length != actual.length) { + System.out.println("\n>>>>>>>>>>>>>>>>>>>> expected:"); + System.out.println(new String(expected, encoding)); + System.out.println("==================== actual:"); + System.out.println(new String(actual, encoding)); + System.out.println("<<<<<<<<<<<<<<<<<<<<"); + if (res != null) { + System.out.println("URL="+res.getUrlAsString()); + } + fail("arrays have different length, expected is " + expected.length + ", actual is " + actual.length); + } + else { + for(int i = 0; i < expected.length; i++) { + if(expected[i] != actual[i]) { + System.out.println("\n>>>>>>>>>>>>>>>>>>>> expected:"); + System.out.println(new String(expected,0,i+1, encoding)); + System.out.println("==================== actual:"); + System.out.println(new String(actual,0,i+1, encoding)); + System.out.println("<<<<<<<<<<<<<<<<<<<<"); +/* + // Useful to when debugging + for(int j = 0; j < expected.length; j++) { + System.out.print(expected[j] + " "); + } + System.out.println(); + for(int j = 0; j < actual.length; j++) { + System.out.print(actual[j] + " "); + } + System.out.println(); +*/ + if (res != null) { + System.out.println("URL="+res.getUrlAsString()); + } + fail("byte at position " + i + " is different, expected is " + expected[i] + ", actual is " + actual[i]); + } + } + } + } + else { + if (res != null) { + System.out.println("URL="+res.getUrlAsString()); + } + fail("expected or actual byte arrays were null"); + } + } + + /** + * Create the expected output multipart/form-data, with only form data, + * and no file multipart. + * This method is copied from the PostWriterTest class + * + * @param lastMultipart true if this is the last multipart in the request + */ + private byte[] createExpectedFormdataOutput( + String boundaryString, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + boolean firstMultipart, + boolean lastMultipart) throws IOException { + // The encoding used for http headers and control information + final byte[] DASH_DASH = "--".getBytes(ISO_8859_1); + + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + if(firstMultipart) { + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + output.write(CRLF); + } + output.write("Content-Disposition: form-data; name=\"".getBytes(ISO_8859_1)); + output.write(titleField.getBytes(ISO_8859_1)); + output.write("\"".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Type: text/plain".getBytes(ISO_8859_1)); + if(contentEncoding != null) { + output.write("; charset=".getBytes(ISO_8859_1)); + output.write(contentEncoding.getBytes(ISO_8859_1)); + } + output.write(CRLF); + output.write("Content-Transfer-Encoding: 8bit".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write(CRLF); + if(contentEncoding != null) { + output.write(titleValue.getBytes(contentEncoding)); + } + else { + output.write(titleValue.getBytes()); // TODO - charset? + } + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Disposition: form-data; name=\"".getBytes(ISO_8859_1)); + output.write(descriptionField.getBytes(ISO_8859_1)); + output.write("\"".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Type: text/plain".getBytes(ISO_8859_1)); + if(contentEncoding != null) { + output.write("; charset=".getBytes(ISO_8859_1)); + output.write(contentEncoding.getBytes(ISO_8859_1)); + } + output.write(CRLF); + output.write("Content-Transfer-Encoding: 8bit".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write(CRLF); + if(contentEncoding != null) { + output.write(descriptionValue.getBytes(contentEncoding)); + } + else { + output.write(descriptionValue.getBytes()); // TODO - charset? + } + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + if(lastMultipart) { + output.write(DASH_DASH); + } + output.write(CRLF); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Create the expected file multipart + * + * @param lastMultipart true if this is the last multipart in the request + */ + private byte[] createExpectedFilepartOutput( + String boundaryString, + String fileField, + File file, + String mimeType, + byte[] fileContent, + boolean firstMultipart, + boolean lastMultipart) throws IOException { + final byte[] DASH_DASH = "--".getBytes(ISO_8859_1); + + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + if(firstMultipart) { + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + output.write(CRLF); + } + // replace all backslash with double backslash + String filename = file.getName(); + output.write("Content-Disposition: form-data; name=\"".getBytes(ISO_8859_1)); + output.write(fileField.getBytes(ISO_8859_1)); + output.write(("\"; filename=\"" + filename + "\"").getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Type: ".getBytes(ISO_8859_1)); + output.write(mimeType.getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Transfer-Encoding: binary".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write(CRLF); + output.write(fileContent); + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + if(lastMultipart) { + output.write(DASH_DASH); + } + output.write(CRLF); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Create the expected output post body for form data and file multiparts + * with specified values, when request is multipart + */ + private byte[] createExpectedFormAndUploadOutput( + String boundaryString, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + String fileField, + File fileValue, + String fileMimeType, + byte[] fileContent) throws IOException { + // Create the multiparts + byte[] formdataMultipart = createExpectedFormdataOutput(boundaryString, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, false); + byte[] fileMultipart = createExpectedFilepartOutput(boundaryString, fileField, fileValue, fileMimeType, fileContent, false, true); + + // Join the two multiparts + ByteArrayOutputStream output = new ByteArrayOutputStream(); + output.write(formdataMultipart); + output.write(fileMultipart); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + private HTTPSamplerBase createHttpSampler(int samplerType) { + switch(samplerType) { + case HTTP_SAMPLER: + return new HTTPSampler(); + case HTTP_SAMPLER2: + return new HTTPSampler2(); + case HTTP_SAMPLER3: + return new HTTPSampler3(); + default: + break; + } + throw new IllegalArgumentException("Unexpected type: "+samplerType); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/util/TestHTTPArgument.java b/test/src/org/apache/jmeter/protocol/http/util/TestHTTPArgument.java new file mode 100644 index 00000000000..fdcb6aec757 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/util/TestHTTPArgument.java @@ -0,0 +1,96 @@ +/* + * 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.protocol.http.util; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.testelement.property.CollectionProperty; + +public class TestHTTPArgument extends TestCase { + public TestHTTPArgument(String name) { + super(name); + } + + public void testCloning() throws Exception { + HTTPArgument arg = new HTTPArgument("name.?", "value_ here"); + assertEquals("name.?", arg.getName()); + assertEquals("value_ here", arg.getValue()); + assertEquals("name.%3F", arg.getEncodedName()); + assertEquals("value_+here", arg.getEncodedValue()); + HTTPArgument clone = (HTTPArgument) arg.clone(); + assertEquals("name.%3F", clone.getEncodedName()); + assertEquals("value_+here", clone.getEncodedValue()); + assertEquals("name.?", clone.getName()); + assertEquals("value_ here", clone.getValue()); + } + + public void testConversion() throws Exception { + Arguments args = new Arguments(); + args.addArgument("name.?", "value_ here"); + args.addArgument("name$of property", "value_.+"); + HTTPArgument.convertArgumentsToHTTP(args); + CollectionProperty argList = args.getArguments(); + HTTPArgument httpArg = (HTTPArgument) argList.get(0).getObjectValue(); + assertEquals("name.%3F", httpArg.getEncodedName()); + assertEquals("value_+here", httpArg.getEncodedValue()); + httpArg = (HTTPArgument) argList.get(1).getObjectValue(); + assertEquals("name%24of+property", httpArg.getEncodedName()); + assertEquals("value_.%2B", httpArg.getEncodedValue()); + } + + public void testEncoding() throws Exception { + HTTPArgument arg; + arg = new HTTPArgument("name.?", "value_ here", false); + assertEquals("name.?", arg.getName()); + assertEquals("value_ here", arg.getValue()); + assertEquals("name.%3F", arg.getEncodedName()); + assertEquals("value_+here", arg.getEncodedValue()); + // Show that can bypass encoding: + arg.setAlwaysEncoded(false); + assertEquals("name.?", arg.getEncodedName()); + assertEquals("value_ here", arg.getEncodedValue()); + + // The sample does not use a valid encoding + arg = new HTTPArgument("name.?", "value_ here", true); + assertEquals("name.?", arg.getName()); + assertEquals("value_ here", arg.getValue()); + assertEquals("name.%3F", arg.getEncodedName()); + assertEquals("value_+here", arg.getEncodedValue()); + arg.setAlwaysEncoded(false); // by default, name/value are encoded on fetch + assertEquals("name.?", arg.getEncodedName()); + assertEquals("value_ here", arg.getEncodedValue()); + + // Try a real encoded argument + arg = new HTTPArgument("name.%3F", "value_+here", true); + assertEquals("name.?", arg.getName()); + assertEquals("value_ here", arg.getValue()); + assertEquals("name.%3F", arg.getEncodedName()); + assertEquals("value_+here", arg.getEncodedValue()); + // Show that can bypass encoding: + arg.setAlwaysEncoded(false); + assertEquals("name.?", arg.getEncodedName()); + assertEquals("value_ here", arg.getEncodedValue()); + + arg = new HTTPArgument("", "\00\01\07", "", false); + arg.setAlwaysEncoded(false); + assertEquals("", arg.getEncodedName()); + assertEquals("\00\01\07", arg.getEncodedValue()); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/util/TestHTTPFileArg.java b/test/src/org/apache/jmeter/protocol/http/util/TestHTTPFileArg.java new file mode 100644 index 00000000000..308999b4c2f --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/util/TestHTTPFileArg.java @@ -0,0 +1,66 @@ +/* + * 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.protocol.http.util; + +import junit.framework.TestCase; + +public class TestHTTPFileArg extends TestCase { + public TestHTTPFileArg(String name) { + super(name); + } + + public void testConstructors() throws Exception { + HTTPFileArg file = new HTTPFileArg(); + assertEquals("no parameter failure", "", file.getPath()); + assertEquals("no parameter failure", "", file.getParamName()); + assertEquals("no parameter failure", "", file.getMimeType()); + file = new HTTPFileArg("path"); + assertEquals("single parameter failure", "path", file.getPath()); + assertEquals("single parameter failure", "", file.getParamName()); + assertEquals("single parameter failure", "", file.getMimeType()); + file = new HTTPFileArg("path", "param", "mimetype"); + assertEquals("three parameter failure", "path", file.getPath()); + assertEquals("three parameter failure", "param", file.getParamName()); + assertEquals("three parameter failure", "mimetype", file.getMimeType()); + HTTPFileArg file2 = new HTTPFileArg(file); + assertEquals("copy constructor failure", "path", file2.getPath()); + assertEquals("copy constructor failure", "param", file2.getParamName()); + assertEquals("copy constructor failure", "mimetype", file2.getMimeType()); + } + + public void testGettersSetters() throws Exception { + HTTPFileArg file = new HTTPFileArg(); + assertEquals("", file.getPath()); + assertEquals("", file.getParamName()); + assertEquals("", file.getMimeType()); + file.setPath("path"); + file.setParamName("param"); + file.setMimeType("mimetype"); + file.setHeader("header"); + assertEquals("path", file.getPath()); + assertEquals("param", file.getParamName()); + assertEquals("mimetype", file.getMimeType()); + assertEquals("header", file.getHeader()); + } + + public void testToString() throws Exception { + HTTPFileArg file = new HTTPFileArg("path1", "param1", "mimetype1"); + assertEquals("path:'path1'|param:'param1'|mimetype:'mimetype1'", file.toString()); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/util/TestHTTPFileArgs.java b/test/src/org/apache/jmeter/protocol/http/util/TestHTTPFileArgs.java new file mode 100644 index 00000000000..5e31f72b487 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/util/TestHTTPFileArgs.java @@ -0,0 +1,121 @@ +/* + * 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.protocol.http.util; + +import java.util.List; +import java.util.LinkedList; +import junit.framework.TestCase; + +import org.apache.jmeter.testelement.property.PropertyIterator; + +public class TestHTTPFileArgs extends TestCase { + public TestHTTPFileArgs(String name) { + super(name); + } + + public void testConstructors() throws Exception { + HTTPFileArgs files = new HTTPFileArgs(); + assertEquals(0, files.getHTTPFileArgCount()); + } + + public void testAdding() throws Exception { + HTTPFileArgs files = new HTTPFileArgs(); + assertEquals(0, files.getHTTPFileArgCount()); + files.addHTTPFileArg("hede"); + assertEquals(1, files.getHTTPFileArgCount()); + assertEquals("hede", ((HTTPFileArg) files.iterator().next().getObjectValue()).getPath()); + HTTPFileArg file = new HTTPFileArg("hodo"); + files.addHTTPFileArg(file); + assertEquals(2, files.getHTTPFileArgCount()); + PropertyIterator iter = files.iterator(); + assertEquals("hede", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("hodo", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.addEmptyHTTPFileArg(); + assertEquals(3, files.getHTTPFileArgCount()); + iter = files.iterator(); + assertEquals("hede", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("hodo", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + } + + public void testSetHTTPFileArgs() throws Exception { + List newHTTPFileArgs = new LinkedList(); + newHTTPFileArgs.add(new HTTPFileArg("hede")); + HTTPFileArgs files = new HTTPFileArgs(); + files.setHTTPFileArgs(newHTTPFileArgs); + assertEquals(1, files.getHTTPFileArgCount()); + assertEquals("hede", ((HTTPFileArg) files.iterator().next().getObjectValue()).getPath()); + } + + public void testRemoving() throws Exception { + HTTPFileArgs files = new HTTPFileArgs(); + assertEquals(0, files.getHTTPFileArgCount()); + files.addHTTPFileArg("hede"); + assertEquals(1, files.getHTTPFileArgCount()); + files.clear(); + assertEquals(0, files.getHTTPFileArgCount()); + files.addHTTPFileArg("file1"); + files.addHTTPFileArg("file2"); + files.addHTTPFileArg("file3"); + HTTPFileArg file = new HTTPFileArg("file4"); + files.addHTTPFileArg(file); + files.addHTTPFileArg("file5"); + files.addHTTPFileArg("file6"); + assertEquals(6, files.getHTTPFileArgCount()); + files.removeHTTPFileArg("file3"); + assertEquals(5, files.getHTTPFileArgCount()); + PropertyIterator iter = files.iterator(); + assertEquals("file1", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file2", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file4", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file5", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file6", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.removeHTTPFileArg(file); + assertEquals(4, files.getHTTPFileArgCount()); + iter = files.iterator(); + assertEquals("file1", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file2", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file5", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file6", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.removeHTTPFileArg(new HTTPFileArg("file5")); + assertEquals(3, files.getHTTPFileArgCount()); + iter = files.iterator(); + assertEquals("file1", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file2", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file6", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.removeHTTPFileArg(1); + assertEquals(2, files.getHTTPFileArgCount()); + iter = files.iterator(); + assertEquals("file1", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file6", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.removeAllHTTPFileArgs(); + assertEquals(0, files.getHTTPFileArgCount()); + } + + public void testToString() throws Exception { + HTTPFileArgs files = new HTTPFileArgs(); + files.addHTTPFileArg("file1"); + files.addHTTPFileArg("file2"); + files.addHTTPFileArg("file3"); + assertEquals("path:'file1'|param:''|mimetype:''\n" + +"path:'file2'|param:''|mimetype:''\n" + +"path:'file3'|param:''|mimetype:''", + files.toString()); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/util/TestHTTPUtils.java b/test/src/org/apache/jmeter/protocol/http/util/TestHTTPUtils.java new file mode 100644 index 00000000000..57e75e3f2a8 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/util/TestHTTPUtils.java @@ -0,0 +1,115 @@ +/* + * 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.protocol.http.util; + +import java.net.URI; +import java.net.URL; + +import junit.framework.TestCase; + +public class TestHTTPUtils extends TestCase { + public TestHTTPUtils(String name) { + super(name); + } + + public void testgetEncoding() throws Exception { + assertNull(ConversionUtils.getEncodingFromContentType("xyx")); + assertEquals("utf8",ConversionUtils.getEncodingFromContentType("charset=utf8")); + assertEquals("utf8",ConversionUtils.getEncodingFromContentType("charset=\"utf8\"")); + assertEquals("utf8",ConversionUtils.getEncodingFromContentType("text/plain ;charset=utf8")); + assertEquals("utf8",ConversionUtils.getEncodingFromContentType("text/html ;charset=utf8;charset=def")); + assertNull(ConversionUtils.getEncodingFromContentType("charset=")); + assertNull(ConversionUtils.getEncodingFromContentType(";charset=;")); + assertNull(ConversionUtils.getEncodingFromContentType(";charset=no-such-charset;")); + } + + public void testMakeRelativeURL() throws Exception { + URL base = new URL("http://192.168.0.1/a/b/c"); // Trailing file + assertEquals(new URL("http://192.168.0.1/a/b/d"),ConversionUtils.makeRelativeURL(base,"d")); + assertEquals(new URL("http://192.168.0.1/a/d"),ConversionUtils.makeRelativeURL(base,"../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../../../d")); + assertEquals(new URL("http://192.168.0.1/../d"),ConversionUtils.makeRelativeURL(base,"/../d")); + assertEquals(new URL("http://192.168.0.1/a/b/d"),ConversionUtils.makeRelativeURL(base,"./d")); + } + + public void testMakeRelativeURL2() throws Exception { + URL base = new URL("http://192.168.0.1/a/b/c/"); // Trailing directory + assertEquals(new URL("http://192.168.0.1/a/b/c/d"),ConversionUtils.makeRelativeURL(base,"d")); + assertEquals(new URL("http://192.168.0.1/a/b/d"),ConversionUtils.makeRelativeURL(base,"../d")); + assertEquals(new URL("http://192.168.0.1/a/d"),ConversionUtils.makeRelativeURL(base,"../../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../../../d")); + assertEquals(new URL("http://192.168.0.1/../d"),ConversionUtils.makeRelativeURL(base,"/../d")); + assertEquals(new URL("http://192.168.0.1/a/b/c/d"),ConversionUtils.makeRelativeURL(base,"./d")); + } + + // Test that location urls with a protocol are passed unchanged + public void testMakeRelativeURL3() throws Exception { + URL base = new URL("http://ahost.invalid/a/b/c"); + assertEquals(new URL("http://host.invalid/e"),ConversionUtils.makeRelativeURL(base ,"http://host.invalid/e")); + assertEquals(new URL("https://host.invalid/e"),ConversionUtils.makeRelativeURL(base ,"https://host.invalid/e")); + assertEquals(new URL("http://host.invalid:8081/e"),ConversionUtils.makeRelativeURL(base ,"http://host.invalid:8081/e")); + assertEquals(new URL("https://host.invalid:8081/e"),ConversionUtils.makeRelativeURL(base ,"https://host.invalid:8081/e")); + } + + public void testRemoveSlashDotDot() + { + assertEquals("/path/", ConversionUtils.removeSlashDotDot("/path/")); + assertEquals("http://host/", ConversionUtils.removeSlashDotDot("http://host/")); + assertEquals("http://host/one", ConversionUtils.removeSlashDotDot("http://host/one")); + assertEquals("/two", ConversionUtils.removeSlashDotDot("/one/../two")); + assertEquals("http://host:8080/two", ConversionUtils.removeSlashDotDot("http://host:8080/one/../two")); + assertEquals("http://host:8080/two/", ConversionUtils.removeSlashDotDot("http://host:8080/one/../two/")); + assertEquals("http://usr@host:8080/two/", ConversionUtils.removeSlashDotDot("http://usr@host:8080/one/../two/")); + assertEquals("http://host:8080/two/?query#anchor", ConversionUtils.removeSlashDotDot("http://host:8080/one/../two/?query#anchor")); + assertEquals("one", ConversionUtils.removeSlashDotDot("one/two/..")); + assertEquals("../../path", ConversionUtils.removeSlashDotDot("../../path")); + assertEquals("/", ConversionUtils.removeSlashDotDot("/one/..")); + assertEquals("/", ConversionUtils.removeSlashDotDot("/one/../")); + assertEquals("/?a", ConversionUtils.removeSlashDotDot("/one/..?a")); + assertEquals("http://host/one", ConversionUtils.removeSlashDotDot("http://host/one/../one")); + assertEquals("http://host/one/two", ConversionUtils.removeSlashDotDot("http://host/one/two/../../one/two")); + assertEquals("http://host/..", ConversionUtils.removeSlashDotDot("http://host/..")); + assertEquals("http://host/../abc", ConversionUtils.removeSlashDotDot("http://host/../abc")); + } + + public void testsanitizeUrl() throws Exception { + testSanitizeUrl("http://localhost/", "http://localhost/"); // normal, no encoding needed + testSanitizeUrl("http://localhost/a/b/c%7Cd", "http://localhost/a/b/c|d"); // pipe needs encoding + testSanitizeUrl("http://localhost:8080/%5B%5D", "http://localhost:8080/%5B%5D"); // already encoded + testSanitizeUrl("http://localhost:8080/?%5B%5D", "http://localhost:8080/?%5B%5D"); //already encoded + testSanitizeUrl("http://localhost:8080/?!£$*():@~;'%22%25%5E%7B%7D[]%3C%3E%7C%5C#", + "http://localhost:8080/?!£$*():@~;'\"%^{}[]<>|\\#"); // unencoded query + testSanitizeUrl("http://localhost:8080/?!£$*():@~;'%22%25%5E%7B%7D[]%3C%3E%7C%5C#", + "http://localhost:8080/?!£$*():@~;'%22%25%5E%7B%7D[]%3C%3E%7C%5C#"); // encoded + testSanitizeUrl("http://localhost:8080/!£$*():@~;'%22%25%5E%7B%7D%5B%5D%3C%3E%7C%5C#", + "http://localhost:8080/!£$*():@~;'\"%^{}[]<>|\\#"); // unencoded path + testSanitizeUrl("http://localhost:8080/!£$*():@~;'%22%25%5E%7B%7D%5B%5D%3C%3E%7C%5C#", + "http://localhost:8080/!£$*():@~;'%22%25%5E%7B%7D%5B%5D%3C%3E%7C%5C#"); // encoded + } + + + private void testSanitizeUrl(String expected, String input) throws Exception { + final URL url = new URL(input); + final URI uri = new URI(expected); + assertEquals(uri, ConversionUtils.sanitizeUrl(url)); + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/util/accesslog/TestLogFilter.java b/test/src/org/apache/jmeter/protocol/http/util/accesslog/TestLogFilter.java new file mode 100644 index 00000000000..ab48bf13b63 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/util/accesslog/TestLogFilter.java @@ -0,0 +1,158 @@ +/* + * 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.protocol.http.util.accesslog; + +import org.apache.jmeter.junit.JMeterTestCase; + +public class TestLogFilter extends JMeterTestCase { + + private static final String TESTSTR = "/test/helloworld.html"; + + private static final String TESTSTROUT = "/test/helloworld.jsp"; + + private static class TestData { + private final String file; + + private final boolean exclfile; + + private final boolean inclfile; + + private final boolean exclpatt; + + private final boolean inclpatt; + + TestData(String f, boolean exf, boolean inf, boolean exp, boolean inp) { + file = f; + exclfile = exf; + inclfile = inf; + exclpatt = exp; + inclpatt = inp; + } + } + + private static final String[] INCL = { "hello.html", "index.html", "/index.jsp" }; + + private static final String[] PATTERNS = { "index", ".jtml" }; + + private static final TestData[] TESTDATA = { + // file exclf inclf exclp inclp + new TestData("/test/hello.jsp", true, false, true, false), + new TestData("/test/one/hello.html", false, true, true, false), + new TestData("hello.jsp", true, false, true, false), + new TestData("hello.htm", true, false, true, false), + new TestData("/test/open.jsp", true, false, true, false), + new TestData("/test/open.html", true, false, true, false), + new TestData("/index.jsp", false, true, false, true), + new TestData("/index.jhtml", true, false, false, true), + new TestData("newindex.jsp", true, false, false, true), + new TestData("oldindex.jsp", true, false, false, true), + new TestData("oldindex1.jsp", true, false, false, true), + new TestData("oldindex2.jsp", true, false, false, true), + new TestData("oldindex3.jsp", true, false, false, true), + new TestData("oldindex4.jsp", true, false, false, true), + new TestData("oldindex5.jsp", true, false, false, true), + new TestData("oldindex6.jsp", true, false, false, true), + new TestData("/test/index.htm", true, false, false, true) }; + + public void testConstruct() { + new LogFilter(); + } + + private LogFilter testf; + + @Override + public void setUp() { + testf = new LogFilter(); + } + + public void testReplaceExtension() { + testf.setReplaceExtension("html", "jsp"); + testf.isFiltered(TESTSTR,null);// set the required variables + assertEquals(TESTSTROUT, testf.filter(TESTSTR)); + } + + public void testExcludeFiles() { + testf.excludeFiles(INCL); + for (int idx = 0; idx < TESTDATA.length; idx++) { + TestData td = TESTDATA[idx]; + String theFile = td.file; + boolean expect = td.exclfile; + + testf.isFiltered(theFile,null); + String line = testf.filter(theFile); + if (line != null) { + assertTrue("Expect to accept " + theFile, expect); + } else { + assertFalse("Expect to reject " + theFile, expect); + } + } + } + + public void testIncludeFiles() { + testf.includeFiles(INCL); + for (int idx = 0; idx < TESTDATA.length; idx++) { + TestData td = TESTDATA[idx]; + String theFile = td.file; + boolean expect = td.inclfile; + + testf.isFiltered(theFile,null); + String line = testf.filter(theFile); + if (line != null) { + assertTrue("Expect to accept " + theFile, expect); + } else { + assertFalse("Expect to reject " + theFile, expect); + } + } + + } + + public void testExcludePattern() { + testf.excludePattern(PATTERNS); + for (int idx = 0; idx < TESTDATA.length; idx++) { + TestData td = TESTDATA[idx]; + String theFile = td.file; + boolean expect = td.exclpatt; + + assertEquals(!expect, testf.isFiltered(theFile,null)); + String line = testf.filter(theFile); + if (line != null) { + assertTrue("Expect to accept " + theFile, expect); + } else { + assertFalse("Expect to reject " + theFile, expect); + } + } + } + + public void testIncludePattern() { + testf.includePattern(PATTERNS); + for (int idx = 0; idx < TESTDATA.length; idx++) { + TestData td = TESTDATA[idx]; + String theFile = td.file; + boolean expect = td.inclpatt; + + assertEquals(!expect, testf.isFiltered(theFile,null)); + String line = testf.filter(theFile); + if (line != null) { + assertTrue("Expect to accept " + theFile, expect); + } else { + assertFalse("Expect to reject " + theFile, expect); + } + } + } +} diff --git a/test/src/org/apache/jmeter/protocol/http/util/accesslog/TestTCLogParser.java b/test/src/org/apache/jmeter/protocol/http/util/accesslog/TestTCLogParser.java new file mode 100644 index 00000000000..44a395e350e --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/http/util/accesslog/TestTCLogParser.java @@ -0,0 +1,62 @@ +/* + * 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.protocol.http.util.accesslog; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; + +// TODO - more tests needed + +public class TestTCLogParser extends JMeterTestCase { + private static final TCLogParser tclp = new TCLogParser(); + + private static final String URL1 = "127.0.0.1 - - [08/Jan/2003:07:03:54 -0500] \"GET /addrbook/ HTTP/1.1\" 200 1981"; + + private static final String URL2 = "127.0.0.1 - - [08/Jan/2003:07:03:54 -0500] \"GET /addrbook?x=y HTTP/1.1\" 200 1981"; + + private static final String TEST3 = "127.0.0.1 - - [08/Jan/2003:07:03:54 -0500] \"HEAD /addrbook/ HTTP/1.1\" 200 1981"; + + public void testConstruct() throws Exception { + TCLogParser tcp; + tcp = new TCLogParser(); + assertNull("Should not have set the filename", tcp.FILENAME); + + String file = "testfiles/access.log"; + tcp = new TCLogParser(file); + assertEquals("Filename should have been saved", file, tcp.FILENAME); + } + + public void testcleanURL() throws Exception { + String res = tclp.cleanURL(URL1); + assertEquals("/addrbook/", res); + assertNull(tclp.stripFile(res, new HTTPNullSampler())); + } + + public void testcheckURL() throws Exception { + assertFalse("URL does not have a query", tclp.checkURL(URL1)); + assertTrue("URL is a query", tclp.checkURL(URL2)); + } + + public void testHEAD() throws Exception { + String res = tclp.cleanURL(TEST3); + assertEquals("/addrbook/", res); + assertNull(tclp.stripFile(res, new HTTPNullSampler())); + } + +} diff --git a/test/src/org/apache/jmeter/protocol/ldap/config/gui/PackageTest.java b/test/src/org/apache/jmeter/protocol/ldap/config/gui/PackageTest.java new file mode 100644 index 00000000000..ffc5230b3ef --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/ldap/config/gui/PackageTest.java @@ -0,0 +1,51 @@ +/* + * 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.protocol.ldap.config.gui; + +import junit.framework.TestCase; + +public class PackageTest extends TestCase { + /** + * Create a new test. + * + * @param name + * the name of the test + */ + public PackageTest(String name) { + super(name); + } + + /** + * Test that adding an argument to the table results in an appropriate + * TestElement being created. + * + * @throws Exception + * if an exception occurred during the test + */ + public void testLDAPArgumentCreation() throws Exception { + LDAPArgumentsPanel gui = new LDAPArgumentsPanel(); + gui.tableModel.addRow(new LDAPArgument()); + gui.tableModel.setValueAt("howdy", 0, 0); + gui.tableModel.addRow(new LDAPArgument()); + gui.tableModel.setValueAt("doody", 0, 1); + + assertEquals("=", ((LDAPArgument) ((LDAPArguments) gui.createTestElement()).getArguments().get(0) + .getObjectValue()).getMetaData()); + } +} diff --git a/test/src/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImplTest.java b/test/src/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImplTest.java new file mode 100644 index 00000000000..458f17e5ca3 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImplTest.java @@ -0,0 +1,86 @@ +/* + * 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. + * + */ + +/* + * Test class for BinaryTCPClientImpl utility methods. + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.apache.jorphan.util.JOrphanUtils; + +public class BinaryTCPClientImplTest extends TestCase { + + public void testHexStringToByteArray() throws Exception { + byte [] ba; + ba = BinaryTCPClientImpl.hexStringToByteArray(""); + assertEquals(0, ba.length); + + ba = BinaryTCPClientImpl.hexStringToByteArray("00"); + assertEquals(1, ba.length); + assertEquals(0, ba[0]); + + ba = BinaryTCPClientImpl.hexStringToByteArray("0f107F8081ff"); + assertEquals(6, ba.length); + assertEquals(15, ba[0]); + assertEquals(16, ba[1]); + assertEquals(127, ba[2]); + assertEquals(-128, ba[3]); + assertEquals(-127, ba[4]); + assertEquals(-1, ba[5]); + try { + ba = BinaryTCPClientImpl.hexStringToByteArray("0f107f8081ff1");// odd chars + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + try { + BinaryTCPClientImpl.hexStringToByteArray("0f107xxf8081ff"); // invalid + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + } + + public void testLoopBack() throws Exception { + assertEquals("0f107f8081ff", JOrphanUtils.baToHexString(BinaryTCPClientImpl.hexStringToByteArray("0f107f8081ff"))); + } + + public void testRoundTrip() throws Exception { + BinaryTCPClientImpl bi = new BinaryTCPClientImpl(); + InputStream is = null; + try { + bi.write(null, is); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + // ignored + } + ByteArrayOutputStream os = new ByteArrayOutputStream(); + bi.write(os, "3132333435"); // '12345' + os.close(); + assertEquals("12345",os.toString("ISO-8859-1")); + ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray()); + assertEquals("3132333435",bi.read(bis)); + } +} diff --git a/test/src/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImplTest.java b/test/src/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImplTest.java new file mode 100644 index 00000000000..59adb0e95eb --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImplTest.java @@ -0,0 +1,55 @@ +/* + * 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. + * + */ + +/* + * Test class for BinaryTCPClientImpl utility methods. + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import junit.framework.TestCase; + +public class LengthPrefixedBinaryTCPClientImplTest extends TestCase { + + public void testError() throws Exception { + ByteArrayOutputStream os = null; + ByteArrayInputStream is = null; + LengthPrefixedBinaryTCPClientImpl lp = new LengthPrefixedBinaryTCPClientImpl(); + try { + lp.write(os, is); + fail("Expected java.lang.UnsupportedOperationException"); + } catch (java.lang.UnsupportedOperationException expected) { + } + } + + public void testValid() throws Exception { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LengthPrefixedBinaryTCPClientImpl lp = new LengthPrefixedBinaryTCPClientImpl(); + final String DATA = "31323334353637"; + lp.write(os, DATA); + os.close(); + final byte[] byteArray = os.toByteArray(); + assertEquals(2+(DATA.length()/2), byteArray.length); + ByteArrayInputStream is = new ByteArrayInputStream(byteArray); + assertEquals(DATA, lp.read(is)); + } + +} diff --git a/test/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecoratorTest.java b/test/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecoratorTest.java new file mode 100644 index 00000000000..66acfb79b41 --- /dev/null +++ b/test/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecoratorTest.java @@ -0,0 +1,247 @@ +/* + * 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. + * + */ + +/* + * Test class for TCPClientDecorator utility methods. + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import junit.framework.TestCase; + +public class TCPClientDecoratorTest extends TestCase { + + + public void testIntToByteArray() throws Exception { + byte[] ba; + int len = 2; + ba = TCPClientDecorator.intToByteArray(0, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + + ba = TCPClientDecorator.intToByteArray(15, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(15, ba[1]); + + ba = TCPClientDecorator.intToByteArray(255, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(-1, ba[1]); + + ba = TCPClientDecorator.intToByteArray(256, len); + assertEquals(len, ba.length); + assertEquals(1, ba[0]); + assertEquals(0, ba[1]); + + ba = TCPClientDecorator.intToByteArray(-1, len); + assertEquals(len, ba.length); + assertEquals(-1, ba[0]); + assertEquals(-1, ba[1]); + + ba = TCPClientDecorator.intToByteArray(Short.MAX_VALUE, len); + assertEquals(len, ba.length); + assertEquals(127, ba[0]); + assertEquals(-1, ba[1]); + + ba = TCPClientDecorator.intToByteArray(Short.MIN_VALUE, len); + assertEquals(len, ba.length); + assertEquals(-128, ba[0]); + assertEquals(0, ba[1]); + + try { + ba = TCPClientDecorator.intToByteArray(Short.MIN_VALUE-1, len); + fail(); + } catch (IllegalArgumentException iae) { + } + + try { + ba = TCPClientDecorator.intToByteArray(Short.MAX_VALUE+1, len); + fail(); + } catch (IllegalArgumentException iae) { + } + + len = 4; + ba = TCPClientDecorator.intToByteArray(0, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(0, ba[2]); + assertEquals(0, ba[3]); + + ba = TCPClientDecorator.intToByteArray(15, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(0, ba[2]); + assertEquals(15, ba[3]); + + ba = TCPClientDecorator.intToByteArray(255, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(0, ba[2]); + assertEquals(-1, ba[3]); + + ba = TCPClientDecorator.intToByteArray(-1, len); + assertEquals(len, ba.length); + assertEquals(-1, ba[0]); + assertEquals(-1, ba[1]); + assertEquals(-1, ba[2]); + assertEquals(-1, ba[3]); + + ba = TCPClientDecorator.intToByteArray(256, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(1, ba[2]); + assertEquals(0, ba[3]); + + ba = TCPClientDecorator.intToByteArray(65535, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(-1, ba[2]); + assertEquals(-1, ba[3]); + + ba = TCPClientDecorator.intToByteArray(65536, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(1, ba[1]); + assertEquals(0, ba[2]); + assertEquals(0, ba[3]); + + ba = TCPClientDecorator.intToByteArray(Integer.MIN_VALUE, len); + assertEquals(len, ba.length); + assertEquals(-128, ba[0]); + assertEquals(0, ba[1]); + assertEquals(0, ba[2]); + assertEquals(0, ba[3]); + + ba = TCPClientDecorator.intToByteArray(Integer.MAX_VALUE, len); + assertEquals(len, ba.length); + assertEquals(127, ba[0]); + assertEquals(-1, ba[1]); + assertEquals(-1, ba[2]); + assertEquals(-1, ba[3]); + + // Check illegal array lengths + try { + ba = TCPClientDecorator.intToByteArray(0, 0); + fail(); + } catch (IllegalArgumentException iae) { + } + + try { + ba = TCPClientDecorator.intToByteArray(0, 1); + fail(); + } catch (IllegalArgumentException iae) { + } + + try { + ba = TCPClientDecorator.intToByteArray(0, 3); + fail(); + } catch (IllegalArgumentException iae) { + } + try { + TCPClientDecorator.intToByteArray(0, 5); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + } + } + + public void testByteArrayToInt() throws Exception { + byte[] ba; + + ba = new byte[] { 0, 0 }; + assertEquals(0, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, 15 }; + assertEquals(15, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, -1 }; + assertEquals(255, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 1, 0 }; + assertEquals(256, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { -1, -1 }; + assertEquals(-1, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, 0, -1, -1 }; + assertEquals(65535, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, 1, 0, 0 }; + assertEquals(65536, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, 0, 0, 0 }; + assertEquals(0, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { -128, 0, 0, 0 }; + assertEquals(Integer.MIN_VALUE, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 127, -1, -1, -1 }; + assertEquals(Integer.MAX_VALUE, TCPClientDecorator.byteArrayToInt(ba)); + + // test invalid byte arrays + try { + TCPClientDecorator.byteArrayToInt(null); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + try { + TCPClientDecorator.byteArrayToInt(new byte[]{}); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + try { + TCPClientDecorator.byteArrayToInt(new byte[]{0}); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + try { + TCPClientDecorator.byteArrayToInt(new byte[]{0,0,0}); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + try { + TCPClientDecorator.byteArrayToInt(new byte[]{0,0,0}); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + } + + + public void testLoopBack() throws Exception { + assertEquals(Short.MIN_VALUE, TCPClientDecorator.byteArrayToInt(TCPClientDecorator.intToByteArray(Short.MIN_VALUE, 2))); + assertEquals(Short.MAX_VALUE, TCPClientDecorator.byteArrayToInt(TCPClientDecorator.intToByteArray(Short.MAX_VALUE, 2))); + assertEquals(Integer.MIN_VALUE, TCPClientDecorator.byteArrayToInt(TCPClientDecorator.intToByteArray(Integer.MIN_VALUE, 4))); + assertEquals(Integer.MAX_VALUE, TCPClientDecorator.byteArrayToInt(TCPClientDecorator.intToByteArray(Integer.MAX_VALUE, 4))); + } +} diff --git a/test/src/org/apache/jmeter/resources/PackageTest.java b/test/src/org/apache/jmeter/resources/PackageTest.java new file mode 100644 index 00000000000..285808f3752 --- /dev/null +++ b/test/src/org/apache/jmeter/resources/PackageTest.java @@ -0,0 +1,467 @@ +/* + * 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.resources; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Properties; +import java.util.PropertyResourceBundle; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import org.apache.jmeter.gui.util.JMeterMenuBar; +import org.apache.jorphan.util.JOrphanUtils; + +/* + * Created on Nov 29, 2003 + * + * Test the composition of the messages*.properties files + * - properties files exist + * - properties files don't have duplicate keys + * - non-default properties files don't have any extra keys. + * + * N.B. If there is a default resource, ResourceBundle does not detect missing + * resources, i.e. the presence of messages.properties means that the + * ResourceBundle for Locale "XYZ" would still be found, and have the same keys + * as the default. This makes it not very useful for checking properties files. + * + * This is why the tests use Class.getResourceAsStream() etc + * + * The tests don't quite follow the normal JUnit test strategy of one test per + * possible failure. This was done in order to make it easier to report exactly + * why the tests failed. + */ + +public class PackageTest extends TestCase { + private static final String basedir = new File(System.getProperty("user.dir")).getParent(); // assumes the test starts in the bin directory + + private static final File srcFiledir = new File(basedir,"src"); + + private static final String MESSAGES = "messages"; + + private static PropertyResourceBundle defaultPRB; // current default language properties file + + private static PropertyResourceBundle messagePRB; // messages.properties + + private static final CharsetEncoder ASCII_ENCODER = + Charset.forName("US-ASCII").newEncoder(); // Ensure properties files don't use special characters + + private static boolean isPureAscii(String v) { + return ASCII_ENCODER.canEncode(v); + } + + // Read resource into ResourceBundle and store in List + private PropertyResourceBundle getRAS(String res) throws Exception { + InputStream ras = this.getClass().getResourceAsStream(res); + if (ras == null){ + return null; + } + return new PropertyResourceBundle(ras); + } + + private static final Object[] DUMMY_PARAMS = new Object[] { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + // Read resource file saving the keys + private int readRF(String res, List l) throws Exception { + int fails = 0; + InputStream ras = this.getClass().getResourceAsStream(res); + if (ras==null){ + if (MESSAGES.equals(resourcePrefix)|| lang.length() == 0 ){ + throw new IOException("Cannot open resource file "+res); + } else { + return 0; + } + } + BufferedReader fileReader = null; + try { + fileReader = new BufferedReader(new InputStreamReader(ras)); + String s; + while ((s = fileReader.readLine()) != null) { + if (s.length() > 0 && !s.startsWith("#") && !s.startsWith("!")) { + int equ = s.indexOf('='); + String key = s.substring(0, equ); + if (resourcePrefix.equals(MESSAGES)){// Only relevant for messages + /* + * JMeterUtils.getResString() converts space to _ and lowercases + * the key, so make sure all keys pass the test + */ + if ((key.indexOf(' ') >= 0) || !key.toLowerCase(java.util.Locale.ENGLISH).equals(key)) { + System.out.println("Invalid key for JMeterUtils " + key); + fails++; + } + } + String val = s.substring(equ + 1); + l.add(key); // Store the key + /* + * Now check for invalid message format: if string contains {0} + * and ' there may be a problem, so do a format with dummy + * parameters and check if there is a { in the output. A bit + * crude, but should be enough for now. + */ + if (val.indexOf("{0}") > 0 && val.indexOf('\'') > 0) { + String m = java.text.MessageFormat.format(val, DUMMY_PARAMS); + if (m.indexOf('{') > 0) { + fails++; + System.out.println("Incorrect message format ? (input/output) for: "+key); + System.out.println(val); + System.out.println(m); + } + } + + if (!isPureAscii(val)) { + fails++; + System.out.println("Incorrect char value in: "+s); + } + } + } + return fails; + } + finally { + JOrphanUtils.closeQuietly(fileReader); + } + } + + // Helper method to construct resource name + private String getResName(String lang) { + if (lang.length() == 0) { + return resourcePrefix+".properties"; + } else { + return resourcePrefix+"_" + lang + ".properties"; + } + } + + private void check(String resname) throws Exception { + check(resname, true);// check that there aren't any extra entries + } + + /* + * perform the checks on the resources + * + */ + private void check(String resname, boolean checkUnexpected) throws Exception { + ArrayList alf = new ArrayList(500);// holds keys from file + String res = getResName(resname); + subTestFailures += readRF(res, alf); + Collections.sort(alf); + + // Look for duplicate keys in the file + String last = ""; + for (int i = 0; i < alf.size(); i++) { + String curr = alf.get(i); + if (curr.equals(last)) { + subTestFailures++; + System.out.println("\nDuplicate key =" + curr + " in " + res); + } + last = curr; + } + + if (resname.length() == 0) // Must be the default resource file + { + defaultPRB = getRAS(res); + if (defaultPRB == null){ + throw new IOException("Could not find required file: "+res); + } + if (resourcePrefix.endsWith(MESSAGES)) { + messagePRB = defaultPRB; + } + } else if (checkUnexpected) { + // Check all the keys are in the default props file + PropertyResourceBundle prb = getRAS(res); + if (prb == null){ + return; + } + final ArrayList list = Collections.list(prb.getKeys()); + Collections.sort(list); + final boolean mainResourceFile = resname.startsWith("messages"); + for (String key : list) { + try { + String val = defaultPRB.getString(key); // Also Check key is in default + if (mainResourceFile && val.equals(prb.getString(key))){ + System.out.println("Duplicate value? "+key+"="+val+" in "+res); + subTestFailures++; + } + } catch (MissingResourceException e) { + subTestFailures++; + System.out.println(resourcePrefix + "_" + resname + " has unexpected key: " + key); + } + } + } + + if (subTestFailures > 0) { + fail("One or more subtests failed"); + } + } + + private static final String[] prefixList = getResources(srcFiledir); + + /** + * Find I18N resources in classpath + * @param srcFiledir directory in which the files reside + * @return list of properties files subject to I18N + */ + public static final String[] getResources(File srcFiledir) { + Set set = new TreeSet(); + findFile(srcFiledir, set, new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return new File(dir, name).isDirectory() + || ( + name.equals("messages.properties") || + (name.endsWith("Resources.properties") + && !name.matches("Example\\d+Resources\\.properties"))); + } + }); + return set.toArray(new String[set.size()]); + } + + /** + * Find resources matching filenamefiler and adds them to set removing + * everything before "/org" + * + * @param file + * directory in which the files reside + * @param set + * container into which the names of the files should be added + * @param filenameFilter + * filter that the files must satisfy to be included into + * set + */ + private static void findFile(File file, Set set, + FilenameFilter filenameFilter) { + File[] foundFiles = file.listFiles(filenameFilter); + assertNotNull("Not a directory: "+file, foundFiles); + for (File file2 : foundFiles) { + if(file2.isDirectory()) { + findFile(file2, set, filenameFilter); + } else { + String absPath2 = file2.getAbsolutePath().replace('\\', '/'); // Fix up Windows paths + int indexOfOrg = absPath2.indexOf("/org"); + int lastIndex = absPath2.lastIndexOf('.'); + set.add(absPath2.substring(indexOfOrg, lastIndex)); + } + } + } + + /* + * Use a suite to ensure that the default is done first + */ + public static Test suite() { + TestSuite ts = new TestSuite("Resources PackageTest"); + String languages[] = JMeterMenuBar.getLanguages(); + for(String prefix : prefixList){ + TestSuite pfx = new TestSuite(prefix) ; + pfx.addTest(new PackageTest("testLang","", prefix)); // load the default resource + for(String language : languages){ + if (!"en".equals(language)){ // Don't try to check the default language + pfx.addTest(new PackageTest("testLang", language, prefix)); + } + } + ts.addTest(pfx); + } + ts.addTest(new PackageTest("checkI18n", "fr")); + // TODO Add these some day +// ts.addTest(new PackageTest("checkI18n", "es")); +// ts.addTest(new PackageTest("checkI18n", "pl")); +// ts.addTest(new PackageTest("checkI18n", "pt_BR")); +// ts.addTest(new PackageTest("checkI18n", "tr")); +// ts.addTest(new PackageTest("checkI18n", Locale.JAPANESE.toString())); +// ts.addTest(new PackageTest("checkI18n", Locale.SIMPLIFIED_CHINESE.toString())); +// ts.addTest(new PackageTest("checkI18n", Locale.TRADITIONAL_CHINESE.toString())); + ts.addTest(new PackageTest("checkResourceReferences", "")); + return ts; + } + + + private int subTestFailures; + + private final String lang; + + private final String resourcePrefix; // e.g. "/org/apache/jmeter/resources/messages" + + public PackageTest(String testName, String _lang) { + this(testName, _lang, MESSAGES); + } + + public PackageTest(String testName, String _lang, String propName) { + super(testName); + lang=_lang; + subTestFailures = 0; + resourcePrefix = propName; + } + + public void testLang() throws Exception{ + check(lang); + } + + /** + * Check all messages are available in one language + * @throws Exception if something fails + */ + public void checkI18n() throws Exception { + Map> missingLabelsPerBundle = new HashMap>(); + for (String prefix : prefixList) { + Properties messages = new Properties(); + messages.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(prefix.substring(1)+".properties")); + checkMessagesForLanguage( missingLabelsPerBundle , missingLabelsPerBundle, messages,prefix.substring(1), lang); + } + + assertEquals(missingLabelsPerBundle.size()+" missing labels, labels missing:"+printLabels(missingLabelsPerBundle), 0, missingLabelsPerBundle.size()); + } + + /** + * Check messages are available in language + * @param missingLabelsPerBundle2 + * @param missingLabelsPerBundle + * @param messages Properties messages in english + * @param language Language + * @throws IOException + */ + private void checkMessagesForLanguage(Map> missingLabelsPerBundle, Map> missingLabelsPerBundle2, Properties messages, String bundlePath,String language) + throws IOException { + Properties messagesFr = new Properties(); + String languageBundle = bundlePath+"_"+language+ ".properties"; + InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(languageBundle); + if(inputStream == null) { + Map messagesAsProperties = new HashMap(); + for (Iterator> iterator = messages.entrySet().iterator(); iterator.hasNext();) { + Map.Entry entry = iterator.next(); + messagesAsProperties.put((String) entry.getKey(), (String) entry.getValue()); + } + missingLabelsPerBundle.put(languageBundle, messagesAsProperties); + return; + } + messagesFr.load(inputStream); + + Map missingLabels = new TreeMap(); + for (Iterator> iterator = messages.entrySet().iterator(); iterator.hasNext();) { + Map.Entry entry = iterator.next(); + String key = (String)entry.getKey(); + final String I18NString = "[\\d% ]+";// numeric, space and % don't need translation + if(!messagesFr.containsKey(key)) { + String value = (String) entry.getValue(); + // TODO improve check of values that don't need translation + if (value.matches(I18NString)) { + // System.out.println("Ignoring missing "+key+"="+value+" in "+languageBundle); // TODO convert to list and display at end + } else { + missingLabels.put(key,(String) entry.getValue()); + } + } else { + String value = (String) entry.getValue(); + if (value.matches(I18NString)) { + System.out.println("Unnecessary entry "+key+"="+value+" in "+languageBundle); + } + } + } + if(!missingLabels.isEmpty()) { + missingLabelsPerBundle.put(languageBundle, missingLabels); + } + } + + /** + * Build message with misssing labels per bundle + * @param missingLabelsPerBundle + * @return String + */ + private String printLabels(Map> missingLabelsPerBundle) { + StringBuilder builder = new StringBuilder(); + for (Iterator>> iterator = missingLabelsPerBundle.entrySet().iterator(); iterator.hasNext();) { + Map.Entry> entry = iterator.next(); + builder.append("Missing labels in bundle:"+entry.getKey()+"\r\n"); + for (Iterator> it2 = entry.getValue().entrySet().iterator(); it2.hasNext();) { + Map.Entry entry2 = it2.next(); + builder.append(entry2.getKey()+"="+entry2.getValue()+"\r\n"); + } + builder.append("======================================================\r\n"); + } + return builder.toString(); + } + + // Check that calls to getResString use a valid property key name + public void checkResourceReferences() { + final AtomicInteger errors = new AtomicInteger(0); + findFile(srcFiledir, null, new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + final File file = new File(dir, name); + // Look for calls to JMeterUtils.getResString() + final Pattern pat = Pattern.compile(".*getResString\\(\"([^\"]+)\"\\).*"); + if (name.endsWith(".java")) { + BufferedReader fileReader = null; + try { + fileReader = new BufferedReader(new FileReader(file)); + String s; + while ((s = fileReader.readLine()) != null) { + if (s.matches("\\s*//.*")) { // leading comment + continue; + } + Matcher m = pat.matcher(s); + if (m.matches()) { + final String key = m.group(1); + // Resource keys cannot contain spaces, and are forced to lower case + String resKey = key.replace(' ', '_'); // $NON-NLS-1$ // $NON-NLS-2$ + resKey = resKey.toLowerCase(java.util.Locale.ENGLISH); + if (!key.equals(resKey)) { + System.out.println(file+": non-standard message key: '"+key+"'"); + } + try { + messagePRB.getString(resKey); + } catch (MissingResourceException e) { + System.out.println(file+": missing message key: '"+key+"'"); + errors.incrementAndGet(); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + JOrphanUtils.closeQuietly(fileReader); + } + + } + return file.isDirectory(); + } + }); + int errs = errors.get(); + if (errs > 0) { + fail("Detected "+errs+" missing message property keys"); + } + } +} diff --git a/test/src/org/apache/jmeter/samplers/NullSampler.java b/test/src/org/apache/jmeter/samplers/NullSampler.java new file mode 100644 index 00000000000..8c888acf22e --- /dev/null +++ b/test/src/org/apache/jmeter/samplers/NullSampler.java @@ -0,0 +1,35 @@ +/* + * 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.samplers; + +/** + * + * Dummy class for testing purposes + * + */ +public class NullSampler extends AbstractSampler { + + private static final long serialVersionUID = 240L; + + @Override + public SampleResult sample(Entry e) { + return new SampleResult(); + } + +} diff --git a/test/src/org/apache/jmeter/samplers/TestSampleResult.java b/test/src/org/apache/jmeter/samplers/TestSampleResult.java new file mode 100644 index 00000000000..62fe6d07786 --- /dev/null +++ b/test/src/org/apache/jmeter/samplers/TestSampleResult.java @@ -0,0 +1,328 @@ +/* + * 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.samplers; + +import java.io.StringWriter; + +import junit.framework.TestCase; + +import org.apache.jmeter.util.Calculator; +import org.apache.log.LogTarget; +import org.apache.log.format.Formatter; +import org.apache.log.format.RawFormatter; +import org.apache.log.output.io.WriterTarget; + +// TODO need more tests - particularly for the new functions + +public class TestSampleResult extends TestCase { + public TestSampleResult(String name) { + super(name); + } + + public void testElapsedTrue() throws Exception { + SampleResult res = new SampleResult(true); + + // Check sample increments OK + res.sampleStart(); + Thread.sleep(110); // Needs to be greater than the minimum to allow for boundary errors + res.sampleEnd(); + long time = res.getTime(); + if(time < 100){ + fail("Sample time should be >=100, actual "+time); + } + } + + public void testElapsedFalse() throws Exception { + SampleResult res = new SampleResult(false); + + // Check sample increments OK + res.sampleStart(); + Thread.sleep(110); // Needs to be greater than the minimum to allow for boundary errors + res.sampleEnd(); + long time = res.getTime(); + if(time < 100){ + fail("Sample time should be >=100, actual "+time); + } + } + + public void testPauseFalse() throws Exception { + SampleResult res = new SampleResult(false); + // Check sample increments OK + res.sampleStart(); + Thread.sleep(100); + res.samplePause(); + + Thread.sleep(200); + + // Re-increment + res.sampleResume(); + Thread.sleep(100); + res.sampleEnd(); + long sampleTime = res.getTime(); + if ((sampleTime < 180) || (sampleTime > 290)) { + fail("Accumulated time (" + sampleTime + ") was not between 180 and 290 ms"); + } + } + + public void testPauseTrue() throws Exception { + SampleResult res = new SampleResult(true); + // Check sample increments OK + res.sampleStart(); + Thread.sleep(100); + res.samplePause(); + + Thread.sleep(200); + + // Re-increment + res.sampleResume(); + Thread.sleep(100); + res.sampleEnd(); + long sampleTime = res.getTime(); + if ((sampleTime < 180) || (sampleTime > 290)) { + fail("Accumulated time (" + sampleTime + ") was not between 180 and 290 ms"); + } + } + + private static final Formatter fmt = new RawFormatter(); + + private StringWriter wr = null; + + private void divertLog() {// N.B. This needs to divert the log for SampleResult + wr = new StringWriter(1000); + LogTarget[] lt = { new WriterTarget(wr, fmt) }; + SampleResult.log.setLogTargets(lt); + } + + public void testPause2True() throws Exception { + divertLog(); + SampleResult res = new SampleResult(true); + res.sampleStart(); + res.samplePause(); + assertEquals(0, wr.toString().length()); + res.samplePause(); + assertFalse(wr.toString().length() == 0); + } + + public void testPause2False() throws Exception { + divertLog(); + SampleResult res = new SampleResult(false); + res.sampleStart(); + res.samplePause(); + assertEquals(0, wr.toString().length()); + res.samplePause(); + assertFalse(wr.toString().length() == 0); + } + + public void testByteCount() throws Exception { + SampleResult res = new SampleResult(); + + res.sampleStart(); + res.setBytes(100); + res.setSampleLabel("sample of size 100 bytes"); + res.sampleEnd(); + assertEquals(100, res.getBytes()); + assertEquals("sample of size 100 bytes", res.getSampleLabel()); + } + + public void testSubResultsTrue() throws Exception { + testSubResults(true, 0); + } + + public void testSubResultsTrueThread() throws Exception { + testSubResults(true, 500L, 0); + } + + public void testSubResultsFalse() throws Exception { + testSubResults(false, 0); + } + + public void testSubResultsFalseThread() throws Exception { + testSubResults(false, 500L, 0); + } + + public void testSubResultsTruePause() throws Exception { + testSubResults(true, 100); + } + + public void testSubResultsTruePauseThread() throws Exception { + testSubResults(true, 500L, 100); + } + + public void testSubResultsFalsePause() throws Exception { + testSubResults(false, 100); + } + + public void testSubResultsFalsePauseThread() throws Exception { + testSubResults(false, 500L, 100); + } + + // temp test case for exploring settings + public void xtestUntilFail() throws Exception { + while(true) { + testSubResultsTruePause(); + testSubResultsFalsePause(); + } + } + + private void testSubResults(boolean nanoTime, long pause) throws Exception { + testSubResults(nanoTime, 0L, pause); // Don't use nanoThread + } + + private void testSubResults(boolean nanoTime, long nanoThreadSleep, long pause) throws Exception { + // This test tries to emulate a http sample, with two + // subsamples, representing images that are downloaded for the + // page representing the first sample. + + // Sample that will get two sub results, simulates a web page load + SampleResult parent = new SampleResult(nanoTime, nanoThreadSleep); + + assertEquals(nanoTime, parent.useNanoTime); + assertEquals(nanoThreadSleep, parent.nanoThreadSleep); + + long beginTest = parent.currentTimeInMillis(); + + parent.sampleStart(); + Thread.sleep(100); + parent.setBytes(300); + parent.setSampleLabel("Parent Sample"); + parent.setSuccessful(true); + parent.sampleEnd(); + long parentElapsed = parent.getTime(); + + // Sample with no sub results, simulates an image download + SampleResult child1 = new SampleResult(nanoTime); + child1.sampleStart(); + Thread.sleep(100); + child1.setBytes(100); + child1.setSampleLabel("Child1 Sample"); + child1.setSuccessful(true); + child1.sampleEnd(); + long child1Elapsed = child1.getTime(); + + assertTrue(child1.isSuccessful()); + assertEquals(100, child1.getBytes()); + assertEquals("Child1 Sample", child1.getSampleLabel()); + assertEquals(1, child1.getSampleCount()); + assertEquals(0, child1.getSubResults().length); + + long actualPause = 0; + if (pause > 0) { + long t1 = parent.currentTimeInMillis(); + Thread.sleep(pause); + actualPause = parent.currentTimeInMillis() - t1; + } + + // Sample with no sub results, simulates an image download + SampleResult child2 = new SampleResult(nanoTime); + child2.sampleStart(); + Thread.sleep(100); + child2.setBytes(200); + child2.setSampleLabel("Child2 Sample"); + child2.setSuccessful(true); + child2.sampleEnd(); + long child2Elapsed = child2.getTime(); + + assertTrue(child2.isSuccessful()); + assertEquals(200, child2.getBytes()); + assertEquals("Child2 Sample", child2.getSampleLabel()); + assertEquals(1, child2.getSampleCount()); + assertEquals(0, child2.getSubResults().length); + + // Now add the subsamples to the sample + parent.addSubResult(child1); + parent.addSubResult(child2); + assertTrue(parent.isSuccessful()); + assertEquals(600, parent.getBytes()); + assertEquals("Parent Sample", parent.getSampleLabel()); + assertEquals(1, parent.getSampleCount()); + assertEquals(2, parent.getSubResults().length); + long parentElapsedTotal = parent.getTime(); + + long overallTime = parent.currentTimeInMillis() - beginTest; + + long sumSamplesTimes = parentElapsed + child1Elapsed + actualPause + child2Elapsed; + + /* + * Parent elapsed total should be no smaller than the sum of the individual samples. + * It may be greater by the timer granularity. + */ + + long diff = parentElapsedTotal - sumSamplesTimes; + long maxDiff = nanoTime ? 2 : 16; // TimeMillis has granularity of 10-20 + if (diff < 0 || diff > maxDiff) { + fail("ParentElapsed: " + parentElapsedTotal + " - " + " sum(samples): " + sumSamplesTimes + + " = " + diff + " not in [0," + maxDiff + "]; nanotime=" + nanoTime); + } + + /** + * The overall time to run the test must be no less than, + * and may be greater (but not much greater) than the parent elapsed time + */ + + diff = overallTime - parentElapsedTotal; + if (diff < 0 || diff > maxDiff) { + fail("TestElapsed: " + overallTime + " - " + " ParentElapsed: " + parentElapsedTotal + + " = " + diff + " not in [0," + maxDiff + "]; nanotime="+nanoTime); + } + + // Check that calculator gets the correct statistics from the sample + Calculator calculator = new Calculator(); + calculator.addSample(parent); + assertEquals(600, calculator.getTotalBytes()); + assertEquals(1, calculator.getCount()); + assertEquals(1d / (parentElapsedTotal / 1000d), calculator.getRate(),0.0001d); // Allow for some margin of error + // Check that the throughput uses the time elapsed for the sub results + assertFalse(1d / (parentElapsed / 1000d) <= calculator.getRate()); + } + + // TODO some more invalid sequence tests needed + + public void testEncodingAndType() throws Exception { + // check default + SampleResult res = new SampleResult(); + assertEquals(SampleResult.DEFAULT_ENCODING,res.getDataEncodingWithDefault()); + assertEquals("DataType should be blank","",res.getDataType()); + assertNull(res.getDataEncodingNoDefault()); + + // check null changes nothing + res.setEncodingAndType(null); + assertEquals(SampleResult.DEFAULT_ENCODING,res.getDataEncodingWithDefault()); + assertEquals("DataType should be blank","",res.getDataType()); + assertNull(res.getDataEncodingNoDefault()); + + // check no charset + res.setEncodingAndType("text/html"); + assertEquals(SampleResult.DEFAULT_ENCODING,res.getDataEncodingWithDefault()); + assertEquals("text",res.getDataType()); + assertNull(res.getDataEncodingNoDefault()); + + // Check unquoted charset + res.setEncodingAndType("text/html; charset=aBcd"); + assertEquals("aBcd",res.getDataEncodingWithDefault()); + assertEquals("aBcd",res.getDataEncodingNoDefault()); + assertEquals("text",res.getDataType()); + + // Check quoted charset + res.setEncodingAndType("text/html; charset=\"aBCd\""); + assertEquals("aBCd",res.getDataEncodingWithDefault()); + assertEquals("aBCd",res.getDataEncodingNoDefault()); + assertEquals("text",res.getDataType()); + } +} + diff --git a/test/src/org/apache/jmeter/samplers/TestSampleSaveConfiguration.java b/test/src/org/apache/jmeter/samplers/TestSampleSaveConfiguration.java new file mode 100644 index 00000000000..460005c3a0b --- /dev/null +++ b/test/src/org/apache/jmeter/samplers/TestSampleSaveConfiguration.java @@ -0,0 +1,139 @@ +/* + * 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.samplers; + +import java.text.SimpleDateFormat; + +import org.apache.jmeter.junit.JMeterTestCase; + +// Extends JMeterTest case because it needs access to JMeter properties +public class TestSampleSaveConfiguration extends JMeterTestCase { + public TestSampleSaveConfiguration(String name) { + super(name); + } + + public void testClone() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(); + a.setUrl(false); + a.setAssertions(true); + a.setDefaultDelimiter(); + a.setDefaultTimeStampFormat(); + a.setDataType(true); + assertFalse(a.saveUrl()); + assertNotNull(a.getDelimiter()); + assertTrue(a.saveAssertions()); + assertTrue(a.saveDataType()); + + // Original and clone should be equal + SampleSaveConfiguration cloneA = (SampleSaveConfiguration) a.clone(); + assertNotSame(a, cloneA); + assertEquals(a, cloneA); + assertTrue(a.equals(cloneA)); + assertTrue(cloneA.equals(a)); + assertEquals(a.hashCode(), cloneA.hashCode()); + + // Change the original + a.setUrl(true); + assertFalse(a.equals(cloneA)); + assertFalse(cloneA.equals(a)); + assertFalse(a.hashCode() == cloneA.hashCode()); + + // Change the original back again + a.setUrl(false); + assertEquals(a, cloneA); + assertTrue(a.equals(cloneA)); + assertTrue(cloneA.equals(a)); + assertEquals(a.hashCode(), cloneA.hashCode()); + } + + public void testEqualsAndHashCode() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(); + a.setUrl(false); + a.setAssertions(true); + a.setDefaultDelimiter(); + a.setDefaultTimeStampFormat(); + a.setDataType(true); + SampleSaveConfiguration b = new SampleSaveConfiguration(); + b.setUrl(false); + b.setAssertions(true); + b.setDefaultDelimiter(); + b.setDefaultTimeStampFormat(); + b.setDataType(true); + + // a and b should be equal + assertEquals(a, b); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a.saveUrl(), b.saveUrl()); + assertEquals(a.saveAssertions(), b.saveAssertions()); + assertEquals(a.getDelimiter(), b.getDelimiter()); + assertEquals(a.saveDataType(), b.saveDataType()); + + a.setAssertions(false); + // a and b should not be equal + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + assertFalse(a.hashCode() == b.hashCode()); + assertFalse(a.saveAssertions() == b.saveAssertions()); + } + + public void testFalse() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(false); + SampleSaveConfiguration b = new SampleSaveConfiguration(false); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + } + + public void testTrue() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(true); + SampleSaveConfiguration b = new SampleSaveConfiguration(true); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + } + public void testFalseTrue() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(false); + SampleSaveConfiguration b = new SampleSaveConfiguration(true); + assertFalse("Hash codes should not be equal",a.hashCode() == b.hashCode()); + assertFalse("Objects should not be equal",a.equals(b)); + assertFalse("Objects should not be equal",b.equals(a)); + } + + public void testFormatter() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(false); + SampleSaveConfiguration b = new SampleSaveConfiguration(false); + a.setFormatter(null); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + b.setFormatter(null); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + a.setFormatter(new SimpleDateFormat()); + b.setFormatter(new SimpleDateFormat()); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + } + + } + diff --git a/test/src/org/apache/jmeter/save/TestCSVSaveService.java b/test/src/org/apache/jmeter/save/TestCSVSaveService.java new file mode 100644 index 00000000000..0454f0ef64c --- /dev/null +++ b/test/src/org/apache/jmeter/save/TestCSVSaveService.java @@ -0,0 +1,141 @@ +/* + * 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.save; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; + +import org.apache.jmeter.junit.JMeterTestCase; + +public class TestCSVSaveService extends JMeterTestCase { + + public TestCSVSaveService(String name) { + super(name); + } + + private void checkSplitString(String input, char delim, String []expected) throws Exception { + String out[] = CSVSaveService.csvSplitString(input, delim); + checkStrings(expected, out); + } + + private void checkStrings(String[] expected, String[] out) { + assertEquals("Incorrect number of strings returned",expected.length, out.length); + for(int i = 0; i < out.length; i++){ + assertEquals("Incorrect entry returned",expected[i], out[i]); + } + } + + // This is what JOrphanUtils.split() does + public void testSplitEmpty() throws Exception { + checkSplitString("", ',', new String[]{}); + } + + // These tests should agree with those for JOrphanUtils.split() as far as possible + + public void testSplitUnquoted() throws Exception { + checkSplitString("a", ',', new String[]{"a"}); + checkSplitString("a,bc,d,e", ',', new String[]{"a","bc","d","e"}); + checkSplitString(",bc,d,e", ',', new String[]{"","bc","d","e"}); + checkSplitString("a,,d,e", ',', new String[]{"a","","d","e"}); + checkSplitString("a,bc, ,e", ',', new String[]{"a","bc"," ","e"}); + checkSplitString("a,bc,d, ", ',', new String[]{"a","bc","d"," "}); + checkSplitString("a,bc,d,", ',', new String[]{"a","bc","d",""}); + checkSplitString("a,bc,,", ',', new String[]{"a","bc","",""}); + checkSplitString("a,,,", ',', new String[]{"a","","",""}); + checkSplitString("a,bc,d,\n",',', new String[]{"a","bc","d",""}); + + // \u00e7 = LATIN SMALL LETTER C WITH CEDILLA + // \u00e9 = LATIN SMALL LETTER E WITH ACUTE + checkSplitString("a,b\u00e7,d,\u00e9", ',', new String[]{"a","b\u00e7","d","\u00e9"}); + } + + public void testSplitQuoted() throws Exception { + checkSplitString("a,bc,d,e", ',', new String[]{"a","bc","d","e"}); + checkSplitString(",bc,d,e", ',', new String[]{"","bc","d","e"}); + checkSplitString("\"\",bc,d,e", ',', new String[]{"","bc","d","e"}); + checkSplitString("a,,d,e", ',', new String[]{"a","","d","e"}); + checkSplitString("a,\"\",d,e", ',', new String[]{"a","","d","e"}); + checkSplitString("a,bc, ,e", ',', new String[]{"a","bc"," ","e"}); + checkSplitString("a,bc,\" \",e", ',', new String[]{"a","bc"," ","e"}); + checkSplitString("a,bc,d, ", ',', new String[]{"a","bc","d"," "}); + checkSplitString("a,bc,d,\" \"", ',', new String[]{"a","bc","d"," "}); + checkSplitString("a,bc,d,", ',', new String[]{"a","bc","d",""}); + checkSplitString("a,bc,d,\"\"", ',', new String[]{"a","bc","d",""}); + checkSplitString("a,bc,d,\"\"\n",',', new String[]{"a","bc","d",""}); + + // \u00e7 = LATIN SMALL LETTER C WITH CEDILLA + // \u00e9 = LATIN SMALL LETTER E WITH ACUTE + checkSplitString("\"a\",\"b\u00e7\",\"d\",\"\u00e9\"", ',', new String[]{"a","b\u00e7","d","\u00e9"}); + } + + public void testSplitBadQuote() throws Exception { + try { + checkSplitString("a\"b",',',null); + fail("Should have generated IOException"); + } catch (IOException e) { + } + } + + public void testSplitMultiLine() throws Exception { + String line="a,,\"c\nd\",e\n,,f,g,\n\n"; + String[] out; + BufferedReader br = new BufferedReader(new StringReader(line)); + out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{"a","","c\nd","e"}, out); + out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{"","","f","g",""}, out); + out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{""}, out); // Blank line + assertEquals("Expected to be at EOF",-1,br.read()); + // Empty strings at EOF + out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{}, out); + out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{}, out); + } + + public void testBlankLine() throws Exception { + BufferedReader br = new BufferedReader(new StringReader("\n")); + String[] out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{""}, out); + assertEquals("Expected to be at EOF",-1,br.read()); + } + + public void testBlankLineQuoted() throws Exception { + BufferedReader br = new BufferedReader(new StringReader("\"\"\n")); + String[] out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{""}, out); + assertEquals("Expected to be at EOF",-1,br.read()); + } + + public void testEmptyFile() throws Exception { + BufferedReader br = new BufferedReader(new StringReader("")); + String[] out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{}, out); + assertEquals("Expected to be at EOF",-1,br.read()); + } + + public void testShortFile() throws Exception { + BufferedReader br = new BufferedReader(new StringReader("a")); + String[] out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{"a"}, out); + assertEquals("Expected to be at EOF",-1,br.read()); + } +} diff --git a/test/src/org/apache/jmeter/save/TestSaveService.java b/test/src/org/apache/jmeter/save/TestSaveService.java new file mode 100644 index 00000000000..f18ede41ba1 --- /dev/null +++ b/test/src/org/apache/jmeter/save/TestSaveService.java @@ -0,0 +1,201 @@ +/* + * 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.save; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.InputStreamReader; +import java.util.List; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; + +public class TestSaveService extends JMeterTestCase { + + // testLoadAndSave test files + private static final String[] FILES = new String[] { + "AssertionTestPlan.jmx", + "AuthManagerTestPlan.jmx", + "HeaderManagerTestPlan.jmx", + "InterleaveTestPlan2.jmx", + "InterleaveTestPlan.jmx", + "LoopTestPlan.jmx", + "Modification Manager.jmx", + "OnceOnlyTestPlan.jmx", + "proxy.jmx", + "ProxyServerTestPlan.jmx", + "SimpleTestPlan.jmx", + "GuiTest.jmx", + "GuiTest231.jmx", + "GenTest27.jmx", + "GenTest210.jmx", + }; + + // Test files for testLoadAndSave; output will generally be different in size but same number of lines + private static final String[] FILES_LINES = new String[] { + "GuiTest231_original.jmx", + "GenTest25.jmx", // GraphAccumVisualizer obsolete, BSFSamplerGui now a TestBean + "GenTest251.jmx", // GraphAccumVisualizer obsolete, BSFSamplerGui now a TestBean + "GenTest26.jmx", // GraphAccumVisualizer now obsolete + "GenTest27_original.jmx", // CTT changed to use intProp for mode + }; + + // Test files for testLoad; output will generally be different in size and line count + private static final String[] FILES_LOAD_ONLY = new String[] { + "GuiTest_original.jmx", + "GenTest22.jmx", + "GenTest231.jmx", + "GenTest24.jmx", + }; + + private static final boolean saveOut = JMeterUtils.getPropDefault("testsaveservice.saveout", false); + + public TestSaveService(String name) { + super(name); + } + public void testPropfile() throws Exception { + assertTrue("Property Version mismatch, ensure you update SaveService#PROPVERSION field with _version property value from saveservice.properties", SaveService.checkPropertyVersion()); + assertTrue("Property File Version mismatch, ensure you update SaveService#FILEVERSION field with revision id of saveservice.properties", SaveService.checkFileVersion()); + } + + public void testVersions() throws Exception { + assertTrue("Unexpected version found", SaveService.checkVersions()); + } + + public void testLoadAndSave() throws Exception { + boolean failed = false; // Did a test fail? + + for (int i = 0; i < FILES.length; i++) { + final String fileName = FILES[i]; + final File testFile = findTestFile("testfiles/" + fileName); + failed |= loadAndSave(testFile, fileName, true); + } + for (int i = 0; i < FILES_LINES.length; i++) { + final String fileName = FILES[i]; + final File testFile = findTestFile("testfiles/" + fileName); + failed |= loadAndSave(testFile, fileName, false); + } + if (failed) // TODO make these separate tests? + { + fail("One or more failures detected"); + } + } + + private boolean loadAndSave(File testFile, String fileName, boolean checkSize) throws Exception { + + boolean failed = false; + + int [] orig = readFile(new BufferedReader(new FileReader(testFile))); + + HashTree tree = SaveService.loadTree(testFile); + + ByteArrayOutputStream out = new ByteArrayOutputStream(1000000); + try { + SaveService.saveTree(tree, out); + } finally { + out.close(); // Make sure all the data is flushed out + } + + ByteArrayInputStream ins = new ByteArrayInputStream(out.toByteArray()); + + int [] output = readFile(new BufferedReader(new InputStreamReader(ins))); + // We only check the length of the result. Comparing the + // actual result (out.toByteArray==original) will usually + // fail, because the order of the properties within each + // test element may change. Comparing the lengths should be + // enough to detect most problem cases... + if ((checkSize && (orig[0] != output[0] ))|| orig[1] != output[1]) { + failed = true; + System.out.println(); + System.out.println("Loading file testfiles/" + fileName + " and " + + "saving it back changes its size from " + orig[0] + " to " + output[0] + "."); + if (orig[1] != output[1]) { + System.out.println("Number of lines changes from " + orig[1] + " to " + output[1]); + } + if (saveOut) { + final File outFile = findTestFile("testfiles/" + fileName + ".out"); + System.out.println("Write " + outFile); + FileOutputStream outf = null; + try { + outf = new FileOutputStream(outFile); + outf.write(out.toByteArray()); + } finally { + if(outf != null) { + outf.close(); + } + } + System.out.println("Wrote " + outFile); + } + } + + // Note this test will fail if a property is added or + // removed to any of the components used in the test + // files. The way to solve this is to appropriately change + // the test file. + return failed; + } + + /** + * Calculate size and line count ignoring EOL and + * "jmeterTestPlan" element which may vary because of + * different attributes/attribute lengths. + */ + private int[] readFile(BufferedReader br) throws Exception { + try { + int length=0; + int lines=0; + String line; + while((line=br.readLine()) != null) { + lines++; + if (!line.startsWith(" missingClasses = SaveService.checkClasses(); + if(missingClasses.size()>0) { + fail("One or more classes not found:"+missingClasses); + } + } +} diff --git a/test/src/org/apache/jmeter/services/TestFileServer.java b/test/src/org/apache/jmeter/services/TestFileServer.java new file mode 100644 index 00000000000..3f70df5b5ff --- /dev/null +++ b/test/src/org/apache/jmeter/services/TestFileServer.java @@ -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 to test FileServer methods + */ + +package org.apache.jmeter.services; + +import java.io.File; +import java.io.IOException; + +import org.apache.jmeter.junit.JMeterTestCase; + +public class TestFileServer extends JMeterTestCase { + + private static final FileServer FS = FileServer.getFileServer(); + + public TestFileServer() { + super(); + } + + public TestFileServer(String arg0) { + super(arg0); + } + + @Override + public void setUp() throws IOException { + FS.resetBase(); + } + + @Override + public void tearDown() throws IOException{ + FS.closeFiles(); + } + + public void testopen() throws Exception { + try { + FS.readLine("test"); + fail("Expected IOException"); + } catch (IOException ignored){ + } + try { + FS.write("test",""); + fail("Expected IOException"); + } catch (IOException ignored){ + } + assertFalse("Should not have any files open",FS.filesOpen()); + FS.closeFile("xxx"); // Unrecognised files are ignored + assertFalse("Should not have any files open",FS.filesOpen()); + String infile=findTestPath("testfiles/test.csv"); + FS.reserveFile(infile); // Does not open file + assertFalse("Should not have any files open",FS.filesOpen()); + assertEquals("a1,b1,c1,d1",FS.readLine(infile)); + assertTrue("Should have some files open",FS.filesOpen()); + assertNotNull(FS.readLine(infile)); + assertNotNull(FS.readLine(infile)); + assertNotNull(FS.readLine(infile)); + assertEquals("a1,b1,c1,d1",FS.readLine(infile));// Re-read 1st line + assertNotNull(FS.readLine(infile)); + try { + FS.write(infile,"");// should not be able to write to it ... + fail("Expected IOException"); + } catch (IOException ignored){ + } + FS.closeFile(infile); // does not remove the entry + assertFalse("Should not have any files open",FS.filesOpen()); + assertEquals("a1,b1,c1,d1",FS.readLine(infile));// Re-read 1st line + assertTrue("Should have some files open",FS.filesOpen()); + FS.closeFiles(); // removes all entries + assertFalse("Should not have any files open",FS.filesOpen()); + try { + FS.readLine(infile); + fail("Expected IOException"); + } catch (IOException ignored){ + } + infile=findTestPath("testfiles/test.csv"); + FS.reserveFile(infile); // Does not open file + assertFalse("Should not have any files open",FS.filesOpen()); + assertEquals("a1,b1,c1,d1",FS.readLine(infile)); + try { + FS.setBasedir("x"); + fail("Expected IllegalStateException"); + } catch (IllegalStateException ignored){ + } + FS.closeFile(infile); + FS.setBasedir("y"); + FS.closeFiles(); + } + + public void testRelative() throws Exception { + final String base = FileServer.getDefaultBase(); + final File basefile = new File(base); + FS.setBaseForScript(basefile); + assertEquals(".",FS.getBaseDirRelative().toString()); + FS.setBaseForScript(basefile.getParentFile()); + assertEquals(".",FS.getBaseDirRelative().toString()); + FS.setBaseForScript(new File(basefile.getParentFile(),"abcd/defg.jmx")); + assertEquals(".",FS.getBaseDirRelative().toString()); + File file = new File(basefile,"abcd/defg.jmx"); + FS.setBaseForScript(file); + assertEquals("abcd",FS.getBaseDirRelative().toString()); + } + + public void testHeaderMissingFile() throws Exception { + final String missing = "no-such-file"; + final String alias = "missing"; + final String charsetName = "UTF-8"; + + try { + FS.reserveFile(missing,charsetName,alias,true); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertTrue("Expected FNF", e.getCause() instanceof java.io.FileNotFoundException); + } + // Ensure second invocation gets same behaviour + try { + FS.reserveFile(missing,charsetName,alias,true); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertTrue("Expected FNF", e.getCause() instanceof java.io.FileNotFoundException); + } + } + + public void testHeaderEmptyFile() throws Exception { + final String empty = findTestPath("testfiles/empty.csv"); + final String alias = "empty"; + final String charsetName = "UTF-8"; + + try { + String hdr= FS.reserveFile(empty,charsetName,alias,true); + fail("Expected IllegalArgumentException|"+hdr+"|"); + } catch (IllegalArgumentException e) { + assertTrue("Expected EOF", e.getCause() instanceof java.io.EOFException); + } + // Ensure second invocation gets same behaviour + try { + FS.reserveFile(empty,charsetName,alias,true); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertTrue("Expected EOF", e.getCause() instanceof java.io.EOFException); + } + } +} diff --git a/test/src/org/apache/jmeter/testbeans/gui/PackageTest.java b/test/src/org/apache/jmeter/testbeans/gui/PackageTest.java new file mode 100644 index 00000000000..cc34e0a161d --- /dev/null +++ b/test/src/org/apache/jmeter/testbeans/gui/PackageTest.java @@ -0,0 +1,220 @@ +/* + * 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.testbeans.gui; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +import org.apache.jmeter.gui.util.JMeterMenuBar; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +import junit.framework.Test; +// import junit.framework.TestCase; +import junit.framework.TestSuite; + +/* + * Find all beans out there and check their resource property files: - Check + * that non-default property files don't have any extra keys. - Check all + * necessary properties are defined at least in the default property file, + * except for beans whose name contains "Experimental" or "Alpha". + * + * TODO: - Check property files don't have duplicate keys (is this important) + * + */ +public final class PackageTest extends JMeterTestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // ResourceBundle i18nEdit= + // ResourceBundle.getBundle("org.apache.jmeter.resources.i18nedit"); + private static final Locale defaultLocale = new Locale("en",""); // i18nEdit.getString("locale.default"); + + private final ResourceBundle defaultBundle; + + private final Class testBeanClass; + + private final Locale testLocale; + + private PackageTest(Class testBeanClass, Locale locale, ResourceBundle defaultBundle) { + super(testBeanClass.getName() + " - " + locale.getLanguage() + " - " + locale.getCountry()); + this.testBeanClass = testBeanClass; + this.testLocale = locale; + this.defaultBundle = defaultBundle; + } + + private PackageTest(String name){ + super(name); + this.testBeanClass = null; + this.testLocale = null; + this.defaultBundle = null; + } + + private BeanInfo beanInfo; + + private ResourceBundle bundle; + + @Override + public void setUp() { + if (testLocale == null) { + return;// errorDetected() + } + JMeterUtils.setLocale(testLocale); + Introspector.flushFromCaches(testBeanClass); + try { + beanInfo = Introspector.getBeanInfo(testBeanClass); + bundle = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE); + } catch (IntrospectionException e) { + log.error("Can't get beanInfo for " + testBeanClass.getName(), e); + throw new Error(e.toString(), e); // Programming error. Don't continue. + } + if (bundle == null) { + throw new Error("This can't happen!"); + } + } + + @Override + public void tearDown() { + JMeterUtils.setLocale(Locale.getDefault()); + } + + @Override + public void runTest() throws Throwable { + if (testLocale == null) { + super.runTest(); + return;// errorDetected() + } + if (bundle == defaultBundle) { + checkAllNecessaryKeysPresent(); + } else { + checkNoInventedKeys(); + } + } + + public void checkNoInventedKeys() { + // Check that all keys in the bundle are also in the default bundle: + for (Enumeration keys = bundle.getKeys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + defaultBundle.getString(key); + // Will throw MissingResourceException if key is not there. + } + } + + public void checkAllNecessaryKeysPresent() { + // Check that all necessary keys are there: + + // displayName is always mandatory: + String dn = defaultBundle.getString("displayName").toUpperCase(Locale.ENGLISH); + + // Skip the rest of this test for alpha/experimental beans: + if (dn.indexOf("(ALPHA") != -1 || dn.indexOf("(EXPERIMENTAL") != -1) { + return; + } + + // Check for property- and group-related texts: + PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); + for (int i = 0; i < descriptors.length; i++) { + // Skip non-editable properties, that is: + // Ignore hidden, read-only, and write-only properties + if (descriptors[i].isHidden() || descriptors[i].getReadMethod() == null + || descriptors[i].getWriteMethod() == null) { + continue; + } + // Ignore TestElement properties which don't have an explicit + // editor: + if (TestElement.class.isAssignableFrom(descriptors[i].getPropertyType()) + && descriptors[i].getPropertyEditorClass() == null) { + continue; + } + // Done -- we're working with an editable property. + + String name = descriptors[i].getName(); + + bundle.getString(name + ".displayName"); + // bundle.getString(name+".shortDescription"); NOT MANDATORY + + String group = (String) descriptors[i].getValue(GenericTestBeanCustomizer.GROUP); + if (group != null) { + bundle.getString( group + ".displayName"); + } + } + } + + public static Test suite() throws Exception { + TestSuite suite = new TestSuite("Bean Resource Test Suite"); + + List testBaeanclassNames = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { TestBean.class }); + + boolean errorDetected = false; + JMeterUtils.setLocale(defaultLocale); + for (String className : testBaeanclassNames) { + Class testBeanClass = Class.forName(className); + ResourceBundle defaultBundle = null; + try { + defaultBundle = (ResourceBundle) Introspector.getBeanInfo(testBeanClass).getBeanDescriptor().getValue( + GenericTestBeanCustomizer.RESOURCE_BUNDLE); + } catch (IntrospectionException e) { + log.error("Can't get beanInfo for " + testBeanClass.getName(), e); + throw new Error(e.toString(), e); // Programming error. Don't + // continue. + } + + if (defaultBundle == null) { + if (className.startsWith("org.apache.jmeter.examples.")) { + log.info("No default bundle found for " + className); + continue; + } + errorDetected=true; + log.error("No default bundle found for " + className + " using " + defaultLocale.toString()); + //throw new Error("No default bundle for class " + className); + continue; + } + + suite.addTest(new PackageTest(testBeanClass, defaultLocale, defaultBundle)); + + String [] languages = JMeterMenuBar.getLanguages(); + for (int i=0; i < languages.length; i++){ + final String[] language = languages[i].split("_"); + if (language.length == 1){ + suite.addTest(new PackageTest(testBeanClass, new Locale(language[0]), defaultBundle)); + } else if (language.length == 2){ + suite.addTest(new PackageTest(testBeanClass, new Locale(language[0], language[1]), defaultBundle)); + } + } + } + + if (errorDetected) + { + suite.addTest(new PackageTest("errorDetected")); + } + return suite; + } + + public void errorDetected(){ + fail("One or more errors detected - see log file"); + } +} diff --git a/test/src/org/apache/jmeter/testbeans/gui/TestBooleanPropertyEditor.java b/test/src/org/apache/jmeter/testbeans/gui/TestBooleanPropertyEditor.java new file mode 100644 index 00000000000..cdfef8f3b24 --- /dev/null +++ b/test/src/org/apache/jmeter/testbeans/gui/TestBooleanPropertyEditor.java @@ -0,0 +1,82 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyEditor; +import java.beans.PropertyEditorManager; + +/** + * Test class to check that the JVM provides sensible behaviour for the boolean PropertyEditor, i.e. + * that getAsText() can only return values that match getTags(). + * + * Also checks that BooleanPropertyEditor behaves in the same way. + */ +public class TestBooleanPropertyEditor extends junit.framework.TestCase { + + /* + * N.B. + * These values are NOT the same as Boolean.FALSE|TRUE.toString() + * which returns lower-case only. The getAsText() method converts + * the result to mixed case. + */ + private static final String FALSE = "False"; // $NON-NLS-1$ + private static final String TRUE = "True"; // $NON-NLS-1$ + + public TestBooleanPropertyEditor(String name) { + super(name); + } + + public void testBooleanEditor(){ + PropertyEditor propertyEditor = PropertyEditorManager.findEditor(boolean.class); + testBooleanEditor(propertyEditor); + } + + public void testBooleanPropertyEditor() { + PropertyEditor propertyEditor = new BooleanPropertyEditor(); + testBooleanEditor(propertyEditor); + } + + private void testBooleanEditor(PropertyEditor propertyEditor) { + assertNotNull("Expected to find property editor", propertyEditor); + String tags[] = propertyEditor.getTags(); + assertEquals(2,tags.length); + assertEquals(TRUE,tags[0]); + assertEquals(FALSE,tags[1]); + + propertyEditor.setValue(Boolean.FALSE); + assertEquals(FALSE,propertyEditor.getAsText()); + propertyEditor.setAsText(FALSE); + assertEquals(FALSE,propertyEditor.getAsText()); + propertyEditor.setAsText("false"); + assertEquals(FALSE,propertyEditor.getAsText()); + propertyEditor.setAsText("False"); + assertEquals(FALSE,propertyEditor.getAsText()); + propertyEditor.setAsText("FALSE"); + assertEquals(FALSE,propertyEditor.getAsText()); + + propertyEditor.setValue(Boolean.TRUE); + assertEquals(TRUE,propertyEditor.getAsText()); + propertyEditor.setAsText(TRUE); + assertEquals(TRUE,propertyEditor.getAsText()); + propertyEditor.setAsText("true"); + assertEquals(TRUE,propertyEditor.getAsText()); + propertyEditor.setAsText("True"); + assertEquals(TRUE,propertyEditor.getAsText()); + propertyEditor.setAsText("TRUE"); + assertEquals(TRUE,propertyEditor.getAsText()); + } +} diff --git a/test/src/org/apache/jmeter/testbeans/gui/TestComboStringEditor.java b/test/src/org/apache/jmeter/testbeans/gui/TestComboStringEditor.java new file mode 100644 index 00000000000..d53a2955163 --- /dev/null +++ b/test/src/org/apache/jmeter/testbeans/gui/TestComboStringEditor.java @@ -0,0 +1,57 @@ +/* + * 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.testbeans.gui; + +public class TestComboStringEditor extends junit.framework.TestCase { + public TestComboStringEditor(String name) { + super(name); + } + + private void testSetGet(ComboStringEditor e, Object value) throws Exception { + e.setValue(value); + assertEquals(value, e.getValue()); + } + + private void testSetGetAsText(ComboStringEditor e, String text) throws Exception { + e.setAsText(text); + assertEquals(text, e.getAsText()); + } + + public void testSetGet() throws Exception { + @SuppressWarnings("deprecation") // test code, intentional + ComboStringEditor e = new ComboStringEditor(); + + testSetGet(e, "any string"); + testSetGet(e, ""); + testSetGet(e, null); + testSetGet(e, "${var}"); + } + + public void testSetGetAsText() throws Exception { + @SuppressWarnings("deprecation") // test code, intentional + ComboStringEditor e = new ComboStringEditor(); + + testSetGetAsText(e, "any string"); + testSetGetAsText(e, ""); + testSetGetAsText(e, null); + testSetGetAsText(e, "${var}"); + + // Check "Undefined" does not become a "reserved word": + e.setAsText(e.UNDEFINED.toString()); + assertNotNull(e.getAsText()); + } +} diff --git a/test/src/org/apache/jmeter/testbeans/gui/TestFieldStringEditor.java b/test/src/org/apache/jmeter/testbeans/gui/TestFieldStringEditor.java new file mode 100644 index 00000000000..02d0ba3eac6 --- /dev/null +++ b/test/src/org/apache/jmeter/testbeans/gui/TestFieldStringEditor.java @@ -0,0 +1,52 @@ +/* + * 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.testbeans.gui; + + +public class TestFieldStringEditor extends junit.framework.TestCase { + public TestFieldStringEditor(String name) { + super(name); + } + + private void testSetGet(ComboStringEditor e, Object value) throws Exception { + e.setValue(value); + assertEquals(value, e.getValue()); + } + + private void testSetGetAsText(ComboStringEditor e, String text) throws Exception { + e.setAsText(text); + assertEquals(text, e.getAsText()); + } + + public void testSetGet() throws Exception { + @SuppressWarnings("deprecation") // test code, intentional + ComboStringEditor e = new ComboStringEditor(); + + testSetGet(e, "any string"); + testSetGet(e, ""); + testSetGet(e, "${var}"); + } + + public void testSetGetAsText() throws Exception { + @SuppressWarnings("deprecation") // test code, intentional + ComboStringEditor e = new ComboStringEditor(); + + testSetGetAsText(e, "any string"); + testSetGetAsText(e, ""); + testSetGetAsText(e, "${var}"); + } +} diff --git a/test/src/org/apache/jmeter/testelement/PackageTest.java b/test/src/org/apache/jmeter/testelement/PackageTest.java new file mode 100644 index 00000000000..4e221e550db --- /dev/null +++ b/test/src/org/apache/jmeter/testelement/PackageTest.java @@ -0,0 +1,111 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 16, 2003 + * + */ +package org.apache.jmeter.testelement; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.LoginConfig; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.sampler.DebugSampler; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; + +public class PackageTest extends TestCase { + public PackageTest(String arg0) { + super(arg0); + } + + // Test needs to run in this package in order to give access to AbstractTestElement.addProperty() + public void DISABLEDtestBug50799() throws Exception { + HeaderManager headerManager = new HeaderManager(); + headerManager.add(new Header("1stLevelTestHeader", "testValue1")); + HeaderManager headerManager2 = new HeaderManager(); + headerManager2.add(new Header("2ndLevelTestHeader", "testValue2")); + + DebugSampler debugSampler = new DebugSampler(); + debugSampler.addProperty(new StringProperty("name", "DebugSampler_50799")); + debugSampler.setRunningVersion(true); + assertTrue(debugSampler.getProperty("HeaderManager.headers") instanceof NullProperty); + debugSampler.addTestElement(headerManager); + assertFalse(debugSampler.getProperty("HeaderManager.headers") instanceof NullProperty); + assertEquals(debugSampler.getProperty("HeaderManager.headers").getStringValue() ,"[1stLevelTestHeader\ttestValue1]"); + + debugSampler.addTestElement(headerManager2); + assertEquals(debugSampler.getProperty("HeaderManager.headers").getStringValue() ,"[1stLevelTestHeader\ttestValue1, 2ndLevelTestHeader\ttestValue2]"); + assertEquals(2, ((CollectionProperty)debugSampler.getProperty("HeaderManager.headers")).size()); + + headerManager.recoverRunningVersion(); + headerManager2.recoverRunningVersion(); + debugSampler.recoverRunningVersion(); + + assertEquals(1, headerManager.size()); + assertEquals(1, headerManager2.size()); + assertEquals(0, ((CollectionProperty)debugSampler.getProperty("HeaderManager.headers")).size()); + assertEquals(new Header("1stLevelTestHeader", "testValue1"), headerManager.get(0)); + assertEquals(new Header("2ndLevelTestHeader", "testValue2"), headerManager2.get(0)); + } + + public void testRecovery() throws Exception { + ConfigTestElement config = new ConfigTestElement(); + config.addProperty(new StringProperty("name", "config")); + config.setRunningVersion(true); + LoginConfig loginConfig = new LoginConfig(); + loginConfig.setUsername("user1"); + loginConfig.setPassword("pass1"); + assertTrue(config.getProperty("login") instanceof NullProperty); + // This test should work whether or not all Nulls are equal + assertEquals(new NullProperty("login"), config.getProperty("login")); + config.addProperty(new TestElementProperty("login", loginConfig)); + assertEquals(loginConfig.toString(), config.getPropertyAsString("login")); + config.recoverRunningVersion(); + assertTrue(config.getProperty("login") instanceof NullProperty); + assertEquals(new NullProperty("login"), config.getProperty("login")); + } + + public void testArguments() throws Exception { + Arguments args = new Arguments(); + args.addArgument("arg1", "val1", "="); + TestElementProperty prop = new TestElementProperty("args", args); + ConfigTestElement te = new ConfigTestElement(); + te.addProperty(prop); + te.setRunningVersion(true); + Arguments config = new Arguments(); + config.addArgument("config1", "configValue", "="); + TestElementProperty configProp = new TestElementProperty("args", config); + ConfigTestElement te2 = new ConfigTestElement(); + te2.addProperty(configProp); + te.addTestElement(te2); + assertEquals(2, args.getArgumentCount()); + assertEquals("config1=configValue", args.getArgument(1).toString()); + te.recoverRunningVersion(); + te.addTestElement(te2); + assertEquals(2, args.getArgumentCount()); + assertEquals("config1=configValue", args.getArgument(1).toString()); + + } +} diff --git a/test/src/org/apache/jmeter/testelement/property/PackageTest.java b/test/src/org/apache/jmeter/testelement/property/PackageTest.java new file mode 100644 index 00000000000..360a0d1753d --- /dev/null +++ b/test/src/org/apache/jmeter/testelement/property/PackageTest.java @@ -0,0 +1,314 @@ +/* + * 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.testelement.property; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.LoginConfig; + +/** + * Class for testing the property package. + */ +public class PackageTest extends TestCase { + + public PackageTest(String name) { + super(name); + } + + public void testStringProperty() throws Exception { + StringProperty prop = new StringProperty("name", "value"); + prop.setRunningVersion(true); + prop.setObjectValue("new Value"); + assertEquals("new Value", prop.getStringValue()); + prop.recoverRunningVersion(null); + assertEquals("value", prop.getStringValue()); + prop.setObjectValue("new Value"); + prop.setObjectValue("2nd Value"); + assertEquals("2nd Value", prop.getStringValue()); + prop.recoverRunningVersion(null); + assertEquals("value", prop.getStringValue()); + } + + public void testElementProperty() throws Exception { + LoginConfig config = new LoginConfig(); + config.setUsername("username"); + config.setPassword("password"); + TestElementProperty prop = new TestElementProperty("name", config); + prop.setRunningVersion(true); + config = new LoginConfig(); + config.setUsername("user2"); + config.setPassword("pass2"); + prop.setObjectValue(config); + assertEquals("user2=pass2", prop.getStringValue()); + prop.recoverRunningVersion(null); + assertEquals("username=password", prop.getStringValue()); + config = new LoginConfig(); + config.setUsername("user2"); + config.setPassword("pass2"); + prop.setObjectValue(config); + config = new LoginConfig(); + config.setUsername("user3"); + config.setPassword("pass3"); + prop.setObjectValue(config); + assertEquals("user3=pass3", prop.getStringValue()); + prop.recoverRunningVersion(null); + assertEquals("username=password", prop.getStringValue()); + } + + private void checkEquals(JMeterProperty jp1, JMeterProperty jp2) { + assertEquals(jp1, jp2); + assertEquals(jp2, jp1); + assertEquals(jp1, jp1); + assertEquals(jp2, jp2); + assertEquals(jp1.hashCode(), jp2.hashCode()); + + } + + private void checkNotEquals(JMeterProperty jp1, JMeterProperty jp2) { + assertEquals(jp1, jp1); + assertEquals(jp2, jp2); + assertFalse(jp1.equals(jp2)); + assertFalse(jp2.equals(jp1)); + // do not check hashcodes; unequal objects may have equal hashcodes + } + + public void testBooleanEquality() throws Exception { + BooleanProperty jpn1 = new BooleanProperty(); + BooleanProperty jpn2 = new BooleanProperty(); + BooleanProperty jp1 = new BooleanProperty("name1", true); + BooleanProperty jp2 = new BooleanProperty("name1", true); + BooleanProperty jp3 = new BooleanProperty("name2", true); + BooleanProperty jp4 = new BooleanProperty("name2", false); + checkEquals(jpn1, jpn2); + checkNotEquals(jpn1, jp1); + checkNotEquals(jpn1, jp2); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + } + + public void testDoubleEquality() throws Exception { + DoubleProperty jpn1 = new DoubleProperty(); + DoubleProperty jpn2 = new DoubleProperty(); + DoubleProperty jp1 = new DoubleProperty("name1", 123.4); + DoubleProperty jp2 = new DoubleProperty("name1", 123.4); + DoubleProperty jp3 = new DoubleProperty("name2", -123.4); + DoubleProperty jp4 = new DoubleProperty("name2", 123.4); + DoubleProperty jp5 = new DoubleProperty("name2", Double.NEGATIVE_INFINITY); + DoubleProperty jp6 = new DoubleProperty("name2", Double.NEGATIVE_INFINITY); + DoubleProperty jp7 = new DoubleProperty("name2", Double.POSITIVE_INFINITY); + DoubleProperty jp8 = new DoubleProperty("name2", Double.POSITIVE_INFINITY); + DoubleProperty jp9 = new DoubleProperty("name2", Double.NaN); + DoubleProperty jp10 = new DoubleProperty("name2", Double.NaN); + DoubleProperty jp11 = new DoubleProperty("name1", Double.NaN); + DoubleProperty jp12 = new DoubleProperty("name1", Double.MIN_VALUE); + DoubleProperty jp13 = new DoubleProperty("name2", Double.MIN_VALUE); + DoubleProperty jp14 = new DoubleProperty("name2", Double.MIN_VALUE); + DoubleProperty jp15 = new DoubleProperty("name1", Double.MAX_VALUE); + DoubleProperty jp16 = new DoubleProperty("name2", Double.MAX_VALUE); + DoubleProperty jp17 = new DoubleProperty("name2", Double.MAX_VALUE); + checkEquals(jpn1, jpn2); + checkNotEquals(jpn1, jp1); + checkNotEquals(jpn1, jp2); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp6); + checkEquals(jp7, jp8); + checkNotEquals(jp4, jp7); + checkNotEquals(jp8, jp9); + checkEquals(jp9, jp10); + checkNotEquals(jp10, jp11); + checkNotEquals(jp5, jp10); + checkNotEquals(jp12, jp14); + checkEquals(jp13, jp14); + checkNotEquals(jp15, jp16); + checkEquals(jp16, jp17); + } + + public void testFloatEquality() throws Exception { + FloatProperty jp1 = new FloatProperty("name1", 123.4f); + FloatProperty jp2 = new FloatProperty("name1", 123.4f); + FloatProperty jp3 = new FloatProperty("name2", -123.4f); + FloatProperty jp4 = new FloatProperty("name2", 123.4f); + FloatProperty jp5 = new FloatProperty("name2", Float.NEGATIVE_INFINITY); + FloatProperty jp6 = new FloatProperty("name2", Float.NEGATIVE_INFINITY); + FloatProperty jp7 = new FloatProperty("name2", Float.POSITIVE_INFINITY); + FloatProperty jp8 = new FloatProperty("name2", Float.POSITIVE_INFINITY); + FloatProperty jp9 = new FloatProperty("name2", Float.NaN); + FloatProperty jp10 = new FloatProperty("name2", Float.NaN); + FloatProperty jp11 = new FloatProperty("name1", Float.NaN); + FloatProperty jp12 = new FloatProperty("name1", Float.MIN_VALUE); + FloatProperty jp13 = new FloatProperty("name2", Float.MIN_VALUE); + FloatProperty jp14 = new FloatProperty("name2", Float.MIN_VALUE); + FloatProperty jp15 = new FloatProperty("name1", Float.MAX_VALUE); + FloatProperty jp16 = new FloatProperty("name2", Float.MAX_VALUE); + FloatProperty jp17 = new FloatProperty("name2", Float.MAX_VALUE); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp6); + checkEquals(jp7, jp8); + checkNotEquals(jp4, jp7); + checkNotEquals(jp8, jp9); + checkEquals(jp9, jp10); + checkNotEquals(jp10, jp11); + checkNotEquals(jp5, jp10); + checkNotEquals(jp12, jp14); + checkEquals(jp13, jp14); + checkNotEquals(jp15, jp16); + checkEquals(jp16, jp17); + } + + public void testIntegerEquality() throws Exception { + IntegerProperty jp1 = new IntegerProperty("name1", 123); + IntegerProperty jp2 = new IntegerProperty("name1", 123); + IntegerProperty jp3 = new IntegerProperty("name2", -123); + IntegerProperty jp4 = new IntegerProperty("name2", 123); + IntegerProperty jp5 = new IntegerProperty("name2", Integer.MIN_VALUE); + IntegerProperty jp6 = new IntegerProperty("name2", Integer.MIN_VALUE); + IntegerProperty jp7 = new IntegerProperty("name2", Integer.MAX_VALUE); + IntegerProperty jp8 = new IntegerProperty("name2", Integer.MAX_VALUE); + IntegerProperty jp9 = new IntegerProperty("name1", Integer.MIN_VALUE); + IntegerProperty jp10 = new IntegerProperty("name1", Integer.MAX_VALUE); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp6); + checkEquals(jp7, jp8); + checkNotEquals(jp4, jp7); + checkNotEquals(jp9, jp5); + checkNotEquals(jp10, jp7); + checkNotEquals(jp9, jp10); + try { + new IntegerProperty(null); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + try { + new IntegerProperty(null, 0); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + } + + public void testLongEquality() throws Exception { + LongProperty jp1 = new LongProperty("name1", 123); + LongProperty jp2 = new LongProperty("name1", 123); + LongProperty jp3 = new LongProperty("name2", -123); + LongProperty jp4 = new LongProperty("name2", 123); + LongProperty jp5 = new LongProperty("name2", Long.MIN_VALUE); + LongProperty jp6 = new LongProperty("name2", Long.MIN_VALUE); + LongProperty jp7 = new LongProperty("name2", Long.MAX_VALUE); + LongProperty jp8 = new LongProperty("name2", Long.MAX_VALUE); + LongProperty jp9 = new LongProperty("name1", Long.MIN_VALUE); + LongProperty jp10 = new LongProperty("name1", Long.MAX_VALUE); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp6); + checkEquals(jp7, jp8); + checkNotEquals(jp4, jp7); + checkNotEquals(jp9, jp5); + checkNotEquals(jp10, jp7); + checkNotEquals(jp9, jp10); + try { + new LongProperty(null, 0L); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + } + + public void testMapEquality() throws Exception { + try { + new MapProperty(null, null); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + + } + + public void testNullEquality() throws Exception { + NullProperty jpn1 = new NullProperty(); + NullProperty jpn2 = new NullProperty(); + try { + new NullProperty(null); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + NullProperty jp1 = new NullProperty("name1"); + NullProperty jp2 = new NullProperty("name1"); + NullProperty jp3 = new NullProperty("name2"); + NullProperty jp4 = new NullProperty("name2"); + checkEquals(jpn1, jpn2); + checkNotEquals(jpn1, jp1); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkEquals(jp3, jp4); + } + + public void testStringEquality() throws Exception { + StringProperty jpn1 = new StringProperty(); + StringProperty jpn2 = new StringProperty(); + StringProperty jp1 = new StringProperty("name1", "value1"); + StringProperty jp2 = new StringProperty("name1", "value1"); + StringProperty jp3 = new StringProperty("name2", "value1"); + StringProperty jp4 = new StringProperty("name2", "value2"); + StringProperty jp5 = new StringProperty("name1", null); + StringProperty jp6 = new StringProperty("name1", null); + StringProperty jp7 = new StringProperty("name2", null); + checkEquals(jpn1, jpn2); + checkNotEquals(jpn1, jp1); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp5); + checkNotEquals(jp6, jp7); + try { + new StringProperty(null, ""); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + try { + new StringProperty(null, null); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + + } + public void testAddingProperties() throws Exception { + CollectionProperty coll = new CollectionProperty(); + coll.addItem("joe"); + coll.addProperty(new FunctionProperty()); + assertEquals("joe", coll.get(0).getStringValue()); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", coll.get(1).getClass().getName()); + } +} diff --git a/test/src/org/apache/jmeter/threads/TestJMeterContextService.java b/test/src/org/apache/jmeter/threads/TestJMeterContextService.java new file mode 100644 index 00000000000..decc5620367 --- /dev/null +++ b/test/src/org/apache/jmeter/threads/TestJMeterContextService.java @@ -0,0 +1,52 @@ +/* + * 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.threads; + +import junit.framework.TestCase; + +public class TestJMeterContextService extends TestCase { + + public TestJMeterContextService(String name) { + super(name); + } + + public void testCounts(){ + assertEquals(0,JMeterContextService.getNumberOfThreads()); + assertEquals(0,JMeterContextService.getTotalThreads()); + incrNumberOfThreads(); + assertEquals(1,JMeterContextService.getNumberOfThreads()); + assertEquals(0,JMeterContextService.getTotalThreads()); + decrNumberOfThreads(); + assertEquals(0,JMeterContextService.getTotalThreads()); + assertEquals(0,JMeterContextService.getNumberOfThreads()); + JMeterContextService.addTotalThreads(27); + JMeterContextService.addTotalThreads(27); + assertEquals(54,JMeterContextService.getTotalThreads()); + assertEquals(0,JMeterContextService.getNumberOfThreads()); + } + + // Give access to the method for test code + public static void incrNumberOfThreads(){ + JMeterContextService.incrNumberOfThreads(); + } + // Give access to the method for test code + public static void decrNumberOfThreads(){ + JMeterContextService.decrNumberOfThreads(); + } +} diff --git a/test/src/org/apache/jmeter/threads/TestTestCompiler.java b/test/src/org/apache/jmeter/threads/TestTestCompiler.java new file mode 100644 index 00000000000..a5be3fb42da --- /dev/null +++ b/test/src/org/apache/jmeter/threads/TestTestCompiler.java @@ -0,0 +1,63 @@ +/* + * 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.threads; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.control.GenericController; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.collections.ListedHashTree; + +public class TestTestCompiler extends junit.framework.TestCase { + public TestTestCompiler(String name) { + super(name); + } + + public void testConfigGathering() throws Exception { + ListedHashTree testing = new ListedHashTree(); + GenericController controller = new GenericController(); + ConfigTestElement config1 = new ConfigTestElement(); + config1.setName("config1"); + config1.setProperty("test.property", "A test value"); + TestSampler sampler = new TestSampler(); + sampler.setName("sampler"); + testing.add(controller, config1); + testing.add(controller, sampler); + TestCompiler.initialize(); + + TestCompiler compiler = new TestCompiler(testing); + testing.traverse(compiler); + sampler = (TestSampler) compiler.configureSampler(sampler).getSampler(); + assertEquals("A test value", sampler.getPropertyAsString("test.property")); + } + + class TestSampler extends AbstractSampler { + private static final long serialVersionUID = 240L; + + @Override + public SampleResult sample(org.apache.jmeter.samplers.Entry e) { + return null; + } + + @Override + public Object clone() { + return new TestSampler(); + } + } +} diff --git a/test/src/org/apache/jmeter/timers/PackageTest.java b/test/src/org/apache/jmeter/timers/PackageTest.java new file mode 100644 index 00000000000..00468e8144b --- /dev/null +++ b/test/src/org/apache/jmeter/timers/PackageTest.java @@ -0,0 +1,97 @@ +/* + * 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.timers; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.TestJMeterContextService; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class PackageTest extends JMeterTestCase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public PackageTest(String arg0) { + super(arg0); + } + + public void testTimer1() throws Exception { + ConstantThroughputTimer timer = new ConstantThroughputTimer(); + assertEquals(0,timer.getCalcMode());// Assume this thread only + timer.setThroughput(60.0);// 1 per second + long delay = timer.delay(); // Initialise + assertEquals(0,delay); + Thread.sleep(500); + assertEquals("Expected delay of approx 500",500, timer.delay(), 50); + } + + public void testTimer2() throws Exception { + ConstantThroughputTimer timer = new ConstantThroughputTimer(); + assertEquals(0,timer.getCalcMode());// Assume this thread only + timer.setThroughput(60.0);// 1 per second + assertEquals(1000,timer.calculateCurrentTarget(0)); // Should delay for 1 second + timer.setThroughput(60000.0);// 1 per milli-second + assertEquals(1,timer.calculateCurrentTarget(0)); // Should delay for 1 milli-second + } + + public void testTimer3() throws Exception { + ConstantThroughputTimer timer = new ConstantThroughputTimer(); + timer.setMode(ConstantThroughputTimer.Mode.AllActiveThreads); //$NON-NLS-1$ - all threads + assertEquals(1,timer.getCalcMode());// All threads + for(int i=1; i<=10; i++){ + TestJMeterContextService.incrNumberOfThreads(); + } + assertEquals(10,JMeterContextService.getNumberOfThreads()); + timer.setThroughput(600.0);// 10 per second + assertEquals(1000,timer.calculateCurrentTarget(0)); // Should delay for 1 second + timer.setThroughput(600000.0);// 10 per milli-second + assertEquals(1,timer.calculateCurrentTarget(0)); // Should delay for 1 milli-second + for(int i=1; i<=990; i++){ + TestJMeterContextService.incrNumberOfThreads(); + } + assertEquals(1000,JMeterContextService.getNumberOfThreads()); + timer.setThroughput(60000000.0);// 1000 per milli-second + assertEquals(1,timer.calculateCurrentTarget(0)); // Should delay for 1 milli-second + } + + public void testTimerBSH() throws Exception { + if (!BeanShellInterpreter.isInterpreterPresent()){ + final String msg = "BeanShell jar not present, test ignored"; + log.warn(msg); + return; + } + BeanShellTimer timer = new BeanShellTimer(); + long delay; + + timer.setScript("\"60\""); + delay = timer.delay(); + assertEquals(60,delay); + + timer.setScript("60"); + delay = timer.delay(); + assertEquals(60,delay); + + timer.setScript("5*3*4"); + delay = timer.delay(); + assertEquals(60,delay); + } + +} diff --git a/test/src/org/apache/jmeter/util/PackageTest.java b/test/src/org/apache/jmeter/util/PackageTest.java new file mode 100644 index 00000000000..dab3c18b98b --- /dev/null +++ b/test/src/org/apache/jmeter/util/PackageTest.java @@ -0,0 +1,66 @@ +/* + * 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.util; + +import junit.framework.TestCase; + +public class PackageTest extends TestCase { + + public PackageTest() { + super(); + } + + public PackageTest(String arg0) { + super(arg0); + } + + public void testServer() throws Exception { + BeanShellServer bshs = new BeanShellServer(9876, ""); + assertNotNull(bshs); + // Not sure we can test anything else here + } + public void testSub1() throws Exception { + String input = "http://jakarta.apache.org/jmeter/index.html"; + String pattern = "jakarta.apache.org"; + String sub = "${server}"; + assertEquals("http://${server}/jmeter/index.html", StringUtilities.substitute(input, pattern, sub)); + } + + public void testSub2() throws Exception { + String input = "arg1=param1;param1"; + String pattern = "param1"; + String sub = "${value}"; + assertEquals("arg1=${value};${value}", StringUtilities.substitute(input, pattern, sub)); + } + + public void testSub3() throws Exception { + String input = "jakarta.apache.org"; + String pattern = "jakarta.apache.org"; + String sub = "${server}"; + assertEquals("${server}", StringUtilities.substitute(input, pattern, sub)); + } + + public void testSub4() throws Exception { + String input = "//a///b////c"; + String pattern = "//"; + String sub = "/"; + assertEquals("/a//b//c", StringUtilities.substitute(input, pattern, sub)); + } + +} diff --git a/test/src/org/apache/jmeter/util/TestJMeterUtils.java b/test/src/org/apache/jmeter/util/TestJMeterUtils.java new file mode 100644 index 00000000000..b6936635f77 --- /dev/null +++ b/test/src/org/apache/jmeter/util/TestJMeterUtils.java @@ -0,0 +1,40 @@ +/* + * 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 to test JMeterUtils methods + */ + +package org.apache.jmeter.util; + +import junit.framework.TestCase; + +public class TestJMeterUtils extends TestCase { + + public TestJMeterUtils() { + super(); + } + + public TestJMeterUtils(String arg0) { + super(arg0); + } + //TODO add some real tests now that split() has been removed + public void test1() throws Exception{ + + } +} diff --git a/test/src/org/apache/jmeter/visualizers/GenerateTreeGui.java b/test/src/org/apache/jmeter/visualizers/GenerateTreeGui.java new file mode 100644 index 00000000000..4667c4723c6 --- /dev/null +++ b/test/src/org/apache/jmeter/visualizers/GenerateTreeGui.java @@ -0,0 +1,253 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.control.GenericController; +import org.apache.jmeter.control.gui.LogicControllerGui; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Workbench test element to create a test plan containing samples of each test element + * (apart from Threads and Test Fragment). + *

+ * The user creates a Thread Group, and the elements are created as child elements of + * Simple Controllers. + *

+ * Note: the code currently runs on all versions of JMeter back to 2.2. + * Beware of making changes that rely on more recent APIs. + */ +public class GenerateTreeGui extends AbstractConfigGui implements + ActionListener, UnsharedComponent { + + private static final long serialVersionUID = 1L; + + private JButton generateButton = new JButton("Generate"); + + public GenerateTreeGui() { + super(); + new Throwable().printStackTrace(); + init(); + } + + @Override + public String getLabelResource() { + new Throwable().printStackTrace(); + return "test_plan"; // $NON-NLS-1$ + } + + @Override + public String getStaticLabel() { + new Throwable().printStackTrace(); + return "Test Generator"; // $NON-NLS-1$ + } + + @Override + public String getDocAnchor() { + new Throwable().printStackTrace(); + return super.getDocAnchor(); + } + + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.NON_TEST_ELEMENTS }); + } + + @Override + public void actionPerformed(ActionEvent action) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeModel treeModel = guiPackage.getTreeModel(); + JMeterTreeNode myTarget = findFirstNodeOfType(org.apache.jmeter.threads.ThreadGroup.class, treeModel); + if (myTarget == null) { + JMeterUtils.reportErrorToUser("Cannot find Thread Group"); + return; + } + + addElements(MenuFactory.CONTROLLERS, "Controllers", guiPackage, treeModel, myTarget); + addElements(MenuFactory.CONFIG_ELEMENTS, "Config Elements", guiPackage, treeModel, myTarget); + addElements(MenuFactory.TIMERS, "Timers", guiPackage, treeModel, myTarget); + addElements(MenuFactory.PRE_PROCESSORS, "Pre Processors", guiPackage, treeModel, myTarget); + addElements(MenuFactory.SAMPLERS, "Samplers", guiPackage, treeModel, myTarget); + addElements(MenuFactory.POST_PROCESSORS, "Post Processors", guiPackage, treeModel, myTarget); + addElements(MenuFactory.ASSERTIONS, "Assertions", guiPackage, treeModel, myTarget); + addElements(MenuFactory.LISTENERS, "Listeners", guiPackage, treeModel, myTarget); + } + + private void addElements(String menuKey, String title, GuiPackage guiPackage, JMeterTreeModel treeModel, + JMeterTreeNode myTarget) { + myTarget = addSimpleController(treeModel, myTarget, title); + JPopupMenu jp = MenuFactory.makeMenu(menuKey, "").getPopupMenu(); + for (Component comp : jp.getComponents()) { + JMenuItem jmi = (JMenuItem) comp; + try { + TestElement testElement = guiPackage.createTestElement(jmi.getName()); + addToTree(treeModel, myTarget, testElement); + } catch (Exception e) { + addSimpleController(treeModel, myTarget, jmi.getName()+" "+e.getMessage()); + } + } + } + + @Override + public TestElement createTestElement() { + TestElement el = new ConfigTestElement(); + modifyTestElement(el); + return el; + } + + @Override + public void modifyTestElement(TestElement element) { + configureTestElement(element); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + private Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + ButtonGroup bg = new ButtonGroup(); + bg.add(generateButton); + generateButton.addActionListener(this); + labelPanel.add(generateButton); + return labelPanel; + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel p = new JPanel(); + p.setLayout(new BorderLayout()); + p.add(makeLabelPanel(), BorderLayout.NORTH); + // Force a minimum table height of 70 pixels + p.add(Box.createVerticalStrut(70), BorderLayout.WEST); + add(p, BorderLayout.CENTER); + } + /** + * Helper method to add a Simple Controller to contain the elements. + * Called from Application Thread that needs to update GUI (JMeterTreeModel) + * @param model + * Test component tree model + * @param node + * Node in the tree where we will add the Controller + * @param name + * A name for the Controller + * @return the new node + */ + private JMeterTreeNode addSimpleController(JMeterTreeModel model, JMeterTreeNode node, String name) { + final TestElement sc = new GenericController(); + sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI); + sc.setProperty(TestElement.NAME, name); // Use old style + return addToTree(model, node, sc); + } + + private static class RunGUI implements Runnable { + private final JMeterTreeModel model; + private final JMeterTreeNode node; + private final TestElement testElement; + RunGUI(JMeterTreeModel model, JMeterTreeNode node, TestElement testElement) { + super(); + this.model = model; + this.node = node; + this.testElement = testElement; + } + + volatile JMeterTreeNode newNode; + + @Override + public void run() { + try { + newNode = model.addComponent(testElement, node); + } catch (IllegalUserActionException e) { + throw new Error(e); + } + } + } + + private JMeterTreeNode addToTree(final JMeterTreeModel model, + final JMeterTreeNode node, final TestElement sc) { + RunGUI runnable = new RunGUI(model, node, sc); + if(SwingUtilities.isEventDispatchThread()) { + runnable.run(); + } else { + try { + SwingUtilities.invokeAndWait(runnable); + } catch (InterruptedException e) { + throw new Error(e); + } catch (InvocationTargetException e) { + throw new Error(e); + } + } + return runnable.newNode; + } + + private static final String LOGIC_CONTROLLER_GUI = LogicControllerGui.class.getName(); + + /** + * Finds the first enabled node of a given type in the tree. + * + * @param type + * class of the node to be found + * @param treeModel the tree to search in + * + * @return the first node of the given type in the test component tree, or + * null if none was found. + */ + private JMeterTreeNode findFirstNodeOfType(Class type, JMeterTreeModel treeModel) { + List nodes = treeModel.getNodesOfType(type); + for (JMeterTreeNode node : nodes) { + if (node.isEnabled()) { + return node; + } + } + return null; + } +} diff --git a/test/src/org/apache/jmeter/visualizers/TestRenderAsJson.java b/test/src/org/apache/jmeter/visualizers/TestRenderAsJson.java new file mode 100644 index 00000000000..1cbaff472f0 --- /dev/null +++ b/test/src/org/apache/jmeter/visualizers/TestRenderAsJson.java @@ -0,0 +1,68 @@ +package org.apache.jmeter.visualizers; + +import java.lang.reflect.Method; + +import junit.framework.TestCase; + +import org.junit.Test; + +public class TestRenderAsJson extends TestCase { + + private Method prettyJSON; + private final String TAB = ": "; + + private String prettyJSON(String prettify) throws Exception { + return (String) prettyJSON.invoke(null, prettify); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + prettyJSON = RenderAsJSON.class.getDeclaredMethod("prettyJSON", + String.class); + prettyJSON.setAccessible(true); + } + + @Test + public void testRenderResultWithLongStringBug54826() throws Exception { + StringBuilder json = new StringBuilder(); + json.append("\"customData\":\""); + for (int i = 0; i < 100; i++) { + json.append("somenotsorandomtext"); + } + json.append("\""); + + assertEquals("{\n" + TAB + json.toString() + "\n}", + prettyJSON("{" + json.toString() + "}")); + } + + @Test + public void testRenderResultSimpleObject() throws Exception { + assertEquals("{\n}", prettyJSON("{}")); + } + + @Test + public void testRenderResultSimpleArray() throws Exception { + assertEquals("[\n]", prettyJSON("[]")); + } + + @Test + public void testRenderResultSimpleNumber() throws Exception { + assertEquals("42", prettyJSON("42")); + } + + @Test + public void testRenderResultSimpleString() throws Exception { + assertEquals("Hello World", prettyJSON("Hello World")); + } + + @Test + public void testRenderResultSimpleStructur() throws Exception { + assertEquals( + "{\n" + TAB + "\"Hello\": \"World\", \n" + TAB + "\"more\": \n" + + TAB + "[\n" + TAB + TAB + "\"Something\", \n" + TAB + + TAB + "\"else\", \n" + TAB + "]\n}", + prettyJSON("{\"Hello\": \"World\", \"more\": [\"Something\", \"else\", ]}")); + } + +} diff --git a/test/src/org/apache/jmeter/visualizers/TestSamplingStatCalculator.java b/test/src/org/apache/jmeter/visualizers/TestSamplingStatCalculator.java new file mode 100644 index 00000000000..be854006ee5 --- /dev/null +++ b/test/src/org/apache/jmeter/visualizers/TestSamplingStatCalculator.java @@ -0,0 +1,152 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.samplers.SampleResult; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class TestSamplingStatCalculator { + + private SamplingStatCalculator ssc; + @Before + public void setUp(){ + ssc = new SamplingStatCalculator("JUnit"); + } + + @Test + public void testGetCurrentSample() { + Assert.assertNotNull(ssc.getCurrentSample()); // probably needed to avoid NPEs with GUIs + } + +// @Test +// public void testGetElapsed() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetRate() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetBytesPerSecond() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetKBPerSecond() { +// fail("Not yet implemented"); +// } + + @Test + public void testGetAvgPageBytes() { + SampleResult res = new SampleResult(); + Assert.assertEquals(0,ssc.getAvgPageBytes(),0); + res.setResponseData("abcdef", "UTF-8"); + ssc.addSample(res); + res.setResponseData("abcde", "UTF-8"); + ssc.addSample(res); + res.setResponseData("abcd", "UTF-8"); + ssc.addSample(res); + Assert.assertEquals(5,ssc.getAvgPageBytes(),0); + } + +// @Test +// public void testGetLabel() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testAddSample() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetErrorPercentage() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testToString() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetErrorCount() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMaxThroughput() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetDistribution() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetPercentPointDouble() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetCount() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMax() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMean() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMeanAsNumber() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMedian() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMin() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetPercentPointFloat() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetStandardDeviation() { +// fail("Not yet implemented"); +// } + +} diff --git a/test/src/org/apache/jorphan/TestFunctorUsers.java b/test/src/org/apache/jorphan/TestFunctorUsers.java new file mode 100644 index 00000000000..f81c9d13e05 --- /dev/null +++ b/test/src/org/apache/jorphan/TestFunctorUsers.java @@ -0,0 +1,69 @@ +/* + * 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.jorphan; + +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel; +import org.apache.jmeter.protocol.ldap.config.gui.LDAPArgumentsPanel; +import org.apache.jmeter.visualizers.StatGraphVisualizer; +import org.apache.jmeter.visualizers.StatVisualizer; +import org.apache.jmeter.visualizers.SummaryReport; +import org.apache.jmeter.visualizers.TableVisualizer; + +/* + * Unit tests for classes that use Functors + * + */ +public class TestFunctorUsers extends JMeterTestCase { + + public TestFunctorUsers(String arg0) { + super(arg0); + } + + @SuppressWarnings("deprecation") + public void testSummaryReport() throws Exception{ + assertTrue("SummaryReport Functor",SummaryReport.testFunctors()); + } + + public void testTableVisualizer() throws Exception{ + assertTrue("TableVisualizer Functor",TableVisualizer.testFunctors()); + } + + public void testStatGraphVisualizer() throws Exception{ + assertTrue("StatGraphVisualizer Functor",StatGraphVisualizer.testFunctors()); + } + + @SuppressWarnings("deprecation") + public void testStatVisualizer() throws Exception{ + assertTrue("StatVisualizer Functor",StatVisualizer.testFunctors()); + } + + public void testArgumentsPanel() throws Exception{ + assertTrue("ArgumentsPanel Functor",ArgumentsPanel.testFunctors()); + } + + public void testHTTPArgumentsPanel() throws Exception{ + assertTrue("HTTPArgumentsPanel Functor",HTTPArgumentsPanel.testFunctors()); + } + + public void testLDAPArgumentsPanel() throws Exception{ + assertTrue("LDAPArgumentsPanel Functor",LDAPArgumentsPanel.testFunctors()); + } +} diff --git a/test/src/org/apache/jorphan/TestXMLBuffer.java b/test/src/org/apache/jorphan/TestXMLBuffer.java new file mode 100644 index 00000000000..7bb93ea024d --- /dev/null +++ b/test/src/org/apache/jorphan/TestXMLBuffer.java @@ -0,0 +1,56 @@ +/* + * 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.jorphan; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jorphan.util.XMLBuffer; + +public class TestXMLBuffer extends JMeterTestCase { + + public TestXMLBuffer(String arg0) { + super(arg0); + } + + public void test1() throws Exception{ + XMLBuffer xb = new XMLBuffer(); + xb.openTag("start"); + assertEquals("\n",xb.toString()); + } + + public void test2() throws Exception{ + XMLBuffer xb = new XMLBuffer(); + xb.tag("start","now"); + assertEquals("now\n",xb.toString()); + } + public void test3() throws Exception{ + XMLBuffer xb = new XMLBuffer(); + xb.openTag("abc"); + xb.closeTag("abc"); + assertEquals("\n",xb.toString()); + } + public void test4() throws Exception{ + XMLBuffer xb = new XMLBuffer(); + xb.openTag("abc"); + try { + xb.closeTag("abcd"); + fail("Should have caused IllegalArgumentException"); + } catch (IllegalArgumentException e) { + } + } +} diff --git a/test/src/org/apache/jorphan/collections/PackageTest.java b/test/src/org/apache/jorphan/collections/PackageTest.java new file mode 100644 index 00000000000..87d02f365c3 --- /dev/null +++ b/test/src/org/apache/jorphan/collections/PackageTest.java @@ -0,0 +1,175 @@ +/* + * 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.jorphan.collections; + +import java.util.Arrays; +import java.util.Collection; + +import junit.framework.TestCase; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class PackageTest extends TestCase { + public PackageTest(String name) { + super(name); + } + + public void testAdd1() throws Exception { + Logger log = LoggingManager.getLoggerForClass(); + Collection treePath = Arrays.asList(new String[] { "1", "2", "3", "4" }); + HashTree tree = new HashTree(); + log.debug("treePath = " + treePath); + tree.add(treePath, "value"); + log.debug("Now treePath = " + treePath); + log.debug(tree.toString()); + assertEquals(1, tree.list(treePath).size()); + assertEquals("value", tree.getArray(treePath)[0]); + } + + public void testEqualsAndHashCode1() throws Exception { + HashTree tree1 = new HashTree("abcd"); + HashTree tree2 = new HashTree("abcd"); + HashTree tree3 = new HashTree("abcde"); + HashTree tree4 = new HashTree("abcde"); + + assertTrue(tree1.equals(tree1)); + assertTrue(tree1.equals(tree2)); + assertTrue(tree2.equals(tree1)); + assertTrue(tree2.equals(tree2)); + assertEquals(tree1.hashCode(), tree2.hashCode()); + + assertTrue(tree3.equals(tree3)); + assertTrue(tree3.equals(tree4)); + assertTrue(tree4.equals(tree3)); + assertTrue(tree4.equals(tree4)); + assertEquals(tree3.hashCode(), tree4.hashCode()); + + assertNotSame(tree1, tree2); + assertNotSame(tree1, tree3); + assertNotSame(tree1, tree4); + assertNotSame(tree2, tree3); + assertNotSame(tree2, tree4); + + assertFalse(tree1.equals(tree3)); + assertFalse(tree1.equals(tree4)); + assertFalse(tree2.equals(tree3)); + assertFalse(tree2.equals(tree4)); + + assertNotNull(tree1); + assertNotNull(tree2); + + tree1.add("abcd", tree3); + assertFalse(tree1.equals(tree2)); + assertFalse(tree2.equals(tree1));// Check reflexive + if (tree1.hashCode() == tree2.hashCode()) { + // This is not a requirement + System.out.println("WARN: unequal HashTrees should not have equal hashCodes"); + } + tree2.add("abcd", tree4); + assertTrue(tree1.equals(tree2)); + assertTrue(tree2.equals(tree1)); + assertEquals(tree1.hashCode(), tree2.hashCode()); + } + + + public void testAddObjectAndTree() throws Exception { + ListedHashTree tree = new ListedHashTree("key"); + ListedHashTree newTree = new ListedHashTree("value"); + tree.add("key", newTree); + assertEquals(tree.list().size(), 1); + assertEquals("key", tree.getArray()[0]); + assertEquals(1, tree.getTree("key").list().size()); + assertEquals(0, tree.getTree("key").getTree("value").size()); + assertEquals(tree.getTree("key").getArray()[0], "value"); + assertNotNull(tree.getTree("key").get("value")); + } + + public void testEqualsAndHashCode2() throws Exception { + ListedHashTree tree1 = new ListedHashTree("abcd"); + ListedHashTree tree2 = new ListedHashTree("abcd"); + ListedHashTree tree3 = new ListedHashTree("abcde"); + ListedHashTree tree4 = new ListedHashTree("abcde"); + + assertTrue(tree1.equals(tree1)); + assertTrue(tree1.equals(tree2)); + assertTrue(tree2.equals(tree1)); + assertTrue(tree2.equals(tree2)); + assertEquals(tree1.hashCode(), tree2.hashCode()); + + assertTrue(tree3.equals(tree3)); + assertTrue(tree3.equals(tree4)); + assertTrue(tree4.equals(tree3)); + assertTrue(tree4.equals(tree4)); + assertEquals(tree3.hashCode(), tree4.hashCode()); + + assertNotSame(tree1, tree2); + assertNotSame(tree1, tree3); + assertFalse(tree1.equals(tree3)); + assertFalse(tree3.equals(tree1)); + assertFalse(tree1.equals(tree4)); + assertFalse(tree4.equals(tree1)); + + assertFalse(tree2.equals(tree3)); + assertFalse(tree3.equals(tree2)); + assertFalse(tree2.equals(tree4)); + assertFalse(tree4.equals(tree2)); + + tree1.add("abcd", tree3); + assertFalse(tree1.equals(tree2)); + assertFalse(tree2.equals(tree1)); + + tree2.add("abcd", tree4); + assertTrue(tree1.equals(tree2)); + assertTrue(tree2.equals(tree1)); + assertEquals(tree1.hashCode(), tree2.hashCode()); + + tree1.add("a1"); + tree1.add("a2"); + // tree1.add("a3"); + tree2.add("a2"); + tree2.add("a1"); + + assertFalse(tree1.equals(tree2)); + assertFalse(tree2.equals(tree1)); + if (tree1.hashCode() == tree2.hashCode()) { + // This is not a requirement + System.out.println("WARN: unequal ListedHashTrees should not have equal hashcodes"); + + } + + tree4.add("abcdef"); + assertFalse(tree3.equals(tree4)); + assertFalse(tree4.equals(tree3)); + } + + + public void testSearch() throws Exception { + ListedHashTree tree = new ListedHashTree(); + SearchByClass searcher = new SearchByClass(Integer.class); + String one = "one"; + String two = "two"; + Integer o = Integer.valueOf(1); + tree.add(one, o); + tree.getTree(one).add(o, two); + tree.traverse(searcher); + assertEquals(1, searcher.getSearchResults().size()); + } + +} diff --git a/test/src/org/apache/jorphan/exec/TestKeyToolUtils.java b/test/src/org/apache/jorphan/exec/TestKeyToolUtils.java new file mode 100644 index 00000000000..ab2ebfd75a7 --- /dev/null +++ b/test/src/org/apache/jorphan/exec/TestKeyToolUtils.java @@ -0,0 +1,60 @@ +/* + * 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 to test JOrphanUtils methods + */ + +package org.apache.jorphan.exec; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +public class TestKeyToolUtils extends TestCase { + + public TestKeyToolUtils() { + super(); + } + + public TestKeyToolUtils(String arg0) { + super(arg0); + } + + /* + * Check the assumption that a missing executable will generate + * either an IOException or status which is neither 0 nor 1 + * + */ + public void testCheckKeytool() throws Exception { + SystemCommand sc = new SystemCommand(null, null); + List arguments = new ArrayList(); + arguments.add("xyzqwas"); // should not exist + try { + int status = sc.run(arguments); + if (status == 0 || status ==1) { + fail("Unexpected status " + status); + } +// System.out.println("testCheckKeytool:status="+status); + } catch (IOException expected) { +// System.out.println("testCheckKeytool:Exception="+e); + } + } +} diff --git a/test/src/org/apache/jorphan/math/TestStatCalculator.java b/test/src/org/apache/jorphan/math/TestStatCalculator.java new file mode 100644 index 00000000000..81ebb323f62 --- /dev/null +++ b/test/src/org/apache/jorphan/math/TestStatCalculator.java @@ -0,0 +1,154 @@ +/* + * 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.jorphan.math; + +import java.util.Map; + +import junit.framework.TestCase; + +public class TestStatCalculator extends TestCase { + + private StatCalculatorLong calc; + + /** + * + */ + public TestStatCalculator() { + super(); + } + + public TestStatCalculator(String arg0) { + super(arg0); + } + + @Override + public void setUp() { + calc = new StatCalculatorLong(); + } + + public void testPercentagePoint() throws Exception { + calc.addValue(10); + calc.addValue(9); + calc.addValue(5); + calc.addValue(6); + calc.addValue(1); + calc.addValue(3); + calc.addValue(8); + calc.addValue(2); + calc.addValue(7); + calc.addValue(4); + assertEquals(10, calc.getCount()); + assertEquals(9, calc.getPercentPoint(0.8999999).intValue()); + } + public void testCalculation() { + assertEquals(Long.MIN_VALUE, calc.getMax().longValue()); + assertEquals(Long.MAX_VALUE, calc.getMin().longValue()); + calc.addValue(18); + calc.addValue(10); + calc.addValue(9); + calc.addValue(11); + calc.addValue(28); + calc.addValue(3); + calc.addValue(30); + calc.addValue(15); + calc.addValue(15); + calc.addValue(21); + assertEquals(16, (int) calc.getMean()); + assertEquals(8.0622577F, (float) calc.getStandardDeviation(), 0F); + assertEquals(30, calc.getMax().intValue()); + assertEquals(3, calc.getMin().intValue()); + assertEquals(15, calc.getMedian().intValue()); + } + public void testLong(){ + calc.addValue(0L); + calc.addValue(2L); + calc.addValue(2L); + final Long long0 = Long.valueOf(0); + final Long long2 = Long.valueOf(2); + assertEquals(long2,calc.getMax()); + assertEquals(long0,calc.getMin()); + Map map = calc.getDistribution(); + assertTrue(map.containsKey(long0)); + assertTrue(map.containsKey(long2)); + } + + public void testInteger(){ + StatCalculatorInteger calci = new StatCalculatorInteger(); + assertEquals(Integer.MIN_VALUE, calci.getMax().intValue()); + assertEquals(Integer.MAX_VALUE, calci.getMin().intValue()); + calci.addValue(0); + calci.addValue(2); + calci.addValue(2); + assertEquals(Integer.valueOf(2),calci.getMax()); + assertEquals(Integer.valueOf(0),calci.getMin()); + Map map = calci.getDistribution(); + assertTrue(map.containsKey(Integer.valueOf(0))); + assertTrue(map.containsKey(Integer.valueOf(2))); + } + + @SuppressWarnings("boxing") + public void testBug52125_1(){ // No duplicates when adding + calc.addValue(1L); + calc.addValue(2L); + calc.addValue(3L); + calc.addValue(2L); + calc.addValue(2L); + calc.addValue(2L); + assertEquals(6, calc.getCount()); + assertEquals(12.0, calc.getSum()); + assertEquals(0.5773502691896255, calc.getStandardDeviation()); + } + + @SuppressWarnings("boxing") + public void testBug52125_2(){ // add duplicates + calc.addValue(1L); + calc.addValue(2L); + calc.addValue(3L); + calc.addEachValue(2L, 3); + assertEquals(6, calc.getCount()); + assertEquals(12.0, calc.getSum()); + assertEquals(0.5773502691896255, calc.getStandardDeviation()); + } + + @SuppressWarnings("boxing") + public void testBug52125_2A(){ // as above, but with aggregate sample instead + calc.addValue(1L); + calc.addValue(2L); + calc.addValue(3L); + calc.addValue(6L, 3); + assertEquals(6, calc.getCount()); + assertEquals(12.0, calc.getSum()); + assertEquals(0.5773502691896255, calc.getStandardDeviation()); + } + + @SuppressWarnings("boxing") + public void testBug52125_3(){ // add duplicates as per bug + calc.addValue(1L); + calc.addValue(2L); + calc.addValue(3L); + StatCalculatorLong calc2 = new StatCalculatorLong(); + calc2.addValue(2L); + calc2.addValue(2L); + calc2.addValue(2L); + calc.addAll(calc2); + assertEquals(6, calc.getCount()); + assertEquals(12.0, calc.getSum()); + assertEquals(0.5773502691896255, calc.getStandardDeviation()); + } +} diff --git a/test/src/org/apache/jorphan/reflect/TestClassTools.java b/test/src/org/apache/jorphan/reflect/TestClassTools.java new file mode 100644 index 00000000000..6f2e65421c7 --- /dev/null +++ b/test/src/org/apache/jorphan/reflect/TestClassTools.java @@ -0,0 +1,113 @@ +/* + * 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.jorphan.reflect; + +import junit.framework.TestCase; + +import org.apache.jorphan.util.JMeterException; +import org.junit.Test; + +/** + * Test various aspects of the {@link ClassTools} class + */ +public class TestClassTools extends TestCase { + + /** + * Test that a class can be constructed using the default constructor + * + * @throws JMeterException + * when something fails during object construction + */ + @Test + public void testConstructString() throws JMeterException { + String dummy = (String) ClassTools.construct("java.lang.String"); + assertNotNull(dummy); + assertEquals("", dummy); + } + + /** + * Test that a class can be constructed using an constructor with an integer + * parameter + * + * @throws JMeterException + * when something fails during object construction + */ + @Test + public void testConstructStringInt() throws JMeterException { + Integer dummy = (Integer) ClassTools.construct("java.lang.Integer", 23); + assertNotNull(dummy); + assertEquals(Integer.valueOf(23), dummy); + } + + /** + * Test that a class can be constructed using an constructor with an string + * parameter + * + * @throws JMeterException + * when something fails during object construction + */ + @Test + public void testConstructStringString() throws JMeterException { + String dummy = (String) ClassTools.construct("java.lang.String", + "hello"); + assertNotNull(dummy); + assertEquals("hello", dummy); + } + + /** + * Test that a simple method can be invoked on an object + * + * @throws SecurityException + * when the method can not be used because of security concerns + * @throws IllegalArgumentException + * when the method parameters does not match the given ones + * @throws JMeterException + * when something fails while invoking the method + */ + @Test + public void testInvoke() throws SecurityException, + IllegalArgumentException, JMeterException { + Dummy dummy = new Dummy(); + ClassTools.invoke(dummy, "callMe"); + assertEquals(dummy.wasCalled(), true); + } + + /** + * Dummy class to be used for construction and invocation tests + * + */ + public static class Dummy { + private boolean called = false; + + /** + * @return true if {@link Dummy#callMe()} was called on + * this instance + */ + public boolean wasCalled() { + return this.called; + } + + /** + * Simple method to be called on void invocation + */ + public void callMe() { + this.called = true; + } + } + +} diff --git a/test/src/org/apache/jorphan/reflect/TestFunctor.java b/test/src/org/apache/jorphan/reflect/TestFunctor.java new file mode 100644 index 00000000000..c302d68ddfa --- /dev/null +++ b/test/src/org/apache/jorphan/reflect/TestFunctor.java @@ -0,0 +1,228 @@ +/* + * 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.jorphan.reflect; + +import java.util.Map; +import java.util.Properties; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; + +/* + * Unit tests for classes that use Functors + * + */ +public class TestFunctor extends JMeterTestCase { + + interface HasName { + String getName(); + } + + interface HasString { + String getString(String s); + } + + class Test1 implements HasName { + private final String name; + public Test1(){ + this(""); + } + public Test1(String s){ + name=s; + } + @Override + public String getName(){ + return name; + } + public String getString(String s){ + return s; + } + } + class Test1a extends Test1{ + Test1a(){ + super("1a"); + } + Test1a(String s){ + super("1a:"+s); + } + @Override + public String getName(){ + return super.getName()+"."; + } + } + static class Test2 implements HasName, HasString { + private final String name; + public Test2(){ + this(""); + } + public Test2(String s){ + name=s; + } + @Override + public String getName(){ + return name; + } + @Override + public String getString(String s){ + return s; + } + } + + public TestFunctor(String arg0) { + super(arg0); + } + + @Override + public void setUp(){ + LoggingManager.setPriority("FATAL_ERROR",LoggingManager.removePrefix(Functor.class.getName())); + } + + public void testName() throws Exception{ + Functor f1 = new Functor("getName"); + Functor f2 = new Functor("getName"); + Functor f1a = new Functor("getName"); + Test1 t1 = new Test1("t1"); + Test2 t2 = new Test2("t2"); + Test1a t1a = new Test1a("aa"); + assertEquals("t1",f1.invoke(t1)); + //assertEquals("t1",f1.invoke()); + try { + f1.invoke(t2); + fail("Should have generated error"); + } catch (JMeterError e){ + + } + assertEquals("t2",f2.invoke(t2)); + //assertEquals("t2",f2.invoke()); + assertEquals("1a:aa.",f1a.invoke(t1a)); + //assertEquals("1a:aa.",f1a.invoke()); + try { + f1a.invoke(t1);// can't call invoke using super class + fail("Should have generated error"); + } catch (JMeterError e){ + + } + // OK (currently) to invoke using sub-class + assertEquals("1a:aa.",f1.invoke(t1a)); + //assertEquals("1a:aa.",f1.invoke());// N.B. returns different result from before + } + + public void testNameTypes() throws Exception{ + Functor f = new Functor("getString",new Class[]{String.class}); + Functor f2 = new Functor("getString");// Args will be provided later + Test1 t1 = new Test1("t1"); + assertEquals("x1",f.invoke(t1,new String[]{"x1"})); + try { + assertEquals("x1",f.invoke(t1)); + fail("Should have generated an Exception"); + } catch (JMeterError ok){ + } + assertEquals("x2",f2.invoke(t1,new String[]{"x2"})); + try { + assertEquals("x2",f2.invoke(t1)); + fail("Should have generated an Exception"); + } catch (JMeterError ok){ + } + } + public void testObjectName() throws Exception{ + Test1 t1 = new Test1("t1"); + Test2 t2 = new Test2("t2"); + Functor f1 = new Functor(t1,"getName"); + assertEquals("t1",f1.invoke(t1)); + assertEquals("t1",f1.invoke(t2)); // should use original object + } + + // Check how Class definition behaves + public void testClass() throws Exception{ + Test1 t1 = new Test1("t1"); + Test1 t1a = new Test1a("t1a"); + Test2 t2 = new Test2("t2"); + Functor f1 = new Functor(HasName.class,"getName"); + assertEquals("t1",f1.invoke(t1)); + assertEquals("1a:t1a.",f1.invoke(t1a)); + assertEquals("t2",f1.invoke(t2)); + try { + f1.invoke(); + fail("Should have failed"); + } catch (IllegalStateException ok){ + + } + Functor f2 = new Functor(HasString.class,"getString"); + assertEquals("xyz",f2.invoke(t2,new String[]{"xyz"})); + try { + f2.invoke(t1,new String[]{"xyz"}); + fail("Should have failed"); + } catch (JMeterError ok){ + + } + Functor f3 = new Functor(t2,"getString"); + assertEquals("xyz",f3.invoke(t2,new Object[]{"xyz"})); + + Properties p = new Properties(); + p.put("Name","Value"); + Functor fk = new Functor(Map.Entry.class,"getKey"); + Functor fv = new Functor(Map.Entry.class,"getValue"); + Object o = p.entrySet().iterator().next(); + assertEquals("Name",fk.invoke(o)); + assertEquals("Value",fv.invoke(o)); + } + + public void testBadParameters() throws Exception{ + try { + new Functor(null); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(null,new Class[]{}); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(null,new Object[]{}); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(String.class,null); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(new Object(),null); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(new Object(),null, new Class[]{}); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(new Object(),null, new Object[]{}); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + } + public void testIllegalState() throws Exception{ + Functor f = new Functor("method"); + try { + f.invoke(); + fail("should have generated IllegalStateException;"); + } catch (IllegalStateException ok){} + try { + f.invoke(new Object[]{}); + fail("should have generated IllegalStateException;"); + } catch (IllegalStateException ok){} + } +} diff --git a/test/src/org/apache/jorphan/test/AllTests.java b/test/src/org/apache/jorphan/test/AllTests.java new file mode 100644 index 00000000000..ebcaaed2736 --- /dev/null +++ b/test/src/org/apache/jorphan/test/AllTests.java @@ -0,0 +1,385 @@ +/* + * 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.jorphan.test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Locale; +import java.util.Properties; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import junit.textui.TestRunner; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Provides a quick and easy way to run all junit unit tests in your java + * project. It will find all unit test classes and run all their test methods. + * There is no need to configure it in any way to find these classes except to + * give it a path to search. + *

+ * Here is an example Ant target (See Ant at Apache) that runs all your unit + * tests: + * + *

+ * 
+ *       <target name="test" depends="compile">
+ *           <java classname="org.apache.jorphan.test.AllTests" fork="yes">
+ *               <classpath>
+ *                   <path refid="YOUR_CLASSPATH"/>
+ *                   <pathelement location="ROOT_DIR_OF_YOUR_COMPILED_CLASSES"/>
+ *               </classpath>
+ *               <arg value="SEARCH_PATH/"/>
+ *               <arg value="PROPERTY_FILE"/>
+ *               <arg value="NAME_OF_UNITTESTMANAGER_CLASS"/>
+ *           </java>
+ *       </target>
+ *  
+ * 
+ * + *
+ *
YOUR_CLASSPATH
+ *
Refers to the classpath that includes all jars and libraries need to run + * your unit tests
+ * + *
ROOT_DIR_OF_YOUR_COMPILED_CLASSES
+ *
The classpath should include the directory where all your project's + * classes are compiled to, if it doesn't already.
+ * + *
SEARCH_PATH
+ *
The first argument tells AllTests where to look for unit test classes to + * execute. In most cases, it is identical to ROOT_DIR_OF_YOUR_COMPILED_CLASSES. + * You can specify multiple directories or jars to search by providing a + * comma-delimited list.
+ * + *
PROPERTY_FILE
+ *
A simple property file that sets logging parameters. It is optional and + * is only relevant if you use the same logging packages that JOrphan uses.
+ * + *
NAME_OF_UNITTESTMANAGER_CLASS
+ *
If your system requires some configuration to run correctly, you can + * implement the {@link UnitTestManager} interface and be given an opportunity + * to initialize your system from a configuration file.
+ *
+ * + * @see UnitTestManager + */ +public final class AllTests { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Private constructor to prevent instantiation. + */ + private AllTests() { + } + + private static void logprop(String prop, boolean show) { + String value = System.getProperty(prop); + log.info(prop + "=" + value); + if (show) { + System.out.println(prop + "=" + value); + } + } + + private static void logprop(String prop) { + logprop(prop, false); + } + + /** + * Starts a run through all unit tests found in the specified classpaths. + * The first argument should be a list of paths to search. The second + * argument is optional and specifies a properties file used to initialize + * logging. The third argument is also optional, and specifies a class that + * implements the UnitTestManager interface. This provides a means of + * initializing your application with a configuration file prior to the + * start of any unit tests. + * + * @param args + * the command line arguments + */ + public static void main(String[] args) { + if (args.length < 1) { + System.out.println("You must specify a comma-delimited list of paths to search " + "for unit tests"); + return; + } + String home=new File(System.getProperty("user.dir")).getParent(); + System.out.println("Setting JMeterHome: "+home); + JMeterUtils.setJMeterHome(home); + initializeLogging(args); + initializeManager(args); + + String version = "JMeterVersion="+JMeterUtils.getJMeterVersion(); + log.info(version); + System.out.println(version); + logprop("java.version", true); + logprop("java.vm.name"); + logprop("java.vendor"); + logprop("java.home", true); + logprop("file.encoding", true); + // Display actual encoding used (will differ if file.encoding is not recognised) + String msg = "default encoding="+Charset.defaultCharset(); + System.out.println(msg); + log.info(msg); + logprop("user.home"); + logprop("user.dir", true); + logprop("user.language"); + logprop("user.region"); + logprop("user.country"); + logprop("user.variant"); + final String showLocale = "Locale="+Locale.getDefault().toString(); + log.info(showLocale); + System.out.println(showLocale); + logprop("os.name", true); + logprop("os.version", true); + logprop("os.arch"); + logprop("java.class.version"); + // logprop("java.class.path"); + String cp = System.getProperty("java.class.path"); + String cpe[] = JOrphanUtils.split(cp, java.io.File.pathSeparator); + StringBuilder sb = new StringBuilder(3000); + sb.append("java.class.path="); + for (int i = 0; i < cpe.length; i++) { + sb.append("\n"); + sb.append(cpe[i]); + if (new java.io.File(cpe[i]).exists()) { + sb.append(" - OK"); + } else { + sb.append(" - ??"); + } + } + log.info(sb.toString()); + + // ++ + // GUI tests throw the error + // testArgumentCreation(org.apache.jmeter.config.gui.ArgumentsPanel$Test)java.lang.NoClassDefFoundError + // at java.lang.Class.forName0(Native Method) + // at java.lang.Class.forName(Class.java:141) + // at + // java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(GraphicsEnvironment.java:62) + // + // Try to find out why this is ... + + System.out.println("+++++++++++"); + logprop("java.awt.headless", true); + logprop("java.awt.graphicsenv", true); + // + // try {// + // Class c = Class.forName(n); + // System.out.println("Found class: "+n); + // // c.newInstance(); + // // System.out.println("Instantiated: "+n); + // } catch (Exception e1) { + // System.out.println("Error finding class "+n+" "+e1); + // } catch (java.lang.InternalError e1){ + // System.out.println("Error finding class "+n+" "+e1); + // } + // + System.out.println("------------"); + // don't call isHeadless() here, as it has a side effect. + // -- + System.out.println("Creating test suite"); + TestSuite suite = suite(args[0]); + int countTestCases = suite.countTestCases(); + System.out.println("Starting test run, test count = "+countTestCases); +// for (int i=0;i= 2) { + Properties props = new Properties(); + InputStream inputStream = null; + try { + System.out.println("Setting up logging props using file: " + args[1]); + inputStream = new FileInputStream(args[1]); + props.load(inputStream); + LoggingManager.initializeLogging(props); + } catch (FileNotFoundException e) { + System.out.println(e.getLocalizedMessage()); + } catch (IOException e) { + System.out.println(e.getLocalizedMessage()); + } finally { + JOrphanUtils.closeQuietly(inputStream); + } + } + } + + /** + * An overridable method that that instantiates a UnitTestManager (if one + * was specified in the command-line arguments), and hands it the name of + * the properties file to use to configure the system. + * + * @param args arguments with the initialization parameter + * arg[0] - not used + * arg[1] - relative name of properties file + * arg[2] - used as label + */ + protected static void initializeManager(String[] args) { + if (args.length >= 3) { + try { + System.out.println("Using initializeProperties() from " + args[2]); + UnitTestManager um = (UnitTestManager) Class.forName(args[2]).newInstance(); + System.out.println("Setting up initial properties using: " + args[1]); + um.initializeProperties(args[1]); + } catch (ClassNotFoundException e) { + System.out.println("Couldn't create: " + args[2]); + e.printStackTrace(); + } catch (InstantiationException e) { + System.out.println("Couldn't create: " + args[2]); + e.printStackTrace(); + } catch (IllegalAccessException e) { + System.out.println("Couldn't create: " + args[2]); + e.printStackTrace(); + } + } + } + + /* + * Externally callable suite() method for use by JUnit Allows tests to be + * run directly under JUnit, rather than using the startup code in the rest + * of the module. No parameters can be passed in, so it is less flexible. + */ + public static TestSuite suite() { + String args[] = { "../lib/ext", "./testfiles/jmetertest.properties", "org.apache.jmeter.util.JMeterUtils" }; + + initializeManager(args); + return suite(args[0]); + } + + /** + * A unit test suite for JUnit. + * + * @return The test suite + */ + private static TestSuite suite(String searchPaths) { + TestSuite suite = new TestSuite("All Tests"); + System.out.println("Scanning "+searchPaths+ " for test cases"); + int tests=0; + int suites=0; + try { + log.info("ClassFinder(TestCase)"); + List classList = ClassFinder.findClassesThatExtend(JOrphanUtils.split(searchPaths, ","), + new Class[] { TestCase.class }, true); + int sz=classList.size(); + log.info("ClassFinder(TestCase) found: "+sz+ " TestCase classes"); + System.out.println("ClassFinder found: "+sz+ " TestCase classes"); + for (String name : classList) { + try { + /* + * TestSuite only finds testXXX() methods, and does not look + * for suite() methods. + * + * To provide more compatibilty with stand-alone tests, + * where JUnit does look for a suite() method, check for it + * first here. + * + */ + + Class clazz = Class.forName(name); + Test t = null; + try { + Method m = clazz.getMethod("suite", new Class[0]); + t = (Test) m.invoke(clazz, (Object[])null); + suites++; + } catch (NoSuchMethodException e) { + } // this is not an error, the others are + // catch (SecurityException e) {} + // catch (IllegalAccessException e) {} + // catch (IllegalArgumentException e) {} + // catch (InvocationTargetException e) {} + + if (t == null) { + t = new TestSuite(clazz); + } + + tests++; + suite.addTest(t); + } catch (Exception ex) { + System.out.println("ERROR: (see logfile) could not add test for class " + name + " " + ExceptionUtils.getStackTrace(ex)); + log.error("error adding test :", ex); + } + } + } catch (IOException e) { + log.error("", e); + } + System.out.println("Created: "+tests+" tests including "+suites+" suites"); + return suite; + } +} diff --git a/test/src/org/apache/jorphan/util/TestConverter.java b/test/src/org/apache/jorphan/util/TestConverter.java new file mode 100644 index 00000000000..a64646d2187 --- /dev/null +++ b/test/src/org/apache/jorphan/util/TestConverter.java @@ -0,0 +1,136 @@ +/* + * 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.jorphan.util; + +import java.text.DateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import junit.framework.TestCase; + +import org.junit.Test; + +/** + * Tests for {@link Converter} + * + */ +public class TestConverter extends TestCase { + + /** + * Test {@link Converter#getCalendar(Object, Calendar)} with a given Date + * and null as default value + */ + @Test + public void testGetCalendarObjectCalendarWithTimeAndNullDefault() { + Calendar cal = new GregorianCalendar(); + Date time = cal.getTime(); + assertEquals(cal, Converter.getCalendar(time, null)); + } + + /** + * Test {@link Converter#getCalendar(Object, Calendar)} with null as Date + * and a sensible default value + */ + @Test + public void testGetCalendarObjectCalendarWithNullAndCalendarAsDefault() { + Calendar cal = new GregorianCalendar(); + assertEquals(cal, Converter.getCalendar(null, cal)); + } + + /** + * Test {@link Converter#getCalendar(Object, Calendar)} with correctly + * formatted strings and null as default value + */ + @Test + public void testGetCalendarObjectCalendarWithValidStringAndNullDefault() { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + Date time = cal.getTime(); + for (int formatId : Arrays.asList(DateFormat.SHORT, DateFormat.MEDIUM, + DateFormat.LONG, DateFormat.FULL)) { + DateFormat formatter = DateFormat.getDateInstance(formatId); + assertEquals(cal, + Converter.getCalendar(formatter.format(time), null)); + } + } + + /** + * Test {@link Converter#getCalendar(Object, Calendar)} with an invalid + * string and null as default value + */ + @Test + public void testGetCalendarObjectCalendarWithInvalidStringAndNullDefault() { + assertEquals(null, Converter.getCalendar("invalid date", null)); + } + + /** + * Test {@link Converter#getDate(Object, Date)} with a given Date + * and null as default value + */ + @Test + public void testGetDateObjectDateWithTimeAndNullDefault() { + Date time = new Date(); + assertEquals(time, Converter.getDate(time, null)); + } + + /** + * Test {@link Converter#getDate(Object, Date)} with null as Date + * and a sensible default value + */ + @Test + public void testGetDateObjectDateWithNullAndDateAsDefault() { + Date date = new Date(); + assertEquals(date, Converter.getDate(null, date)); + } + + /** + * Test {@link Converter#getDate(Object, Date)} with correctly + * formatted strings and null as default value + */ + @Test + public void testGetDateObjectDateWithValidStringAndNullDefault() { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + Date time = cal.getTime(); + for (int formatId : Arrays.asList(DateFormat.SHORT, DateFormat.MEDIUM, + DateFormat.LONG, DateFormat.FULL)) { + DateFormat formatter = DateFormat.getDateInstance(formatId); + assertEquals(time, + Converter.getDate(formatter.format(time), null)); + } + } + + /** + * Test {@link Converter#getDate(Object, Date)} with an invalid + * string and null as default value + */ + @Test + public void testGetDateObjectDateWithInvalidStringAndNullDefault() { + assertEquals(null, Converter.getDate("invalid date", null)); + } + +} diff --git a/test/src/org/apache/jorphan/util/TestJorphanUtils.java b/test/src/org/apache/jorphan/util/TestJorphanUtils.java new file mode 100644 index 00000000000..d2f2d498bc2 --- /dev/null +++ b/test/src/org/apache/jorphan/util/TestJorphanUtils.java @@ -0,0 +1,324 @@ +/* + * 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 to test JOrphanUtils methods + */ + +package org.apache.jorphan.util; + +import junit.framework.TestCase; + +public class TestJorphanUtils extends TestCase { + + public TestJorphanUtils() { + super(); + } + + public TestJorphanUtils(String arg0) { + super(arg0); + } + + public void testReplace1() { + assertEquals("xyzdef", JOrphanUtils.replaceFirst("abcdef", "abc", "xyz")); + } + + public void testReplace2() { + assertEquals("axyzdef", JOrphanUtils.replaceFirst("abcdef", "bc", "xyz")); + } + + public void testReplace3() { + assertEquals("abcxyz", JOrphanUtils.replaceFirst("abcdef", "def", "xyz")); + } + + public void testReplace4() { + assertEquals("abcdef", JOrphanUtils.replaceFirst("abcdef", "bce", "xyz")); + } + + public void testReplace5() { + assertEquals("abcdef", JOrphanUtils.replaceFirst("abcdef", "alt=\"\" ", "")); + } + + public void testReplace6() { + assertEquals("abcdef", JOrphanUtils.replaceFirst("abcdef", "alt=\"\" ", "")); + } + + public void testReplace7() { + assertEquals("alt=\"\"", JOrphanUtils.replaceFirst("alt=\"\"", "alt=\"\" ", "")); + } + + public void testReplace8() { + assertEquals("img src=xyz ", JOrphanUtils.replaceFirst("img src=xyz alt=\"\" ", "alt=\"\" ", "")); + } + + // Note: the split tests should agree as far as possible with CSVSaveService.csvSplitString() + + // Tests for split(String,String,boolean) + public void testSplit1() { + String in = "a,bc,,"; // Test ignore trailing split characters + String out[] = JOrphanUtils.split(in, ",",true);// Ignore adjacent delimiters + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + out = JOrphanUtils.split(in, ",",false); + assertEquals("Should detect the trailing split chars; ", 4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + } + + public void testSplit2() { + String in = ",,a,bc"; // Test leading split characters + String out[] = JOrphanUtils.split(in, ",",true); + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + out = JOrphanUtils.split(in, ",",false); + assertEquals("Should detect the leading split chars; ", 4, out.length); + assertEquals("", out[0]); + assertEquals("", out[1]); + assertEquals("a", out[2]); + assertEquals("bc", out[3]); + } + + public void testSplit3() { + String in = "a,bc,,"; // Test ignore trailing split characters + String out[] = JOrphanUtils.split(in, ",",true);// Ignore adjacent delimiters + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + out = JOrphanUtils.split(in, ",",false); + assertEquals("Should detect the trailing split chars; ", 4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + } + + public void testSplit4() { + String in = " , ,a ,bc"; // Test leading split characters + String out[] = JOrphanUtils.split(in, " ,",true); + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + out = JOrphanUtils.split(in, " ,",false); + assertEquals("Should detect the leading split chars; ", 4, out.length); + assertEquals("", out[0]); + assertEquals("", out[1]); + assertEquals("a", out[2]); + assertEquals("bc", out[3]); + } + + public void testTruncate() throws Exception + { + String in = "a;,b;,;,;,d;,e;,;,f"; + String[] out = JOrphanUtils.split(in,";,",true); + assertEquals(5, out.length); + assertEquals("a",out[0]); + assertEquals("b",out[1]); + assertEquals("d",out[2]); + assertEquals("e",out[3]); + assertEquals("f",out[4]); + out = JOrphanUtils.split(in,";,",false); + assertEquals(8, out.length); + assertEquals("a",out[0]); + assertEquals("b",out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + assertEquals("d",out[4]); + assertEquals("e",out[5]); + assertEquals("", out[6]); + assertEquals("f",out[7]); + + } + + public void testSplit5() throws Exception + { + String in = "a;;b;;;;;;d;;e;;;;f"; + String[] out = JOrphanUtils.split(in,";;",true); + assertEquals(5, out.length); + assertEquals("a",out[0]); + assertEquals("b",out[1]); + assertEquals("d",out[2]); + assertEquals("e",out[3]); + assertEquals("f",out[4]); + out = JOrphanUtils.split(in,";;",false); + assertEquals(8, out.length); + assertEquals("a",out[0]); + assertEquals("b",out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + assertEquals("d",out[4]); + assertEquals("e",out[5]); + assertEquals("", out[6]); + assertEquals("f",out[7]); + + } + + // Empty string + public void testEmpty(){ + String out[] = JOrphanUtils.split("", ",",false); + assertEquals(0,out.length); + } + + // Tests for split(String,String,String) + public void testSplitSSS1() { + String in = "a,bc,,"; // Test non-empty parameters + String out[] = JOrphanUtils.split(in, ",","?"); + assertEquals(4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals("?", out[2]); + assertEquals("?", out[3]); + } + + public void testSplitSSS2() { + String in = "a,bc,,"; // Empty default + String out[] = JOrphanUtils.split(in, ",",""); + assertEquals(4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + } + + public void testSplitSSS3() { + String in = "a,bc,,"; // Empty delimiter + String out[] = JOrphanUtils.split(in, "","?"); + assertEquals(1, out.length); + assertEquals(in, out[0]); + } + + public void testSplitSSS4() { + String in = "a,b;c,,"; // Multiple delimiters + String out[]; + out = JOrphanUtils.split(in, ",;","?"); + assertEquals(5, out.length); + assertEquals("a", out[0]); + assertEquals("b", out[1]); + assertEquals("c", out[2]); + assertEquals("?", out[3]); + assertEquals("?", out[4]); + out = JOrphanUtils.split(in, ",;",""); + assertEquals(5, out.length); + assertEquals("a", out[0]); + assertEquals("b", out[1]); + assertEquals("c", out[2]); + assertEquals("", out[3]); + assertEquals("", out[4]); + } + + public void testSplitSSS5() { + String in = "a,bc,,"; // Delimiter same as splitter + String out[] = JOrphanUtils.split(in, ",",","); + assertEquals(4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals(",", out[2]); + assertEquals(",", out[3]); + } + + public void testSplitSSSNulls() { + String in = "a,bc,,"; + String out[]; + try { + out = JOrphanUtils.split(null, ",","?"); + assertEquals(0, out.length); + fail("Expecting NullPointerException"); + } catch (NullPointerException ignored){ + //Ignored + } + try{ + out = JOrphanUtils.split(in, null,"?"); + assertEquals(0, out.length); + fail("Expecting NullPointerException"); + } catch (NullPointerException ignored){ + //Ignored + } + } + + public void testSplitSSSNull() { + String out[]; + out = JOrphanUtils.split("a,bc,,", ",",null); + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + + out = JOrphanUtils.split("a,;bc,;,", ",;",null); + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + } + + public void testSplitSSSNone() { + String out[]; + out = JOrphanUtils.split("", "," ,"x"); + assertEquals(0, out.length); + + out = JOrphanUtils.split("a,;bc,;,", "","x"); + assertEquals(1, out.length); + assertEquals("a,;bc,;,", out[0]); + } + + public void testreplaceAllChars(){ + assertEquals(JOrphanUtils.replaceAllChars("",' ', "+"),""); + String in,out; + in="source"; + assertEquals(JOrphanUtils.replaceAllChars(in,' ', "+"),in); + out="so+rce"; + assertEquals(JOrphanUtils.replaceAllChars(in,'u', "+"),out); + in="A B C "; out="A+B++C+"; + assertEquals(JOrphanUtils.replaceAllChars(in,' ', "+"),out); + } + + public void testTrim(){ + assertEquals("",JOrphanUtils.trim("", " ;")); + assertEquals("",JOrphanUtils.trim(" ", " ;")); + assertEquals("",JOrphanUtils.trim("; ", " ;")); + assertEquals("",JOrphanUtils.trim(";;", " ;")); + assertEquals("",JOrphanUtils.trim(" ", " ;")); + assertEquals("abc",JOrphanUtils.trim("abc ;", " ;")); + } + + public void testbaToHexString(){ + assertEquals("",JOrphanUtils.baToHexString(new byte[]{})); + assertEquals("00",JOrphanUtils.baToHexString(new byte[]{0})); + assertEquals("0f107f8081ff",JOrphanUtils.baToHexString(new byte[]{15,16,127,-128,-127,-1})); + } + + public void testbaToByte() throws Exception{ + assertEqualsArray(new byte[]{},JOrphanUtils.baToHexBytes(new byte[]{})); + assertEqualsArray(new byte[]{'0','0'},JOrphanUtils.baToHexBytes(new byte[]{0})); + assertEqualsArray("0f107f8081ff".getBytes("UTF-8"),JOrphanUtils.baToHexBytes(new byte[]{15,16,127,-128,-127,-1})); + } + + private void assertEqualsArray(byte[] expected, byte[] actual){ + assertEquals("arrays must be same length",expected.length, actual.length); + for(int i=0; i < expected.length; i++){ + assertEquals("values must be the same for index: "+i,expected[i],actual[i]); + } + } + + public void testIsBlank() { + assertTrue(JOrphanUtils.isBlank("")); + assertTrue(JOrphanUtils.isBlank(null)); + assertTrue(JOrphanUtils.isBlank(" ")); + assertFalse(JOrphanUtils.isBlank(" zdazd dzd ")); + } +} diff --git a/xdocs/building.xml b/xdocs/building.xml new file mode 100644 index 00000000000..e83808cd430 --- /dev/null +++ b/xdocs/building.xml @@ -0,0 +1,75 @@ + + + + + Building JMeter and Add-Ons + + +
+ +Note to developers: +This is a very brief overview. +There is more infomation on the JMeter Wiki or in eclipse.readme in root folder of sources. + +

Building Add-Ons

+

+There is no need to build JMeter if you just want to build an add-on. +Just download the binary archive and add the jars to the classpath or use Maven artifacts to build your add-ons. +You may want to also download the source so it can be used by the IDE. +

+

See the extras/addons* files in the source tree for some suggestions

+ +

Building JMeter

+

Acquiring the source

+

The full source is distributed alongside the binary, it can also be downloaded from SVN or found on Apache JMeter Github Mirror .

+

+The source archive and SVN do not contain any of the required library files. +These need to be downloaded by running the Ant command:

+ +ant download_jars + +

Or you can download the binary distribution archive for a release and unpack it into the same directory structure as the source. +This will ensure that the lib/ directory contains the jar files needed for running JMeter. +There are a few additional jars that are needed to build JMeter, download these using: +

+ +ant download_jars + +

+This will retrieve any missing jars. +

+

Compiling and packaging JMeter using Ant

+

+JMeter can be built entirely using Ant. +The basic command is:

+ +ant [install] + +See build.xml (or call ant -p) for the other targets that can be used. +

Compiling and packaging JMeter using Eclipse

+

+Once you have downloaded the source from SVN or the release archives and run the ant download_jars target to +install the dependent jars, you can configure Eclipse. The easiest way to do this is to replace the Eclipse .classpath +file with the eclipse.classpath file provided with JMeter. This will set up the source-paths and most of the libraries. +

+

+Ensure your read eclipse.readme for project configuration. +

+
+ +
diff --git a/xdocs/changes.xml b/xdocs/changes.xml new file mode 100644 index 00000000000..98b6ed21664 --- /dev/null +++ b/xdocs/changes.xml @@ -0,0 +1,239 @@ + + + + + JMeter developers + Changes + + +
+ + +This page details the changes made in the current version only. +

+Earlier changes are detailed in the History of Previous Changes. +
+ + + + +

Version 2.14

+ +Summary + + +New and Noteworthy + + + + + + +Incompatible changes + +
    +
+ + + +Improvements + +

HTTP Samplers and Test Script Recorder

+
    +
  • 57696HTTP Request : Improve responseMessage when resource download fails. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
+ +

Other samplers

+
    +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
+Non-functional changes +
    +
  • Updated to tika-core and tika-parsers 1.8 (from 1.7)
  • +
  • Updated to commons-math3 3.5 (from 3.4.1)
  • +
+ + + +Bug fixes + +

HTTP Samplers and Test Script Recorder

+
    +
  • 57806"audio/x-mpegurl" mime type is erroneously considered as binary by ViewResultsTree. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57858Don't call sampleEnd twice in HTTPHC4Impl when a RuntimeException or an IOException occurs in the sample method.
  • +
+ +

Other Samplers

+
    +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
+ +

Functions

+
    +
  • 57825__Random function fails if min value is equal to max value (regression related to 54453)
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 54826Don't fail on long strings in JSON responses when displaying them as JSON in View Results Tree.
  • +
  • 57734Maven transient dependencies are incorrect for 2.13
  • +
  • 57821Command-line option "-X --remoteexit" doesn't work since 2.13 (regression related to 57500)
  • +
  • 57731TESTSTART.MS has always the value of the first Test started in Server mode in NON GUI Distributed testing
  • +
+ + + +Thanks +

We thank all contributors mentioned in bug and improvement sections above: +

+ +
+We also thank bug reporters who helped us improve JMeter.
+For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes: +
    +
+ +Apologies if we have omitted anyone else. +

+ + +Known bugs + +
    +
  • The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).
  • + +
  • +The numbers that appear to the left of the green box are the number of active threads / total number of threads, +the total number of threads only applies to a locally run test, otherwise it will show 0 (see 55510). +
  • + +
  • +Note that there is a bug in Java +on some Linux systems that manifests itself as the following error when running the test cases or JMeter itself: +
    + [java] WARNING: Couldn't flush user prefs:
    + java.util.prefs.BackingStoreException:
    + java.lang.IllegalArgumentException: Not supported: indent-number
    +
    +This does not affect JMeter operation. This issue is fixed since Java 7b05. +
  • + +
  • +Note that under some windows systems you may have this WARNING: +
    +java.util.prefs.WindowsPreferences 
    +WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0
    +x80000002. Windows RegCreateKeyEx(...) returned error code 5.
    +
    +The fix is to run JMeter as Administrator, it will create the registry key for you, then you can restart JMeter as a normal user and you won't have the warning anymore. +
  • + +
  • +With Java 1.6 and Gnome 3 on Linux systems, the JMeter menu may not work correctly (shift between mouse's click and the menu). +This is a known Java bug (see 54477). +A workaround is to use a Java 7 runtime (OpenJDK or Oracle JDK). +
  • + +
  • +With Oracle Java 7 and Mac Book Pro Retina Display, the JMeter GUI may look blurry. +This is a known Java bug, see Bug JDK-8000629. +A workaround is to use a Java 7 update 40 runtime which fixes this issue. +
  • + +
  • +You may encounter the following error: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints + if you run a HTTPS request on a web site with a SSL certificate (itself or one of SSL certificates in its chain of trust) with a signature + algorithm using MD2 (like md2WithRSAEncryption) or with a SSL certificate with a size lower than 1024 bits. +This error is related to increased security in Java 7 version u16 (MD2) and version u40 (Certificate size lower than 1024 bits), and Java 8 too. +

    +To allow you to perform your HTTPS request, you can downgrade the security of your Java installation by editing +the Java jdk.certpath.disabledAlgorithms property. Remove the MD2 value or the constraint on size, depending on your case. +

    +This property is in this file: +
    JAVA_HOME/jre/lib/security/java.security
    +See 56357 for details. +
  • + +
+ +
+ +
diff --git a/xdocs/changes_history.xml b/xdocs/changes_history.xml new file mode 100644 index 00000000000..be02ddcc7c4 --- /dev/null +++ b/xdocs/changes_history.xml @@ -0,0 +1,5477 @@ + + + + + JMeter developers + History of Previous Changes + + +
+ +This page details the changes made in previous versions only. +

+Current changes are detailed in Changes. +
+

Changes sections are chronologically ordered from top (most recent) to bottom +(least recent)

+ + + +

Version 2.13

+ +Summary + + +New and Noteworthy + + + + +New Elements + +New Async BackendListener with Graphite implementation +

A new Async BackendListener has been added to allow sending result data to a backend listener. +JMeter ships with a GraphiteBackendListenerClient that allows sending results to a Graphite server using Pickle ot Plaintext protocols. +You can implement your own backend by extending AbstractBackendListenerClient. This backend could be +a database (JDBC), a Message Oriented Middleware (JMS), a Webservice or anything you want. +

+
+

This is the kind of Live Dashboard you can obtain using Grafana and InfluxDB
+Read this for more details.

+
Grafana dashboard
+ +Core Improvements + +New connect time metric +

Starting with this version a new metric called connectTime has been added. It represents the time to establish connection. +By default it is not saved to CSV or XML, to have it saved add to user.properties:
+ +jmeter.save.saveservice.connect_time=true + +

+
+
+ +Aggregate Graph and Report +

The listeners Aggregate Graph and Aggregate Report previously showed only the 90 percentile (historical behavior), the 95 percentile and the 99 percentile have been added and are customizable. +To setup the percentiles value you want, add to user.properties:
+ +aggregate_rpt_pct1=90
+aggregate_rpt_pct2=95
+aggregate_rpt_pct3=99 +
+

+

+ +HTTP(S) Test Script Recorder +

Now component is able to detect authentication schemes and automatically adds a pre-configured HTTP Authorization Manager with the correct Mechanism. +

+ +HTTP Request +

The CalDAV verbs (Calendar extensions to WebDAV) REPORT and MKCALENDAR have been added in the HTTP Request sampler. +

+

+ +JDBC Request +

The ResultSet can be get as a object, this allows to handle more easily the results after in BeanShell, JSR223 scripts... +

+

+ +Distributed Testing +

To allow better usage of Distributed Testing in the cloud, retry behaviour has been added when starting test on servers. +Read this for more details. +

+

+ +Distributed Testing performance +

Since JMeter 2.13, Stripping modes (StrippingBatch being the default mode) now also strip responses from SubResults improving consumed network bandwidth. +

+ +Documentation refresh +

A new style for website (responsive and more up to date) has been created by Felix Schumacher. +Documentations have been refreshed particularly: +

+

+ +GUI Improvements + +Module Controller +

The Module Controller now shows the target controller in a tree view (instead of combo list). +

+

+ +Toolbar +

JMeter's toolbar has been refreshed for some icons (start, toogle, etc.). Three sizes are now avialable for the icons: 22x22, 32x32 and 48x48.
+The property to define your prefered size is: +

jmeter.toolbar.icons.size=value
+with the value 22x22 (default size), 32x32 or 48x48.

+

The toolbar with 22x22 pixels icons +

+

+ +

The toolbar with 32x32 pixels icons +

+

+ +

The toolbar with 48x48 pixels icons +

+

+ +HTTP(S) Test Script Recorder +

If your Test Plan does not contains a Recording Controller, a new warning message will appear if the + HTTP(S) Test Script Recorder is configured to send the samples into a Recording Controller. +

+

+ + + +Incompatible changes + +
    +
  • Since 2.13, Aggregate Graph, Summary Report and Aggregate Report now export percentages to %, before they exported the decimal value which differed from what was shown in GUI
  • +
  • Third party plugins may be impacted by fix of 57586, ensure that your subclass of HttpTestSampleGui implements ItemListener if you relied on parent class doing so.
  • +
  • Report package has been removed, ApacheJMeter_report.jar is not generated anymore as a consequence, see 57269
  • +
+ + + +Improvements + +

HTTP Samplers and Test Script Recorder

+
    +
  • 25430HTTP(S) Test Script Recorder : Make it populate HTTP Authorization Manager. Partly based on a patch from Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • 57381HTTP(S) Test Script Recorder should display an error if Target Controller references a Recording Controller and no Recording Controller exists. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57488Performance : Improve SSLContext reset for Two-way SSL Authentication
  • +
  • 57565SamplerCreator : Add method to allow implementations to add children to created sampler
  • +
  • 57606HTTPSamplerBase#errorResult changes the sample label on exception
  • +
  • 57613HTTP Sampler : Added CalDAV verbs (REPORT, MKCALENDAR). Contributed by Richard Brigham (richard.brigham at teamaol.com)
  • +
  • 48799Add time to establish connection to available sample metrics. Implemented by Andrey Pokhilko (andrey at blazemeter.com) and contributed by BlazeMeter Ltd. and Pieter Ennes (apache.org at spam.ennes.nl)
  • +
  • 57500Introduce retry behavior for distributed testing. Implemented by Andrey Pokhilko and Dzimitry Kashlach and contributed by BlazeMeter Ltd.
  • +
+ +

Other samplers

+
    +
  • 57322JDBC Test elements: add ResultHandler to deal with ResultSets(cursors) returned by callable statements. Contributed by Yngvi &THORN;&oacute;r Sigurj&oacute;nsson (blitzkopf at gmail.com)
  • +
+ +

Controllers

+
    +
  • 57561Module controller UI : Replace combobox by tree. Contributed by Maciej Franek (maciej.franek at gmail.com)
  • +
  • 57648TestFragment should be disabled when created. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
+ +

Listeners

+
    +
  • 55932Create a Async BackendListener to allow easy plug of new listener (Graphite, JDBC, Console,...)
  • +
  • 57246BackendListener : Create a Graphite implementation
  • +
  • 57217Aggregate graph and Aggregate report improvements (3 configurable percentiles, same data in both, factor out code). Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57537BackendListener : Allow implementations to drop samples
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
+ +

Functions

+
    +
  • 54453Performance enhancements : Replace Random by ThreadLocalRandom in __Random function
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 57518Icons for toolbar with several sizes
  • +
  • 57605When there is an error loading Test Plan, SaveService.loadTree returns null leading to NPE in callers
  • +
  • 57269Drop org.apache.jmeter.reports package
  • +
  • 53764Website : Create a new style for website
  • +
+Non-functional changes +
    +
  • Updated to jsoup-1.8.1.jar (from 1.7.3)
  • +
  • Updated to tika-core and tika-parsers 1.7 (from 1.6)
  • +
  • Updated to commons-codec-1.10.jar (from 1.9)
  • +
  • Updated to dnsjava-2.1.7.jar (from 2.1.6)
  • +
  • Updated to jodd-3.6.4.jar (from 3.6.1)
  • +
  • Updated to junit-4.12.jar (from 4.11)
  • +
  • Updated to rhino-1.7R5 (from 1.7R4)
  • +
  • Updated to rsyntaxtextarea-2.5.6 (from 2.5.3)
  • +
  • Updated to slf4j-1.7.10 (from 1.7.5)
  • +
  • 57276RMIC no longer needed since Java 5
  • +
  • 57310Replace System.getProperty("file.separator") with File.separator throughout (Also "path.separator" with File.pathSeparator)
  • +
  • 57389Fix potential NPE in converters
  • +
  • 57417Remove unused method isTemporary from NullProperty. This was a leftover from a refactoring done in 2003.
  • +
  • 57418Remove unused constructor from Workbench
  • +
  • 57419Remove unused interface ModelListener.
  • +
  • 57466IncludeController : Remove an unneeded set creation. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • Added property loggerpanel.usejsyntaxtext to disable the use of JSyntaxTextArea for the Console Logger (in case of memory or other issues)
  • +
  • 57586HttpTestSampleGui: Remove interface ItemListener implementation
  • +
+ + + +Bug fixes + +

HTTP Samplers and Test Script Recorder

+
    +
  • 57385Getting empty thread name in xml result for HTTP requests with "Follow Redirects" set. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57579NullPointerException error is raised on main sample if "RETURN_NO_SAMPLE" is used (default) and "Use Cache-Control / Expires header..." is checked in HTTP Cache Manager
  • +
+ +

Other Samplers

+
    +
+ +

Controllers

+
    +
  • 57447Use only the user listed DNS Servers, when "use custom DNS resolver" option is enabled.
  • +
+ +

Listeners

+
    +
  • 57262Aggregate Report, Aggregate Graph and Summary Report export : headers use keys instead of labels
  • +
  • 57346Summariser : The + (difference) reports show wrong elapsed time and throughput
  • +
  • 57449Distributed Testing: Stripped modes do not strip responses from SubResults (affects load tests that use Download of embedded resources). Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57562View Results Tree CSS/JQuery Tester : Nothing happens when there is an error in syntax and an exception occurs in jmeter.log
  • +
  • 57514Aggregate Graph, Summary Report and Aggregate Report show wrong percentage reporting in saved file
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 57607Constant Throughput Timer : Wrong throughput computed in shared modes due to rounding error
  • +
+ +

General

+
    +
  • 57365Selected LAF is not correctly setup due to call of UIManager.setLookAndFeel too late. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57364Options < Look And Feel does not update all windows LAF. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57394When constructing an instance with ClassTools#construct(String, int) the integer was ignored and the default constructor was used instead.
  • +
  • 57440OutOfMemoryError after introduction of JSyntaxTextArea in LoggerPanel due to disableUndo not being taken into account.
  • +
  • 57569FileServer.reserveFile - inconsistent behaviour when hasHeader is true
  • +
  • 57555Cannot use JMeter 2.12 as a maven dependency. Contributed by Pascal Schumacher (pascal.schumacher at t-systems.com)
  • +
  • 57608Fix start script compatibility with old Unix shells, e.g. on Solaris
  • +
+ + + +Thanks +

We thank all contributors mentioned in bug and improvement sections above: +

    +
  • Ubik Load Pack
  • +
  • Yngvi &THORN;&oacute;r Sigurj&oacute;nsson (blitzkopf at gmail.com)
  • +
  • Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • BlazeMeter Ltd.
  • +
  • Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • Pascal Schumacher (pascal.schumacher at t-systems.com)
  • +
  • Maciej Franek (maciej.franek at gmail.com)
  • +
  • Richard Brigham (richard.brigham at teamaol.com)
  • +
  • Pieter Ennes (apache.org at spam.ennes.nl)
  • +
+ +
+We also thank bug reporters who helped us improve JMeter.
+For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes: +
    +
  • Chaitanya Bhatt (bhatt.chaitanya at gmail.com) for his thorough testing of new BackendListener and Graphite Client implementation.
  • +
  • Marcelo Jara (marcelojara at hotmail.com) for his clear report on 57607.
  • +
+ +Apologies if we have omitted anyone else. +

+ + +Known bugs + +
    +
  • The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).
  • + +
  • +The numbers that appear to the left of the green box are the number of active threads / total number of threads, +the total number of threads only applies to a locally run test, otherwise it will show 0 (see 55510). +
  • + +
  • +Note that there is a bug in Java +on some Linux systems that manifests itself as the following error when running the test cases or JMeter itself: +
    + [java] WARNING: Couldn't flush user prefs:
    + java.util.prefs.BackingStoreException:
    + java.lang.IllegalArgumentException: Not supported: indent-number
    +
    +This does not affect JMeter operation. This issue is fixed since Java 7b05. +
  • + +
  • +Note that under some windows systems you may have this WARNING: +
    +java.util.prefs.WindowsPreferences 
    +WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0
    +x80000002. Windows RegCreateKeyEx(...) returned error code 5.
    +
    +The fix is to run JMeter as Administrator, it will create the registry key for you, then you can restart JMeter as a normal user and you won't have the warning anymore. +
  • + +
  • +With Java 1.6 and Gnome 3 on Linux systems, the JMeter menu may not work correctly (shift between mouse's click and the menu). +This is a known Java bug (see 54477). +A workaround is to use a Java 7 runtime (OpenJDK or Oracle JDK). +
  • + +
  • +With Oracle Java 7 and Mac Book Pro Retina Display, the JMeter GUI may look blurry. +This is a known Java bug, see Bug JDK-8000629. +A workaround is to use a Java 7 update 40 runtime which fixes this issue. +
  • + +
  • +You may encounter the following error: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints + if you run a HTTPS request on a web site with a SSL certificate (itself or one of SSL certificates in its chain of trust) with a signature + algorithm using MD2 (like md2WithRSAEncryption) or with a SSL certificate with a size lower than 1024 bits. +This error is related to increased security in Java 7 version u16 (MD2) and version u40 (Certificate size lower than 1024 bits), and Java 8 too. +

    +To allow you to perform your HTTPS request, you can downgrade the security of your Java installation by editing +the Java jdk.certpath.disabledAlgorithms property. Remove the MD2 value or the constraint on size, depending on your case. +

    +This property is in this file: +
    JAVA_HOME/jre/lib/security/java.security
    +See 56357 for details. +
  • + +
+ + + +

Version 2.12

+ +Summary + + +New and Noteworthy + + + + +Java 8 support +

Now, JMeter 2.12 is compliant with Java 8.

+ +New Elements +Critical Section Controller +

The Critical Section Controller allow to serialize the execution of a section in your tree. +Only one instance of the section will be executed at the same time during the test.

+
+ +DNS Cache Manager +

The new configuration element DNS Cache Manager(see 56841) improves the testing of: +

    +
  • CDN (Content Delivery Network)
  • +
  • DNS load balancing.
  • +
  • Load Balancers like Amazon Elastic Load Balancer
  • +
+

+
+Core Improvements + +Smarter Recording of Http Test Plans +

Test Script Recorder has been improved in many ways

+
    +
  • Better matching of Variables in Requests, making Test Script Recorder variabilize your sampler during recording more versatile
  • +
  • Ability to filter from View Results Tree the Samples that are excluded from recording, this lets you concentrate on recorded Samplers analysis and not bother with useless Sample Results +
    +
  • +
  • Better defaults for recording, since this version Recorder will number created Samplers letting you find them much easily in View Results Tree. + Grouping of Samplers under Transaction Controller will be smarter making all requests emitted by a web page be children as new Transaction Controller
  • +
+ +Support of Webdav requests +

You can now test against WebDav server using HttpClient4 Implementation of Http Request

+
+ +Better handling of embedded resources +

When download embedded resources is checked, JMeter now uses User Agent header to download or not resources embedded within conditionnal comments as per About conditional comments.

+ +Ability to customize Cache Manager (Browser cache simulation) handling of cached resources +

You can now configure the behaviour of JMeter when a resource is found in Cache, this can be controlled with cache_manager.cached_resource_mode property

+
+ + +JMS Publisher / JMS Point-to-Point +

Add JMSPriority and JMSExpiration fields for these samplers.

+
+ +
+ +Mail Reader Sampler +

You can now specify the number of messages that want you retrieve (before all messages were retrieved). +In addition, you can fetch only the message header now.

+
+ +SMTP Sampler +

Adding the Connection timeout and the Read timeout to the SMTP Sampler.

+
+ +Synchronizing Timer +

Adding a timeout to define the maximum time to waiting of the group of virtual users.

+
+ +Performance improvements +

A big improvement in performances of Functions has been made by lifting useless synchronization. It concerns all functions except __StringFromFile, __XPath and __BeanShell, see 57114

+

__jexl2 performances have been improved to avoid contention point, see 56708

+ +GUI Improvements + +Undo/Redo support +

Undo / Redo has been introduced and allows user to undo/redo changes made on Test Plan Tree. This feature (ALPHA MODE) is disabled by default, to enable it set property undo.history.size=25

+
+ +View Results Tree +

Improve the ergonomics of View Results Tree by changing placement of Renderers and allowing custom ordering +(with the property view.results.tree.renderers_order).

+
+ +Response Time Graph +

Adding the ability for the Response Time Graph listener to save/restore format its settings in/from the jmx file.

+
+ +Log Viewer +

Starting with this version, the last lines of JMeter's log file (jmeter.log) can be viewed directly in GUI by clicking on Warning icon in the upper right corner. +This will unfold the Log Viewer panel and show logs.

+
+ +File Opening +

Now, "Open File dialog" uses last opened file folder as start folder, see 52707

+ + + +Known bugs + +
    +
  • The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).
  • + +
  • +The numbers that appear to the left of the green box are the number of active threads / total number of threads, +the total number of threads only applies to a locally run test, otherwise it will show 0 (see 55510). +
  • + +
  • +Note that there is a bug in Java +on some Linux systems that manifests itself as the following error when running the test cases or JMeter itself: +
    + [java] WARNING: Couldn't flush user prefs:
    + java.util.prefs.BackingStoreException:
    + java.lang.IllegalArgumentException: Not supported: indent-number
    +
    +This does not affect JMeter operation. This issue is fixed since Java 7b05. +
  • + +
  • +Note that under some windows systems you may have this WARNING: +
    +java.util.prefs.WindowsPreferences 
    +WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0
    +x80000002. Windows RegCreateKeyEx(...) returned error code 5.
    +
    +The fix is to run JMeter as Administrator, it will create the registry key for you, then you can restart JMeter as a normal user and you won't have the warning anymore. +
  • + +
  • +With Java 1.6 and Gnome 3 on Linux systems, the JMeter menu may not work correctly (shift between mouse's click and the menu). +This is a known Java bug (see 54477 ). +A workaround is to use a Java 7 runtime (OpenJDK or Oracle JDK). +
  • + +
  • +With Oracle Java 7 and Mac Book Pro Retina Display, the JMeter GUI may look blurry. +This is a known Java bug, see Bug JDK-8000629. +A workaround is to use a Java 7 update 40 runtime which fixes this issue. +
  • + +
  • +You may encounter the following error: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints + if you run a HTTPS request on a web site with a SSL certificate (itself or one of SSL certificates in its chain of trust) with a signature + algorithm using MD2 (like md2WithRSAEncryption) or with a SSL certificate with a size lower than 1024 bits. +This error is related to increased security in Java 7 version u16 (MD2) and version u40 (Certificate size lower than 1024 bits), and Java 8 too. +

    +To allow you to perform your HTTPS request, you can downgrade the security of your Java installation by editing +the Java jdk.certpath.disabledAlgorithms property. Remove the MD2 value or the constraint on size, depending on your case. +

    +This property is in this file: +
    JAVA_HOME/jre/lib/security/java.security
    +See 56357 for details. +
  • + +
+ + + +Incompatible changes + +
    +
  • Since JMeter 2.12, active threads in all thread groups and active threads in current thread group are saved by default to CSV or XML results, see 57025. +This is usually the expected behaviour as you want to have the number of running threads during the test. But if you want to revert to previous behaviour, set property jmeter.save.saveservice.thread_counts=false
  • +
  • Since JMeter 2.12, Mail Reader Sampler will show 1 for number of samples instead of number of messages retrieved, see 56539
  • +
  • Since JMeter 2.12, when using Cache Manager, if resource is found in cache no SampleResult will be created, in previous version a SampleResult with empty content and 204 return code was returned, see 54778. +You can choose between different ways to handle this case, see cache_manager.cached_resource_mode in jmeter.properties.
  • +
  • Since JMeter 2.12, Log Viewer will no more clear logs when closed and will have logs available even if closed. See 56920. Read Hints and Tips > Enabling Debug logging +for details on configuring this component.
  • +
+ + + +Bug fixes + +

HTTP Samplers and Test Script Recorder

+
    +
  • 55998 - HTTP recording – Replacing port value by user defined variable does not work
  • +
  • 56178 - keytool error: Invalid escaped character in AVA: - some characters must be escaped
  • +
  • 56222 - NPE if jmeter.httpclient.strict_rfc2616=true and location is not absolute
  • +
  • 56263 - DefaultSamplerCreator should set BrowserCompatible Multipart true
  • +
  • 56231 - Move redirect location processing from HC3/HC4 samplers to HTTPSamplerBase#followRedirects()
  • +
  • 56207 - URLs get encoded on redirects in HC3.1 & HC4 samplers
  • +
  • 56303 - The width of target controller's combo list should be set to the current panel size, not on label size of the controllers
  • +
  • 54778 - HTTP Sampler should not return 204 when resource is found in Cache, make it configurable with new property cache_manager.cached_resource_mode
  • +
+ +

Other Samplers

+
    +
  • 55977 - JDBC pool keepalive flooding
  • +
  • 55999 - Scroll bar on jms point-to-point sampler does not work when content exceeds display
  • +
  • 56198 - JMSSampler : NullPointerException is thrown when JNDI underlying implementation of JMS provider does not comply with Context.getEnvironment contract
  • +
  • 56428 - MailReaderSampler - should it use mail.pop3s.* properties?
  • +
  • 46932 - Alias given in select statement is not used as column header in response data for a JDBC request.Based on report and analysis of Nicola Ambrosetti
  • +
  • 56539 - Mail reader sampler: When Number of messages to retrieve is superior to 1, Number of samples should only show 1 not the number of messages retrieved
  • +
  • 56809 - JMSSampler closes InitialContext too early. Contributed by Bradford Hovinen (hovinen at gmail.com)
  • +
  • 56761 - JMeter tries to stop already stopped JMS connection and displays "The connection is closed"
  • +
  • 57068 - No error thrown when negative duration is entered in Test Action
  • +
  • 57078 - LagartoBasedHTMLParser fails to parse page that contains input with no type
  • +
  • 57183 - JMSSampler: For input string: "" java.lang.NumberFormatException (for Expiration or Priority fields)
  • +
+ +

Controllers

+
    +
  • 56243 - Foreach works incorrectly with indexes on subsequent iterations
  • +
  • 56276 - Loop controller becomes broken once loop count evaluates to zero
  • +
  • 56160 - StackOverflowError when using WhileController within IfController
  • +
  • 56811 - "Start Next Thread Loop" in Result Status Action Handler or on Thread Group and "Go to next Loop iteration" in Test Action behave incorrectly with TransactionController that has "Generate Parent Sampler" checked
  • +
+ +

Listeners

+
    +
  • 56706 - SampleResult#getResponseDataAsString() does not use encoding in response body impacting PostProcessors and ViewResultsTree. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57052 - ArithmeticException: / by zero when sampleCount is equal to 0
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 56162 - HTTP Cache Manager should not cache PUT/POST etc.
  • +
  • 56227 - AssertionGUI : NPE in assertion on mouse selection
  • +
  • 41319 - URLRewritingModifier : Allow Parameter value to be url encoded
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
  • 56111 - "comments" in german translation is not correct
  • +
+ +

General

+
    +
  • 56059 - Older TestBeans incompatible with 2.11 when using TextAreaEditor
  • +
  • 56080 - Conversion error com.thoughtworks.xstream.converters.ConversionException with Java 8 Early Access Build
  • +
  • 56182 - Can't trigger bsh script using bshclient.jar; socket is closed unexpectedly
  • +
  • 56360 - HashTree and ListedHashTree fail to compile with Java 8
  • +
  • 56419 - JMeter silently fails to save results
  • +
  • 56662 - Save as xml in a listener is not remembered
  • +
  • 56367 - JMeter 2.11 on maven central triggers a not existing dependency rsyntaxtextarea 2.5.1, upgrade to 2.5.3
  • +
  • 56743 - Wrong mailing list archives on mail2.xml. Contributed by Felix Schumacher (felix.schumacher at internetallee.de)
  • +
  • 56763 - Removing the Oracle icons, not used by JMeter (and missing license)
  • +
  • 54100 - Switching languages fails to preserve toolbar button states (enabled/disabled)
  • +
  • 54648 - JMeter GUI on OS X crashes when using CMD+C (keyboard shortcut or UI menu entry) on an element from the tree
  • +
  • 56962 - JMS GUIs should disable all fields affected by jndi.properties checkbox
  • +
  • 57061 - Save as Test Fragment fails to clone deeply selected node. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57075 - BeanInfoSupport.MULTILINE attribute is not processed
  • +
  • 57076 - BooleanPropertyEditor#getAsText() must return a value that is in getTags()
  • +
  • 57088 - NPE in ResultCollector.testEnded
  • +
+ + + +Improvements + +

HTTP Samplers and Test Script Recorder

+
    +
  • 55959 - Improve error message when Test Script Recorder fails due to I/O problem
  • +
  • 52013 - Test Script Recorder's Child View Results Tree does not take into account Test Script Recorder excluded/included URLs. Based on report and analysis of James Liang
  • +
  • 56119 - File uploads fail every other attempt using timers. Enable idle timeouts for servers that don't send Keep-Alive headers.
  • +
  • 56272 - MirrorServer should support query parameters for status and redirects
  • +
  • 56772 - Handle IE Conditional comments when parsing embedded resources
  • +
  • 57026 - HTTP(S) Test Script Recorder : Better default settings. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57107 - Patch proposal: Add DAV verbs to HTTP Sampler. Contributed by Philippe Jung (apache at famille-jung.fr)
  • +
  • 56357 - Certificates does not conform to algorithm constraints: Adding a note to indicate how to remove of the Java installation these new security constraints
  • +
+ +

Other samplers

+
    +
  • 56033 - Add Connection timeout and Read timeout to SMTP Sampler
  • +
  • 56429 - MailReaderSampler - no need to fetch all Messages if not all wanted
  • +
  • 56427 - MailReaderSampler enhancement: read message header only
  • +
  • 56510 - JMS Publisher/Point to Point: Add JMSPriority and JMSExpiration
  • +
+ +

Controllers

+
    +
  • 56728 - New Critical Section Controller to serialize blocks of a Test. Based partly on a patch contributed by Mikhail Epikhin(epihin-m at yandex.ru)
  • +
  • 57145 - RandomController : Use ThreadLocalRandom instead of Random for better performances
  • +
+ +

Listeners

+
    +
  • 56228 - View Results Tree : Improve ergonomy by changing placement of Renderers and allowing custom ordering
  • +
  • 56349 - "summary" is a bad name for a Generate Summary Results component, documentation clarified
  • +
  • 56769 - Adds the ability for the Response Time Graph listener to save/restore format settings in/from the jmx file
  • +
  • 57025 - SaveService : Better defaults, save thread counts by default
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 56691 - Synchronizing Timer : Add timeout on waiting
  • +
  • 56701 - HTTP Authorization Manager/ Kerberos Authentication: add port to SPN when server port is neither 80 nor 443. Based on patches from Dan Haughey (dan.haughey at swinton.co.uk) and Felix Schumacher (felix.schumacher at internetallee.de)
  • +
  • 56841 - New configuration element: DNS Cache Manager to improve the testing of CDN. Based on patch from Dzmitry Kashlach (dzmitrykashlach at gmail.com), and contributed by BlazeMeter Ltd.
  • +
  • 52061 - Allow access to Request Headers in Regex Extractor. Based on patch from Dzmitry Kashlach (dzmitrykashlach at gmail.com), and contributed by BlazeMeter Ltd.
  • +
+ +

Functions

+
    +
  • 56708 - __jexl2 doesn't scale with multiple CPU cores. Based on analysis and patch contributed by Mikhail Epikhin(epihin-m at yandex.ru)
  • +
  • 57114 - Performance : Functions that only have values as instance variable should not synchronize execute. Based on analysis by Ubik Load Pack support and Vladimir Sitnikov, patch contributed by Vladimir Sitnikov (sitnikov.vladimir at gmail.com)
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 21695 - Unix jmeter start script assumes it is on PATH, not a link
  • +
  • 56292 - Add the check of the Java's version in startup files and disable some options when is Java v8 engine
  • +
  • 56298 - JSR223 language display does not show which engine will be used
  • +
  • 56455 - Batch files: drop support for non-NT Windows shell scripts
  • +
  • 52707 - Make Open File dialog use last opened file folder as start folder. Based on patch from Dzmitry Kashlach (dzmitrykashlach at gmail.com), and contributed by BlazeMeter Ltd.
  • +
  • 56807 - Ability to force flush of ResultCollector file. Contributed by Andrey Pohilko (apc4 at ya.ru)
  • +
  • 56921 - Templates : Improve Recording template to ignore embedded resources case and URL parameters. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 42248 - Undo-redo support on Test Plan tree modification. Developed by Andrey Pohilko (apc4 at ya.ru) and contributed by BlazeMeter Ltd. Additional contribution by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 56920 - LogViewer : Make it receive all log events even when it is closed. Contributed by Ubik Load Pack (support at ubikloadpack.com)
  • +
  • 57083 - simplified the CachedResourceMode enum. Contributed by Graham Russel (graham at ham1.co.uk)
  • +
  • 57082 - ComboStringEditor : Added hashCode to an inner class which overwrote equals. Contributed by Graham Russel (graham at ham1.co.uk)
  • +
  • 57081 - Updating checkstyle to only check for tabs in java, xml, xsd, dtd, htm, html and txt files (not images!). Contributed by Graham Russell (graham at ham1.co.uk)
  • +
  • 56178 - Really replace backslashes in user name before generating proxy certificate. Contributed by Graham Russel (graham at ham1.co.uk)
  • +
  • 57084 - Close socket after usage in BeanShellClient. Contributed by Graham Russel (graham at ham1.co.uk)
  • +
+Non-functional changes +
    +
  • 57117 - Increase the default cipher for HTTPS Test Script Recorder from SSLv3 to TLS
  • +
  • Updated to commons-lang3 3.3.2 (from 3.1)
  • +
  • Updated to commons-codec 1.9 (from 1.8)
  • +
  • Updated to commons-logging 1.2 (from 1.1.3)
  • +
  • Updated to tika 1.6 (from 1.4)
  • +
  • Updated to xercesImpl 2.11.0 (from 2.9.1)
  • +
  • Updated to xml-apis 1.4.01 (from 1.3.04)
  • +
  • Updated to xstream 1.4.8 (from 1.4.4)
  • +
  • Updated to jodd 3.6.1 (from 3.4.10)
  • +
  • Updated to rsyntaxtextarea 2.5.3 (from 2.5.1)
  • +
  • Updated xalan and serializer to 2.7.2 (from 2.7.1)
  • +
  • Updated to jsoup-1.8.1.jar (from 1.7.3)
  • +
+ +Thanks +

We thank all contributors mentioned in bug and improvement sections above: +

    +
  • James Liang (jliang at andera.com)
  • +
  • Emmanuel Bourg (ebourg at apache.org)
  • +
  • Nicola Ambrosetti (ambrosetti.nicola at gmail.com)
  • +
  • Ubik Load Pack
  • +
  • Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • Dan Haughey (dan.haughey at swinton.co.uk)
  • +
  • Felix Schumacher (felix.schumacher at internetallee.de)
  • +
  • Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • Andrey Pohilko (apc4 at ya.ru)
  • +
  • Bradford Hovinen (hovinen at gmail.com)
  • +
  • BlazeMeter Ltd.
  • +
  • Graham Russell (graham at ham1.co.uk)
  • +
  • Philippe Jung (apache at famille-jung.fr)
  • +
  • Vladimir Sitnikov (sitnikov.vladimir at gmail.com)
  • +
+ +
+We also thank bug reporters who helped us improve JMeter.
+For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes: +
    +
  • Oliver LLoyd (email at oliverlloyd.com) for his help on 56119
  • +
  • Vladimir Ryabtsev (greatvovan at gmail.com) for his help on 56243 and 56276
  • +
  • Adrian Speteanu (asp.adieu at gmail.com) and Matt Kilbride (matt.kilbride at gmail.com) for their feedback and tests on 54648
  • +
  • Shmuel Krakower (shmulikk at gmail.com) for his tests and reports on Undo/Redo feature
  • +
+ +Apologies if we have omitted anyone else. +

+ + + +

Version 2.11

+ +Summary + + +New and Noteworthy + + +HTTP(S) Test Script Recorder improvements +

+Following improvements have been made since major changes introduced in JMeter 2.10 on HTTP(S) Test Script Recorder: +

    +
  • Better detection of missing or invalid configuration of keytool utility
  • +
  • New system property keytool.directory (see system.properties) lets you configure directory containing keytool in case on non-standard installation
  • +
+

+ +JMS Publisher/Point to Point : Add ability to set typed values in JMS header properties +

In the samplers JMS Publisher and JMS Point-to-Point, you can now set up the class of values for the JMS header properties. Previously only String was possible.

+

+

+

+ +View Results Tree : Add an XPath Tester +

In View Results Tree listener, a new XPath tester can be used to test XPATH expressions.

+

+

+

+ +Ability to choose the client alias for the cert key in JsseSslManager such that Mutual SSL auth testing can be made more flexible +

When testing client based certificate authentications you have now better control on certificate you use through a new field "Variable name holding certificate alias", this +field lets you select the certificate you want to send to server to authenticate. You can use a CSV Data Set as a holder for the variable value.

+

+

+

+ +Add a "Save as Test Fragment" option +

In the file menu, a new option allow to save a group of elements as a Test fragment.

+

+

+

+ +Summariser is be enabled by default in Non GUI mode +

When you run JMeter from command line, now JMeter displays some statistics from the Summariser mode.

+

+

+

+ +Transaction Controller:Change default property "Include duration of timer..." for newly created element +

Starting from 2.11, Transaction Controller is configured by default to exclude processing time of pre/post processors as long as timers pause.

+

+

+

+ + + + + + + +Known bugs + +
    +
  • The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).
  • + +
  • Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +
  • + +
  • +The numbers that appear to the left of the green box are the number of active threads / total number of threads, +the total number of threads only applies to a locally run test, otherwise it will show 0 (see 55510). +
  • + +
  • +Note that there is a bug in Java +on some Linux systems that manifests itself as the following error when running the test cases or JMeter itself: +
    + [java] WARNING: Couldn't flush user prefs:
    + java.util.prefs.BackingStoreException:
    + java.lang.IllegalArgumentException: Not supported: indent-number
    +
    +This does not affect JMeter operation. This issue is fixed since Java 7b05. +
  • + +
  • +With Java 1.6 and Gnome 3 on Linux systems, the JMeter menu may not work correctly (shift between mouse's click and the menu). +This is a known Java bug (see 54477 ). +A workaround is to use a Java 7 runtime (OpenJDK or Oracle JDK). +
  • + +
  • +With Oracle Java 7 and Mac Book Pro Retina Display, the JMeter GUI may look blurry. +This is a known Java bug, see Bug JDK-8000629. +A workaround is to use a Java 7 update 40 runtime which fixes this issue. +
  • +
+ + + +Incompatible changes + +
    +
  • When creating a new Transaction Controller, property "Include duration of timer and pre-post processors in generated sample" will be unchecked starting from version 2.11
  • +
  • In Non GUI mode, since 2.11 summariser is enabled with a 30 seconds frequency
  • +
  • JMeter is more lenient with redirect handling and relaxes on RFC2616 by allowing relative locations. See property "jmeter.httpclient.strict_rfc2616" in jmeter.properties to change this behaviour, see 55717
  • +
  • When creating a new Response Assertion, property "Pattern Matching Rules" now defaults to Substring starting from version 2.11
  • +
+ + + +Bug fixes + +

HTTP Samplers and Test Script Recorder

+
    +
  • 55815 - Proxy#getDomainMatch does not handle wildcards correctly
  • +
  • 55717 - Bad handling of Redirect when URLs are in relative format by HttpClient4 and HttpClient3.1
  • +
+ +

Other Samplers

+
    +
  • 55685 - OS Sampler: timeout option don't save and restore correctly value and don't init correctly timeout
  • +
+ +

Controllers

+
    +
  • 55816 - Transaction Controller with "Include duration of timer..." unchecked does not ignore processing time of last child sampler
  • +
+ +

Listeners

+
    +
  • 55826 - Unsynchronised concurrent accesses to list in field RespTimeGraphVisualizer.internalList
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 55694 - Assertions and Extractors : Avoid NullPointerException when scope is variable and variable is missing
  • +
  • 55721 - HTTP Cache Manager - no-store directive is wrongly interpreted
  • +
+ +

Functions

+
    +
  • 55871 - Wrong result with intSum() function when a space character is present before/after the number. Contributed by Milamber based on a proposal by James Liang.
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 55739 - Remote Test : Total threads in GUI mode shows invalid total number of threads
  • +
+ + + +Improvements + +

HTTP Samplers and Proxy

+
    +
+ +

Other samplers

+
    +
  • 55589 - JMS Publisher/Point to Point : Add ability to set typed values in JMS header properties.
  • +
+ +

Controllers

+
    +
  • 55854 - Transaction Controller:Change default property "Include duration of timer..." for newly created element
  • +
+ +

Listeners

+
    +
  • 55610 - View Results Tree : Add an XPath Tester
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 55908 - Response assertion : Change Pattern Matching Rules default to Substring on creation for better performances
  • +
  • 54977 - Ability to choose the client alias for the cert key in JsseSslManager such that Mutual SSL auth testing can be made more flexible. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • 55693 - Add a "Save as Test Fragment" option
  • +
  • 55753 - Improve FilePanel behaviour to start from the value set in Filename field if any. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
  • 55756 - HTTP Mirror Server : Add ability to set Headers
  • +
  • 55852 - Be more lenient in parsing when charset value is surrounded with single quotes
  • +
  • 55857 - Performance : AbstractProperty should test for emptiness to avoid Exception throwing
  • +
  • 55858 - Startup Performance : On Startup, BeanInfoSupport should test for key availability instead of throwing
  • +
  • 55865 - Performance :Disable stale check by default in HttpClient 4 and 3.1
  • +
  • 55512 - Summariser should be enabled by default in Non GUI mode
  • +
+ +Non-functional changes +
    +
  • Updated to rsyntaxtextarea-2.5.1.jar (from 2.5.0)
  • +
  • Updated to jodd-core-3.4.9.jar from (3.4.8) and jodd-lagarto-3.4.9.jar (from 3.4.9)
  • +
  • Updated to jsoup-1.7.3.jar (from 1.7.2)
  • +
  • Updated to mail-1.5.0-b01 (from 1.4.4)
  • +
  • Updated to mongo-java-driver-2.11.3 (from 2.11.2)
  • +
+ +Thanks +

We thank all contributors mentioned in bug and improvement sections above: +

    +
  • James Liang (jliang at andera.com)
  • +
  • UBIK Load Pack (support at ubikloadpack.com)
  • +
+
+We also thank bug reporters who helped us improve JMeter.
+For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes: +
    +
  • John Natsioulas (john_natsioulas at yahoo.com.au)
  • +
  • Antonio Gomes Rodrigues (ra0077 at gmail.com)
  • +
+ +Apologies if we have omitted anyone else. +

+ + + + +

Version 2.10

+ +Summary + + +New and Noteworthy + +Core Improvements + +New Performance improvements +

+

    +
  • A Huge performance improvement has been made on High Throughput Tests (no pause), see 54777
  • +
  • An issue with unnecessary SSL Context reset has been fixed which improves performances of pure HTTP tests, see 55023
  • +
  • Important performance improvement in parsing of Embedded resource in HTML pages thanks to a switch to JODD/Lagarto HTML Parser, see 55632
  • +
+

+ +New CSS/JQuery Tester in View Tree Results +

A new CSS/JQuery Tester in View Tree Results that makes CSS/JQuery Extractor a first class +citizen in JMeter, you can now test your expressions very easily

+

+

+

+ +Many improvements in HTTP(S) Recording have been made +

+

+ +The "HTTP Proxy Server" test element has been renamed as "HTTP(S) Test Script Recorder". + +
    +
  • Better recording of HTTPS sites, embedded resources using subdomains will more easily be recorded when using JDK 7. See 55507. +See updated documentation: +
  • +
  • Redirection are now more smartly detected by HTTP Proxy Server, see 55531
  • +
  • Many fixes on edge cases with HTTPS have been made, see 55502, 55504, 55506
  • +
  • Many encoding fixes have been made, see 54482, 54142, 54293
  • +
+

+ +You can now load test MongoDB through new MongoDB Source Config +

+

+

+

+

+

+ +Kerberos authentication has been added to Auth Manager +

+

+

+ +Device can now be used in addition to source IP address + +

+

+

+ +You can now do functional testing of MongoDB scripts through new MongoDB Script +

+

+

+ +Timeout has been added to OS Process Sampler +

+

+

+ +Query timeout has been added to JDBC Request +

+

+

+ +New functions (__urlencode and __urldecode) are now available to encode/decode URL encoded chars +

+

+

+ +Continuous Integration is now eased by addition of a new flag that forces NON-GUI JVM to exit after test end +

See jmeter property:

+jmeterengine.force.system.exit +

+ +HttpSampler now allows DELETE Http Method to have a body (works for HC4 and HC31 implementations). This allows for example to test Elastic Search APIs +

+

+

+ +2 implementations of HtmlParser have been added to improve Embedded resources parsing +

+You can choose the implementation to use for parsing Embedded resources in HTML pages: +See jmeter.properties and look at property "htmlParser.className". +

    +
  • org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser for optimal performances
  • +
  • org.apache.jmeter.protocol.http.parser.JSoupBasedHtmlParser for most accurate parsing and functional testing
  • +
+

+ +Distributed testing has been improved +

+

    +
  • +Number of threads on each node are now reported to controller. +

    +

    +

    +

    +

    +

    + +
  • +
  • Performance improvement on BatchSampleSender(55423)
  • +
  • Addition of 2 SampleSender modes (StrippedAsynch and StrippedDiskStore), see jmeter.properties
  • +
+

+ +ModuleController has been improved to better handle changes to referenced controllers + +Improved class loader configuration, see 55503 +

+

    +
  • New property "plugin_dependency_paths" for plugin dependencies
  • +
  • Properties "search_paths", "user.classpath" and "plugin_dependency_paths" + now automatically add all jars from configured directories
  • +
+

+ +Best-practices section has been improved, ensure you read it to get the most out of JMeter +

See Best Practices +

+GUI and ergonomy Improvements + + +New Templates feature that allows you to create test plan from existing template or merge +template into your Test Plan +

+

+

+

+

+

+ +Workbench can now be saved +

+

+

+ +Syntax color has been added to scripts elements (BeanShell, BSF, and JSR223), MongoDB and JDBC elements making code much more readable and allowing UNDO/REDO through CTRL+Z/CTRL+Y +

BSF Sampler with syntax color +

+

+

JSR223 Pre Processor with syntax color +

+

+ +Better editors are now available for Test Elements with large text content, like HTTP Sampler, and JMS related Test Element providing line numbering and allowing UNDO/REDO through CTRL+Z/CTRL+Y + +JMeter GUI can now be fully Internationalized, all remaining issues have been fixed +
Currently French has all its labels translated. Other languages are partly translated, feel free to +contribute translations by reading Localisation (Translator's Guide)
+ +Moving elements in Test plan has been improved in many ways +
Drag and drop of elements in Test Plan tree is now much easier and possible on multiple nodes
+

+

+

+

+Note that due to this bug in Java, +you cannot drop a node after last node. The workaround is to drop it before this last node and then Drag and Drop the last node +before the one you just dropped. +

+
New shortcuts have been added to move elements in the tree.
+

(alt + Arrow Up) and (alt + Arrow Down) move the element within the parent node
+(alt + Arrow Left) and (alt + Arrow Right) move the element up and down in the tree depth

+ +Response Time Graph Y axis can now be scaled +

+

+

+ +JUnit Sampler gives now more details on configuration errors + + + + +Known bugs + +
    +
  • The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).
  • + +
  • Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +
  • + +
  • Webservice sampler does not consider the HTTP response status to compute the status of a response, thus a response 500 containing a non empty body will be considered as successful, see 54006. +To workaround this issue, ensure you always read the response and add a Response Assertion checking text inside the response. +
  • + +
  • +The numbers that appear to the left of the green box are the number of active threads / total number of threads, +these only apply to a locally run test; they do not include any threads started on remote systems when using client-server mode, (see 54152). +
  • + +
  • +Note that there is a bug in Java +on some Linux systems that manifests itself as the following error when running the test cases or JMeter itself: +
    + [java] WARNING: Couldn't flush user prefs:
    + java.util.prefs.BackingStoreException:
    + java.lang.IllegalArgumentException: Not supported: indent-number
    +
    +This does not affect JMeter operation. This issue is fixed since Java 7b05. +
  • + +
  • +With Java 1.6 and Gnome 3 on Linux systems, the JMeter menu may not work correctly (shift between mouse's click and the menu). +This is a known Java bug (see 54477 ). +A workaround is to use a Java 7 runtime (OpenJDK or Oracle JDK). +
  • + +
  • +With Oracle Java 7 and Mac Book Pro Retina Display, the JMeter GUI may look blurry. +This is a known Java bug, see Bug JDK-8000629. +A workaround is to use a Java 7 update 40 runtime which fixes this issue. +
  • +
+ + + +Incompatible changes + +
    +
  • SMTP Sampler now uses eml file subject if subject field is empty
  • + +
  • With this version autoFlush has been turned off on PrintWriter in charge of writing test results. +This results in improved throughput for intensive tests but can result in more test data loss in case +of JMeter crash (extremely rare). To revert to previous behaviour set jmeter.save.saveservice.autoflush property to true.
  • + +
  • +Shortcut for Function Helper Dialog is now CTRL+SHIFT+F1 (CMD + SHIFT + F1 for Mac OS). +The original key sequence (Ctrl+F1) did not work in some locations (it is consumed by the Java Swing ToolTipManager). +It was therefore necessary to change the shortcut. +
  • + +
  • +Webservice (SOAP) Request has been removed by default from GUI as Element is deprecated. (Use HTTP Request +with Body Data, see also the Template Building a SOAP Webservice Test Plan), if you need to show it, see property not_in_menu in jmeter.properties +
  • + +
  • +Transaction Controller now sets Response Code of Generated Parent Sampler +(if Generated Parent Sampler is checked) to response code of first failing child in case of failure of one of the children, in previous versions Response Code was empty. +
  • + +
  • +In previous versions, IncludeController could run Test Elements located inside a Thread Group, this behaviour (which was not documented) +ould result in weird behaviour, it has been removed in this version (see 55464). +The correct way to include Test Elements is to use Test Fragment as stated in documentation of Include Controller. +
  • + +
  • +The retry count for the HttpClient 3.1 and HttpClient 4.x samplers has been changed to 0. +Previously the default was 1, which could cause unexpected additional traffic. +
  • + +
  • Starting with this version, the HTTP(S) Test Script Recorder tries to detect when a sample is the result of a previous +redirect. If the current response is a redirect, JMeter will save the redirect URL. When the next request is received, +it is compared with the saved redirect URL and if there is a match, JMeter will disable the generated sample. +To revert to previous behaviour, set the property proxy.redirect.disabling=false +
  • + +
  • Starting with this version, in HTTP(S) Test Script Recorder if Grouping is set to Put each group in a new Transaction Controller, +the Recorder will create Transaction Controller instances with Include duration of timer and pre-post processors in generated sample set +to false. This default value reflect more accurately response time. +
  • + +
  • __escapeOroRegexpChars function (which escapes ORO reserved characters) no longer trims the value (see 55328) +
  • + +
  • The commons-lang-2.6.jar has been removed from embedded libraries in jmeter/lib folder as it is not needed by JMeter at run-time +(it is only used by Apache Velocity for generating documentation). +If you use any plugin or third-party code that depends on it, you need to add it in jmeter/lib folder +
  • +
+ + + +Bug fixes + +

HTTP Samplers and Proxy

+
    +
  • 54627 - JMeter Proxy GUI: Type of sampler setting takes the whole screen when there are samplers with long names.
  • +
  • 54629 - HTMLParser does not extract &lt;object&gt; tag urls.
  • +
  • 55023 - SSL Context reuse feature (51380) adversely affects non-ssl request performance/throughput. based on analysis by Brent Cromarty (brent.cromarty at yahoo.ca)
  • +
  • 55092 - Log message "WARN - jmeter.protocol.http.sampler.HTTPSamplerBase: Null URL detected (should not happen)" displayed when embedded resource URL is malformed.
  • +
  • 55161 - Useless processing in SoapSampler.setPostHeaders. Contributed by Adrian Nistor (nistor1 at illinois.edu)
  • +
  • 54482 - HC fails to follow redirects with non-encoded chars.
  • +
  • 54142 - HTTP Proxy Server throws an exception when path contains "|" character.
  • +
  • 55388 - HC3 does not allow IP Source field to override httpclient.localaddress.
  • +
  • 55450 - HEAD redirects should remain as HEAD
  • +
  • 55455 - HTTPS with HTTPClient4 ignores cps setting
  • +
  • 55502 - Proxy generates empty http:/ entries when recording
  • +
  • 55504 - Proxy incorrectly issues CONNECT requests when browser prompts for certificate override
  • +
  • 55506 - Proxy should deliver failed requests to any configured Listeners
  • +
  • 55545 - HTTP Proxy Server GUI should not allow both Follow and Auto redirect to be selected
  • +
+ +

Other Samplers

+
    +
  • 54913 - JMSPublisherGui incorrectly restores its state. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 55027 - Test Action regression, duration value is not recorded (nightly build).
  • +
  • 55163 - BeanShellTestElement fails to quote string when calling testStarted(String)/testEnded(String).
  • +
  • 55349 - NativeCommand hangs if no input file is specified and the application requests input.
  • +
  • 55462 - System Sampler should not change the sampler label if a sample fails
  • +
+ +

Controllers

+
    +
  • 54467 - Loop Controller: compute loop value only once per parent iteration.
  • +
  • 54985 - Make Transaction Controller set Response Code of Generated Parent Sampler to response code of first failing child in case of failure of one of its children. Contributed by Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • 54950 - ModuleController : Changes to referenced Module are not taken into account if changes occur after first run and referenced node is disabled.
  • +
  • 55201 - ForEach controller excludes start index and includes end index (clarified documentation).
  • +
  • 55334 - Adding Include Controller to test plan (made of Include Controllers) without saving TestPlan leads to included code not being taken into account until save.
  • +
  • 55375 - StackOverflowError with ModuleController in Non-GUI mode if its name is the same as the target node.
  • +
  • 55464 - Include Controller running included thread group
  • +
+ +

Listeners

+
    +
  • 54589 - View Results Tree have a lot of Garbage characters if html page uses double-byte charset.
  • +
  • 54753 - StringIndexOutOfBoundsException at SampleResult.getSampleLabel() if key_on_threadname=false when using Statistical mode.
  • +
  • 54685 - ArrayIndexOutOfBoundsException if "sample_variable" is set in client but not server.
  • +
  • 55111 - ViewResultsTree: text not refitted if vertical scrollbar is required. Contributed by Milamber
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 54540 - "HTML Parameter Mask" are not marked deprecated in the IHM.
  • +
  • 54575 - CSS/JQuery Extractor : Choosing JODD Implementation always uses JSOUP.
  • +
  • 54901 - Response Assertion GUI behaves weirdly.
  • +
  • 54924 - XMLAssertion uses JMeter JVM file.encoding instead of response encoding and does not clean threadlocal variable.
  • +
  • 53679 - Constant Throughput Timer bug with localization. Reported by Ludovic Garcia
  • +
+ +

Functions

+
    +
  • 55328 - __escapeOroRegexpChars trims spaces.
  • +
+ +

I18N

+
    +
  • 55437 - ComboStringEditor does not translate EDIT and UNDEFINED strings on language change
  • +
  • 55501 - Incorrect encoding for French description of __char function. Contributed by Antonio Gomes Rodrigues (ra0077 at gmail.com)
  • +
+ +

General

+
    +
  • 54504 - Resource string not found: [clipboard_node_read_error].
  • +
  • 54538 - GUI: context menu is too big.
  • +
  • 54847 - Cut & Paste is broken with tree multi-selection. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 54870 - Tree drag and drop may lose leaf nodes (affected nightly build). Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 55056 - wasted work in Data.append(). Contributed by Adrian Nistor (nistor1 at illinois.edu)
  • +
  • 55129 - Change Javadoc generation per CVE-2013-1571, VU#225657.
  • +
  • 55187 - Integer overflow when computing ONE_YEAR_MS in HTTP CacheManager.
  • +
  • 55208 - JSR223 language entries are duplicated; fold to lower case.
  • +
  • 55203 - TestBeanGUI - wrong language settings found.
  • +
  • 55065 - Useless processing in Spline3.converge(). Contributed by Adrian Nistor (nistor1 at illinois.edu)
  • +
  • 55064 - Useless processing in ReportTreeListener.isValidDragAction(). Contributed by Adrian Nistor (nistor1 at illinois.edu)
  • +
  • 55242 - BeanShell Client jar throws exceptions after upgrading to 2.8.
  • +
  • 55288 - JMeter should default to 0 retries for HTTP requests.
  • +
  • 55405 - ant download_jars task fails if lib/api or lib/doc are missing. Contributed by Antonio Gomes Rodrigues.
  • +
  • 55427 - TestBeanHelper should ignore properties not supported by GenericTestBeanCustomizer
  • +
  • 55459 - Elements using ComboStringEditor lose the input value if user selects another Test Element
  • +
  • 54152 - In distributed testing : activeThreads always show 0 in GUI and Summariser
  • +
  • 55509 - Allow Plugins to be notified of remote thread number progression
  • +
  • 55572 - Detail popup of parameter does not show a Scrollbar when content exceeds display
  • +
  • 55580 - Help pane does not scroll to start for <a href="#"> links
  • +
  • 55600 - JSyntaxTextArea : Strange behaviour on first undo
  • +
  • 55655 - NullPointerException when Remote stopping /shutdown all if one engine did not start correctly. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
  • 55657 - Remote and Local Stop/Shutdown buttons state does not take into account local / remote status
  • +
+ + + +Improvements + +

HTTP Samplers and Proxy

+
    +
  • HTTP Request: Small user interaction improvements in Row parameter Detail Box. Contributed by Milamber
  • +
  • 55255 - Allow Body in HTTP DELETE method to support API that use it (like ElasticSearch).
  • +
  • 53480 - Add Kerberos support to Http Sampler (HttpClient4). Based on patch by Felix Schumacher (felix.schumacher at internetallee.de)
  • +
  • 54874 - Support device in addition to source IP address. Based on patch by Dan Fruehauf (malkodan at gmail.com)
  • +
  • 55488 - Add .ico and .woff file extension to default suggested exclusions in proxy recorder. Contributed by Antonio Gomes Rodrigues
  • +
  • 55525 - Proxy should support alias for keyserver entry
  • +
  • 55531 - Proxy recording and redirects. Added code to disable redirected samples.
  • +
  • 55507 - Proxy SSL recording does not handle external embedded resources well
  • +
  • 55632 - Have a new implementation of htmlParser for embedded resources parsing with better performances
  • +
  • 55653 - HTTP(S) Test Script Recorder should set TransactionController property "Include duration of timer and pre-post processors in generated sample" to false
  • +
+ +

Other samplers

+
    +
  • 54788 - JMS Point-to-Point Sampler - GUI enhancements to increase readability and ease of use. Contributed by Bruno Antunes (b.m.antunes at gmail.com)
  • +
  • 54798 - Using subject from EML-file for SMTP Sampler. Contributed by Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • 54759 - SSLPeerUnverifiedException using HTTPS , property documented.
  • +
  • 54896 - JUnit sampler gives only "failed to create an instance of the class" message with constructor problems.
  • +
  • 55084 - Add timeout support for JDBC Request. Contributed by Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • 55403 - Enhancement to OS sampler: Support for timeout
  • +
  • 55518 - Add ability to limit number of cached PreparedStatements per connection when "Prepared Select Statement", "Prepared Update Statement" or "Callable Statement" query type is selected
  • +
+ +

Controllers

+
    +
  • 54271 - Module Controller breaks if test plan is renamed.
  • +
+ +

Listeners

+
    +
  • 54532 - Improve Response Time Graph Y axis scale with huge values or small values (&lt; 1000ms). Add a new field to define increment scale. Contributed by Milamber based on patch by Luca Maragnani (luca.maragnani at gmail.com)
  • +
  • 54576 - View Results Tree : Add a CSS/JQuery Tester.
  • +
  • 54777 - Improve Performance of default ResultCollector. Based on patch by Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • 55389 - Show IP source address in request data
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 54789 - XPath Assertion - GUI enhancements to increase readability and ease of use.
  • +
+ +

Functions

+
    +
  • 54991 - Add functions to encode/decode URL encoded chars (__urlencode and __urldecode). Contributed by Milamber.
  • +
+ +

I18N

+
    +
  • 55241 - Need GUI Editor to process fields which are based on Enums with localised display strings
  • +
  • 55440 - ComboStringEditor should allow tags to be language dependent
  • +
  • 55432 - CSV Dataset Config loses sharing mode when switching languages
  • +
+ +

General

+
    +
  • 54584 - MongoDB plugin. Based on patch by Jan Paul Ettles (janpaulettles at gmail.com)
  • +
  • 54669 - Add flag forcing non-GUI JVM to exit after test. Contributed by Scott Emmons
  • +
  • 42428 - Workbench not saved with Test Plan. Contributed by Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • 54825 - Add shortcuts to move elements in the tree. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 54834 - Improve Drag & Drop in the jmeter tree. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 54839 - Set the application name on Mac. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 54841 - Correctly handle the quit shortcut on Mac Os (CMD-Q). Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 54844 - Set the application icon on Mac Os. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 54864 - Enable multi selection drag & drop in the tree without having to start dragging before releasing Shift or Control. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 54945 - Add Shutdown Hook to enable trapping kill or CTRL+C signals.
  • +
  • 54990 - Download large files avoiding outOfMemory.
  • +
  • 55085 - UX Improvement : Ability to create New Test Plan from Templates. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
  • 55172 - Provide plugins a way to add Top Menu and menu items.
  • +
  • 55202 - Add syntax color for scripts elements (BeanShell, BSF, and JSR223) and JDBC elements with RSyntaxTextArea. Contributed by Milamber based on patch by Marko Vlahovic (vlahovic74 at gmail.com)
  • +
  • 55175 - HTTPHC4Impl refactoring to allow better inheritance.
  • +
  • 55236 - Templates - provide button to reload template details.
  • +
  • 55237 - Template system should support relative fileName entries.
  • +
  • 55423 - BatchSampleSender: Reduce locking granularity by moving listener.processBatch outside of synchronized block
  • +
  • 55424 - Add Stripping to existing SampleSenders
  • +
  • 55451 - Test Element GUI with JSyntaxTextArea scroll down when text content is long enough to add a Scrollbar
  • +
  • 55513 - StreamCopier cannot be used with System.err or System.out as it closes the output stream
  • +
  • 55514 - SystemCommand should support arbitrary input and output streams
  • +
  • 55515 - SystemCommand should support chaining of commands
  • +
  • 55606 - Use JSyntaxtTextArea for Http Request, JMS Test Elements
  • +
  • 55651 - Change JMeter application icon to Apache plume icon
  • +
+ +Non-functional changes +
    +
  • Updated to jsoup-1.7.2
  • +
  • 54776 - Update the dependency on Bouncy Castle to 1.48. Contributed by Emmanuel Bourg (ebourg at apache.org)
  • +
  • Updated to HttpComponents Client 4.2.6 (from 4.2.3)
  • +
  • Updated to HttpComponents Core 4.2.5 (from 4.2.3)
  • +
  • Updated to commons-codec 1.8 (from 1.6)
  • +
  • Updated to commons-io 2.4 (from 2.2)
  • +
  • Updated to commons-logging 1.1.3 (from 1.1.1)
  • +
  • Updated to commons-net 3.3 (from 3.1)
  • +
  • Updated to jdom-1.1.3 (from 1.1.2)
  • +
  • Updated to jodd-lagarto and jodd-core 3.4.8 (from 3.4.1)
  • +
  • Updated to junit 4.11 (from 4.10)
  • +
  • Updated to slf4j-api 1.7.5 (from 1.7.2)
  • +
  • Updated to tika 1.4 (from 1.3)
  • +
  • Updated to xmlgraphics-commons 1.5 (from 1.3.1)
  • +
  • Updated to xstream 1.4.4 (from 1.4.2)
  • +
  • Updated to BouncyCastle 1.49 (from 1.48)
  • +
  • 54912 - JMeterTreeListener should use constants. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 54903 - Remove the dependency on the Activation Framework. Contributed by Emmanuel Bourg (ebourg at apache.org)
  • +
  • Moved commons-lang (2.6) to lib/doc as it's only needed by Velocity.
  • +
  • Re-organised and simplified NOTICE and LICENSE files.
  • +
  • 55411 - NativeCommand could be useful elsewhere. Copied code to o.a.jorphan.exec.
  • +
  • 55435 - ComboStringEditor could be simplified to make most settings final
  • +
  • 55436 - ComboStringEditor should implement ClearGui
  • +
  • 55463 - Component.requestFocus() is discouraged; use requestFocusInWindow() instead
  • +
  • 55486 - New JMeter Logo. Contributed by UBIK Load Pack (support at ubikloadpack.com)
  • +
  • 55548 - Tidy up use of TestElement.ENABLED; use TestElement.isEnabled()/setEnabled() throughout
  • +
  • 55617 - Improvements to jorphan collection. Contributed by Benoit Wiart (benoit.wiart at gmail.com)
  • +
  • 55623 - Invalid/unexpected configuration values should not be silently ignored
  • +
  • 55626 - Rename HTTP Proxy Server as HTTP(S) Test Script Recorder
  • +
+ +Thanks +

We thank all contributors mentioned in bug and improvement sections above: +

    +
  • Bruno Antunes (b.m.antunes at gmail.com)
  • +
  • Emmanuel Bourg (ebourg at apache.org)
  • +
  • Scott Emmons
  • +
  • Mikhail Epikhin (epihin-m at yandex.ru)
  • +
  • Dzmitry Kashlach (dzmitrykashlach at gmail.com)
  • +
  • Luca Maragnani (luca.maragnani at gmail.com)
  • +
  • Milamber
  • +
  • Adrian Nistor (nistor1 at illinois.edu)
  • +
  • Antonio Gomes Rodrigues (ra0077 at gmail.com)
  • +
  • UBIK Load Pack (support at ubikloadpack.com)
  • +
  • Benoit Wiart (benoit.wiart at gmail.com)
  • +
+
+We also thank bug reporters who helped us improve JMeter.
+For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes: +
    +
  • Immanuel Hayden (immanuel.hayden at gmail.com)
  • +
  • Danny Lade (dlade at web.de)
  • +
  • Brent Cromarty (brent.cromarty at yahoo.ca)
  • +
  • Wolfgang Heider (wolfgang.heider at racon.at)
  • +
  • Shmuel Krakower (shmulikk at gmail.com)
  • +
+ + +Apologies if we have omitted anyone else. +

+ + + +

Version 2.9

+ +

New and Noteworthy

+ +

Core Improvements:

+ +

* A new Extractor that uses CSS or jquery-like selector syntax has been introduced, +it allows using either JODD or JSOUP implementations

+

+

+

+

Result: the title of the page in a JMeter variable +

+

+

* JMeter can now handle different types of documents (PDF, MsOffice files, Apache OpenOffice's files...) + within different elements

+
    +
  • Regular Expression Extractor, extract text from documents
  • +
  • Assertion Response, check text in documents
  • +
  • View Results Tree, view as a text the documents
  • +
+

+

+

+ +

* A new Regex User Parameters Pre-Processor that enables injecting input parameter names and values +using a reference extracted by Regular Expression Extractor from a previous response

+

+

+

+ +

* TCP Sampler: new options

+

TCP Sampler has been enhanced with new options to allow setting Close Connection, + SO_LINGER and End of line(EOL) byte value +

+

+

* A new function __escapeOroRegexpChars(,) has been introduced quote ORO regexp meta characters

+

* ForEach Controller: new fields

+

ForEach Controller has now 2 new fields to control start and end of loop +

+

+

* Result Status Action Handler now has a new option to "Start next thread loop"

+

+

+

+ +

* JMS Publisher: new option

+

JMS Publisher can now send Bytes Messages

+
+ +

* Memory and performance improvements

+

Significant improvements have been done in this version on memory usage per Thread and CPU when more +than one Post Processor is used as child of a Sampler

+

JSR223 Elements (enable using Groovy, Scala... as scripting languages) have been improved to enable caching +of Compilation results when scripts are passed in Text area

+
+ +

Some configuration defaults have changed to improve performances by default(see 54412), +see description in New and Noteworthy section. +

    +
  • Distributed testing now uses MODE_STRIPPED_BATCH, which returns samples in batch mode (every 100 samples + or every minute by default). Note also that MODE_STRIPPED_BATCH strips response data from SampleResult, + so if you need it change to another mode (mode property in jmeter.properties)
  • +
  • Result data are now saved to CSV by default (jmeter.save.saveservice.output_format in jmeter.properties)
  • +
+

+ +

* XPath Assertion now enables using a JMeter variable as input

+

+

+

+ +

GUI and ergonomy Improvements:

+

* Search feature has been improved to search within more internal fields of elements and expand search results

+

* Copy/paste is now possible between 2 JMeter instances >= 2.9 version

+

Copy element(s) from one JMeter instance: +

+

+

Paste element(s) into a second JMeter instance: +

+

+

* HTTP Header Manager

+

Allow copy from clipboard to HeaderPanel, headers are supposed to be separated by new line + and have the following form name:value +

+

+

* Module Controller

+

Module Controller has been improved to better render referenced controller and expand it by clicking on a new button +

+

+

* HTTP Proxy Server

+

HTTP Proxy Server now has a button to add a set of default exclusions for URL patterns, +this list can be configured through property : proxy.excludes.suggested +

+

+

* Rendering of target controller has been improved in HTTP Proxy Server

+ +

HTTP Proxy Server recording:

+

* HTTP Proxy Server now automatically uses HTTP Request with Raw Post Body mode for +samples that only have one unnamed argument (JSON, XML, GWT...)

+

* HTTP Proxy Server does not force user to select the type of Sampler in HTTP Sampler Settings, + this allows easier switch between implementations as Sampler do not have this information set anymore

+

+

+

+

* SamplerCreator interface has been enriched to meet new requirements for plug-in providers

+

* It is now possible to create binary sampler for x-www-form-urlencoded POST request by +modifying proxy.binary.types property to add application/x-www-form-urlencoded

+

* Improved timestamp format auto-detection when reading CSV files

+ + + +

Known bugs

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ +

Webservice sampler does not consider the HTTP response status to compute the status of a response, thus a response 500 containing a non empty body will be considered as successful, see 54006. +To workaround this issue, ensure you always read the response and add a Response Assertion checking text inside the response. +

+ +

+Changing language can break part of the configuration of the following elements (see 53679): +

    +
  • CSV Data Set Config (sharing mode will be lost)
  • +
  • Constant Throughput Timer (Calculate throughput based on will be lost)
  • +
+

+ +

+The numbers that appear to the left of the green box are the number of active threads / total number of threads, +these only apply to a locally run test; they do not include any threads started on remote systems when using client-server mode, (see 54152). +

+ +

+Note that there is a bug in Java on some Linux systems that manifests +itself as the following error when running the test cases or JMeter itself: +

+ [java] WARNING: Couldn't flush user prefs:
+ java.util.prefs.BackingStoreException:
+ java.lang.IllegalArgumentException: Not supported: indent-number
+
+This does not affect JMeter operation. +

+ + + +

Incompatible changes

+ +

JMeter requires now a Java 6 runtime or higher.

+ +

Some configuration defaults have changed to improve performances by default (see 54412), +see description in New and Noteworthy section.

+ +

Webservice sampler now adds to request the headers that are set through Header Manager, these were previously ignored

+ +

jdbcsampler.cachesize property has been removed, it previously limited the size of a per connection cache of Map < String, +PreparedStatement > , it also limited the size of this +map which held the PreparedStatement for SQL queries. This limitation provoked a bug 53995. +It has been removed so now size of these 2 maps is not limited anymore. This change changes behaviour as starting from +this version no PreparedStatement will be closed during the test.

+ +

Starting with this version, there are some important changes on JSR223 Test Elements: +

    +
  • JSR223 Test Elements that have an invalid filename (not existing or unreadable) will make test fail instead of + making the element silently work
  • +
  • In JSR223 Test Elements: responseCodeOk, responseMessageOK and successful are set before + script is executed, if responseData is set it will not be overriden anymore by a toString() on script return value
  • +
+

+ +

View Results Tree now considers response with missing content type as text.

+ +

In remote Test mode, JMeter now exits in error if one of the remote engines cannot be configured, +previously it started the test with available engines.

+ + + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • Don't log spurious warning messages when using concurrent pool embedded downloads with Cache Manager or CookieManager
  • +
  • 54057- Proxy option to set user and password at startup (-u and -a) not working with HTTPClient 4
  • +
  • 54187 - Request tab does not show headers if request fails
  • +
  • 53840 - Proxy Recording : Response message: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "" "
  • +
  • 54351 - HC4 and URI fragments is failing
  • +
+ +

Other Samplers

+
    +
  • 53997 - LDAP Extended Request: Escape ampersand (&), left angle bracket (<) +and right angle bracket (>) in search filter tag in XML response data
  • +
  • 53995 - AbstractJDBCTestElement shares PreparedStatement between multi-threads
  • +
  • 54119 - HTTP 307 response is not redirected
  • +
  • 54326 - AjpSampler send file in post throws FileNotFoundException
  • +
  • 54331 - AjpSampler throws null pointer on GET request that are protected
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • 54088 - The type video/f4m is text, not binary
  • +
  • 54166 - ViewResultsTree could not render the HTML response: handle failure to parse HTML
  • +
  • 54287 - Incorrect Timestamp in Response Time Graph when using a date with time in Date format field
  • +
  • 54451 - Response Time Graph reports wrong times when the are many samples for same time
  • +
  • 54459 - CSVSaveService does not handle date parsing very well
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 54058 - In HTTP Request Defaults, the value of field "Embedded URLs must match: is not saved if the check box "Retrieve All Embedded Resources" is not checked.
  • +
  • 54375 - Regular Expression Extractor : When regex syntax is wrong, post processing is stopped
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • 53975 - Variables replacement doesn't work with option "Delay thread creation until needed"
  • +
  • 54055 - View Results tree: = signs are stripped from parameter values at HTTP tab
  • +
  • 54129 - Search Feature does not find text although existing in elements
  • +
  • 54023 - Unable to start JMeter from a root directory and if the full path of JMeter installation contains one or more spaces (Unix/linux)
  • +
  • 54172 - Duplicate shortcut key not working and CTRL+C / CTRL+V / CTRL+V do not cancel default event
  • +
  • 54057 - Proxy option to set user and password at startup (-u and -a) not working with HTTPClient 4
  • +
  • 54267 - Start Next Thread Loop setting doesn't work in custom thread groups
  • +
  • 54413 - DataStrippingSampleSender returns 0 for number of bytes of any response
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 54185 - Allow query strings in paths that start with HTTP or HTTPS
  • +
+ +

Other samplers

+
    +
  • 54004 - Webservice Sampler : Allow adding headers to request with Header Manager
  • +
  • 54106 - JSR223TestElement should check for file existence when a filename is set instead of using Text Area content
  • +
  • 54107 - JSR223TestElement : Enable compilation and caching of Script Text
  • +
  • 54109 - JSR223TestElement : SampleResult properties should be set before entering script to allow user setting different code
  • +
  • 54230 - TCP Sampler, additions of "Close Connection", "SO_LINGER" and "End of line(EOL) byte value" options
  • +
  • 54182 - Support sending of ByteMessage for JMS Publisher.
  • +
+ +

Controllers

+
    +
  • 54131 - ForEach Controller : Add start and end index for looping over variables
  • +
  • 54132 - Module Controller GUI : Improve rendering of referenced controller
  • +
  • 54155 - ModuleController : Add a shortcut button to unfold the tree up to referenced controller and highlight it
  • +
+ +

Listeners

+
    +
  • 54200 - Add support of several document types (like Apache OpenOffice's files, MS Office's files, PDF's files, etc.) +to the elements View Results Tree, Assertion Response and Regular Expression Extractor (using Apache Tika)
  • +
  • 54226 - View Results Tree : Show response even when server does not return ContentType header
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 54259 - Introduce a new Extractor that uses CSS or jquery-like selector syntax
  • +
  • 45772 - RegEx User Parameters Post Processor
  • +
  • 54160 - Add support for xpath assertion to apply to a JMeter variable.
  • +
+ +

Functions

+
    +
  • 54189 - Add a function to quote ORO regexp meta characters
  • +
  • 54418 - UUID Function
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 54005 - HTTP Mirror Server : Add special headers "X-" to control Response status and response content
  • +
  • 53875 - Include suggested defaults for URL filters on HTTP Proxy
  • +
  • 54031 - Add tooltip to running/total threads indicator
  • +
  • Webservice (SOAP) Request has been deprecated
  • +
  • 54161 - Proxy : be able to create binary sampler for x-www-form-urlencoded POST request
  • +
  • 54154 - HTTP Proxy Server should not force user to select the type of Sampler in HTTP Sampler Settings
  • +
  • 54165 - Proxy Server: Improve rendering of target controller
  • +
  • 46677 - Copying Test Elements between test plans
  • +
  • 54204 - Result Status Action Handler : Add start next thread loop option
  • +
  • 54232 - Search Feature : Add a button to search and expand results
  • +
  • 54251 - Add tristate checkbox implementation
  • +
  • 54257 - Enhance SamplerCreator interface to meet new requirements
  • +
  • 54258 - Proxy : Use Raw Post Body when Sampler has one unnamed argument, useful for Samplers using POST method by of type JSON, XML, GWT body
  • +
  • 54268 - Improve CPU and memory usage
  • +
  • 54376 - ScopePanel : Allow configuring more precisely scopes
  • +
  • 54412 - Changing JMeter defaults to ensure better performances by default
  • +
  • 54414 - Remote Test should not start if one of the engines fails to start correctly
  • +
+ +

Non-functional changes

+
    +
  • 53956 - Add ability to paste (a list of values) from clipboard for Header Manager
  • +
  • Updated to HttpComponents Client 4.2.3 (from 4.2.1)
  • +
  • Updated to HttpComponents Core 4.2.3 (from 4.2.2)
  • +
  • 54110 - BSFTestElement and JSR223TestElement should use shared super-class for common fields
  • +
  • 54199 - Move to Java 6
  • +
  • Upgraded to rhino 1.7R4
  • +
+ + + +

Version 2.8

+ +

New and Noteworthy

+ +

Core Improvements:

+ +

Thread Group: New Option Delay thread creation until needed

+

New Option "Delay thread creation until needed" that will create and start threads when needed instead of creating them on Test startup

+This new feature allows running tests with a huge number of short lived threads. +

+

+ +

HTTP Cookie Manager (IPv6 support)

+

Add HTTPClient 4 cookie implementation in JMeter.
+Cookie Manager has now the default HC3.1 implementation and a new choice HC4 implementation (compliant with IPv6 address) +

+

+ +

Memory and performance improvements

+

Significant improvements have been done in this version on memory usage of JMeterThread

+

JSR223 Elements (enable using Groovy, scala... as scripting languages) have been improved to enable: +

    +
  • usage of Compilable interface when available to boost CPU usage
  • +
  • caching of Compilation when scripts are used as Files
  • +
+See JMeter Performances across versions +

+ +

OS Process Sampler

+

Allow defining files for stdout/stderr/stdin. +

+

+ +

HTTP Request: PATCH verb

+

Add PATCH verb to HTTP sampler +

+

+ +

HTTP Request: HTTPClient 4 is now the default implementation

+

HTTPClient 4 is now the default HTTP Request implementation (and for Proxy element when generating HTTP requests).
+Previously the default was the HTTP Java implementation (i.e. the implementation provided by the JVM) +

+

+ +

HTTP Request

+

Add Embedded URL Filter to HTTP Request Defaults Control (it was already present for HTTP Requests) +

+

+ +

Miscellanous

+
    +
  • CSV Dataset : Embedded new lines are now supported in quoted data
  • +
  • JMX files now contain the version of JMeter that created the file
  • +
  • JMeter Version is now available as property "jmeter.version"
  • +
+

Reporting Improvements:

+ +

Response Time Graph

+

Add a new visualizer Response Time Graph to draw a line graph showing the evolution of response time for a test +

+

+

Settings for Response Time Graph +

+

+ +

View Results in Table

+

Add latency to View Result in Table listener +

+

+ +

Aggregate Graph

+

Small improvements: legend at left or right is now on 1 column (instead of 1 large line), ... +

+

+ +

GUI and ergonomy Improvements:

+

HTTP Proxy Server simplifications

+

HTTPS Spoofing options have been removed from Proxy as HTTPS recording is directly available since JMeter 2.4. +

+

+ +

HTTP Proxy Server

+

Allow URL Filters to be pasted from clipboard +

+

+ +

Find in JMeter

+

CTRL + F for the new Find feature +

+ +ESC key now closes popups. +

+ +

User Interface in GNOME 3

+

Display 'Apache JMeter' title in app title bar in Gnome 3 +

+

+ + + +

Known bugs

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ +

+Changing language can break part of the configuration of the following elements (see 53679): +

    +
  • CSV Data Set Config (sharing mode will be lost)
  • +
  • Constant Throughput Timer (Calculate throughput based on will be lost)
  • +
+

+ +

+Note that there is a bug in Java on some Linux systems that manifests +itself as the following error when running the test cases or JMeter itself: +

+ [java] WARNING: Couldn't flush user prefs:
+ java.util.prefs.BackingStoreException:
+ java.lang.IllegalArgumentException: Not supported: indent-number
+
+This does not affect JMeter operation. +

+ + + +

Incompatible changes

+ +

+When using CacheManager, JMeter now caches responses for GET queries provided header Cache-Control is different from "no-cache" as described in specification. +Furthermore it doesn't put anymore in Cache deprecated entries for "no-cache" responses. See 53521 and 53522 +

+ +

+A major change has occured on JSR223 Test Elements, previously variables set up before script execution where stored in ScriptEngineManager which was created once per execution, +now ScriptEngineManager is a singleton shared by all JSR223 elements and only ScriptEngine is created once per execution, variables set up before script execution are now stored +in Bindings created on each execution, see 53365. +

+ +

+JSR223 Test Elements using Script file are now Compiled if ScriptEngine supports this feature, see 53520. +

+ +

+Shortcut for Function Helper Dialog is now CTRL+F1 (CMD + F1 for Mac OS), CTRL+F (CMD+F1 for Mac OS) now opens Search Dialog. +

+ +

+By default, the TestCompiler now stores details of which pairs it has seen in Controller instances rather than in a static Set. +[53796] +This gives much better memory behaviour for delayed start test plans, as memory used is proportional to the number of concurrent threads. +With the static Set memory usage was proportional to the total thread count. +This change is very unlikely to cause a problem. +The original behaviour can be restored by setting the property TestCompiler.useStaticSet=true +

+ +

+HTTPS Spoofing options have been removed from Proxy as HTTPS recording is directly available since JMeter 2.4. +

+ + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 53521 - Cache Manager should cache content with Cache-control=private
  • +
  • 53522 - Cache Manager should not store at all response with header "no-cache" and store other types of Cache-Control having max-age value
  • +
  • 53838 - Pressing "Stop" does not interrupt the TCP sampler
  • +
  • 53911 - JmeterKeystore does not allow for key down the list of certificate
  • +
+ +

Other Samplers

+
    +
  • 53348 - JMeter JMS Point-to-Point Request-Response sampler doesn't work when Request-queue and Receive-queue are different
  • +
  • 53357 - JMS Point to Point reports too high response times in Request Response Mode
  • +
  • 53440 - SSL connection leads to ArrayStoreException on JDK 6 with some KeyManagerFactory SPI
  • +
  • 53511 - access log sampler SessionFilter throws NullPointerException - cookie manager not initialized properly
  • +
  • 53715 - JMeter does not load WSDL
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • 53742 - When jmeter.save.saveservice.sample_count is set to true, elapsed time read by listener is always equal to 0
  • +
  • 53774 - RequestViewRaw does not show headers unless samplerData is non-null
  • +
  • 53802 - IdleTime values are not saved to CSV log
  • +
  • 53874 - View Results Tree : If some parameter containing special characters like % is not encoded, RequestViewHTTP fails with java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern and Response is not displayed
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 51512 - Cookies aren't inserted into HTTP request with IPv6 Host header
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • 53365 - JSR223TestElement should cache ScriptEngineManager
  • +
  • 53520 - JSR223 Elements : Use Compilable interface to improve performances on File scripts
  • +
  • 53501 - Synchronization timer blocks test end.
  • +
  • 53750 - TestCompiler saves unnecessary entries in pairing collection
  • +
  • 52266 - Code:Inconsistent synchronization
  • +
  • 53841 - CSVSaveService reads file using JVM default file encoding instead of using the one configured in saveservice.properties
  • +
  • 53953 New: Typo in monitor test plan documentation
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 53675 - Add PATCH verb to HTTP sampler
  • +
  • 53931 - Define HTTPClient 4 for the default HTTP Request (and Proxy element to generate the HTTP requests). Before the default, it was the HTTP Java Sampler
  • +
  • 53934 - Removes HTTPS spoofing options in JMeter HTTP Proxy Server. Since JMeter 2.4, the HTTPS protocol is directly supported by the proxy
  • +
+ +

Other samplers

+
    +
  • 55310 - TestAction should implement Interruptible
  • +
  • 53318 - Add Embedded URL Filter to HTTP Request Defaults Control
  • +
  • 53782 - Enhance JavaSampler handling of JavaSamplerClient cleanup to use less memory
  • +
  • 53168 - OS Process - allow specification of stdout/stderr/stdin
  • +
  • 53844 - JDBC related elements should check class of Variable Name supposed to contain JDBC Connection Configuration to avoid ClassCastException
  • +
+ +

Controllers

+
    +
  • 53671 - tearDown thread group to run even if shutdown test happens
  • +
+ +

Listeners

+
    +
  • 53566 - Don't log partial responses to the jmeter log
  • +
  • 53716 - Small improvements in aggregate graph: legend at left or right is now on 1 column (instead of 1 large line), no border to the reference's square color, reduce width on some fields
  • +
  • 53718 - Add a new visualizer 'Response Time Graph' to draw a line graph showing the evolution of response time for a test
  • +
  • 53738 - Keep track of number of threads started and finished
  • +
  • 53753 - Summariser: no point displaying fractional time in most cases
  • +
  • 53749 - TestListener interface could perhaps be split up. +This should reduce per-thread memory requirements and processing, +as only test elements that actually use testIterationStart functionality now need to be handled.
  • +
  • 53941 - Add latency to View Result table listener
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 53755 - Adding a HttpClient 4 cookie implementation in JMeter. +Cookie Manager has now the default HC3.1 implementation and a new choice HC4 implementation (compliant with IPv6 address)
  • +
+ +

Functions

+
    +
  • 51527 - __time() function : add another option to __time() to provide *seconds* since epoch
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 53364 - Sort list of Functions in Function Helper Dialog
  • +
  • 53418 - New Option "Delay thread creation until needed" that will create and start threads when needed instead of creating them on Test startup
  • +
  • 42245 - Show clear passwords in HTTP Authorization Manager
  • +
  • 53616 - Display 'Apache JMeter' title in app title bar in Gnome 3
  • +
  • 53759 - ClientJMeterEngine perfoms unnecessary traverse using SearchByClass(TestListener)
  • +
  • 52601 - CTRL + F for the new Find feature
  • +
  • 53796 - TestCompiler uses static Set which can grow huge
  • +
  • 53673 - Add JMeter version in the jmx file
  • +
  • Add support for HeapDump to the JMeter non-GUI and GUI client
  • +
  • 53862 - Would be nice to have the JMeter Version available as a property
  • +
  • 53806 - FileServer should provide thread-safe parsing
  • +
  • 53807 - CSV Dataset does not handle embedded new lines in quoted data
  • +
  • 53879 - GUI : Allow Popups to be closed with ESC key
  • +
  • 53876 - Allow URL Filters (HTTP Proxy) to be pasted from clipboard
  • +
+ +

Non-functional changes

+
    +
  • 53311 - JMeterUtils#runSafe should not throw Error when interrupted
  • +
  • Updated to commons-net-3.1 (from 3.0.1)
  • +
  • Updated to HttpComponents Core 4.2.2 (from 4.1.4) and HttpComponents Client 4.2.1 (from 4.1.3)
  • +
  • 53765 - Switch to commons-lang3-3.1
  • +
  • 53884 - wrong Maven groupId for commons-lang
  • +
+ + + +

Version 2.7

+ +

New and Noteworthy

+ +

OS Process Sampler

+

A new System Sampler that can be used to execute commands on the local machine. +

+

+ +

OS Process Sampler results example with DNS lookup command 'dig' +

+

+ +

JMS Samplers improvements

+

Addition of a "Non Persistent Delivery" option to send "Non-Persistent" (Guaranteed to be delivered at most once. Message loss is not a concern.) JMS messages +

+

+ +

Support sending of JMS Object Messages to enable sending Objects unmarshalled from XML by XStream +

+

+ +

Enable setting JMS Properties through JMS Publisher sampler +

+

+ +

Test Action sampler

+

Allow premature exit from a loop +

+

+ +

Webservice Sampler improvements

+

Add a jmeter property soap.document_cache to control size of Document Cache +

+

+ +

Make Maintain HTTP Session configurable +

+

+ +

Aggregate graph: Clustered Bar char with average, median, 90% line, min and max columns

+

Aggregate graph changes to Clustered Bar chart, add more columns (median, 90% line, min, max) and options, fixed some bugs +

+

+ +

New settings for aggregate graph +

+

+ +

Improvements of HTML report design generated by JMeter Ant task in extras folder

+

HTML report example +

+

+ +

HTML report example with some assertion errors +

+

+ +

Mailer Visualizer

+

    +
  • Enable authentication, and connection security with SSL or TLS
  • +
  • Improve GUI design
  • +
  • Add internationalisation (i18n) support
  • +
+
+

+ +

New Visual Indicator of number of ERROR/FATAL messages in logs

+

Indicator shows number of ERROR/FATAL messsages in logs, it can be clicked to toggle Log Viewer panel +

+

+ +

Dialog box to show detail of a parameter row

+

Add a detail button on parameters table to show detail of a Row +

+

+ +

Detail box example +

+

+ +

Plugin writers

+

+New interface org.apache.jmeter.engine.util.ConfigMergabilityIndicator has been introduced to tell whether a ConfigTestElement can be merged in Sampler (see 53042):
+

public boolean applies(ConfigTestElement configElement);
+

+ +

New interface org.apache.jmeter.protocol.http.proxy.SamplerCreator to allow plugging HTTP based samplers that differ from default HTTP Samplers through Proxy during Recording Phase (see 52674):
+

public String[] getManagedContentTypes();
+
public HTTPSamplerBase createSampler(HttpRequestHdr request, Map<String, String> pageEncodings, Map<String, String> formEncodings);
+
public void populateSampler(HTTPSamplerBase sampler, HttpRequestHdr request, Map<String, String> pageEncodings, Map<String, String> formEncodings) throws Exception;
+

+ + + + +

Known bugs

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ + + +

Incompatible changes

+ +

+When doing replacement of User Defined Variables, Proxy will not substitute partial values anymore when "Regexp matching" is used. It will use Perl 5 word matching ("\b") +

+ +

+In User Defined Variables, Test Plan, HTTP Sampler Arguments Table, Java Request Defaults, JMS Sampler and Publisher, LDAP Request Defaults and LDAP Extended Request Defaults, rows with +empty Name and Value are no more saved. +

+ +

+JMeter now expands the Test Plan tree to the testplan level and no further and selects the root of the tree. Furthermore default value of onload.expandtree is false. +

+ +

+Graph Full Results Listener has been removed. +

+ +

+When calling "Clear All" command, if Log Viewer is displayed its content will be cleared. +

+ + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 52613 - Using Raw Post Body option, text gets encoded
  • +
  • 52781 - Content-Disposition header garbled even if browser compatible headers is checked (HC4)
  • +
  • 52796 - MonitorHandler fails to clear variables when starting a new parse
  • +
  • 52871 - Multiple Certificates not working with HTTP Client 4
  • +
  • 52885 - Proxy : Recording issues with HTTPS, cookies starting with secure are partly truncated
  • +
  • 52886 - Proxy : Recording issues with HTTPS when spoofing is on, secure cookies are not always changed
  • +
  • 52897 - HTTPSampler : Using PUT method with HTTPClient4 and empty Content Encoding and sending files leads to NullPointerException
  • +
  • 53145 - HTTP Sampler - function in path evaluated too early
  • +
+ +

Other Samplers

+
    +
  • 51737 - TCPSampler : Packet gets converted/corrupted
  • +
  • 52868 - BSF language list should be sorted
  • +
  • 52869 - JSR223 language list currently uses BSF list which is wrong
  • +
  • 52932 - JDBC Sampler : Sampler is not marked in error in an Exception which is not of class IOException, SQLException, IOException occurs
  • +
  • 52916 - JDBC Exception if there is an empty user defined variable
  • +
  • 52937 - Webservice Sampler : Clear Soap Documents Cache at end of Test
  • +
  • 53027 - Jmeter starts throwing exceptions while using SMTP Sample in a test plan with HTTP Cookie Mngr or HTTP Request Defaults
  • +
  • 53072 - JDBC PREPARED SELECT statements should return results in variables like non prepared SELECT
  • +
+ +

Controllers

+
    +
  • 52968 - Option Start Next Loop in Thread Group does not mark parent Transaction Sampler in error when an error occurs
  • +
  • 50898 - IncludeController : NullPointerException loading script in non-GUI mode if Includers use same element name
  • +
+ +

Listeners

+
    +
  • 43450 - Listeners/Savers assume SampleResult count is always 1; fixed Generate Summary Results
  • +
+ +

Assertions

+
    +
  • 52848 - NullPointer in "XPath Assertion"
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
  • 52551 - Function Helper Dialog does not switch language correctly
  • +
  • 52552 - Help reference only works in English
  • +
+ +

General

+
    +
  • 52639 - JSplitPane divider for log panel should be hidden if log is not activated
  • +
  • 52672 - Change Controller action deletes all but one child samplers
  • +
  • 52694 - Deadlock in GUI related to non AWT Threads updating GUI
  • +
  • 52678 - Proxy : When doing replacement of UserDefinedVariables, partial values should not be substituted
  • +
  • 52728 - CSV Data Set Config element cannot coexist with BSF Sampler in same Thread Plan
  • +
  • 52762 - Problem with multiples certificates: first index not used until indexes are restarted
  • +
  • 52741 - TestBeanGUI default values do not work at second time or later
  • +
  • 52783 - oro.patterncache.size property never used due to early init
  • +
  • 52789 - Proxy with Regexp Matching can fail with NullPointerException in Value Replacement if value is null
  • +
  • 52645 - Recording with Proxy leads to OutOfMemory
  • +
  • 52679 - User Parameters columns narrow
  • +
  • 52843 - Sample headerSize and bodySize not being accumulated for subsamples
  • +
  • 52967 - The function __P() couldn't use default value when running with remote server in GUI mode.
  • +
  • 50799 - Having a non-HTTP sampler in a http test plan prevents multiple header managers from working
  • +
  • 52997 - Jmeter should not exit without saving Test Plan if saving before exit fails
  • +
  • 53136 - Catching Throwable needs to be carefully handled
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
+ +

Other samplers

+
    +
  • 52775 - JMS Publisher : Add Non Persistent Delivery option
  • +
  • 52810 - Enable setting JMS Properties through JMS Publisher sampler
  • +
  • 52938 - Webservice Sampler : Add a jmeter property soap.document_cache to control size of Document Cache
  • +
  • 52939 - Webservice Sampler : Make MaintainSession configurable
  • +
  • 53073 - Allow to assign the OUT result of a JDBC CALLABLE to JMeter variables
  • +
  • 53164 - New System Sampler
  • +
  • 53172 - OS Process Sampler - allow specification of Environment Variables
  • +
  • 52936 - JMS Publisher : Support sending of JMS Object Messages
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • 52603 - MailerVisualizer : Enable SSL , TLS and Authentication
  • +
  • 52698 - Remove Graph Full Results Listener
  • +
  • 53070 - Change Aggregate graph to Clustered Bar chart, add more columns (median, 90% line, min, max) and options, fixed some bugs
  • +
  • 53246 - Mailer Visualizer: improve GUI design and I18N
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
+ +

Functions

+
    +
+ +

I18N

+
    +
  • Mailer Visualizer has been internationalized. French translation added. (see 53246)
  • +
+ +

General

+
    +
  • 45839 - Test Action : Allow premature exit from a loop
  • +
  • 52614 - MailerModel.sendMail has strange way to calculate debug setting
  • +
  • 52782 - Add a detail button on parameters table to show detail of a Row
  • +
  • 52674 - Proxy : Add a Sampler Creator to allow plugging HTTP based samplers using potentially non textual POST Body (AMF, Silverlight...) and customizing them for others
  • +
  • 52934 - GUI : Open Test plan with the tree expanded to the testplan level and no further and select the root of the tree
  • +
  • 52941 - Improvements of HTML report design generated by JMeter Ant task extra
  • +
  • 53042 - Introduce a new method in Sampler interface to allow Sampler to decide wether a config element applies to Sampler
  • +
  • 52771 - Documentation : Added RSS feed on JMeter Home page under link "Subscribe to What's New"
  • +
  • 42784 - Show the number of errors logged in the GUI
  • +
  • 53256 - Make Clear All command clean LogViewer content
  • +
  • 53261 - Make "Error/fatal" counter added in 42784 open Log Viewer panel when Warn Indicator is clicked
  • +
+ +

Non-functional changes

+
    +
  • Upgraded to rhino 1.7R3 (was js-1.7R2.jar). +Note: the Maven coordinates for the jar were changed from rhino:js to org.mozilla:rhino. +This does not affect JMeter directly, but might cause problems if using JMeter in a Maven project +with other code that depends on an earlier version of the Rhino Javascript jar. +
  • +
  • 52675 - Refactor Proxy and HttpRequestHdr to allow Sampler Creation by Proxy
  • +
  • 52680 - Mention version in which function was introduced
  • +
  • 52788 - HttpRequestHdr : Optimize code to avoid useless work
  • +
  • JMeter Ant (ant-jmeter-1.1.1.jar) task was upgraded from 1.0.9 to 1.1.1
  • +
  • Updated to commons-io 2.2 (from 2.1)
  • +
  • 53129 - Upgrade XStream from 1.3.1 to 1.4.2
  • +
  • Updated to httpcomponents-client 4.1.3 (from 4.1.2)
  • +
  • Updated JMeter distributed testing guide (jmeter_distributed_testing_step_by_step.pdf). Changes source format to OpenOffice odt (from sxw)
  • +
+ + + +

Version 2.6

+ +

New and Noteworthy

+ +

Toolbar

+

A new toolbar on JMeter's main window +

+

+ +

JMeter start test button

+

A new menu option and button allow to start a test ignoring the Pause Timers +

+

+ +

JMeter GUI Look and Feel

+

Allow System or CrossPlatform LAF to be set from options menu +

+

+ +

JMeter GUI - duplicate node

+

Add "duplicate node" in context menu +

+

+ +

JMeter tree view - search facility

+

Functionality to search by keyword in Samplers Tree View +

+

+ +

HTTP Request - raw request pane

+

Improve HTTP Request GUI to better show parameters without name (GWT RPC request or SOAP request for example) +

+

+ +

HTTP Request - other changes

+

    +
  • Allow multiple selection in arguments panel
  • +
  • Allow to add (paste) entries from the clipboard to an arguments list
  • +
  • Ability to move variables up or down in HTTP Request
  • +
+
+

+ +

HTTP Request - file protocol

+

Better support for file: protocol in HTTP sampler +

+

+

Retrieve embedded resources with file: protocol +

+

+ +

HTTP Request - Ignore embedded resources failed

+

Enable "ignore failed" for embedded resources +

+

+

Parent success with a embedded resource failed +

+

+ +

View Results in Table - child sample display

+

Add option to TableVisualiser to display child samples instead of parent +

+

+ +

Key Store - multiple certificates

+

Allowing multiple certificates (JKS) +

+

+ +

Aggregate graph improvements

+

Some improvements on Aggregate Graph Listener: +

  • new GUI for settings
  • +
  • dynamic graph size
  • +
  • allow to change fonts for title graph and legend
  • +
  • allow to change bar color (background and text values)
  • +
  • allow to draw or not bars outlines
  • +
  • allow to select only some samplers by a regexp filter
  • +
  • allow to define Y axis maximum scale
  • +
+
+

+ +

Aggregate Graph bar +

+

+ +

Counter - new reset option

+

Add an option to reset counter on each Thread Group iteration +

+

+ +

Functions

+

    +
  • Add a new function __RandomString to generate random Strings
  • +
  • Add a new function __TestPlanName returning the name of the current "Test Plan"
  • +
  • Add a new function __machineIP returning IP address
  • +
  • Add a new function __jexl2 to support Jexl2
  • +
+
+

+ +

User Defined Variable improvements

+

  • Add a comment field in User Defined Variables
  • +
  • Allow to add (paste) entries from the clipboard to an arguments list
  • +
  • Ability to move up or down variables in User Defined Variables
  • +
+
+

+ +

View Results Tree

+

In View Results Tree rather than showing just a message if the results are to big, show as much of the result as are configured +

+

+ +

Controllers - change elements

+

Add ability to Change Controller elements +

+

+ +

JDBC pre- and post-processor

+

Add JDBC pre- and post-processor +

+

+ +

JDBC transaction isolation option

+

Allow to set the transaction isolation in the JDBC Connection Configuration +

+

+ +

Poisson Timer

+

Add a Poisson based timer +

+

+ +

GUI and OS interaction

+

Support for file Drag and Drop. +

+

+ +

Confirm Remove Dialog box

+

Add a dialog box to confirm removing the element(s) when Remove action is called +

+The dialogue can be skipped by setting the JMeter property confirm.delete.skip=true +

+ +

Remote batching support

+

Use external store to hold samples during distributed testing, +Added DiskStore remote sample sender: like Hold, but saves samples to disk until end of test +

+

+ +

JMS Subscriber sampler

+

With JMS Subscriber, ability to use Selectors +

+

+ +

New Logger Panel

+

A new Log Viewer has been added to the GUI and can be enabled from menu Options > Log Viewer: +

+

+

This Log Viewer shows the jmeter.log file, and useful (for example) to debug BeanShell/BSF scripts: +

+

+ +

The menu item Options / Choose Language is now fully functional

+

+The menu item Options / Choose Language now changes all the displayed text to the new language provided +all messages are translated. You can help on this by translating into your language. +

+ +

Legacy JMX and JTL Avalon format support restored

+

+Support for reading/writing the original Avalon XML format of JMX (script) and JTL (sample result) files was dropped in JMeter version 2.4. +JMeter can now read the Avalon format files again, however there is no support for saving files in the old format. +

+ +

JMeter jars available from Maven repository

+

+JMeter jars are now available from Maven repository. +

+ + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode (see Bugs 40671, 41286, 44973, 50898). +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ + + +

Incompatible changes

+ +

+JMeter versions since 2.1 failed to create a container sample when loading embedded resources. +This has been corrected; can still revert to the 51939 behaviour by setting the following property: +httpsampler.separate.container=false +

+

+Mirror server now uses default port 8081, was 8080 before 2.5.1. +

+

+TCP Sampler handles SocketTimeoutException, SocketException and InterruptedIOException differently since 2.6, when +these occurs, Sampler is marked as failed. +

+

+Sample Sender implementations now resolve their configuration on Client side since 2.6. +This behaviour can be changed with property sample_sender_client_configured (set it to false). +

+ +

+The HTTP User Parameter Modifier test element has been removed; it has been deprecated for a long time. +

+ + + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 51932 - CacheManager does not handle cache-control header with any attributes after max-age
  • +
  • 51918 - GZIP compressed traffic produces errors, when multiple connections allowed
  • +
  • 51939 - Should generate new parent sample if necessary when retrieving embedded resources
  • +
  • 51942 - Synchronisation issue on CacheManager when Concurrent Download is used
  • +
  • 51957 - Concurrent get can hang if a task does not complete
  • +
  • 51925 - Calling Stop on Test leaks executor threads when concurrent download of resources is on
  • +
  • 51980 - HtmlParserHTMLParser double-counts images used in links
  • +
  • 52064 - OutOfMemory Risk in CacheManager
  • +
  • 51919 - Random ConcurrentModificationException or NoSuchElementException in CookieManager#removeMatchingCookies when using Concurrent Download
  • +
  • 52126 - HttpClient4 does not clear cookies between iterations
  • +
  • 52129 - Reported Body Size is wrong when using HTTP Client 4 and Keep Alive connection
  • +
  • 52137 - Problems with HTTP Cache Manager
  • +
  • 52221 - Nullpointer Exception with use Retrieve Embedded Resource without HTTP Cache Manager
  • +
  • 52310 - variable in IPSource failed HTTP request if "Concurrent Pool Size" is enabled
  • +
  • 52371 - API Incompatibility - Methods in HTTPSampler2 now require PostMethod instead of HttpMethod[Base]. Reverted to original types.
  • +
  • 49950 - Proxy : IndexOutOfBoundsException when recording with Proxy server
  • +
  • 52409 - HttpSamplerBase#errorResult modifies sampleResult passed as parameter; +fix code which assumes that a new instance is created (i.e. when adding a sub-sample) +
  • +
  • 52507 - Delete Http User Parameters modifier (deprecated, obsolete)
  • +
+ +

Other Samplers

+
    +
  • 51996 - JMS Initial Context leak newly created Context when Multiple Thread enter InitialContextFactory#lookupContext at the same time
  • +
  • 51691 - Authorization does not work for JMS Publisher and JMS Subscriber
  • +
  • 52036 - Durable Subscription fails with ActiveMQ due to missing clientId field
  • +
  • 52044 - JMS Subscriber used with many threads leads to javax.naming.NamingException: Something already bound with ActiveMQ
  • +
  • 52072 - LengthPrefixedBinaryTcpClientImpl may end a sample prematurely
  • +
  • 52390 - AbstractJDBCTestElement:Memory leak and synchronization issue in perConnCache
  • +
+ +

Controllers

+
    +
  • 51865 - Infinite loop inside thread group does not work properly if "Start next loop after a Sample error" option set
  • +
  • 51868 - A lot of exceptions in jmeter.log while using option "Start next loop" for thread
  • +
  • 51866 - Counter under loop doesn't work properly if "Start next loop on error" option set for thread group
  • +
  • 52296 - TransactionController + Children ThrouputController or InterleaveController leads to ERROR sampleEnd called twice java.lang.Throwable: Invalid call sequence when TPC does not run sample
  • +
  • 52330 - With next-Loop-On-Error after error samples are not executed in next loop
  • +
+ +

Listeners

+
    +
  • 52357 - View results in Table does not allow for multiple result samples
  • +
  • 52491 - Incorrect parsing of Post data parameters in Tree Listener / Http Request view
  • +
+ +

Assertions

+
    +
  • 52519 - XMLSchemaAssertion uses JMeter JVM file.encoding instead of response encoding
  • +
+ +

Functions

+
    +
  • The CRLF example for the char function was wrong; CRLF=(0xD,0xA), not (0xC,0xA)
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 51937 - JMeter does not handle missing TestPlan entry well
  • +
  • 51988 - CSV Data Set Configuration does not resolve default delimiter for header parsing when variables field is empty
  • +
  • 52003 - View Results Tree "Scroll automatically" does not scroll properly in case nodes are expanded
  • +
  • 27112 - User Parameters should use scrollbars
  • +
  • 52029 - Command-line shutdown only gets sent to last engine that was started
  • +
  • 52093 - Toolbar ToolTips don't switch language
  • +
  • 51733 - SyncTimer is messed up if you a interrupt a test plan
  • +
  • 52118 - New toolbar : shutdown and stop buttons not disabled when no test is running
  • +
  • 52125 - StatCalculator.addAll(StatCalculator calc) joins incorrect if there are more samples with the same response time in one of the TreeMap
  • +
  • 52339 - JMeter Statistical mode in distributed testing shows wrong response time
  • +
  • 52215 - Confusing synchronization in StatVisualizer, SummaryReport ,Summariser and issue in StatGraphVisualizer
  • +
  • 52216 - TableVisualizer : currentData field is badly synchronized
  • +
  • 52217 - ViewResultsFullVisualizer : Synchronization issues on root and treeModel
  • +
  • 43294 - XPath Extractor namespace problems
  • +
  • 52224 - TestBeanHelper does not support NOT_UNDEFINED == Boolean.FALSE
  • +
  • 52279 - Switching to another language loses icons in Tree and logs error Can't obtain GUI class from ...
  • +
  • 52280 - The menu item Options / Choose Language does not change all the displayed text to the new language
  • +
  • 52376 - StatCalculator#addValue(T val, int sampleCount) should use long, not int
  • +
  • 49374 - Encoding of embedded element URLs depend on the file.encoding property
  • +
  • 52399 - URLRewritingModifier uses default file.encoding to match text content
  • +
  • 50438 - code calculates average with integer math, expecting double value
  • +
  • 52469 - Changes in Support of SSH-Tunneling of RMI traffic for Remote Testing
  • +
  • 52466 - Upgrade Test Plan feature : NameUpdater does not upgrade properties
  • +
  • 52503 - Unify File->Close and Window close file saving behaviour
  • +
  • 52537 - Help does not scroll to correct anchor when file is first loaded
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 51981 - Better support for file: protocol in HTTP sampler
  • +
  • 52033 - Allowing multiple certificates (JKS)
  • +
  • 52352 - Proxy : Support IPv6 URLs capture
  • +
  • 44301 - Enable "ignore failed" for embedded resources
  • +
+ +

Other samplers

+
    +
  • 51419 - JMS Subscriber: ability to use Selectors
  • +
  • 52088 - JMS Sampler : Add a selector when REQUEST / RESPONSE is chosen
  • +
  • 52104 - TCP Sampler handles badly errors
  • +
  • 52087 - TCPClient interface does not allow for partial reads
  • +
  • 52115 - SOAP/XML-RPC should not send a POST request when file to send is not found
  • +
  • 40750 - TCPSampler : Behaviour when sockets are closed by remote host
  • +
  • 52396 - TCP Sampler in "reuse connection mode" reuses previous sampler's connection even if it's configured with other host, port, user or password
  • +
  • 52048 - BSFSampler, BSFPreProcessor and BSFPostProcessor should share the same GUI
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • 52022 - In View Results Tree rather than showing just a message if the results are to big, show as much of the result as are configured
  • +
  • 52201 - Add option to TableVisualiser to display child samples instead of parent
  • +
  • 52214 - Save Responses to a file - improve naming algorithm
  • +
  • 52340 - Allow remote sampling mode to be changed at run-time
  • +
  • 52452 - Improvements on Aggregate Graph Listener (GUI and settings)
  • +
  • Resurrected OldSaveService to allow reading Avalon format JTL (result) files
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 52128 - Add JDBC pre- and post-processor
  • +
  • 52183 - SyncTimer could be improved (performance+reliability)
  • +
  • 52317 - Counter : Add option to reset counter on each Thread Group iteration
  • +
  • 37073 - Add a Poisson based timer
  • +
  • 52497 - Improve DebugSampler and DebugPostProcessor
  • +
+ +

Functions

+
    +
  • 52006 - Create a function RandomString to generate random Strings
  • +
  • 52016 - It would be useful to support Jexl2
  • +
  • __char() function now supports octal values
  • +
  • New function __machineIP returning IP address
  • +
  • 51091 - New function returning the name of the current "Test Plan"
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 51892 - Default mirror port should be different from default proxy port
  • +
  • 51817 - Moving variables up and down in User Defined Variables control
  • +
  • 51876 - Functionality to search in Samplers TreeView
  • +
  • 52019 - Add menu option to Start a test ignoring Pause Timers
  • +
  • 52027 - Allow System or CrossPlatform LAF to be set from options menu
  • +
  • 52037 - Remember user-set LaF over restarts.
  • +
  • 51861 - Improve HTTP Request GUI to better show parameters without name (GWT RPC requests for example) (UNDER DEVELOPMENT)
  • +
  • 52040 - Add a toolbar in JMeter main window
  • +
  • 51816 - Comment Field in User Defined Variables control.
  • +
  • 52052 - Using a delimiter to separate result-messages for JMS Subscriber
  • +
  • 52103 - Add automatic scrolling option to table visualizer
  • +
  • 52097 - Save As should point to same folder that was used to open a file if MRU list is used
  • +
  • 52085 - Allow multiple selection in arguments panel
  • +
  • 52099 - Allow to set the transaction isolation in the JDBC Connection Configuration
  • +
  • 52116 - Allow to add (paste) entries from the clipboard to an arguments list
  • +
  • 52160 - Don't display TestBeanGui items which are flagged as hidden
  • +
  • 51886 - SampleSender configuration resolved partly on client and partly on server
  • +
  • 52161 - Enable plugins to add own translation rules in addition to upgrade.properties. +Loads any additional properties found in META-INF/resources/org.apache.jmeter.nameupdater.properties files
  • +
  • 42538 - Add "duplicate node" in context menu
  • +
  • 46921 - Add Ability to Change Controller elements
  • +
  • 52240 - TestBeans should support Boolean, Integer and Long
  • +
  • 52241 - GenericTestBeanCustomizer assumes that the default value is the empty string
  • +
  • 52242 - FileEditor does not allow output to be saved in a File
  • +
  • 51093 - when loading a selection previously stored by "Save Selection As", show the file name in the blue window bar
  • +
  • 50086 - Password fields not Hidden in JMS Publisher, JMS Subscriber, Mail Reader sampler, SMTP sampler and Database Configuration
  • +
  • 29352 - Use external store to hold samples during distributed testing, Added DiskStore remote sample sender: like Hold, but saves samples to disk until end of test.
  • +
  • 52333 - Reduce overhead in calculating SampleResult#nanoTimeOffset
  • +
  • 52346 - Shutdown detects if there are any non-daemon threads left which prevent JVM exit.
  • +
  • 52281 - Support for file Drag and Drop
  • +
  • 52471 - Improve Mirror Server performance by Using Pool of threads instead of launching a Thread for each request
  • +
  • Resurrected OldSaveService to allow reading Avalon format JMX files (removed in 2.4)
  • +
  • Add a dialog box to confirm removing the element(s) when Remove action is called
  • +
  • 41788 - Log viewer (console window) needed as an option
  • +
  • Add option to change the pause time (default 2000ms) in the daemon thread which checks for successful JVM exit. +The thread is not now started unless the pause time is greater than 0. +
  • +
+ +

Non-functional changes

+
    +
  • fixes to build.xml: support scripts; localise re-usable property names
  • +
  • 51923 - Counter function bug or documentation issue ? (fixed docs)
  • +
  • Update velocity.jar to 1.7 (from 1.6.2)
  • +
  • Update js.jar to 1.7R3 (from 1.6R5)
  • +
  • Update commons-codec 1.5 => 1.6
  • +
  • Update commons-io 2.0.1 => 2.1
  • +
  • Update commons-jexl 2.0.1 => 2.1.1
  • +
  • Update jdom 1.1 => 1.1.2
  • +
  • Update junit 4.9 => 4.10
  • +
  • 51954 - Generated documents include </br> entries which cause extra blank lines
  • +
  • 52075 - JMeterProperty.clone() currently returns Object; it should return JMeterProperty
  • +
  • Updated httpcore to 4.1.4
  • +
  • 49753 - Please publish jMeter artifacts on Maven central repository
  • +
+ + + +

Version 2.5.1

+ +

Summary of main changes

+ +
    +
  • HttpClient4 sampler now re-uses connections properly (previously it would use one per sample, which could quickly cause resource exhaustion).
  • +
  • Various fixes to JMS samplers
  • +
  • Functions are no longer spuriously invoked when used with a Configuration element
  • +
  • WebService sampler GUI has been re-organized for better design and more user-friendliness. Some improments on WSDL configuration assistant
  • +
  • Better handling of test shutdown. System.exit now only called if there is no other option; even this can be disabled.
  • +
+ + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

The If Controller may cause an infinite loop if the condition is always false from the first iteration. +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+ +

+The HttpClient4 and Commons HttpClient 3.1 samplers previously used a retry count of 3. +This has been changed to default to 1, to be compatible with the Java implementation. +The retry count can be overridden by setting the relevant JMeter property, for example: +

+httpclient4.retrycount=3
+httpclient3.retrycount=3
+
+

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • Fix HttpClient 4 sampler so it reuses HttpClient instances and connections where possible.
  • +
  • Temporary fix to HC4 sampler to work round HTTPCLIENT-1120.
  • +
  • 51863 - Lots of ESTABLISHED connections with HttpClient 4 implementation (vs HttpClient 3.1 impl)
  • +
  • 51750 - Retrieve all embedded resources doesn't follow IFRAME
  • +
  • 51752 - HTTP Cache is broken when using "Retrieve all embedded resources" with concurrent pool
  • +
  • 39219 - HTTP Server: You can't stop it after File->Open
  • +
  • 51775 - Port number duplicates in Host header when capturing by HttpClient (3.1 and 4.x)
  • +
  • 50617 - Monitor Results legend show "dead" server although values from the server are retrieved
  • +
+ +

Other Samplers

+
    +
  • 50424 - Web Methods drop down list box inconsistent
  • +
  • 43293 - Java Request fields not cleared when creating new sampler
  • +
  • 51830 - Webservice Soap Request triggers too many popups when Webservice WSDL URL is down
  • +
  • WebService(SOAP) request - add a connect timeout to get the wsdl used to populate Web Methods when server doesn't response
  • +
  • 51841 - JMS : If an error occurs in ReceiveSubscriber constructor or Publisher, then Connections will stay open
  • +
  • 51691 - Authorization does not work for JMS Publisher and JMS Subscriber
  • +
  • 51840 - JMS : Cache of InitialContext has some issues
  • +
  • 47888 - JUnit Sampler re-uses test object
  • +
+ +

Controllers

+
    +
  • If Controller - Fixed two regressions introduced by 50032 (see 50618 too)
  • +
  • If Controller - Catches a StackOverflowError when a condition returns always false (after at least one iteration with return true) See 50618
  • +
  • 51869 - NullPointer Exception when using Include Controller
  • +
+ +

Listeners

+
    +
+ +

Assertions

+
    +
+ +

Functions

+
    +
  • 48943 - Functions are invoked additional times when used in combination with a Config Element
  • +
+ +

I18N

+
    +
  • WebService(SOAP) request - add I18N for some labels
  • +
+ +

General

+
    +
  • 51831 - Cannot disable UDP server or change the maximum UDP port
  • +
  • 51821 - Add short-cut for Enabling / Disabling (sub)tree or branches in test plan.
  • +
  • 47921 - Variables not released for GC after JMeterThread exits.
  • +
  • 51839 - "... end of run" printed prematurely
  • +
  • 51847 - Some Junit tests are Locale sensitive and fail if Locale is different from US
  • +
  • 51855 - Parent samples may have slightly inaccurate elapsed times
  • +
  • 51880 - The shutdown command is not working if I invoke it before all the thread are started
  • +
  • Remote Shut host menu item was not being enabled.
  • +
  • 51888 - Occasional deadlock when stopping a testplan
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 51380 - Control reuse of cached SSL Context from iteration to iteration
  • +
  • 51882 - HTTPHC3Client uses a default retry count of 3, make it configurable; default is now 1
  • +
  • Change the default HttpClient 4 sampler retry count to 1
  • +
+ +

Other samplers

+
    +
  • Beanshell Sampler now supports Interruptible interface
  • +
  • 51605 - WebService(SOAP) Request - WebMethod field value changes surreptitiously for all the requests when a value is selected in a request
  • +
  • WebService(SOAP) Request - Reorganized GUI for better design and more user-friendliness
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • 42246 - Need for a 'auto-scroll' option in "View Results Tree" and "Assertion Results"
  • +
  • View Results Tree: Regexp Tester - little improvements on user interface
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 51885 - Allow a JMeter Variable as input to XPathExtractor
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • 51822 - (part 1) save 1 invocation of GuiPackage#getCurrentGui
  • +
  • Added AsynchSampleSender which sends samples from server to client asynchronously.
  • +
  • Upgraded to htmlparser 2.1; JavaMail 1.4.4; JUnit 4.9
  • +
+ +

Non-functional changes

+
    +
  • 49976 - FormCharSetFinder visibility is default instead of public.
  • +
  • 50917 - Property CookieManager.save.cookies not honored when set from test plan
  • +
  • Improve error logging when Javascript errors are detected.
  • +
  • Updated documentation footer
  • +
+ + + +

Version 2.5

+ +

Summary of main changes

+ +
    +
  • The HTTP implementation can now be selected at run-time, and JMeter now also supports Apache HttpComponents HttpClient 4.x. +Note that Commons HttpClient 3.1 is no longer actively developed, and support may be removed from JMeter in a future release. +
  • +
  • The HTTP sampler now allows concurrent downloads of embedded resources in an HTML page
  • +
  • The HTTP Sampler can now report the size of a request before decompression.
  • +
  • The JMS and Mail samplers have been improved.
  • +
  • The new Test Fragment Test Element makes using Include Controllers easier
  • +
  • There are various improvements to the View Results Tree Listener
  • +
  • 30563 - Thread Group should have a start next loop option on Sample Error
  • +
  • There are two new Thread Group types - setUp and tearDown - which are run before and after the main Thread groups.
  • +
  • Client-Server mode now supports external stop/shutdown via UDP

    +multiple JMeter server instances can be started on the same host without needing to change the port property.
  • +
  • 50516 - "Host" header in HTTP Header Manager is not included in generated HTTP request
  • +
+ +

+

    +
+

+ + + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+ +

+Unsupported methods are no longer converted to GET by the Commons HttpClient sampler. +

+ +

+Removed method public static long currentTimeInMs(). +This has been replaced by the instance method public long currentTimeInMillis(). +

+ +

+ProxyControl.getSamplerTypeName() now returns a String rather than an int. +This is internal to the workings of the JMeter Proxy & its GUI, so should not affect any user code. +

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 50178 - HeaderManager added as child of Thread Group can create concatenated HeaderManager names and OutOfMemoryException
  • +
  • 50392 - value is trimmed when sending the request in Multipart
  • +
  • 50686 - HeaderManager logging too verbose when merging instances
  • +
  • 50963 - AjpSampler throws java.lang.StringIndexOutOfBoundsException
  • +
  • 50516 - "Host" header in HTTP Header Manager is not included in generated HTTP request
  • +
  • 50544 - In Apache Common Log the HEAD requests cause problems.
  • +
  • 51268 - HTTPS request through an invalid proxy causes NullPointerException and does not show in result tree. +Rather than delegating to the JMeter thread handler for "unexpected" failures, ensure all Exceptions generate a sample error. +
  • +
  • 51275 - Cookie Panel clearGui() sets incorrect default policy in Java 1.6
  • +
+ +

Other Samplers

+
    +
  • 50173 - JDBCSampler discards ResultSet from a PreparedStatement
  • +
  • Ensure JSR223 Sampler has access to the current SampleResult
  • +
  • 50977 - Unable to set TCP Sampler for individual samples
  • +
+ +

Controllers

+
    +
  • 50032 - Last_Sample_Ok along with other controllers doesnt work correctly when the threadgroup has multiple loops
  • +
  • 50080 - Transaction controller incorrectly creates samples including timer duration
  • +
  • 50134 - TransactionController : Reports bad response time when it contains other TransactionControllers
  • +
+ +

Listeners

+
    +
  • 50367 - Clear / Clear all in View results tree does not clear selected element
  • +
+ +

Assertions

+
    +
  • 51488 - Assertion: Variable name scope is shared among all assertions (and 51255)
  • +
+ +

Functions

+
    +
  • 50568 - Function __FileToString(): Could not read file when encoding option is blank/empty
  • +
+ +

I18N

+
    +
  • 50811 - Incomplete Spanish translation
  • +
+ +

General

+
    +
  • 49734 - Null pointer exception on stop Threads command (Run>Stop)
  • +
  • 49666 - CSV Header read as data after EOF
  • +
  • 45703 - Synchronizing Timer
  • +
  • 50088 - fix getAvgPageBytes in SamplingStatCalculator so it returns what it should
  • +
  • 50203 Cannot set property "jmeter.save.saveservice.default_delimiter=\t"
  • +
  • mirror-server.sh - fix classpath to use : separator (not ;)
  • +
  • 50286 - URL Re-writing Modifier: extracted jsessionid value is incorrect when is between XML tags
  • +
  • +System.nanoTime() tends to drift relative to System.currentTimeMillis(). +Change SampleResult to recalculate offset each time. +Also enable reversion to using System.currentTimeMillis() only. +
  • +
  • 50425 - Remove thread groups from Controller add menu
  • +
  • +50675 - CVS Data Set Config incompatible with Remote Start +Fixed RMI startup to provide location of JMX file relative to user.dir. +
  • +
  • 50221 - Renaming elements in the tree does not resize label
  • +
  • 51002 - Stop Thread if CSV file is not available. JMeter now treats IOError as EOF.
  • +
  • Define sun.net.http.allowRestrictedHeaders=true by default. This fixes 51238.
  • +
  • 51645 - CSVDataSet does not read UTF-8 files when file.encoding is UTF-8
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • AJP Sampler now implements Interruptible
  • +
  • Allow HTTP implementation to be selected at run-time
  • +
  • 50684 - Optionally disable Content-Type and Transfer-Encoding in Multipart POST
  • +
  • 50943 - Allowing concurrent downloads of embedded resources in html page
  • +
  • 50170 - Bytes reported by http sampler is after GUnZip

    Add optional properties to allow change the method to get response size
  • +
  • Hiding the proxy password on HTTP Sampler (just on GUI, not in JMX file)
  • +
+ +

Other samplers

+
    +
  • 49622 - Allow sending messages without a subject (SMTP Sampler)
  • +
  • 49603 - Allow accepting expired certificates on Mail Reader Sampler
  • +
  • 49775 - Allow sending messages without a body
  • +
  • 49862 - Improve SMTPSampler Request output.
  • +
  • 50268 - Adds static and dynamic destinations to JMS Publisher
  • +
  • JMS Subscriber - Add dynamic destination
  • +
  • 50666 - JMSSubscriber: support for durable subscriptions
  • +
  • 50937 - TCP Sampler does not provide for / honor connect timeout
  • +
  • 50569 - Jdbc Request Sampler to optionally store result set object data
  • +
  • 51011 - Mail Reader: upon authentication failure, tell what you tried
  • +
+ +

Controllers

+
    +
  • 50475 - Introduction of a Test Fragment Test Element for a better Include flow
  • +
+ +

Listeners

+
    +
  • View Results Tree - Add a dialog's text box on "Sampler result tab > Parsed" to display the long value with a double click on cell
  • +
  • 37156 - Formatted view of Request in Results Tree
  • +
  • 49365 - Allow result set to be written to file in a path relative to the loaded script
  • +
  • 50579 - Error count is long, sample count is int. Changed sample count to long.
  • +
  • View Results Tree - Add new size fields: response headers and response body (in bytes) - derived from 43363
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 48015 - Proposal new icons for pre-processor, post-processor and assertion elements
  • +
  • 50962 - SizeAssertionGui validation prevents the use of variables for the size
  • +
  • Size Assertion - Add response size scope (full, headers, body, code, message) - derived from 43363
  • +
+ +

Functions

+
    +
  • 49975 - New function returning the name of the current sampler
  • +
+ +

I18N

+
    +
  • Add French translation for the new labels and reduce size for some labels (by abbreviation) on HTTP Sample
  • +
+ +

General

+
    +
  • 30563 - Thread Group should have a start next loop option on Sample Error
  • +
  • 50347 - Eclipse setup instructions should remind user to download dependent jars
  • +
  • 50490 - Setup and Post Thread Group enhancements for better test flow.
  • +
  • All BeansShell test elements now have the script variables "prev" and "Label" defined.
  • +
  • 50708 - Classpath jar order in NewDriver not alphabetically
  • +
  • 50659 - JMeter server does not support concurrent tests - prevent client from starting another
  • +
  • Added remote shutdown functionality
  • +
  • Client JMeter engine now supports external stop/shutdown via UDP
  • +
  • UDP shutdown can now use a range of ports, from jmeterengine.nongui.port=4445 to jmeterengine.nongui.maxport=4455, +allowing multiple JMeter instances on the same host without needing to change the port property.
  • +
  • Updated to httpcore 4.1.3 and httpclient 4.1.2
  • +
+ +

Non-functional changes

+
    +
  • 50008 - Allow BatchSampleSender to be subclassed
  • +
  • 50450 - use System.array copy in jacobi solver as, being native, is more performant.
  • +
  • 50487 - runSerialTest verifies objects that never need persisting
  • +
  • Use Thread.setDefaultUncaughtExceptionHandler() instead of private ThreadGroup
  • +
  • Update to Commons Net 3.0
  • +
+ + + +

Version 2.4

+ +

Summary of main changes

+ +

+

    +
  • JMeter now requires at least Java 1.5.
  • +
  • HTTP Proxy can now record HTTPS sessions.
  • +
  • JUnit sampler now supports JUnit4 annotations.
  • +
  • Added JSR223 (javax.script) test elements.
  • +
  • MailReader Sampler can now use any protocol supported by the underlying implementation.
  • +
  • An SMTP Sampler has been added.
  • +
  • JMeter now allows users to provide their own Thread Group implementations.
  • +
  • View Results Tree now supports more display options, including search and Regex Testing.
  • +
  • StatCalculator performance is much improved; Aggregate Report etc. need far less memory.
  • +
  • +JMS samplers have been extensively reworked, and should no longer lose messages. +Correlation processing is improved. +JMS Publisher and Subscriber now support both Topics and Queues. +
  • +
  • Many other improvements have been made, please see below and in the manual.
  • +
+

+ + + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+ +

+HTTP Redirect now defaults to "Follow Redirects" rather than "Redirect Automatically". +This is to enable JMeter to track cookies that may be sent during redirects. +This does not affect existing test plans; it only affects the default for new HTTP Samplers. +

+ +

+The Avalon file format for JMX and JTL files is no longer supported. +Any such files will need to be converted by reading them in JMeter 2.3.4 and resaving them. +

+ +

+The XPath Assertion and XPath Extractor elements no longer fetch external DTDs by default; this can be changed in the GUI. +

+ +

+JMSConfigGui has been renamed as JMSSamplerGui. +This does not affect existing test plans. +

+ +

+The constructor public SampleResult(SampleResult res) has been changed to become a true "copy constructor". +It no longer calls addSubResult(). This may possibly affect some 3rd party add-ons. +

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 47445 - Using Proxy with https-spoofing secure cookies need to be unsecured
  • +
  • 47442 - Missing replacement of https by http for certain conditions using https-spoofing
  • +
  • 48451 - Error in: SoapSampler.setPostHeaders(PostMethod post) in the else branch
  • +
  • 48542 - SoapSampler uses wrong response header field to decide if response is gzip encoded
  • +
  • 48568 - CookieManager broken for AjpSampler
  • +
  • 48570 - AjpSampler doesn't support query parameters (GET/POST)
  • +
  • 46901 - HTTP Sampler does not process var/func refs correctly in first file parameter
  • +
  • 43678 - Handle META tag http-equiv charset?
  • +
  • 49294 - Images not downloaded from redirected-to pages
  • +
  • 49560 - wrong "size in bytes" when following redirections
  • +
+ +

Other Samplers

+
    +
  • 47420 - LDAP extended request not closing connections during add request
  • +
  • 48573 - LDAPExtSampler directory context handling
  • +
  • 47870 - JMSSubscriber fails due to NPE
  • +
  • 47899 - NullPointerExceptions in JMS ReceiveSubscriber constructor
  • +
  • 48144 - NPE in JMS OnMessageSubscriber
  • +
  • 47992 - JMS Point-to-Point Request - Response option doesn't work
  • +
  • 48579 - Single Bind does not show config information when LdapExt Sampler is accessed
  • +
  • 49111 - "Message With ID Not Found" Error on JMS P2P sampler.
  • +
  • 47949 - JMS Subscriber never receives all the messages
  • +
  • 46142 - JMS Point-to-Point correlation problems
  • +
  • 48747 - TCP Sampler swallows exceptions
  • +
  • 48709 - TCP Sampler Config setting "classname" has no effect
  • +
+ +

Controllers

+
    +
  • 47385 - TransactionController should set AllThreads and GroupThreads
  • +
  • 47940 - Module controller incorrectly creates the replacement Sub Tree
  • +
  • 47592 - Run Thread groups consecutively with "Stop test" on error, JMeter will not mark to finished
  • +
  • 48786 - Run Thread groups consecutively: with "Stop test now" on error or manual stop, JMeter leaves the green box active
  • +
  • 48727 - Cannot stop test if all thread groups are disabled
  • +
+ +

Listeners

+
    +
  • 48603 - Mailer Visualiser sends two emails for a single failed response
  • +
  • Correct calculation of min/max/std.dev for aggregated samples (Summary Report)
  • +
  • 48889 - Wrong response time with mode=Statistical and num_sample_threshold > 1
  • +
  • 47398 - SampleEvents are sent twice over RMI in distributed testing and non gui mode
  • +
+ +

Assertions

+
    +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • 47646 - NullPointerException in the "Random Variable" element
  • +
  • Disallow adding any child elements to JDBC Configuration
  • +
  • BeanInfoSupport now caches getBeanDescriptor() - should avoid an NPE on non-Sun JVMs when using CSVDataSet (and some other TestBeans)
  • +
  • 48350 - Deadlock on distributed testing with 2 clients
  • +
  • 48901 - Endless wait by adding Synchronizing Timer
  • +
  • 49149 - usermanual/index.html has typo in link to "Regular Expressions" page
  • +
  • 49394 - Classcast Exception in ActionRouter.postActionPerformed
  • +
  • 48136 - Essential files missing from source tarball.
    +Source archives now contain all source files, including source files previously only provided in the binary archives. +
  • +
  • 48331 - XpathExtractor does not return XML string representations for a Nodeset
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 47622 - enable recording of HTTPS sessions
  • +
  • Allow Proxy Server to be specified on HTTP Sampler GUI and HTTP Config GUI
  • +
  • 47461 - Update Cache Manager to handle Expires HTTP header
  • +
  • 48153 - Support for Cache-Control and Expires headers
  • +
  • 47946 - Proxy should enable Grouping inside a Transaction Controller
  • +
  • 48300 - Allow override of IP source address for HTTP HttpClient requests
  • +
  • 49083 - collapse '/pathsegment/..' in redirect URLs
  • +
+ +

Other samplers

+
    +
  • JUnit sampler now supports JUnit4 tests (using annotations)
  • +
  • 47900 - Allow JMS SubscriberSampler to be interrupted
  • +
  • Added JSR223 Sampler
  • +
  • 47556 - JMS-PointToPoint-Sampler Timeout field should use Strings
  • +
  • 47947 - Mail Reader Sampler should allow port to be overridden
  • +
  • 48155 - Multiple problems / enhancements with JMS protocol classes
  • +
  • Allow MailReader sampler to use arbitrary protocols
  • +
  • 45053 - SMTP-Sampler for JMeter
  • +
  • 49552 - Add Message Headers on SMTPSampler
  • +
  • +JMS Publisher and Subscriber now support both Topics and Queues. +Added read Timeout to JMS Subscriber. +General clean-up of JMS code. +
  • +
+ +

Controllers

+
    +
  • 47909 - TransactionController should sum the latency
  • +
  • 41418 - Exclude timer duration from Transaction Controller runtime in report
  • +
  • 48749 - Allowing custom Thread Groups
  • +
  • 43389 - Allow Include files to be found relative to the current JMX file
  • +
+ +

Listeners

+
    +
  • Added DataStrippingSample sender - supports "Stripped" and "StrippedBatch" modes.
  • +
  • Added Comparison Assertion Visualizer
  • +
  • 47907 - Improvements (enhancements and I18N) Comparison Assertion and Comparison Visualizer
  • +
  • 36726 - add search function to Tree View Listener
  • +
  • 47869 - Ability to cleanup fields of SampleResult
  • +
  • 47952 - Added JSR223 Listener
  • +
  • 47474 - View Results Tree support for plugin renderers
  • +
  • Allow Idle Time to be saved to sample log files
  • +
  • 48259 - Improve StatCalculator performance by using TreeMap
  • +
  • Listeners using SamplingStatCalculator have much reduced memory needs +as the Sample cache has been moved to the new CachingStatCalculator class. +In particular, Aggregate Report can now handle large numbers of samples. +
  • +
  • Aggregate Report and Summary Report now allow column headers to be optionally excluded
  • +
  • 49506 - Add .csv File Extension in open dialog box from "read from file" functionality of listeners
  • +
  • 49545 - Formatted (parsed) view of Sample Result in Results Tree
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 47338 - XPath Extractor forces retrieval of document DTD
  • +
  • Added Comparison Assertion
  • +
  • 47952 - Added JSR223 PreProcessor and PostProcessor
  • +
  • Added JSR223 Assertion
  • +
  • Added BSF Timer and JSR223 Timer
  • +
  • 48511 - add parent,child,all selection to regex extractor
  • +
  • Add Sampler scope selection to XPathExtractor
  • +
  • Regular Expression Extractor, Response Assertion and Size Assertion can now be applied to a JMeter variable
  • +
  • 46790 - CSV Data Set Config should be able to parse CSV headers
  • +
+ +

Functions

+
    +
  • 47565 - [Function] FileToString
  • +
+ +

I18N

+
    +
  • 47938 - Adding some French translations for new elements
  • +
  • 48714 - add new French messages
  • +
+ +

General

+
    +
  • 47223 - Slow Aggregate Report Performance (StatCalculator)
  • +
  • 47980 - hostname resolves to 127.0.0.1 - specifiying IP not possible
  • +
  • 47943 - DisabledComponentRemover is not used in Start class
  • +
  • HeapDumper class for runtime generation of dumps
  • +
  • Basic read-only JavaMail provider implementation for reading raw mail files
  • +
  • 49540 - Sort "Add" menus alphabetically
  • +
+ +

Non-functional changes

+
    +
  • Beanshell, JavaMail and JMS API (Apache Geronimo) jars are now included in the binary archive.
  • +
  • Add TestBean Table Editor support
  • +
  • Removed all external libraries from SVN; added download_jars Ant target
  • +
  • Updated various jar files: +
      +
    • BeanShell - 2.0b4 => 2.0b5
    • +
    • Commons Codec - 1.3 => 1.4
    • +
    • Commons-Collections - 3.2 => 3.2.1
    • +
    • JTidy => r938
    • +
    • JUnit - 3.8.2 => 4.8.1
    • +
    • Logkit - 1.2 => 2.0
    • +
    • Xalan Serializer = 2.7.1 (previously erroneously shown as 2.9.1)
    • +
    • Xerces xml-apis = 1.3.04 (previously erroneously shown as 2.9.1)
    • +
    • Some jar files were renamed.
    • +
    +
  • +
+ + + +

Version 2.3.4

+ +

Summary of main changes

+ +

+This is a minor bug-fix release, mainly to correct some bugs that were accidentally added in 2.3.3. +

+ + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 47321 - HTTPSampler2 response timeout not honored
  • +
+ +

Other Samplers

+
    +
  • 47290 - Infinite loop on connection factory lookup (JMS)
  • +
  • JDBC Sampler should not close Prepared or Callable statements as these are cached
  • +
+ +

Controllers

+
    +
  • 39509 - Once-only controller running twice
  • +
+ +

Listeners

+
    +
  • Change ResultCollector to only warn if the directory was not created
  • +
  • Fix some synchronisation issues in ResultCollector and SampleResult (wrong locks were being used)
  • +
+ +

I18N

+
    +
  • Fixed bug introduced in 2.3.3: JMeter does not start up if there is no messages.properties file for the default Locale.
  • +
+ +

General

+
    +
  • Fix problems with remote clients - bug introduced in 2.3.3
  • +
  • 47377 - Make ClassFinder more robust and close zipfile resources
  • +
  • Fix some errors in generating the documentation (latent bug revealed in 2.3.3 when Velocity was upgraded)
  • +
+ +

Improvements

+ +

Other samplers

+
    +
  • 47266 - FTP Request Sampler: allow specifying an FTP port, other than the default
  • +
+ + + +

Version 2.3.3

+ +

Summary of main changes

+ +

+The handling of test closedown is much improved. +The gradual "Shutdown" command now waits until all threads have stopped, +and does not report an error if threads don't stop within 5 seconds. +The immediate "Stop" command can now be used if "Shutdown" takes too long. +Also the immediate "Stop" command is able to interrupt samplers which support the new Interruptible interface (e.g. HTTP and SOAP, FTP). +This allows immediate completion of pending responses. +Non-GUI mode tests can also now be sent a "Shutdown" or "Stop" message. + now supports a "Stop Now" action, +as do the and Post Processor elements. +

+ +

+HTTP Cookie handling is improved, and HTTP POST can now use variable file names correctly. +HTTP, SOAP/XML-RPC and WebService(SOAP) sampler character encodings updated to be more consistent. +HTTP Samplers now support connection and response timeouts (requires JVM 1.5 for the HTTP Java sampler). +Together with the closedown improvements described above, this should avoid most cases where a test run hangs. +Multiple Header Manager elements are now supported for a single HTTP sampler. +The Proxy Server is improved, and no longer stores "Host" headers by default. +

+ +

+JDBC Request can optionally save the results of Select statements to variables. +JDBC Request now handles quoted strings and UTF-8, and can handle arbitrary variable types. +

+ +

+There are several new functions: +__char() function: allows arbitrary Unicode characters to be entered in fields. +__unescape() function: allows Java-escaped strings to be used. +_unescapeHtml() function: decodes Html-encoded text. +__escapeHtml() function: encodes text using Html-encoding. +A reference to a missing function - e.g. ${__missing(a)} - is now treated the same as a missing variable. +Previously the function name - and leading { - were dropped. This makes it easier to debug test plans. +

+ +

+Some Assertions can now be applied to sub-samples as well as (or instead of) just the parent sample. +There is a new Configuration element. +

+ +

+JMS samplers are much improved (see details below). The now supports some additional clients and is a bit more flexible. +

+ +

+Client-server mode has been improved, and the server can optionally use a fixed RMI port, which should help with setting up firewalls. +

+ +

+Various I18N changes have been made; language change works better (though not perfect yet). +There are improved French translations as well as new Polish and Brazilian Portugese translations. +

+ +

+The BeanShell jar is now included with the binary archive; there is no need to download it separately. +

+ + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+

+When loading sample results from a file, previous results are no longer cleared. +This allows one to merge multiple files. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. +

+

+The test elements "Save Results to a file" and "Generate Summary Results" are now shown as Listeners. +They were previously shown as Post-Processors, even though they are implemented as Listeners. +

+

+The Cookie Manager no longer saves incoming cookies as variables by default. +To save cookies as variables, define the property "CookieManager.save.cookies=true". +Also, cookies names are prefixed with "COOKIE_" before they are stored (this avoids accidental corruption of local variables) +To revert to the original behaviour, define the property "CookieManager.name.prefix= " (one or more spaces). +

+

+The Counter element is now shown as a Configuration element. +It was previously shown as a Pre-Processor, even though it is implemented as a Config item. +

+

+The above changes only affect the icons that are displayed and the locations in the GUI pop-up menus. +They do not affect test plans or test behaviour. +

+

+The PreProcessors are now invoked directly by the JMeterThread class, +rather than by the TestCompiler#configureSampler() method. (JMeterThread handles the PostProcessors). +This does not affect test plans or behaviour, but could perhaps affect 3rd party add-ons (very unlikely). +

+

+Moved the Scoping Rules sub-section from Section 3. "Building a Test Plan" to Section 4. "Elements of a test plan" +

+ +

+The While controller now trims leading and trailing spaces from the condition value before it is compared +with LAST, blank or false. +

+ +

+The "threadName" variable in the _jexl() and __javaScript() functions was previously misspelt as "theadName". +

+ +

+The following deprecated methods were removed from JOrphanUtils: booleanToString(boolean) and valueOf(boolean). +Java 1.4+ has these methods in the Boolean class. +

+ +

+The TestElement interface has some new methods: +

    +
  • void setProperty(String key, String value, String dflt)
  • +
  • void setProperty(String key, boolean value, boolean dflt)
  • +
  • void setProperty(String key, int value)
  • +
  • void setProperty(String key, int value, int dflt)
  • +
  • int getPropertyAsInt(String key, int defaultValue)
  • +
+These are implemented in the AbstractTestElement class which all elements should extend so this is unlikely to cause a problem. +

+

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 46332 - HTTP Cookie Manager ignores manually defined cookies (bug introduced in r707810)
  • +
  • Cookie Manager was not passing cookie policy to runtime threads so they always used compatibility mode
  • +
  • Add version attribute to JMeter Cookie class (needed for proper cookie support)
  • +
  • Cookie Manager now saves/restores cookie versions
  • +
  • Check validity of cookies before storing them.
  • + +
  • HTTPSamplers can now use variables in POSTed file names
  • +
  • Fix processing of first file name in HTTP POST so functions/variables work (bug introduced with multiple file support)
  • +
  • 45831 - WS Sampler reports incorrect throughput if SOAP packet creation fails
  • +
  • HTTP, SOAP/XML-RPC and WebService(SOAP) sampler character encodings updated to be more consistent
  • + +
  • 46148 - HTTP sampler fails on SSL requests when logging for jmeter.util is set to DEBUG
  • +
  • Fix Java 1.6 https error: java.net.SocketException: Unconnected sockets not implemented
  • + +
  • 46838 - if there was no data, still need to set latency in HTTPSampler
  • +
  • 46993 - Saving from Header Manager generates ClassCastException
  • +
  • +46690 - handling of 302 redirects with invalid relative paths. +JMeter now removes extraneous leading "../" segments (as do many browsers) +
  • +
  • 44521 - empty variables for a POST in the HTTP Request don't get ignored
  • +
  • 46977 - JMeter does not handle HTTP headers not delimited by whitespace
  • +
  • Fix bug in HTTP file: handling - read bytes, not characters in the default encoding.
  • + +
  • Remove Host from headers saved by the Proxy server, as that will normally be generated by the HTTP stack
  • +
  • 45199 - don't try to replace blank variables in Proxy recording
  • +
  • Change HTTPS spoofing so https: links are replaced even when URL match fails
  • +
  • 46436 - Improve error reporting in Proxy Gui
  • +
  • 46435 - More verbose error msg for error 501 (Proxy Server)
  • +
+ +

Other Samplers

+
    +
  • The "prev" and "sampler" objects are now defined for BSF test elements
  • +
  • Fix NPE (in DataSourceElement) when using JDBC in client-server mode
  • +
  • 45425 - JDBC Request does not support Unicode (changed sampler to use UTF-8)
  • +
  • 46522 - Incorrect "Response data" in JDBC sample when column names are missing
  • +
  • 46821 - JDBC select request doesn't store the first column in the variables
  • +
  • 43791 - ensure QueueReceiver is closed in JMS Point to Point sampler
  • +
  • 46016 - avoid possible NPE in JMSSampler
  • +
  • 46142 - JMS Receiver now uses MessageID
  • +
  • 45458 - Point to Point JMS in combination with authentication
  • +
  • 45460 - JMS TestPlan elements depend on resource property
  • +
  • Various ReceiveSubscriber thread-safety fixes
  • +
  • JMSPublisher and Subscriber fixes: thread-safety, support dynamic locale changes, locale independence for JMX attribute values
  • +
  • FTP Sampler now logs out before disconnecting.
  • +
  • TCP sampler now calls setupTest() and teardownTest() methods
  • +
  • 45887 - TCPSampler: timeout property incorrectly set
  • +
+ +

Controllers

+
    +
  • Fix NPE when using nested Transaction Controllers with parent samples
  • +
  • Fix processing of Transaction Controller parent mode so current sampler is set to actual sampler
  • +
  • 44941 - Throughput controllers should not share global counters
  • +
  • 47120 - Throughput Controller: change percent executions to total executions, the value is stored in a String and interpreted as 1 execution
  • +
  • 47150 - ThreadGroup with a loop count of zero causes infinite loop
  • +
  • 47009 - Insert parent caused child controller name to be reset
  • +
  • 47165 - Using duplicate Module Controller names in command line mode causes NPE
  • +
+ +

Listeners

+
    +
  • Mailer Visualizer documentation now agrees with code i.e. failure/success counts need to be exceeded to trigger the mail.
  • +
  • Mailer Visualizer now shows the failure count
  • +
  • Mailer Visualiser - fix parsing of multiple e-mail address when using Test button
  • +
  • 45976 - incomplete result file when using remote testing with more than 1 server
  • +
  • Fix Summariser so it works in client server mode
  • +
  • 34096 - Duplicate samples not eliminated when writing to CSV files
  • +
  • Save "Include group Name in Label" setting in Aggregate and Summary reports
  • +
  • The JMeter variable "sample_variables" is sent to all server instances to ensure the data is available to the client.
  • +
  • CSVSaveService - check for EOF while reading quoted string
  • +
+ +

Assertions

+
    +
  • 45749 - Response Assertion does not work with a substring that happens to be an invalid RE
  • +
  • 45904 - Allow 'Not' Response Assertion to succeed with null sample
  • +
+ +

Functions

+
    +
  • Fix regex function - was failing to process $m$mid$n$ correctly
  • +
  • Protect against possible NPE in RegexFunction if called during test shutdown.
  • +
  • Avoid NPE if XPath function does not match any nodes
  • +
  • Correct the variable name "theadName" to "threadName" in the __jexl() and __javaScript() functions
  • +
  • A reference to a missing function - e.g. ${__missing(a)} - is now treated the same as a missing variable. Previously the function name - and leading { - were dropped.
  • +
+ +

I18N

+
    +
  • Fixed language change handling for menus (does not yet work for TestBeans)
  • +
  • Add HeaderAsPropertyRenderer to support header resource names; use this to fix locale changes in various GUI elements
  • +
  • 46424 - corrections to French translation
  • +
  • 46844 - "Library" label in test plan are not I18N
  • +
  • 47064 - fixes for Mac LAF
  • +
  • 47127 - Unable to change language to pl_PL
  • +
  • 47137 - Labels in View Results Tree aren't I18N
  • +
  • 46423 - I18N of Proxy Recorder
  • +
  • 45928 - AJP/1.3 Sampler doesn't retrieve its label from messages.properties
  • +
+ +

General

+
    +
  • Prompt to overwrite an existing file when first saving a new test plan
  • +
  • Amend TestBeans to show the correct popup menu for Listeners
  • +
  • 45185 - CSV dataset blank delimiter causes OOM
  • +
  • Fix incorrect GUI classifications: +"Save Results to a file" and "Generate Summary Results" are now shown as Listeners. +"Counter" is now shown as a Configuration element. +
  • +
  • 41608 - misleading warning log message removed
  • +
  • 46359 - BSF JavaScript Preprocessor cannot access sampler variable on first interation (Implement temporary work-round for BSF-22)
  • +
  • 46407 - BSF elements do not load script files, attempt to interpret filename as script
  • +
  • Better handling of Exceptions during test shutdown
  • +
  • Fix potential thread safety issue in JMeterThread class
  • +
  • 46491 - Incorrect value for the last variable in "CSV Data Set Config" (error in processing quoted strings)
  • + +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 45479 - Support for multiple HTTP Header Manager nodes
  • +
  • HTTP Samplers now support connection and request timeouts (requires Java 1.5 for Java Http sampler)
  • +
  • Apache SOAP 2.3.1 does not give access to HTTP response code/message, so WebService sampler now treats an empty response as an error
  • +
  • Mirror server now supports "X-Sleep" header - if this is set, the responding thread will wait for the specified number of milliseconds
  • +
  • 45694 - Support GZIP compressed logs in Access Log Sampler
  • +
+ +

Other samplers

+
    +
  • JDBC Request can optionally save the results of Select statements to variables.
  • +
  • JDBC Request now handles quoted strings.
  • +
  • JDBC Request now handles arbitrary variable types.
  • +
  • LDAP result data now formatted with line breaks
  • +
  • 45200 - MailReaderSampler: store the whole MIME message in the SamplerResult
  • +
  • 45571 - JMS Sampler correlation enhancement
  • +
  • 46030 - Extend TCP Sampler to Support Length-Prefixed Binary Data
  • +
  • Add classname field to TCP Sampler GUIs
  • +
+ +

Controllers

+
    +
  • Allow If Controller to use variable expressions (not just Javascript)
  • +
  • Trim spaces from While Controller condition before comparing against LAST, blank or false
  • +
+ +

Listeners

+
    +
  • Save Responses to a file can save the generated filename(s) to variables.
  • +
  • Add option to skip suffix generation in Save Responses to a File
  • +
  • 43119 - Save Responses to file: optionally omit the file number
  • +
  • Add BSF Listener element
  • +
  • 47176 - Monitor Results : improve load status graphic
  • +
  • 40045 - Allow Results monitor to select a specific connector
  • +
  • Read XML JTL files more efficiently - pass samples to visualisers as they are read, rather than saving them all and then processing them
  • +
+ +

Assertions, Config, Pre- & Post-Processors

+
    +
  • 45903 - allow Assertions to apply to sub-samples
  • +
  • Add Body (unescaped) source option to Regular Expression Extractor.
  • +
  • Random Variable - new configuration element to create random numeric variables
  • +
+ +

Functions

+
    +
  • Add OUT and log variables to __jexl() function
  • +
  • Use Script to evaluate __jexl() function so can have multiple statements.
  • +
  • Add log variable to the __javaScript() function
  • +
  • Added __char() function: allows arbitrary Unicode characters to be entered in fields.
  • +
  • Added __unescape() function: allows Java-escaped strings to be used.
  • +
  • Added __unescapeHtml() function: decodes Html-encoded text.
  • +
  • Added __escapeHtml() function: encodes text using Html-encoding.
  • +
+ +

I18N

+
    +
  • 45929 - improved French translations
  • +
  • 47132 - Brazilian Portuguese translations
  • +
  • 46900 - Polish translations
  • +
  • Added locales.add property to allow for new Locales
  • +
+ +

General

+
    +
  • Allow spaces in JMeter path names (apply work-round for Java Bug 4496398)
  • +
  • Process JVM_ARGS last in script files so users can override default settings
  • +
  • 46636 - Allow server mode to optionally use a fixed rmi port
  • +
  • Make some samplers interruptible: HTTP (both), SoapSampler, FTPSampler
  • +
  • Test Action now supports "Stop Now" action, as do the Thread Group and Result Status Post Processor elements
  • +
  • The Menu items Stop and Shutdown now behave better. Shutdown will now wait until all threads exit. +In GUI mode it can be cancelled and Stop run instead. +Stop now reports if some threads will not exit, and exits if running in non-GUI mode
  • +
  • Add UDP server to wait for shutdown message if running in non-GUI mode; add UDP client to send the message.
  • +
  • 41209 - JLabeled* and ToolTips
  • +
  • Include BeanShell 2.0b4 jar in binary download.
  • +
+ +

Non-functional changes

+
    +
  • Introduce AbstractListenerGui class to make it easier to create Listeners with no visual output
  • +
  • Assertions are run after PostProcessors; change order of pop-up menus accordingly
  • +
  • Remove unnecessary clone() methods from function classes
  • +
  • Moved PreProcessor invocation to JMeterThread class
  • +
  • Made HashTree Map field final
  • +
  • Improve performance of calling ResultCollector#isSampleWanted() for multiple samples
  • +
  • Updated to new versions of: xmlgraphics-commons (1.3.1), jdom (1.1), xstream (1.3.1), velocity (1.6.2)
  • +
+ + + + +

Version 2.3.2

+ +

Summary of main changes

+ +

Bug fixes

+

+Version 2.3.1 changed the way binary and text content types were determined as far as the View Results Tree Listener was concerned: +originally everything except "image/" content types were considered text, but 2.3.1 introduced a check +for specific content types. This has caused problems, +as several popular types were omitted and these were no longer shown by default in the Response tab. +Rather than try to list all the possible text types, JMeter now just checks for the following binary types: +

    +
  • image/*
  • +
  • audio/*
  • +
  • video/*
  • +
+All other types are now assumed to be text. +

+ +

+JMeter 2.3.1 introduced a bug in the Cookie Manager +- if "Clear Cookie each iteration" was selected, all threads would see the same cookies. +This bug has been corrected. +

+ +

Improvements

+ +

+The Proxy server can now record binary requests. +By default the content types +application/x-amf and application/x-java-serialized-object +will be treated as binary and saved in a file. +To change the content types, update the property proxy.binary.types. +

+ +

+The CSV Dataset configuration element has new file sharing options: per thread group, per thread, per identifier. +This allows for more flexible file processing, e.g. each thread can process the same data in the same order. +

+ +

Switch Controller now works properly with functions and variables, +and the condition can now be a name instead of a number. +Simple Controller now works properly under a While Controller

+ +

CSV fields in JTL files can now contain delimiters. +CSV and XML files can now contain additional variables (define the JMeter property sample_variables).

+ +

Response Assertion can now match on substrings (i.e. not regular expression). +Regex extractor can operate on variables.

+ +

+XPath processing is improved; Tidy errors are handled better. +

+ +

Save Table Data buttons added to Summary and Aggregate reports to allow easy saving of the calculated data.

+ +

+HTTP samplers can now save just the MD5 hash of responses, rather than the entire response. +As a special case, if the HTTP Sampler path starts with "http://" or "https://" then this is used as the full URL, +overriding the host and port fields. +The HTTP Samplers can now POST multiple files. +Webservice(SOAP) Sampler can now load local WSDL files using the "file:" protocol. +

+ +

+A simple HTTP Cache Manager has been added. This needs further development. +

+ +

+View Results Tree Listener now uses Tidy to display XML. +This should allow more content to be displayed succesfully. +It also avoids the need to download remote DTD files, which can slow the rendering considerably. +

+ +

+MailReader sampler now supports POP3S and IMAPS protocols. Individual mails are now added as sub-samples. +

+ +

+Various improvements to the BSF Sampler: now supports Jexl, and Javascript bug works properly. +Added BSF PreProcessor, PostProcessor and Assertion test elements. +All now have access to "props" JMeter Properties object. +

+ +

Number of classes loaded in non-GUI mode is much reduced.

+ +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves OK under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +To override the default local language, set the JMeter property "language" before starting JMeter. +

+

Incompatible changes

+
    +
  • +To reduce the number of classes loaded in non-GUI mode, +Functions will only be found if their classname contains the string +'.functions.' and does not contain the string '.gui.'. +All existing JMeter functions conform to this restriction. +To revert to earlier behaviour, comment or change the properties classfinder.functions.* in jmeter.properties. +
  • +
  • The reference value parameter for intSum() is now optional. +As a consequence, if a variable name is used, it must not be a valid integer.
  • +
  • The supplied TCPClient implementation no longer treats tcp.eolByte=0 as special. +To skip EOL checking, set tcp.eolByte=1000 (or some other value which is not a valid byte) +
  • +
  • +Leading and trailing spaces are trimmed from variable names in function calls. +For example, ${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY '. +
  • +
  • +Synchronization has been removed from the RunningSample class (it was not fully threadsafe anyway). +Developers of 3rd party add-ons that use the class may need to synchronize access. +
  • +
+ +

Bug fixes

+
    +
  • Check that the CSV delimiter is reasonable.
  • +
  • Fix Switch Controller to work properly with functions and variables
  • +
  • 44011 - application/soap+xml not treated as a text type
  • +
  • 43427 - Simple Controller is only partly executed in While loop
  • +
  • 33954 - Stack Overflow in If/While controllers (may have been fixed previously)
  • +
  • 44022 - Memory Leak when closing test plan
  • +
  • 44042 - Regression in Cookie Manager (Bug introduced in 2.3.1)
  • +
  • 41028 - JMeter server doesn't alert the user when the host is defined as a loopback address
  • +
  • 44142 - Function __machineName causes NPE if parameters are omitted.
  • +
  • 44144 - JMS point-to-point: request response test does not work
  • +
  • 44314 - Not possible to add more than one SyncTimer
  • +
  • Capture Tidy console error output and log it
  • +
  • Fix problems using Tidy(tolerant parser) in XPath Assertion and XPath Extractor
  • +
  • 44374 - improve timer calculation
  • +
  • Regular Expression Extractor now deletes all stale variables from previous matches.
  • +
  • 44707 - Running remote test changes internal test plan
  • +
  • 44625 - Cannot have two or more FTP samplers with different "put" and "get" actions
  • +
  • 40850 - BeanShell memory leak
  • +
  • Ensure ResponseCode and ResponseMessage are set for successful JDBC samples
  • +
  • FTPSampler now detects and reports failure to open the remote file
  • +
  • Class directories defined in search_paths and user.classpath no longer need trailing "/"
  • +
  • 44852 SOAP/ XML-RPC Request does not show Request details in View Results Tree
  • +
  • WebService(SOAP) Sampler ResponseData now includes the EOLs sent by server
  • +
  • 44910 - close previous socket (if any) in TCP Sampler
  • +
  • 44912 - Filter not working in Log Parser
  • +
  • The BeanShell and BSF component documentation made some incorrect references to the "SampleResponse" object; +this has been corrected to "SampleResult"
  • +
  • BSF Sampler now works properly with Javascript
  • +
  • Test Action "Stop Test" now works
  • +
  • 42833 - Argument class uses LinkedHashMap in getArgumentsAsMap() to preserve ordering
  • +
  • 45093 - SizeAssertion did not call getBytes()
  • +
  • 45007 - Rewrite Location headers when using Proxy HTTPS spoofing
  • +
  • Use CRLF rather than LF in Proxy when returning headers to the client
  • +
  • 45007 - fix content length header if content may have been changed
  • +
+ +

Improvements

+
    +
  • CSV files can now handle fields with embedded delimiters.
  • +
  • longSum() function added
  • +
  • 43382 - configure Tidy output (warnings, errors) for XPath Assertion and Post-Processor
  • +
  • 43984 - trim spaces from port field
  • +
  • Add optional comment to __log() function
  • +
  • Make Random function variable name optional
  • +
  • Reduce class loading in non-GUI mode by only looking for Functions in class names +that contain '.functions.' and don't contain '.gui.'
  • +
  • 43379 - Switch Controller now supports selection by name as well as number
  • +
  • Can specify list of variable names to be written to JTL files (CSV and XML format)
  • +
  • Now checks that the remoteStart options -r and -R are only used with non_GUI -n option
  • +
  • 44184 - Allow header to be saved with Aggregate Graph data
  • +
  • Added "Save Table Data" buttons to Aggregate and Summary Reports - save table as CSV format with header
  • +
  • Allow most functions to be used on the Test Plan. +Note __evalVar(), __split() and __regex() cannot be used on the Test Plan.
  • +
  • Allow Global properties to be loaded from a file, e.g. -Gglobal.properties
  • +
  • Add "Substring" option to Response Assertion
  • +
  • 44378 - Turkish localisation
  • +
  • Add optional output variable name to Jexl function
  • +
  • Add application/vnd.wap.xhtml+xml as a text type
  • +
  • Add means to override maximum display size in View Results Tree - set the property: view.results.tree.max_size
  • +
  • Use Tidy to display XML in View Results Tree Listener (avoids fetching DTDs)
  • +
  • 44487 - German translation
  • +
  • +As a special case, if the HTTP Sampler path starts with "http://" or "https://" then this is used as the full URL. +
  • +
  • 44575 - Result Saver can now save only successful results
  • +
  • 44650 - CSV Dataset now handles quoted column values
  • +
  • 44600 - 1-ms resolution timer when running with Java 1.5+
  • +
  • 44632 - Text input enhancement to FTP Sampler
  • +
  • 42204 - add thread group name to Aggregate and Summary reports
  • +
  • FTP Sampler sets latency = time to login
  • +
  • FTP Sampler sets a URL if it can
  • +
  • 41921 - add option for samplers to store MD5 of response; done for HTTP Samplers.
  • +
  • Regex Function can now also be applied to a variable rather than just the previous sample result.
  • +
  • Remove HTML Parameter Mask,HTTP User Parameter Modifier from menus as they are deprecated
  • +
  • 44807 - allow session ids to be terminated by backslash
  • +
  • 44784 - allow for broken server returning additional charset
  • +
  • Added TESTSTART.MS property / variable = test start time in milliseconds
  • +
  • Add POP3S and IMAPS protocols to Mail Reader Sampler.
  • +
  • Mail Reader Sampler now creates a sub-sample for each mail.
  • +
  • The supplied TCPClient implementation no longer treats tcp.eolByte=0 as special. +To skip EOL checking, set tcp.eolByte=1000 (or some other value which is not a valid byte) +
  • +
  • JUnit sampler GUI now also finds Test classes defined in user.classpath
  • +
  • +Leading and trailing spaces are trimmed from variable names in function calls. +For example, ${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY ' +
  • +
  • Webservice(SOAP) Sampler can now load local WSDL files using the file: protocol
  • +
  • 44872 - Add "All Files" filter to Open File dialogs
  • +
  • Mirror server can now be run independently (mirror-server.cmd and mirror-server.sh)
  • +
  • 19128 - Added multiple file POST support to HTTP Samplers
  • +
  • Allow use of special name LAST to mean the last test run; applies to -t, -l, -j flags
  • +
  • 44418/42178 - CSV Dataset file handling improvements
  • +
  • Give BeanShell, Javascript and Jexl functions access to JMeter properties via the "props" object
  • +
  • Give BSF Sampler access to JMeter Properties via "props" object
  • +
  • Add Jexl as a supported BSF Sampler language
  • +
  • Give Beanshell test elements access to JMeter Properties via "props" object
  • +
  • Added BSF PreProcessor, PostProcessor and Assertion test elements
  • +
  • All BSF elements now have access to System.out via the variable "OUT"
  • +
  • Summariser updated to handle variable names
  • +
  • Synchronisation added to Summary and Aggregate Report to try to prevent occasional lost samples
  • +
  • 44808,39641 - Proxy support for binary requests
  • +
  • 28502 - HTTP Resource Cache
  • +
+ +

Non-functional changes

+
    +
  • Better handling of MirrorServer startup problems and improved unit test.
  • +
  • Build process now detects missing 3rd party libraries and reports need for both binary and source archives
  • +
  • Skip BeanShell tests if jar is not present
  • +
  • Update to Xerces 2.9.1, Xalan 2.7.1, Commons IO 1.4, Commons Lang 2.4, Commons-Logging 1.1.1, XStream 1.3, XPP3 1.1.4c
  • +
  • Use properties for log/logn function descriptions
  • +
  • Check that all jmx files in the demos directory can be loaded OK
  • +
  • Update copyright to 2008; use copy tag instead of numeric character in HTML output
  • +
  • Methods called from constructors must not be overridable: make GUI init methods private
  • +
  • Make static variables final if possible
  • +
  • Split changes into current and previous
  • +
+ + + +

Version 2.3.1

+

Summary of changes

+ +
JMeter Proxy
+ +

+The Proxy spoof function was broken in 2.3; it has been fixed. +Spoof now supports an optional parameter to limit spoofing to particular URLs. +This is useful for HTTPS pages that have insecure content - e.g. images/stylesheets may be accessed using HTTP. +Spoofed responses now drop the default port (443) from https links to make them work better. +

+

+Ignored proxy samples are now visible in Listeners - the label is enclosed in [ and ] as an indication. +Proxy documentation has been improved. +

+ +
GUI changes
+ +

The Add menus show element types in the order in which they are processed +- see Test Plan Execution Order. +It is no longer possible to add test elements to inappropriate parts of the tree +- e.g. samplers cannot be added directly under a test plan. +This also applies to Paste and drag and drop. +

+ +

+The File menu now supports a "Revert" option, which reloads the current file. +Also the last few file names used are remembered for easy reloading. +

+ +

+The Options Menu now supports Collapse All and Expand All items to collapse and expand the test tree. +

+ +
Remote testing
+ +

+The JMeter server now starts the RMI server directly (by default). +This simplifies testing, and means that the RMI server will be stopped when the server stops. +

+ +

+Functions can now be used in Listener filenames (variables do not work). +

+ +

+Command-line option -G can now be used to define properties for remote servers. +Option -X can be used to stop a remote server after a non-GUI run. +Server can be set to automatically exit after a single test (set property server.exitaftertest=true). +

+ +
Other enhancements
+ +

+JMeter startup no longer loads as many classes; this should reduce memory requirements. +

+ +

+Parameter and file support added to all BeanShell elements. +Javascript function now supports access to JMeter objects; +Jexl function always did have access, but the documentation has now been included. +New functions __eval() and __evalVar() for evaluating variables. +

+ +

+CSV files with the correct header column names are now automatically recognised when loaded. +There is no need to configure the properties. +

+ +

+The hostname can now be saved in CSV and XML output files. +New "Successes only" option added when saving result files. +Errors / Successes only option is now supported when loading XML and CSV files. +

+ +

+General documentation improvements. +

+ +
HTTP
+ +

PUT and DELETE should now work properly. +Cookie Manager no longer clears manually entered cookies. +

+

Now handles the META tag http-equiv charset

+ +
JDBC
+ +

JDBC Sampler now allows INOUT and OUT parameters for Called procedures. +JDBC Sampler now allows per-thread connections - set Max Connections = 0 in JDBC Config. +

+ +
+ + +

Incompatible changes

+
    +
  • JMeter server now creates the RMI registry by default. +If the RMI registry has already been started externally, this will generate a warning message, but the server will continue. +This should not affect JMeter testing. +However, if you are also using the RMI registry for other applications there may be problems. +For example, when the JMeter server shuts down it will stop the RMI registry. +Also user-written command files may need to be adjusted (the ones supplied with JMeter have been updated). +To revert to the earlier behaviour, define the JMeter property: server.rmi.create=false. +
  • +
  • The Proxy server removes If-Modified-Since and If-None-Match headers from generated Header Managers. +To revert to the previous behaviour, define the property proxy.headers.remove with no value
  • +
+ +

Bug fixes

+
    +
  • 43430 - Count of active threads is incorrect for remote samples
  • +
  • Throughput Controller was not working for "all thread" counts
  • +
  • If a POST body is built from parameter values only, these are now encoded if the checkbox is set.
  • +
  • 43584 - Assertion Failure Message contains a comma that is also used as the delimiter for CSV files
  • +
  • HTTP Mirror Server now always returns the exact same content, it used to return incorrect data if UTF-8 encoding was used for HTTP POST body, for example
  • +
  • 43612 - HTTP PUT does not honor request parameters
  • +
  • 43694 - ForEach Controller (empty collection processing error)
  • +
  • 42012 - Variable Listener filenames do not get processed in remote tests. +Filenames can now include function references; variable references do not work.
  • +
  • Ensure Listener nodes get own save configuration when copy-pasted
  • +
  • Correct Proxy Server include and exclude matching description - port and query are included, contrary to previously documented.
  • +
  • Aggregate Graph and Aggregate Report Column Header is KB/Sec; fixed the values to be KB rather than bytes
  • +
  • +Fix SamplingStatCalculator so it no longer adds elapsed time to endTime, as this is handled by SampleResult. +This corrects discrepancies between Summary Report and Aggregate Report throughput calculation. +
  • +
  • Default HTTPSampleResult to ISO-8859-1 encoding
  • +
  • Fix default encoding for blank encoding
  • +
  • Fix Https spoofing (port problem) which was broken in 2.3
  • +
  • Fix HTTP (Java) sampler so http.java.sampler.retries means retries, i.e. does not include initial try
  • +
  • Fix SampleResult dataType checking to better detect TEXT documents
  • +
+ +

Improvements

+
    +
  • Add run_gui Ant target, to package and then start the JMeter GUI from Ant
  • +
  • Add File->Revert to easily drop the current changes and reload the project file currently loaded
  • +
  • 31366 - Remember recently opened file(s)
  • +
  • 43351 - Add support for Parameters and script file to all BeanShell test elements
  • +
  • SaveService no longer needs to instantiate classes
  • +
  • New functions: __eval() and __evalVar()
  • +
  • Menu items now appear in execution order
  • +
  • Test Plan items can now only be dropped/pasted/merged into parts of the tree where they are allowed
  • +
  • Property Display to show the value of System and JMeter properties and allow them to be changed
  • +
  • 43451 - Allow Regex Extractor to operate on Response Code/Message
  • +
  • JDBC Sampler now allows INOUT and OUT parameters for Called procedures
  • +
  • JDBC Sampler now allows per-thread connections
  • +
  • Cookie Manager not longer clears cookies defined in the GUI
  • +
  • HTTP Parameters without names are ignored (except for POST requests with no file)
  • +
  • "Save Selection As" added to main menu; now checks only item is selected
  • +
  • Test Plan now has Paste menu item (paste was already supported via ^V)
  • +
  • If the default delimiter does not work when loading a CSV file, guess the delimiter by analysing the header line.
  • +
  • Add optional "loopback" protocol for HttpClient sampler
  • +
  • HTTP Mirror Server now supports blocking waiting for more data to appear, if content-length header is present in request
  • +
  • HTTP Mirror Server GUI now has the Start and Stop buttons in a more visible place
  • +
  • Server mode now creates the RMI registry; to disable set the JMeter property server.rmi.create=false
  • +
  • HTTP Sampler now supports using MIME Type field to specify content-type request header when body is constructed from parameter values
  • +
  • Enable exit after a single server test - define JMeter property server.exitaftertest=true
  • +
  • Added -G option to set properties in remote servers
  • +
  • Added -X option to stop remote servers after non-GUI run
  • +
  • 43485 - Ability to specify keep-alive on SOAP/XML-RPC request
  • +
  • 43678 - Handle META tag http-equiv charset
  • +
  • 42555 - [I18N] Proposed corrections for the french translation
  • +
  • 43727 - Test Action does not support variables or functions
  • +
  • The Proxy server removes If-Modified-Since and If-None-Match headers from generated Header Managers by default. +To change the list of removed headers, define the property proxy.headers.remove as a comma-separated list of headers to remove
  • +
  • The javaScript function now has access to JMeter variables and context etc. See JavaScript function
  • +
  • Use drop-down list for BSF Sampler language field
  • +
  • Add hostname to items that can be saved in CSV and XML output files.
  • +
  • Errors only flag is now supported when loading XML and CSV files
  • +
  • Ensure ResultCollector uses SaveService encoding
  • +
  • Proxy now rejects attempts to use it with https
  • +
  • Proxy spoofing can now use RE matching to determine which urls to spoof (useful if images are not https)
  • +
  • Proxy spoofing now drops the default HTTPS port (443) when converting https: links to http:
  • +
  • Add Successes Only logging and display
  • +
  • The JMeter log file name is formatted as a SimpleDateFormat (applied to the current date) if it contains paired single-quotes, .e.g. 'jmeter_'yyyyMMddHHmmss'.log'
  • +
  • Added Collapse All and Expand All Option menu items
  • +
  • Allow optional definition of extra content-types that are viewable as text
  • +
+ +

Non-functional Improvements

+
    +
  • Functor code tightened up; Functor can now be used with interfaces, as well as pre-defined targets and parameters.
  • +
  • Save graphics function now prompts before overwriting an existing file
  • +
  • Debug Sampler and Debug PostProcessor added.
  • +
  • Fixed up method names in Calculator and SamplingStatCalculator
  • +
  • Tidied up Listener documentation.
  • +
+ + + +

Version 2.3

+ +

Fixes since 2.3RC4

+ +

Bug fixes

+
    +
  • Fix NPE in SampleResultConverter - XStream PrettyPrintWriter cannot handle nulls
  • +
  • If Java HTTP sampler sees null ResponseMessage, replace with HTTP header
  • +
  • 43332 - 2.3RC4 does not clear Guis based on TestBean
  • +
  • 42948 - Problems with Proxy gui table fields in Java 1.6
  • +
  • Fixup broken jmeter-server script
  • +
  • 43364 - option to revert If Controller to pre 2.3RC3 behaviour
  • +
  • 43449 - Statistical Remote mode does not handle Latency
  • +
  • 43450 (partial fix) - Allow SampleCount and ErrorCount to be saved to/restored from files
  • +
+ +

Improvements

+
    +
  • Add nameSpace option to XPath extractor
  • +
  • Add NULL parameter option to JDBC sampler
  • +
  • Add documentation links for Rhino and BeanShell to functions; clarify variables and properties
  • +
  • Ensure uncaught exceptions are logged
  • +
  • Look for user.properties and system.properties in JMeter bin directory if not found locally
  • +
+ +

Fixes since 2.3RC3

+
    +
  • Fixed NPE in Summariser (bug introduced in 2.3RC3)
  • +
  • Fixed setup of proxy port (bug introduced in 2.3RC3)
  • +
  • Fixed errors when running non-GUI on a headless host (bug introduced in 2.3RC3)
  • +
  • 43054 - SSLManager causes stress tests to saturate and crash (bug introduced in 2.3RC3)
  • +
  • Clarified HTTP Request Defaults usage of the port field
  • +
  • 43006 - NPE if icon.properties file not found
  • +
  • 42918 - Size Assertion now treats an empty response as having zero length
  • +
  • 43007 - Test ends before all threadgroups started
  • +
  • Fix possible NPE in HTTPSampler2 if 302 does not have Location header.
  • +
  • 42919 - Failure Message blank in CSV output [now records first non-blank message]
  • +
  • Add link to Extending JMeter PDF
  • +
  • Allow for quoted charset in Content-Type parsing
  • +
  • 39792 - ClientJMeter synchronisation needed
  • +
  • 43122 - GUI changes not always picked up when short-cut keys used (bug introduced in 2.3RC3)
  • +
  • 42947 - TestBeanGUI changes not picked up when short-cut keys used
  • +
  • Added serializer.jar (needed for update to xalan 2.7.0)
  • +
  • 38687 - Module controller does not work in non-GUI mode
  • +
+ +

Improvements since 2.3RC3

+
    +
  • Add stop thread option to CSV Dataset
  • +
  • Updated commons-httpclient to 3.1
  • +
  • 28715 - allow variable cookie values (set CookieManager.allow_variable_cookies=false to disable)
  • +
  • 40873 - add JMS point-to-point non-persistent delivery option
  • +
  • 43283 - Save action adds .jmx if not present; checks for existing file on Save As
  • +
  • Control+A key does not work for Save All As; changed to Control+Shift+S
  • +
  • 40991 - Allow Assertions to check Headers
  • +
+ +

Version 2.3RC3

+ +

Known problems/restrictions:

+

+The JMeter remote server does not support multiple concurrent tests - each remote test should be run in a separate server. +Otherwise tests may fail with random Exceptions, e.g. ConcurrentModification Exception in StandardJMeterEngine. +See 43168. +

+

+The default HTTP Request (not HTTPClient) sampler may not work for HTTPS connections via a proxy. +This appears to be due to a Java bug, see 39337. +To avoid the problem, try a more recent version of Java, or switch to the HTTPClient version of the HTTP Request sampler. +

+

Transaction Controller parent mode does not support nested Transaction Controllers. +Doing so may cause a Null Pointer Exception in TestCompiler. +

+

Thread active counts are always zero in CSV and XML files when running remote tests. +

+

The property file_format.testlog=2.1 is treated the same as 2.2. +However JMeter does honour the 3 testplan versions.

+

+22510 - JMeter always uses the first entry in the keystore. +

+

+Remote mode does not work if JMeter is installed in a directory where the path name contains spaces. +

+

+BeanShell test elements leak memory. +This can be reduced by using a file instead of including the script in the test element. +

+

+Variables and functions do not work in Listeners in client-server (remote) mode so they cannot be used +to name log files in client-server mode. +

+

+CSV Dataset variables are defined after configuration processing is completed, +so they cannot be used for other configuration items such as JDBC Config. +(see 40394) +

+ +

Summary of changes (for more details, see below)

+

+Some of the main enhancements are: +

+
    +
  • Htmlparser 2.0 now used for parsing
  • +
  • HTTP Authorisation now supports domain and realm
  • +
  • HttpClient options can be specified via httpclient.parameters file
  • +
  • HttpClient now behaves the same as Java Http for SSL certificates
  • +
  • HTTP Mirror Server to allow local testing of HTTP samplers
  • +
  • HTTP Proxy supports XML-RPC recording, and other proxy improvements
  • +
  • __V() function allows support of nested variable references
  • +
  • LDAP Ext sampler optionally parses result sets and supports secure mode
  • +
  • FTP Sampler supports Ascii/Binary mode and upload
  • +
  • Transaction Controller now optionally generates a Sample with subresults
  • +
  • HTTPS session contexts are now per-thread, rather than shared. This gives better emulation of multiple users
  • +
  • BeanShell elements now support ThreadListener and TestListener interfaces
  • +
  • Coloured icons in Tree View Listener and elsewhere to better differentiate failed samples.
  • +
+

+The main bug fixes are: +

+
    +
  • HTTPS (SSL) handling now much improved
  • +
  • Various Remote mode bugs fixed
  • +
  • Control+C and Control+V now work in the test tree
  • +
  • Latency and Encoding now available in CSV log output
  • +
  • Test elements no longer default to previous contents; test elements no longer cleared when changing language.
  • +
+ +

Incompatible changes (usage):

+

+N.B. The javax.net.ssl properties have been moved from jmeter.properties to system.properties, +and will no longer work if defined in jmeter.properties. +

+The new arrangement is more flexible, as it allows arbitrary system properties to be defined. +

+

+SSL session contexts are now created per-thread, rather than being shared. +This generates a more realistic load for HTTPS tests. +The change is likely to slow down tests with many SSL threads. +The original behaviour can be enabled by setting the JMeter property: +

+https.sessioncontext.shared=true
+
+

+

+The LDAP Extended Sampler now uses the same panel for both Thread Bind and Single-Bind tests. +This means that any tests using the Single-bind test will need to be updated to set the username and password. +

+

+41140: JMeterThread behaviour was changed so that PostProcessors are run in forward order +(as they appear in the test plan) rather than reverse order as previously. +The original behaviour can be restored by setting the following JMeter property: +
+jmeterthread.reversePostProcessors=true +

+

+The HTTP Authorisation Manager now has extra columns for domain and realm, +so the temporary work-round of using '\' and '@' in the username to delimit the domain and realm +has been removed. +

+

+Control-Z no longer used for Remote Start All - this now uses Control+Shift+R +

+

+HttpClient now uses pre-emptive authentication. +This can be changed by setting the following: +

+jmeter.properties:
+httpclient.parameters.file=httpclient.parameters
+
+httpclient.parameters:
+http.authentication.preemptive$Boolean=false
+
+

+ +

+The port field in HTTP Request Defaults is no longer ignored for https samplers if it is set to 80. +

+ +

Incompatible changes (development):

+

+N.B.The clear() method was defined in the following interfaces: Clearable, JMeterGUIComponent and TestElement. +The methods serve different purposes, so two of them were renamed: +the Clearable method is now clearData() and the JMeterGUIComponent method is now clearGui(). +3rd party add-ons may need to be rebuilt. +

+

+Calulator and SamplingStatCalculator classes no longer provide any formatting of their data. +Formatting should now be done using the jorphan.gui Renderer classes. +

+

+Removed deprecated method JMeterUtils.split() - use JOrphanUtils version instead. +

+

+Removed method saveUsingJPEGEncoder() from SaveGraphicsService. +It was unused so far, and used the only Sun-specific class in JMeter. +

+ + +

New functionality/improvements:

+
    +
  • Add Domain and Realm support to HTTP Authorisation Manager
  • +
  • HttpClient now behaves the same as the JDK http sampler for invalid certificates etc
  • +
  • Added httpclient.parameters.file to allow HttpClient parameters to be defined
  • +
  • 33964 - Http Requests can send a file as the entire post body if name/type are omitted
  • +
  • 41705 - add content-encoding option to HTTP samplers for POST requests
  • +
  • 40933,40945 - optional RE matching when retrieving embedded resource URLs
  • +
  • 27780 - (patch 19936) create multipart/form-data HTTP request without uploading file
  • +
  • 42098 - Use specified encoding for parameter values in HTTP GET
  • +
  • 42506 - JMeter threads now use independent SSL sessions
  • +
  • 41707 - HTTP Proxy XML-RPC support
  • +
  • 41880 - Add content-type filtering to HTTP Proxy Server
  • +
  • 41876 - Add more options to control what the HTTP Proxy generates
  • +
  • 42158 - Improve support for multipart/form-data requests in HTTP Proxy server
  • +
  • 42173 - Let HTTP Proxy handle encoding of request, and undecode parameter values
  • +
  • 42674 - default to pre-emptive HTTP authorisation if not specified
  • +
  • Support "file" protocol in HTTP Samplers
  • +
  • Http Autoredirects are now enabled by default when creating new samplers
  • + +
  • 40103 - various LDAP enhancements
  • +
  • 40369 - LDAP: Stable search results in sampler
  • +
  • 40381 - LDAP: more descriptive strings
  • + +
  • BeanShell Post-Processor no longer ignores samples with zero-length result data
  • +
  • Added beanshell.init.file property to run a BeanShell script at startup
  • +
  • 39864 - BeanShell init files now found from currrent or bin directory
  • +
  • BeanShell elements now support ThreadListener and TestListener interfaces
  • +
  • BSF Sampler passes additional variables to the script
  • + +
  • Added timeout for WebService (SOAP) Sampler
  • + +
  • 40825 - Add JDBC prepared statement support
  • +
  • Extend JDBC Sampler: Commit, Rollback, AutoCommit
  • + +
  • 41457 - Add TCP Sampler option to not re-use connections
  • + +
  • 41522 - Use JUnit sampler name in sample results
  • + +
  • 42223 - FTP Sampler can now upload files
  • + +
  • 40804 - Change Counter default to max = Long.MAX_VALUE
  • + +
  • Use property jmeter.home (if present) to override user.dir when starting JMeter
  • +
  • New -j option to easily change jmeter log file
  • + +
  • HTTP Mirror Server Workbench element
  • + +
  • 41253 - extend XPathExtractor to work with non-NodeList XPath expressions
  • +
  • 42088 - Add XPath Assertion for booleans
  • + +
  • Added __V variable function to resolve nested variable names
  • + +
  • 40369 - Equals Response Assertion
  • +
  • 41704 - Allow charset encoding to be specified for CSV DataSet
  • +
  • 41259 - Comment field added to all test elements
  • +
  • Add standard deviation to Summary Report
  • +
  • 41873 - Add name to AssertionResult and display AssertionResult in ViewResultsFullVisualizer
  • +
  • 36755 - Save XML test files with UTF-8 encoding
  • +
  • Use ISO date-time format for Tree View Listener (previously the year was not shown)
  • +
  • Improve loading of CSV files: if possible, use header to determine format; guess timestamp format if not milliseconds
  • +
  • 41913 - TransactionController now creates samples as sub-samples of the transaction
  • +
  • 42582 - JSON pretty printing in Tree View Listener
  • +
  • 40099 - Enable use of object variable in ForEachController
  • + +
  • 39693 - View Result Table uses icon instead of check box
  • +
  • 39717 - use icons in the results tree
  • +
  • 42247 - improve HCI
  • +
  • Allow user to cancel out of Close dialogue
  • +
+ +

Non-functional improvements:

+
    +
  • Functor calls can now be unit tested
  • +
  • Replace com.sun.net classes with javax.net
  • +
  • Extract external jar definitions into build.properties file
  • +
  • Use specific jar names in build classpaths so errors are detected sooner
  • +
  • Tidied up ORO calls; now only one cache, size given by oro.patterncache.size, default 1000
  • +
  • 42326 - Order of elements in .jmx files changes
  • +
+ +

External jar updates:

+
    +
  • Htmlparser 2.0-20060923
  • +
  • xstream 1.2.1/xpp3_min-1.1.3.4.O
  • +
  • Batik 1.6
  • +
  • BSF 2.4.0
  • +
  • commons-collections 3.2
  • +
  • commons-httpclient-3.1-rc1
  • +
  • commons-jexl 1.1
  • +
  • commons-lang-2.3 (added)
  • +
  • JUnit 3.8.2
  • +
  • velocity 1.5
  • +
  • commons-io 1.3.1 (added)
  • +
+ +

Bug fixes:

+
    +
  • 39773 - NTLM now needs local host name - fix other call
  • +
  • 40438 - setting "httpclient.localaddress" has no effect
  • +
  • 40419 - Chinese messages translation fix
  • +
  • 39861 - fix typo
  • +
  • 40562 - redirects no longer invoke RE post processors
  • +
  • 40451 - set label if not set by sampler
  • +
  • Fix NPE in CounterConfig.java in Remote mode
  • +
  • 40791 - Calculator used by Summary Report
  • +
  • 40772 - correctly parse missing fields in CSV log files
  • +
  • 40773 - XML log file timestamp not parsed correctly
  • +
  • 41029 - JMeter -t fails to close input JMX file
  • +
  • 40954 - Statistical mode in distributed testing shows wrong results
  • +
  • Fix ClassCast Exception when using sampler that returns null, e..g TestAction
  • +
  • 41140 - Post-processors are run in reverse order
  • +
  • 41277 - add Latency and Encoding to CSV output
  • +
  • 41414 - Mac OS X may add extra item to -jar classpath
  • +
  • Fix NPE when saving thread counts in remote testing
  • +
  • 34261 - NPE in HtmlParser (allow for missing attributes)
  • +
  • 40100 - check FileServer type before calling close
  • +
  • 39887 - jmeter.util.SSLManager: Couldn't load keystore error message
  • +
  • 41543 - exception when webserver returns "500 Internal Server Error" and content-length is 0
  • +
  • 41416 - don't use chunked input for text-box input in SOAP-RPC sampler
  • +
  • 39827 - SOAP Sampler content length for files
  • +
  • Fix Class cast exception in Clear.java
  • +
  • 40383 - don't set content-type if already set
  • +
  • Mailer Visualiser test button now works if test plan has not yet been saved
  • +
  • 36959 - Shortcuts "ctrl c" and "ctrl v" don't work on the tree elements
  • +
  • 40696 - retrieve embedded resources from STYLE URL() attributes
  • +
  • 41568 - Problem when running tests remotely when using a 'Counter'
  • +
  • Fixed various classes that assumed timestamps were always end time stamps: +
      +
    • SamplingStatCalculator
    • +
    • JTLData
    • +
    • RunningSample
    • +
    +
  • +
  • 40325 - allow specification of proxyuser and proxypassword for WebServiceSampler
  • +
  • Change HttpClient proxy definition to use NTCredentials; added http.proxyDomain property for this
  • +
  • 40371 - response assertion "pattern to test" scrollbar problem
  • +
  • 40589 - Unescape XML entities in embedded URLs
  • +
  • 41902 - NPE in HTTPSampler when responseCode = -1
  • +
  • 41903 - ViewResultsFullVisualizer : status column looks bad when you do copy and paste
  • +
  • 41837 - Parameter value corruption in proxy
  • +
  • 41905 - Can't cut/paste/select Header Manager fields in Java 1.6
  • +
  • 41928 - Make all request headers sent by HTTP Request sampler appear in sample result
  • +
  • 41944 - Subresults not handled recursively by ResultSaver
  • +
  • 42022 - HTTPSampler does not allow multiple headers of same name
  • +
  • 42019 - Content type not stored in redirected HTTP request with subresults
  • +
  • 42057 - connection can be null if method is null
  • +
  • 41518 - JMeter changes the HTTP header Content Type for POST request
  • +
  • 42156 - HTTPRequest HTTPClient incorrectly urlencodes parameter value in POST
  • +
  • 42184 - Number of bytes for subsamples not added to sample when sub samples are added
  • +
  • 42185 - If a HTTP Sampler follows a redirect, and is set up to download images, then images are downloaded multiple times
  • +
  • 39808 - Invalid redirect causes incorrect sample time
  • +
  • 42267 - Concurrent GUI update failure in Proxy Recording
  • +
  • 30120 - Name of simple controller is resetted if a new simple controller is added as child
  • +
  • 41078 - merge results in name change of test plan
  • +
  • 40077 - Creating new Elements copies values from Existing elements
  • +
  • 42325 - Implement the "clear" method for the LogicControllers
  • +
  • 25441 - TestPlan changes sometimes detected incorrectly (isDirty)
  • +
  • 39734 - Listeners shared after copy/paste operation
  • +
  • 40851 - Loop controller with 0 iterations, stops evaluating the iterations field
  • +
  • 24684 - remote startup problems if spaces in the path of the jmeter
  • +
  • Use Listener configuration when loading CSV data files
  • +
  • Function methods setParameters() need to be synchronized
  • +
  • Fix CLI long optional argument to require "=" (as for short options)
  • +
  • Fix SlowSocket to work properly with Httpclient (both http and https)
  • +
  • 41612 - Loop nested in If Controller behaves erratically
  • +
  • 42232 - changing language clears UDV contents
  • +
  • Jexl function did not allow variables
  • +
+ +

Version 2.2

+ +

Incompatible changes:

+

+The time stamp is now set to the sampler start time (it was the end). +To revert to the previous behaviour, change the property sampleresult.timestamp.start to false (or comment it) +

+

The JMX output format has been simplified and files are not backwards compatible

+

+The JMeter.BAT file no longer changes directory to JMeter home, but runs from the current working directory. +The jmeter-n.bat and jmeter-t.bat files change to the directory containing the input file. +

+

+Listeners are now started slightly later in order to allow variable names to be used. +This may cause some problems; if so define the following in jmeter.properties: +
+jmeterengine.startlistenerslater=false +

+ +

+The GUI now expands the tree by default when loading a test plan. +This can be disabled by setting the JMeter property onload.expandtree=false +

+ +

Known problems:

+
    +
  • Post-processors run in reverse order (see 41140)
  • +
  • Module Controller does not work in non-GUI mode
  • +
  • Aggregate Report and some other listeners use increasing amounts of memory as a test progresses
  • +
  • Does not always handle non-default encoding properly
  • +
  • Spaces in the installation path cause problems for client-server mode
  • +
  • Change of Language does not propagate to all test elements
  • +
  • SamplingStatCalculator keeps a List of all samples for calculation purposes; +this can cause memory exhaustion in long-running tests
  • +
  • Does not properly handle server certificates if they are expired or not installed locally
  • +
+ +

New functionality:

+
    +
  • Report function
  • +
  • XPath Extractor Post-Processor. Handles single and multiple matches.
  • +
  • Simpler JMX file format (2.2)
  • +
  • BeanshellSampler code can update ResponseData directly
  • +
  • 37490 - Allow UDV as delay in Duration Assertion
  • +
  • Slow connection emulation for HttpClient
  • +
  • Enhanced JUnitSampler so that by default assert errors and exceptions are not appended to the error message. +Users must explicitly check append in the sampler
  • +
  • Enhanced the documentation for webservice sampler to explain how it works with CSVDataSet
  • +
  • Enhanced the documentation for javascript function to explain escaping comma
  • +
  • Allow CSV Data Set file names to be absolute
  • +
  • Report Tree compiler errors better
  • +
  • Don't reset Regex Extractor variable if default is empty
  • +
  • includecontroller.prefix property added
  • +
  • Regular Expression Extractor sets group count
  • +
  • Can now save entire screen as an image, not just the right-hand pane
  • +
  • 38901 - Add optional SOAPAction header to SOAP Sampler
  • +
  • New BeanShell test elements: Timer, PreProcessor, PostProcessor, Listener
  • +
  • __split() function now clears next variable, so it can be used with ForEach Controller
  • +
  • 38682 - add CallableStatement functionality to JDBC Sampler
  • +
  • Make it easier to change the RMI/Server port
  • +
  • Add property jmeter.save.saveservice.xml_pi to provide optional xml processing instruction in JTL files
  • +
  • Add bytes and URL to items that can be saved in sample log files (XML and CSV)
  • +
  • The Post-Processor "Save Responses to a File" now saves the generated file name with the +sample, and the file name can be included in the sample log file. +
  • +
  • Change jmeter.bat DOS script so it works from any directory
  • +
  • New -N option to define nonProxyHosts from command-line
  • +
  • New -S option to define system properties from input file
  • +
  • 26136 - allow configuration of local address
  • +
  • Expand tree by default when loading a test plan - can be disabled by setting property onload.expandtree=false
  • +
  • 11843 - URL Rewriter can now cache the session id
  • +
  • Counter Pre-Processor now supports formatted numbers
  • +
  • Add support for HEAD PUT OPTIONS TRACE and DELETE methods
  • +
  • Allow default HTTP implementation to be changed
  • +
  • Optionally save active thread counts (group and all) to result files
  • +
  • Variables/functions can now be used in Listener file names
  • +
  • New __time() function; define START.MS/START.YMD/START.HMS properties and variables
  • +
  • Add Thread Name to Tree and Table Views
  • +
  • Add debug functions: What class, debug on, debug off
  • +
  • Non-caching Calculator - used by Table Visualiser to reduce memory footprint
  • +
  • Summary Report - similar to Aggregate Report, but uses less memory
  • +
  • 39580 - recycle option for CSV Dataset
  • +
  • 37652 - support for Ajp Tomcat protocol
  • +
  • 39626 - Loading SOAP/XML-RPC requests from file
  • +
  • 39652 - Allow truncation of labels on AxisGraph
  • +
  • Allow use of htmlparser 1.6
  • +
  • 39656 - always use SOAP action if it is provided
  • +
  • Automatically include properties from user.properties file
  • +
  • Add __jexl() function - evaluates Commons JEXL expressions
  • +
  • Optionally load JMeter properties from user.properties and system properties from system.properties.
  • +
  • 39707 - allow Regex match against URL
  • +
  • Add start time to Table Visualiser
  • +
  • HTTP Samplers can now extract embedded resources for any required media types
  • +
+ +

Bug fixes:

+
    +
  • Fix NPE when no module selected in Module Controller
  • +
  • Fix NPE in XStream when no ResponseData present
  • +
  • Remove ?xml prefix when running with Java 1.5 and no x-jars
  • +
  • 37117 - setProperty() function should return ""; added optional return of original setting
  • +
  • Fix CSV output time format
  • +
  • 37140 - handle encoding better in RegexFunction
  • +
  • Load all cookies, not just the first; fix class cast exception
  • +
  • Fix default Cookie path name (remove page name)
  • +
  • Fixed resultcode attribute name
  • +
  • 36898 - apply encoding to RegexExtractor
  • +
  • Add properties for saving subresults, assertions, latency, samplerData, responseHeaders, requestHeaders & encoding
  • +
  • 37705 - Synch Timer now works OK after run is stopped
  • +
  • 37716 - Proxy request now handles file Post correctly
  • +
  • HttpClient Sampler now saves latency
  • +
  • Fix NPE when using JavaScript function on Test Plan
  • +
  • Fix Base Href parsing in htmlparser
  • +
  • 38256 - handle cookie with no path
  • +
  • 38391 - use long when accumulating timer delays
  • +
  • 38554 - Random function now uses long numbers
  • +
  • 35224 - allow duplicate attributes for LDAP sampler
  • +
  • 38693 - Webservice sampler can now use https protocol
  • +
  • 38646 - Regex Extractor now clears old variables on match failure
  • +
  • 38640 - fix WebService Sampler pooling
  • +
  • 38474 - HTML Link Parser doesn't follow frame links
  • +
  • 36430 - Counter now uses long rather than int to increase the range
  • +
  • 38302 - fix XPath function
  • +
  • 38748 - JDBC DataSourceElement fails with remote testing
  • +
  • 38902 - sometimes -1 seems to be returned unnecessarily for response code
  • +
  • 38840 - make XML Assertion thread-safe
  • +
  • 38681 - Include controller now works in non-GUI mode
  • +
  • Add write(OS,IS) implementation to TCPClientImpl
  • +
  • Sample Result converter saves response code as "rc". Previously it saved as "rs" but read with "rc"; it will now also read with "rc". +The XSL stylesheets also now accept either "rc" or "rs"
  • +
  • Fix counter function so each counter instance is independent (previously the per-user counters were shared between instances of the function)
  • +
  • Fix TestBean Examples so that they work
  • +
  • Fix JTidy parser so it does not skip body tags with background images
  • +
  • Fix HtmlParser parser so it catches all background images
  • +
  • 39252 set SoapSampler sample result from XML data
  • +
  • 38694 - WebServiceSampler not setting data encoding correctly
  • +
  • Result Collector now closes input files read by listeners
  • +
  • 25505 - First HTTP sampling fails with "HTTPS hostname wrong: should be 'localhost'"
  • +
  • 25236 - remove double scrollbar from Assertion Result Listener
  • +
  • 38234 - Graph Listener divide by zero problem
  • +
  • 38824 - clarify behaviour of Ignore Status
  • +
  • 38250 - jmeter.properties "language" now supports country suffix, for zh_CN and zh_TW etc
  • +
  • jmeter.properties file is now closed after it has been read
  • +
  • 39533 - StatCalculator added wrong items
  • +
  • 39599 - ConcurrentModificationException
  • +
  • HTTPSampler2 now handles Auto and Follow redirects correctly
  • +
  • 29481 - fix reloading sample results so subresults not counted twice
  • +
  • 30267 - handle AutoRedirects properly
  • +
  • 39677 - allow for space in JMETER_BIN variable
  • +
  • Use Commons HttpClient cookie parsing and management. Fix various problems with cookie handling.
  • +
  • 39773 - NTCredentials needs host name
  • +
+ +

Other changes

+
    +
  • Updated to HTTPClient 3.0 (from 2.0)
  • +
  • Updated to Commons Collections 3.1
  • +
  • Improved formatting of Request Data in Tree View
  • +
  • Expanded user documentation
  • +
  • Added MANIFEST, NOTICE and LICENSE to all jars
  • +
  • Extract htmlparser interface into separate jarfile to make it possible to replace the parser
  • +
  • Removed SQL Config GUI as no longer needed (or working!)
  • +
  • HTTPSampler no longer logs a warning for Page not found (404)
  • +
  • StringFromFile now callable as __StringFromFile (as well as _StringFromFile)
  • +
  • Updated to Commons Logging 1.1
  • +
+ + + + +
+

Version 2.1.1

+

New functionality:

+
    +
  • New Include Controller allows a test plan to reference an external jmx file
  • +
  • New JUnitSampler added for using JUnit Test classes
  • +
  • New Aggregate Graph listener is capable of graphing aggregate statistics
  • +
  • Can provide additional classpath entries using the property user.classpath and on the Test Plan element
  • +
+

Bug fixes:

+
    +
  • AccessLog Sampler and JDBC test elements populated correctly from 2.0 test plans
  • +
  • BSF Sampler now populates filename and parameters from saved test plan
  • +
  • 36500 - handle missing data more gracefully in WebServiceSampler
  • +
  • 35546 - add merge to right-click menu
  • +
  • 36642 - Summariser stopped working in 2.1
  • +
  • 36618 - CSV header line did not match saved data
  • +
  • JMeter should now run under JVM 1.3 (but does not build with 1.3)
  • +
+ + + + +

Version 2.1

+

New functionality:

+
    +
  • New Test Script file format - smaller, more compact, more readable
  • +
  • New Sample Result file format - smaller, more compact
  • +
  • XSchema Assertion
  • +
  • XML Tree display
  • +
  • CSV DataSet Config item
  • +
  • New JDBC Connection Pool Config Element
  • +
  • Synchronisation Timer
  • +
  • setProperty function
  • +
  • Save response data on error
  • +
  • Ant JMeter XSLT now optionally shows failed responses and has internal links
  • +
  • Allow JavaScript variable name to be omitted
  • +
  • Changed following Samplers to set sample label from sampler name
  • +
  • All Test elements can be saved as a graphics image to a file
  • +
  • 35026 - add RE pattern matching to Proxy
  • +
  • 34739 - Enhance constant Throughput timer
  • +
  • 25052 - use response encoding to create comparison string in Response Assertion
  • +
  • New optional icons
  • +
  • Allow icons to be defined via property files
  • +
  • New stylesheets for 2.1 format XML test output
  • +
  • Save samplers, config element and listeners as PNG
  • +
  • Enhanced support for WSDL processing
  • +
  • New JMS sampler for topic and queue messages
  • +
  • How-to for JMS samplers
  • +
  • 35525 - Added Spanish localisation
  • +
  • 30379 - allow server.rmi.port to be overridden
  • +
  • enhanced the monitor listener to save the calculated stats
  • +
  • Functions and variables now work at top level of test plan
  • +
+

Bug fixes:

+
    +
  • 34586 - XPath always remained as /
  • +
  • BeanShellInterpreter did not handle null objects properly
  • +
  • Fix Chinese resource bundle names
  • +
  • Save field names if required to CSV files
  • +
  • Ensure XML file is closed
  • +
  • Correct icons now displayed for TestBean components
  • +
  • Allow for missing optional jar(s) in creating menus
  • +
  • Changed Samplers to set sample label from sampler name as was the case for HTTP
  • +
  • Fix various samplers to avoid NPEs when incomplete data is provided
  • +
  • Fix Cookie Manager to use seconds; add debug
  • +
  • 35067 - set up filename when using -t option
  • +
  • Don't substitute TestElement.* properties by UDVs in Proxy
  • +
  • 35065 - don't save old extensions in File Saver
  • +
  • 25413 - don't enable Restart button unnecessarily
  • +
  • 35059 - Runtime Controller stopped working
  • +
  • Clear up any left-over connections created by LDAP Extended Sampler
  • +
  • 23248 - module controller didn't remember stuff between save and reload
  • +
  • Fix Chinese locales
  • +
  • 29920 - change default locale if necessary to ensure default properties are picked up when English is selected.
  • +
  • Bug fixes for Tomcat monitor captions
  • +
  • Fixed webservice sampler so it works with user defined variables
  • +
  • Fixed screen borders for LDAP config GUI elements
  • +
  • 31184 - make sure encoding is specified in JDBC sampler
  • +
  • TCP sampler - only share sockets with same host:port details; correct the manual
  • +
  • Extract src attribute for embed tags in JTidy and Html Parsers
  • +
+ + + +

Version 2.0.3

+

New functionality:

+
    +
  • XPath Assertion and XPath Function
  • +
  • Switch Controller
  • +
  • ForEach Controller can now loop through sets of groups
  • +
  • Allow CSVRead delimiter to be changed (see jmeter.properties)
  • +
  • 33920 - allow additional property files
  • +
  • 33845 - allow direct override of Home dir
  • +
+

Bug fixes:

+
    +
  • Regex Extractor nested constant not put in correct place 32395
  • +
  • Start time reset to now if necessary so that delay works OK.
  • +
  • Missing start/end times in scheduler are assumed to be now, not 1970
  • +
  • 28661 - 304 responses not appearing in listeners
  • +
  • DOS scripts now handle different disks better
  • +
  • 32345 - HTTP Rewriter does not work with HTTP Request default
  • +
  • Catch Runtime Exceptions so an error in one Listener does not affect others
  • +
  • 33467 - __threadNum() extracted number wrongly
  • +
  • 29186,33299 - fix CLI parsing of "-" in second argument
  • +
  • Fix CLI parse bug: -D arg1=arg2. Log more startup parameters.
  • +
  • Fix JTidy and HTMLParser parsers to handle form src= and link rel=stylesheet
  • +
  • JMeterThread now logs Errors to jmeter.log which were appearing on console
  • +
  • Ensure WhileController condition is dynamically checked
  • +
  • 32790 ensure If Controller condition is re-evaluated each time
  • +
  • 30266 - document how to display proxy recording responses
  • +
  • 33921 - merge should not change file name
  • +
  • Close file now gives chance to save changes
  • +
  • 33559 - fixes to Runtime Controller
  • +
+

Other changes:

+
    +
  • To help with variable evaluation, JMeterThread sets "sampling started" a bit earlier (see jmeter.properties)
  • +
  • 33796 - delete cookies with null/empty values
  • +
  • Better checking of parameter count in JavaScript function
  • +
  • Thread Group now defaults to 1 loop instead of forever
  • +
  • All Beanshell access is now via a single class; only need BSH jar at run-time
  • +
  • 32464 - document Direct Draw settings in jmeter.bat
  • +
  • 33919 - increase Counter field sizes
  • +
  • 32252 - ForEach was not initialising counters
  • +
+ + + +

Version 2.0.2

+

New functionality:

+
    +
  • While Controller
  • +
  • BeanShell intilisation scripts
  • +
  • Result Saver can optionally save failed results only
  • +
  • Display as HTML has option not to download frames and images etc
  • +
  • Multiple Tree elements can now be enabled/disabled/copied/pasted at once
  • +
  • __split() function added
  • +
  • 28699 allow Assertion to regard unsuccessful responses - e.g. 404 - as successful
  • +
  • 29075 Regex Extractor can now extract data out of http response header as well as the body
  • +
  • __log() functions can now write to stdout and stderr
  • +
  • URL Modifier can now optionally ignore query parameters
  • +
+

Bug fixes:

+
    +
  • If controller now works after the first false condition 31390
  • +
  • Regex GUI was losing track of Header/Body checkbox 29853
  • +
  • Display as HTML now handles frames and relative images
  • +
  • Right-click open replaced by merge
  • +
  • Fix some drag and drop problems
  • +
  • Fixed foreach demo example so it works
  • +
  • 30741 SSL password prompt now works again
  • +
  • StringFromFile now closes files at end of test; start and end now optional as intended
  • +
  • 31342 Fixed text of SOAP Sampler headers
  • +
  • Proxy must now be stopped before it can be removed 25145
  • +
  • Link Parser now supports BASE href 25490
  • +
  • 30917 Classfinder ignores duplicate names
  • +
  • 22820 Allow Counter value to be cleared
  • +
  • 28230 Fix NPE in HTTP Sampler retrieving embedded resources
  • +
  • Improve handling of StopTest; catch and log some more errors
  • +
  • ForEach Controller no longer runs any samples if first variable is not defined
  • +
  • 28663 NPE in remote JDBC execution
  • +
  • 30110 Deadlock in stopTest processing
  • +
  • 31696 Duration not working correctly when using Scheduler
  • +
  • JMeterContext now uses ThreadLocal - should fix some potential NPE errors
  • +
+

Version 2.0.1

+

Bug fix release. TBA.

+

Version 2.0

+
    +
  • HTML parsing improved; now has choice of 3 parsers, and most embedded elements can now be detected and downloaded.
  • +
  • Redirects can now be delegated to URLConnection by defining the JMeter property HTTPSamper.delegateRedirects=true (default is false)
  • +
  • Stop Thread and Stop Test methods added for Samplers and Assertions etc. Samplers can call setStopThread(true) or setStopTest(true) if they detect an error that needs to stop the thread of the test after the sample has been processed
  • +
  • Thread Group Gui now has an extra pane to specify what happens after a Sampler error: Continue (as now), Stop Thread or Stop Test. + This needs to be extended to a lower level at some stage.
  • +
  • Added Shutdown to Run Menu. This is the same as Stop except that it lets the Threads finish normally (i.e. after the next sample has been completed)
  • +
  • Remote samples can be cached until the end of a test by defining the property hold_samples=true when running the server. +More work is needed to be able to control this from the GUI
  • +
  • Proxy server has option to skip recording browser headers
  • +
  • Proxy restart works better (stop waits for daemon to finish)
  • +
  • Scheduler ignores start if it has already passed
  • +
  • Scheduler now has delay function
  • +
  • added Summariser test element (mainly for non-GUI) testing. This prints summary statistics to System.out and/or the log file every so oftem (3 minutes by default). Multiple summarisers can be used; samples are accumulated by summariser name.
  • +
  • Extra Proxy Server options: +Create all samplers with keep-alive disabled +Add Separator markers between sets of samples +Add Response Assertion to first sampler in each set
  • +
  • Test Plan has a comment field
  • + +
  • Help Page can now be pushed to background
  • +
  • Separate Function help page
  • +
  • New / amended functions
  • +
      +
    • __property() and __P() functions
    • +
    • __log() and __logn() - for writing to the log file
    • +
    • _StringFromFile can now process a sequence of files, e.g. dir/file01.txt, dir/file02.txt etc
    • +
    • _StringFromFile() funtion can now use a variable or function for the file name
    • +
    +
  • New / amended Assertions
  • +
      +
    • Response Assertion now works for URLs, and it handles null data better
    • +
    • Response Assertion can now match on Response Code and Response message as well
    • +
    • HTML Assertion using JTidy to check for well-formed HTML
    • +
    +
  • If Controller (not fully functional yet)
  • +
  • Transaction Controller (aggregates the times of its children)
  • +
  • New Samplers
  • +
      +
    • Basic BSF Sampler (optional)
    • +
    • BeanShell Sampler (optional, needs to be downloaded from www.beanshell.org
    • +
    • Basic TCP Sampler
    • +
    +
  • Optionally start BeanShell server (allows remote access to JMeter variables and methods)
  • +
+

Version 1.9.1

+

TBA

+

Version 1.9

+
    +
  • Sample result log files can now be in CSV or XML format
  • +
  • New Event model for notification of iteration events during test plan run
  • +
  • New Javascript function for executing arbitrary javascript statements
  • +
  • Many GUI improvements
  • +
  • New Pre-processors and Post-processors replace Modifiers and Response-Based Modifiers.
  • +
  • Compatible with jdk1.3
  • +
  • JMeter functions are now fully recursive and universal (can use functions as parameters to functions)
  • +
  • Integrated help window now supports hypertext links
  • +
  • New Random Function
  • +
  • New XML Assertion
  • +
  • New LDAP Sampler (alpha code)
  • +
  • New Ant Task to run JMeter (in extras folder)
  • +
  • New Java Sampler test implementation (to assist developers)
  • +
  • More efficient use of memory, faster loading of .jmx files
  • +
  • New SOAP Sampler (alpha code)
  • +
  • New Median calculation in Graph Results visualizer
  • +
  • Default config element added for developer benefit
  • +
  • Various performance enhancements during test run
  • +
  • New Simple File recorder for minimal GUI overhead during test run
  • +
  • New Function: StringFromFile - grabs values from a file
  • +
  • New Function: CSVRead - grabs multiple values from a file
  • +
  • Functions now longer need to be encoded - special values should be escaped +with "\" if they are literal values
  • +
  • New cut/copy/paste functionality
  • +
  • SSL testing should work with less user-fudging, and in non-gui mode
  • +
  • Mailer Model works in non-gui mode
  • +
  • New Througput Controller
  • +
  • New Module Controller
  • +
  • Tests can now be scheduled to run from a certain time till a certain time
  • +
  • Remote JMeter servers can be started from a non-gui client. Also, in gui mode, all remote servers can be started with a single click
  • +
  • ThreadGroups can now be run either serially or in parallel (default)
  • +
  • New command line options to override properties
  • +
  • New Size Assertion
  • + +
+ +

Version 1.8.1

+
    +
  • Bug Fix Release. Many bugs were fixed.
  • +
  • Removed redundant "Root" node from test tree.
  • +
  • Re-introduced Icons in test tree.
  • +
  • Some re-organization of code to improve build process.
  • +
  • View Results Tree has added option to view results as web document (still buggy at this point).
  • +
  • New Total line in Aggregate Listener (still buggy at this point).
  • +
  • Improvements to ability to change JMeter's Locale settings.
  • +
  • Improvements to SSL Manager.
  • +
+ +

Version 1.8

+
    +
  • Improvement to Aggregate report's calculations.
  • +
  • Simplified application logging.
  • +
  • New Duration Assertion.
  • +
  • Fixed and improved Mailer Visualizer.
  • +
  • Improvements to HTTP Sampler's recovery of resources (sockets and file handles).
  • +
  • Improving JMeter's internal handling of test start/stop.
  • +
  • Fixing and adding options to behavior of Interleave and Random Controllers.
  • +
  • New Counter config element.
  • +
  • New User Parameters config element.
  • +
  • Improved performance of file opener.
  • +
  • Functions and other elements can access global variables.
  • +
  • Help system available within JMeter's GUI.
  • +
  • Test Elements can be disabled.
  • +
  • Language/Locale can be changed while running JMeter (mostly).
  • +
  • View Results Tree can be configured to record only errors.
  • +
  • Various bug fixes.
  • +
+ +

Version 1.7.3

+
    +
  • New Functions that provide more ability to change requests dynamically during test runs.
  • +
  • New language translations in Japanese and German.
  • +
  • Removed annoying Log4J error messages.
  • +
  • Improved support for loading JMeter 1.7 version test plan files (.jmx files).
  • +
  • JMeter now supports proxy servers that require username/password authentication.
  • +
  • Dialog box indicating test stopping doesn't hang JMeter on problems with stopping test.
  • +
  • GUI can run multiple remote JMeter servers (fixes GUI bug that prevented this).
  • +
  • Dialog box to help created function calls in GUI.
  • +
  • New Keep-alive switch in HTTP Requests to indicate JMeter should or should not use Keep-Alive for sockets.
  • +
  • HTTP Post requests can have GET style arguments in Path field. Proxy records them correctly now.
  • +
  • New User-defined test-wide static variables.
  • +
  • View Results Tree now displays more information, including name of request (matching the name +in the test tree) and full request and POST data.
  • +
  • Removed obsolete View Results Visualizer (use View Results Tree instead).
  • +
  • Performance enhancements.
  • +
  • Memory use enhancements.
  • +
  • Graph visualizer GUI improvements.
  • +
  • Updates and fixes to Mailer Visualizer.
  • +
+ +

Version 1.7.2

+
    +
  • JMeter now notifies user when test has stopped running.
  • +
  • HTTP Proxy server records HTTP Requests with re-direct turned off.
  • +
  • HTTP Requests can be instructed to either follow redirects or ignore them.
  • +
  • Various GUI improvements.
  • +
  • New Random Controller.
  • +
  • New SOAP/XML-RPC Sampler.
  • +
+ +

Version 1.7.1

+
    +
  • JMeter's architecture revamped for a more complete separation between GUI code and +test engine code.
  • +
  • Use of Avalon code to save test plans to XML as Configuration Objects
  • +
  • All listeners can save data to file and load same data at later date.
  • +
+ +

Version 1.7Beta

+
    +
  • Better XML support for special characters (Tushar Bhatia)
  • +
  • Non-GUI functioning & Non-GUI test plan execution (Tushar Bhatia)
  • +
  • Removing Swing dependence from base JMeter classes
  • +
  • Internationalization (Takashi Okamoto)
  • +
  • AllTests bug fix (neth6@atozasia.com)
  • +
  • ClassFinder bug fix (neth6@atozasia.com)
  • +
  • New Loop Controller
  • +
  • Proxy Server records HTTP samples from browser + (and documented in the user manual)
  • Multipart Form support
  • +
  • HTTP Header class for Header customization
  • +
  • Extracting HTTP Header information from responses (Jamie Davidson)
  • +
  • Mailer Visualizer re-added to JMeter
  • +
  • JMeter now url encodes parameter names and values
  • +
  • listeners no longer give exceptions if their gui's haven't been initialized
  • +
  • HTTPS and Authorization working together
  • +
  • New Http sampling that automatically parses HTML response + for images to download, and includes the downloading of these + images in total time for request (Neth neth6@atozasia.com)
  • +
  • HTTP responses from server can be parsed for links and forms, + and dynamic data can be extracted and added to test samples + at run-time (documented)
  • +
  • New Ramp-up feature (Jonathan O'Keefe)
  • +
  • New visualizers (Neth)
  • +
  • New Assertions for functional testing
  • +
+ +

Version 1.6.1

+
    +
  • Fixed saving and loading of test scripts (no more extra lines)
  • +
  • Can save and load special characters (such as "&" and "<").
  • +
  • Can save and load timers and listeners.
  • +
  • Minor bug fix for cookies (if you cookie value + contained an "=", then it broke).
  • +
  • URL's can sample ports other than 80, and can test HTTPS, + provided you have the necessary jars (JSSE)
  • +
+ +

Version 1.6 Alpha

+
    +
  • New UI
  • +
  • Separation of GUI and Logic code
  • +
  • New Plug-in framework for new modules
  • +
  • Enhanced performance
  • +
  • Layering of test logic for greater flexibility
  • +
  • Added support for saving of test elements
  • +
  • Added support for distributed testing using a single client
  • + +
+

Version 1.5.1

+
    +
  • Fixed bug that caused cookies not to be read if header name case not as expected.
  • +
  • Clone entries before sending to sampler - prevents relocations from messing up + information across threads
  • +
  • Minor bug fix to convenience dialog for adding paramters to test sample. + Bug prevented entries in dialog from appearing in test sample.
  • +
  • Added xerces.jar to distribution
  • +
  • Added junit.jar to distribution and created a few tests.
  • +
  • Started work on new framework. New files in cvs, but do not effect program yet.
  • +
  • Fixed bug that prevent HTTPJMeterThread from delaying according to chosen timer.
  • +
+

+

Version 1.5

+
    +
  • Abstracted out the concept of the Sampler, SamplerController, and TestSample. + A Sampler represents code that understands a protocol (such as HTTP, + or FTP, RMI, SMTP, etc..). It is the code that actually makes the + connection to whatever is being tested. A SamplerController + represents code that understands how to organize and run a group + of test samples. It is what binds together a Sampler and its test + samples and runs them. A TestSample represents code that understands + how to gather information from the user about a particular test. + For a website, it would represent a URL and any information to be sent + with the URL.
  • +
  • The UI has been updated to make entering test samples more convenient.
  • +
  • Thread groups have been added, allowing a user to setup multiple test to run + concurrently, and to allow sharing of test samples between those tests.
  • +
  • It is now possible to save and load test samples.
  • +
  • ....and many more minor changes/improvements...
  • +
+

+

+Apache JMeter 1.4.1-dev +

    +
  • Cleaned up URLSampler code after tons of patches for better readability. (SM)
  • +
  • Made JMeter send a special "user-agent" identifier. (SM)
  • +
  • Fixed problems with redirection not sending cookies and authentication info and removed + a warning with jikes compilation. Thanks to Wesley Tanaka for the patches (SM)
  • +
  • Fixed a bug in the URLSampler that caused to skip one URL when testing lists of URLs and + a problem with Cookie handling. Thanks to Graham Johnson for the patches (SM)
  • +
  • Fixed a problem with POST actions. Thanks to Stephen Schaub for the patch (SM)
  • +
+

+

+ Apache JMeter 1.4 - Jul 11 1999 +

    +
  • Fixed a problem with POST actions. Thanks to Brendan Burns for the patch (SM)
  • +
  • Added close button to the About box for those window managers who don't provide it. + Thanks to Jan-Henrik Haukeland for pointing it out. (SM)
  • +
  • Added the simple Spline sample visualizer (JPN)
  • +

+

Apache JMeter 1.3 - Apr 16 1999 +

    +
  • Run the Garbage Collector and run finalization before starting to sampling to ensure + same state every time (SM)
  • +
  • Fixed some NullPointerExceptions here and there (SM)
  • +
  • Added HTTP authentication capabilities (RL)
  • +
  • Added windowed sample visualizer (SM)
  • +
  • Fixed stupid bug for command line arguments. Thanks to Jorge Bracer for pointing this out (SM)
  • +

+

Apache JMeter 1.2 - Mar 17 1999 +

    +
  • Integrated cookie capabilities with JMeter (SM)
  • +
  • Added the Cookie manager and Netscape file parser (SD)
  • +
  • Fixed compilation error for JDK 1.1 (SD)

+

Apache JMeter 1.1 - Feb 24 1999 +

    +
  • Created the opportunity to create URL aliasing from the properties file as well as the + ability to associate aliases to URL sequences instead of single URLs (SM) Thanks to + Simon Chatfield for the very nice suggestions + and code examples.
  • +
  • Removed the TextVisualizer and replaced it with the much more useful FileVisualizer (SM)
  • +
  • Added the known bug list (SM)
  • +
  • Removed the Java Apache logo (SM)
  • +
  • Fixed a couple of typos (SM)
  • +
  • Added UNIX makefile (SD)

+

Apache JMeter 1.0.1 - Jan 25 1999 +

    +
  • Removed pending issues doc issues (SM)
  • +
  • Fixed the unix script (SM)
  • +
  • Added the possibility of running the JAR directly using "java -jar + ApacheJMeter.jar" with Java 2 (SM)
  • +
  • Some small updates: fixed Swing location after Java 2(tm) release, license update and + small cleanups (SM)
  • +

+

Apache JMeter 1.0 - Dec 15 1998 +

    +
  • Initial version. (SM)
  • +

+
+ +
diff --git a/xdocs/css/new-style.css b/xdocs/css/new-style.css new file mode 100644 index 00000000000..3b651833b7a --- /dev/null +++ b/xdocs/css/new-style.css @@ -0,0 +1,398 @@ +/* + 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. +*/ +.main { + font-family: DejaVu Sans, Helvetica, sans-serif; + width: 60em; +} + +img { + border: 0; + width: auto; + max-width: 95%; + height: auto; +} + +.menu, h1, h2, h3, h4, h5, .go-top, .title { + font-family: "Merriweather"; +} + +h1, h2, h3, h4, h5, .title { + border-bottom: 0.2rem solid orange; +} + +.title>.title { + border-bottom: 0px; +} + +.footer { + background-color: #444; + border-top: 1px solid black; + color: white; + margin-top: 3em; + padding: 2em 0 1em; + text-align: center; + box-shadow: 0 -8px 21px 0 rgba(0, 0, 0, 0.2); +} + +.menu { + border: 1px solid lightgray; + box-shadow: 5px 5px 10px rgba(20, 20, 20, 0.3); + list-style: outside none none; + margin: 0.5em; + padding: 0.5em; +} + +.menu+.menu { + margin-top: 1.5em; +} + +.menu img { + margin-left: 0.3em; + vertical-align: middle; + width: auto; + max-width: 95%; + height: auto; +} + +.banner>iframe { + width: 240px; + height: 70px; +} +.banner>iframe>body { + margin: 0px; + padding: 0px; +} +.banner>iframe img { + width: auto; + max-width: 100% + height: auto; +} + +body { + margin: 0px; + padding: 0px; +} + +.section + pre { + background: none repeat scroll 0 0 lightblue; + border: 1px solid gray; + padding: 0.3rem; + margin: 0.3rem; + font-family: dejavu sans mono, monospace, sans-serif; + overflow: auto; +} + +.code { + background: none repeat scroll 0 0 lightblue; + padding: 0.1em; + font-family: dejavu sans mono, monospace, sans-serif; +} + +.required-Yes>span { + font-weight: bold; +} + +.deprecated, .note { + background: none repeat scroll 0 0 #fee; + border: 1px solid #dbb; + margin: 1em; + padding: 1em; +} + +.component { + background: none repeat scroll 0 0 #fff; + margin: 1em; + padding: 0.4em; +} + +.subsection { + background: none repeat scroll 0 0 white; + margin: 1em; + padding: 1em; +} + +.screenshot { + margin: 2em; + padding: 0; +} + +figure { + margin: 1em 2em; +} + +figure>a>img, .screenshot>a>img { + box-shadow: 10px 10px 10px 0 rgba(50, 50, 50, 0.25); + overflow: auto; + width: auto; + max-width: 100%; + height: auto; +} + +.clear { + clear: both; +} + +.nostyle { + border: 1px solid black; +} + +.title { + font-size: 120%; + font-weight: bold; +} + +.example { + background: none repeat scroll 0 0 lightgray; + border: 1px solid gray; + clear: both; + padding: 1em; + margin: 1em; +} + +.property .name, .property + .description, .property + .required { + display: inline-block; +} + +.property .name { + font-style: italic; + vertical-align: top; + width: 20%; + word-wrap: break-word; +} + +.property .description { + vertical-align: top; + width: 60%; +} + +.property .required { + vertical-align: top; + width: 20%; +} + +.property+.property { + margin-top: 0.5em; +} + +.required.req-false { + font-weight: lighter; +} + +.go-top { + margin: 1em 0; + font-size: 120%; +} + +.properties { + background: none repeat scroll 0 0 lightgoldenrodyellow; + border: 1px solid darksalmon; + margin: 1em; + padding: 1em; +} + +.properties .title { + font-size: 100%; +} + +th { + border-bottom: 1px solid black; + font-family: "Merriweather"; + text-align: left; +} + +td { + vertical-align: top; +} + +tr+tr { + margin-top: 0.2em; +} + +table { + border-bottom: 2px solid; + border-top: 2px solid; +} + +.nav { + display: inline-block; + max-width: 20em; + vertical-align: top; + width: 33%; +} + +.main { + display: inline-block; + margin-left: 2em; + max-width: 60em; + width: 60%; +} + +.header { + clear: both; + display: table; + margin-bottom: 1rem; + width: 100%; + box-shadow: 0px 5px 33px 0px rgba(0, 0, 0, 0.2); + padding: 0.1rem 0em 0.2rem; + border-bottom: 1px solid gray; +} + +.header>div { + display: table-cell; + vertical-align: middle; +} + +.header>div+.header>div { + text-align: center; +} + +.sectionlink { + display: none; +} + +:hover>.sectionlink { + display: inline; + color: orange; +} + +.pagelinks { + list-style: none; +} + +.pagelinks li { + display: inline-block; + margin: 1em; +} + +.pagelinks>li { + border: 1px solid #bbb; + box-shadow: 5px 5px 5px rgba(20, 20, 20, 0.2); + padding: 0.5em 1em; +} + +.pagelinks li { + display: inline-block; + margin: 1em; + font-family: "Merriweather"; +} + +.section-index { + font-family: "Merriweather"; + margin:; + list-style: none; +} + +.section-index>li { + margin: 1em; + padding: 1em; + border: 1px solid #bbb; + box-shadow: 5px 5px 5px rgba(20, 20, 20, 0.2); +} + +.hidden { + display: none; +} + +@media screen and (max-width: 900px) { + .nav { + display: block; + width: 95%; + max-width: 95%; + } + .main { + display: block; + width: 95%; + max-width: 95%; + margin-left: 0.5em; + } + .section-index, pagelinks { + padding-left: 0px; + margin-left: 0px; + } + figure { + margin: 1em 0px; + } + .properties { + margin: 1rem 0px; + } + .property .name, .property + .description, .property + .required { + display: block; + width: 100%; + } + .property .required { + border-bottom: 1px solid #ddd; + } + .property .required::before { + content: 'R: '; + } + .property .name::before { + content: 'N: '; + } + .property .description::before { + content: 'D: '; + } + .header { + display: block; + } + .header span { + display: inline; + } + .pagelinks li { + margin: 0.5rem; + } + .header>div { + display: inline-block; + vertical-align: middle; + width: 50% + } + .header>div+.header>div { + text-align: center; + } + .menu ul { + display: none; + } + .menu li:hover>ul { + display: block; + } + .section-index ul { + display: none; + } + .section-index li:hover>ul { + display: block; + } + .subsection { + padding: 0.3rem; + margin: 0.1rem; + } + .header > .twitter { + width: 100%; + text-align: center; + } + .header > .twitter > div { + display: inline; + width: 50%; + } + .header > .banner { + width: 100%; + text-align: center; + } +} diff --git a/xdocs/css/style.css b/xdocs/css/style.css new file mode 100644 index 00000000000..7d3a68293b0 --- /dev/null +++ b/xdocs/css/style.css @@ -0,0 +1,39 @@ +/* + 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. +*/ + +/*Shows the value of the name attribute when hovered*/ +/* Disabled +a[name]:hover:after{ + content: " #" attr(name); + font-size: 90%; + text-decoration: none; +} +*/ + +/* + * Hide class="sectionlink", except when an enclosing heading + * has the :hover property. + * Used to hide the ¶ marker for generating internal links + */ +.sectionlink { + display: none; +} +:hover > .sectionlink { + display: inline; + /* Green so shows up on section headings too */ + color: rgb(0,255,0); +} diff --git a/xdocs/demos/AssertionTestPlan.jmx b/xdocs/demos/AssertionTestPlan.jmx new file mode 100644 index 00000000000..0cd3e3b91c2 --- /dev/null +++ b/xdocs/demos/AssertionTestPlan.jmx @@ -0,0 +1,128 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836421000 + 1211836421000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + GET + true + false + false + false + + + + false + + + + + + </html> + + 2 + Assertion.response_data + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + assertion.dat + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/xdocs/demos/AuthManagerTestPlan.jmx b/xdocs/demos/AuthManagerTestPlan.jmx new file mode 100644 index 00000000000..4b792dcd6b3 --- /dev/null +++ b/xdocs/demos/AuthManagerTestPlan.jmx @@ -0,0 +1,151 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836473000 + 1211836473000 + false + continue + + + + + + + + http://localhost/secret + kevin + spot + + + + + + + + + + + localhost + + http + + / + + + + + + + + + http + + /secret/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /secret/index2.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /index.html + GET + true + false + false + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + auth-manager.dat + + + + + + diff --git a/xdocs/demos/BeanShellAssertion.bsh b/xdocs/demos/BeanShellAssertion.bsh new file mode 100644 index 00000000000..72868132900 --- /dev/null +++ b/xdocs/demos/BeanShellAssertion.bsh @@ -0,0 +1,53 @@ +/* + * 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. + * + */ + +// Sample BeanShell Assertion script +// Derived from http://www.mail-archive.com/jmeter-user@jakarta.apache.org/msg05597.html + +if (ResponseCode != null && ResponseCode.equals ("200") == false ) +{ + // this is standard stuff + Failure=true ; + FailureMessage ="Response code was not a 200 response code it was " + ResponseCode + "." ; + print ( "the return code is " + ResponseCode); // this goes to stdout + log.warn( "the return code is " + ResponseCode); // this goes to the JMeter log file +} else { + try + { + // non standard stuff where BeanShell assertion will be really powerful . + // in my example I just test the size , but you could extend it further + // to actually test the content against another file. + byte [] arr = (byte[]) ResponseData ; + // print ( arr.length ) ; // use this to determine the size + if (arr != null && arr.length != 25218) + { + Failure= true ; + FailureMessage = "The response data size was not as expected" ; + } + else if ( arr == null ) + { + Failure= true ; + FailureMessage = "The response data size was null" ; + } + } + catch ( Throwable t ) + { + print ( t ) ; + log.warn("Error: ",t); + } +} \ No newline at end of file diff --git a/xdocs/demos/ForEachTest2.jmx b/xdocs/demos/ForEachTest2.jmx new file mode 100644 index 00000000000..b19c91fe041 --- /dev/null +++ b/xdocs/demos/ForEachTest2.jmx @@ -0,0 +1,322 @@ + + + + + false + + + + + false + + + + + 1 + false + continue + 1076438592000 + + false + 2 + + 1 + + + 1076438592000 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Sample 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + a b c d + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + (\w)\s + inputVar + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + For 1 ${returnVar} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + ${returnVar1} + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Sample 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + a b c d + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + (\w)\sx + inputVar + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + For 2 ${returnVar} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + ${returnVar} + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/xdocs/demos/HeaderManagerTestPlan.jmx b/xdocs/demos/HeaderManagerTestPlan.jmx new file mode 100644 index 00000000000..71621ee7800 --- /dev/null +++ b/xdocs/demos/HeaderManagerTestPlan.jmx @@ -0,0 +1,95 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836504000 + 1211836504000 + false + continue + + + + + + + + User-Agent + Mozilla/4.0 (compatible; MSIE 5.5; Windows 98) + + + + + + + + + jakarta.apache.org + + http + + / + GET + true + false + false + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + header-manager.dat + + + + + + diff --git a/xdocs/demos/InterleaveTestPlan.jmx b/xdocs/demos/InterleaveTestPlan.jmx new file mode 100644 index 00000000000..1429f0e0498 --- /dev/null +++ b/xdocs/demos/InterleaveTestPlan.jmx @@ -0,0 +1,160 @@ + + + + + + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 5 + + 0 + 2 + false + 0 + continue + + + + + + 0 + + + + + + + ${server} + + http + + /site/news/index.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + + + + + + + GET + false + true + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/xdocs/demos/InterleaveTestPlan2.jmx b/xdocs/demos/InterleaveTestPlan2.jmx new file mode 100644 index 00000000000..1eaabee89f9 --- /dev/null +++ b/xdocs/demos/InterleaveTestPlan2.jmx @@ -0,0 +1,234 @@ + + + + + + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 8 + + 0 + 1 + false + 0 + continue + + + + + + 1 + + + + + + + + + + + + + + + 1 + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + 1 + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + + + ${server} + + + + + GET + true + false + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/xdocs/demos/JDBC-Pre-Post-Processor.jmx b/xdocs/demos/JDBC-Pre-Post-Processor.jmx new file mode 100644 index 00000000000..de46391056f --- /dev/null +++ b/xdocs/demos/JDBC-Pre-Post-Processor.jmx @@ -0,0 +1,445 @@ + + + + + Execute a series of concurrent valuations + false + true + + + + CalculateFees + 1 + = + + + CalculatePerformanceDetails + 1 + = + + + DriverURL + jdbc:jtds:sqlserver: + = + + + DatabasePort + 1433 + = + + + UseMiddleTierValuationEngine + 0 + = + + + MiddleTierRequestTimeout + 500000 + = + + + PricePropagationMode + 2 + = + + + + + + + + + + PCOQuality + 5 + = + + + ValueDate + 2011-07-21 + = + + + ReportingDate + 2011-07-21 12:30:08.337 + = + + + + + + + + Database + HSPAD_MI_440_SSD + = + + + DatabaseHost + GAIA + = + + + DatabaseUser + sa + = + + + DatabasePassword + sa2008 + = + + + + + + + + Pfo_1 + 1548 + = + + + Pfo_2 + 1611 + = + + + Pfo_3 + 1613 + = + + + CutOff Nr 11249, 2011-07-2 / 2011-07-21 12:30:08.337 / DailyNAV Estimate / Within Price Cut-Off + + + + false + + 5000 + + ${DriverURL}//${DatabaseHost}:${DatabasePort}/${Database} + net.sourceforge.jtds.jdbc.Driver + true + ${DatabasePassword} + 25 + 10000 + 60000 + ${DatabaseUser} + 4096 + Connect to local HSPAD_Demo_CO and set its isolation mode to SNAPSHOT (4096) and disable auto commit. + + + + continue + + false + 3 + + 3 + 0 + 1316530469000 + 1316530469000 + false + + + + + + + WorkBench + Concurrent Valuation Test Plan + PCO Valuation + + + + + + continue + + false + 1 + + 1 + 1 + 1320821253000 + 1320821253000 + false + + + + + + 1 + 0 + + + + + + UPDATE T_SettingGlobal SET UseMiddleTierValuationEngine=?, MiddleTierRequestTimeout=? + ${UseMiddleTierValuationEngine}, ${MiddleTierRequestTimeout} + BIT, INTEGER + Prepared Update Statement + + + + + + + Commit + + + + + + + + + + 1 + 0 + 0 + + + + + Update Statement + DBCC DROPCLEANBUFFERS + + + + + + + + + Update Statement + DBCC FREEPROCCACHE + + + + + + + + + + + + true + + + + + Update Statement + BEGIN TRAN COMMIT TRAN + + + + + false + + + + + Callable Statement + PfoVal_Recalculate ?, ?, 1 + ${Pfo_1}, ${PfoValInstance} + INTEGER, INTEGER + + + true + + + + groovy + + + import groovy.sql.Sql +import org.apache.jmeter.protocol.jdbc.config.DataSourceElement +try { + // build Pfo List + println("Building Portfolio List") + def pfoList = "<PfoList>" + def pfoNr = 1 + def pfo = vars.get("Pfo_" + pfoNr) + while(pfo != null) { + println("Pfo: $pfo"); + pfoList = pfoList + "<Pfo ID='$pfo' EmptyValuation='true' PropagatePrice='true'/>" + pfoNr++ + pfo = vars.get("Pfo_" + pfoNr) + } + pfoList = pfoList + "</PfoList>" + vars.put("PfoListXML", pfoList) +} catch (Exception e) { + println(e.toString()); +} + + + + + CreatePriceCutOff ?, ?, ?, ?, ?, ?, ?, ? + ${__threadNum},${ValueDate},${PCOQuality},${ReportingDate},]NULL[,${PCO},${PfoListXML},${PricePropagationMode} + VARCHAR, DATE, INTEGER,TIMESTAMP,INTEGER,OUT INTEGER,CLOB,INTEGER + Callable Statement + + + + + + + Prepared Select Statement + select Nr from PfoValInstance where Pfo=? AND PriceCutOff=? + ${Pfo_1},${PCO} + INTEGER,INTEGER + PfoValInstance + + + + + + DeletePriceCutOff ? + ${PCO} + INTEGER + Callable Statement + + + + + + + ${JMeterThread.last_sample_ok} + false + + + + + Commit + + + + + + Commit the transaction of the valuation + false + + + + + ${JMeterThread.last_sample_ok}==false + false + + + + + Rollback + + + + + + false + + + + + + + false + + saveConfig + + + false + true + false + + false + false + true + false + false + false + false + false + false + true + false + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + diff --git a/xdocs/demos/JMSPointToPoint.jmx b/xdocs/demos/JMSPointToPoint.jmx new file mode 100644 index 00000000000..21e49bccdc5 --- /dev/null +++ b/xdocs/demos/JMSPointToPoint.jmx @@ -0,0 +1,103 @@ + + + + + + + + false + false + + + + + + 1115386407000 + + + 5 + false + + false + 4 + + 1115386407000 + continue + 5 + + + + + + + + + + = + tcp://localhost:61616 + brokerURL + + + = + example.MyQueue + queue.MyQueue + + + = + example.Q.REQ + queue.Q.REQ + + + = + example.Q.RPL + queue.Q.RPL + + + + false + Q.RPL + 5000 + Q.REQ + + ConnectionFactory + org.activemq.jndi.ActiveMQInitialContextFactory + <msg>test</msg> + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + + + + + + + + + diff --git a/xdocs/demos/LoopTestPlan.jmx b/xdocs/demos/LoopTestPlan.jmx new file mode 100644 index 00000000000..5ec16e63a79 --- /dev/null +++ b/xdocs/demos/LoopTestPlan.jmx @@ -0,0 +1,124 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836533000 + 1211836533000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + + + + + + + + + http + + / + GET + true + false + false + false + + + + false + + + + + true + 5 + + + + + + + + + http + + /site/news.html + GET + true + false + false + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + loop-test.dat + + + + + + diff --git a/xdocs/demos/OnceOnlyTestPlan.jmx b/xdocs/demos/OnceOnlyTestPlan.jmx new file mode 100644 index 00000000000..1715a675e85 --- /dev/null +++ b/xdocs/demos/OnceOnlyTestPlan.jmx @@ -0,0 +1,132 @@ + + + + + + + + = + jakarta.apache.org + server + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 3 + + 0 + 2 + false + 0 + continue + + + + + + + + + + + + + + + + + + + + + + ${server} + + + + + GET + true + false + true + false + + + + false + + + + + + + + + + + + + + GET + true + false + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/xdocs/demos/ProxyServerTestPlan.jmx b/xdocs/demos/ProxyServerTestPlan.jmx new file mode 100644 index 00000000000..7fe65112805 --- /dev/null +++ b/xdocs/demos/ProxyServerTestPlan.jmx @@ -0,0 +1,27 @@ + + + + + + + 8080 + + + true + 0 + false + 0 + false + true + true + false + false + false + + + + + + + + diff --git a/xdocs/demos/RegEx-User-Parameters.jmx b/xdocs/demos/RegEx-User-Parameters.jmx new file mode 100644 index 00000000000..fa61974c01a --- /dev/null +++ b/xdocs/demos/RegEx-User-Parameters.jmx @@ -0,0 +1,183 @@ + + + + + Start HTTP Mirror Server for demo + false + false + + + + + + + + continue + + false + 1 + + 1 + 1 + 1357057761000 + 1357057761000 + false + + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Request returning some form + = + + + ResponseCode + 200 + = + + + ResponseMessage + OK + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + <html> <body> <form name="test"> <input name="toto" value="vtoto" /> <input name="tutu" value="vtutu" /> <input name="titi" value="vtiti /> </form> </body> </html> + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + false + listParams + input name="([^"]+?)" value="([^"]+?)" + $1$ + NV + -1 + + + + + false + true + false + + + + + + + false + + = + true + toto + + + false + + = + true + tutu + + + + localhost + 8081 + + + + + /test + POST + true + false + true + false + false + + + + + listParams + 1 + 2 + + + + + + false + + saveConfig + + + true + true + true + + true + false + true + false + false + false + false + false + false + false + true + false + false + false + false + 0 + true + true + true + true + + + ONLY FOR SCRIPTIN + + + + + + + 8081 + 0 + 25 + + + + + + diff --git a/xdocs/demos/SimpleTestPlan.jmx b/xdocs/demos/SimpleTestPlan.jmx new file mode 100644 index 00000000000..a10f97e2a8f --- /dev/null +++ b/xdocs/demos/SimpleTestPlan.jmx @@ -0,0 +1,166 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836583000 + 1211836583000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + + + + + + + + + + + http + + /ant/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /ant/antnews.html + GET + true + false + false + false + + + + false + + + + + + + + + + + + + http + + /log4j/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /log4j/docs/history.html + GET + true + false + false + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + simple-test.dat + + + + + + diff --git a/xdocs/demos/URLRewritingExample.jmx b/xdocs/demos/URLRewritingExample.jmx new file mode 100644 index 00000000000..be60b1eb4e6 --- /dev/null +++ b/xdocs/demos/URLRewritingExample.jmx @@ -0,0 +1,142 @@ + + + + + + + + + false + false + + + + + 1200525828000 + + + 1 + false + + false + -1 + + 1200525828000 + continue + 0 + + + + + + + my.server.com + + + + / + GET + true + false + true + false + + + + false + + + + + + + false + false + SESSION_ID + false + true + + + + + + + = + user + true + username + true + + + = + password + true + password + true + + + + my.server.com + 80 + http + + /main.jsp + POST + true + false + true + false + + + + false + + + + + + + + my.server.com + + http + + /something_interesting.jsp + GET + true + false + true + false + + + + false + + + + + + + + my.server.com + + http + + /another.jsp + POST + true + false + true + false + + + + false + + + + + + + + diff --git a/xdocs/demos/forEachTestPlan.jmx b/xdocs/demos/forEachTestPlan.jmx new file mode 100644 index 00000000000..4dacd017628 --- /dev/null +++ b/xdocs/demos/forEachTestPlan.jmx @@ -0,0 +1,123 @@ + + + + + false + + + + + false + + + + + 1 + false + continue + 1076438592000 + + false + 1 + + 1 + + + 1076438592000 + + + + + + + localhost + 80 + + + / + GET + true + false + true + false + + + + false + + + + + inputVar + <a href="([^"]+)" + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + localhost + 80 + + + ${returnVar} + GET + true + false + true + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/xdocs/download_jmeter.cgi b/xdocs/download_jmeter.cgi new file mode 100755 index 00000000000..ba444668595 --- /dev/null +++ b/xdocs/download_jmeter.cgi @@ -0,0 +1,7 @@ +#!/bin/sh +# Wrapper script around mirrors.cgi script +# (we must change to that directory in order for python to pick up the +# python includes correctly) +cd /www/www.apache.org/dyn/mirrors +/www/www.apache.org/dyn/mirrors/mirrors.cgi $* + \ No newline at end of file diff --git a/xdocs/download_jmeter.xml b/xdocs/download_jmeter.xml new file mode 100644 index 00000000000..1683854ab4d --- /dev/null +++ b/xdocs/download_jmeter.xml @@ -0,0 +1,132 @@ + + + + +]> + + + + Download Apache JMeter + + +
+

+ We recommend you use a mirror to download our release + builds, but you must verify the integrity of + the downloaded files using signatures downloaded from our main + distribution directories. Recent releases (48 hours) may not yet + be available from all the mirrors. +

+ +

+ You are currently using [preferred]. If you + encounter a problem with this mirror, please select another + mirror. If all mirrors are failing, there are backup + mirrors (at the end of the mirrors list) that should be + available. +

+ [if-any logo][end] +

+ +
+

+ Other mirrors: + + +

+
+ +

+ The KEYS link links to the code signing keys used to sign the product. + The PGP link downloads the OpenPGP compatible signature from our main site. + The MD5 link downloads the md5 checksum from the main site. + The SHA link downloads the sha checksum from the main site. + Please verify the integrity + of the downloaded file. +

+

+ For more information concerning Apache JMeter, see the Apache JMeter site. +

+

+ KEYS +

+
+
+ + + + + + + + + + + + + + +
apache-jmeter-&release;.tgzmd5shapgp
apache-jmeter-&release;.zipmd5shapgp
+
+ + + + + + + + + + + + + + +
apache-jmeter-&release;_src.tgzmd5shapgp
apache-jmeter-&release;_src.zipmd5shapgp
+
+
+
+

+ Older releases can be obtained from the archives. +

+ +
+
+

+ It is essential that you verify the integrity of the downloaded files using the PGP signature. + Please read Verifying Apache Software Foundation Releases for more information on why you should verify our releases. +

+
+ +
diff --git a/xdocs/extending.xml b/xdocs/extending.xml new file mode 100644 index 00000000000..dee8c253ab1 --- /dev/null +++ b/xdocs/extending.xml @@ -0,0 +1,152 @@ + + + + + Extending JMeter + + +
+Note to developers: JMeter is undergoing large changes. The following +description of JMeter's architecture will likely change in the near future. If you +would like your changes to work with an upcoming JMeter 1.6, please join our mailing +list, and we will work with you and your modifications. +

Customizing JMeter to suit your needs.

+

Extensible Interfaces

+

+There are five basic objects in JMeter which provide extensibility: + +

    +
  • Visualizers represent the sampling data which is recorded.
  • +
  • Timers specify the delay between samples.
  • +
  • SamplerControllers hold information about all the test cases to be sampled, +and overall information about how the test is conducted.
  • +
  • Samplers are the classes that actually do the sampling of a particular protocol.
  • +
  • TestSamples hold information about a particular test case to be sampled.
  • +
+

+

+ +

Visualizers

+The Visualizer interface exists in the org.apache.jmeter.visualizers package. +JMeter maintains an instance of each visualizer it is aware of for each thread group currently available to the user. +A visualizer provides a method of recording the data which JMeter generates. +A visualizer may represent the data graphically (GraphVisualizer), +persistently (FileVisualizer) or both (TBD). +The visualizer contains three methods: +
+
add(SampleResult result) adds data to the visualization.
+
+JMeter calls the add method to include new data in the visualizer. +The visualizer should add the data into its data representation. +
+
clear() clears all data in the visualizer currently
+
+JMeter calls clear when the user requests that all visualizers be cleared. When the clear method is called the visualizer should clear all data from its representation and re-initialize itself. +
+
getControlPanel() obtains the GUI for the visualizer
+
+JMeter calls getControlPanel at start up time to prepare the +visualizer for display. +
+
+ +

+

+

Timers

+Timers provide a framework for delaying in between samples. This is important in order to obtain a true balanced load on a function rather than a calm-STORM-calm-STORM-calm... pattern. Timers contain two methods: +
+
delay() wait for a Timer specific amount of time
+
JMeter calls this function prior to every sample. The Timer should wait for a period of time and then return.
+
set() prepare for sampling
+
JMeter calls this function prior to the begining of a test session. The timer should initialize itself, read any values from its UI and prepare for operation. +
+
+

+

+ +

SamplerControllers

+The sampler controller is by far the most complicated, but also the most powerful, interface in JMeter. It allows a user to customize what, where and when JMeter tests. It provides six methods: +
+
start()
+
+JMeter calls this immediatly prior to starting a test. It is most often use to disable the SamplerController's GUI. +
+
stop()
+
+JMeter calls this when a user requests a stop to a test. It is most often used to re-enable the SamplerController's GUI. +
+
getControlPanel() Get the GUI for the SamplerController
+
+JMeter calls this at start up to create the its GUI. This is how a user enters +information into the SamplerController. +
+
getName() Get the SamplerController's display name.
+
+JMeter uses this name in the list of SamplerControllers that it displays. +
+
getDefaultThreadGroups()
+
Gets the default list of threadgroups.
+
getSampleThreads(String threadGroup,int numThreads)
+
When the user hits start, use this method to get all the JMeterThread objects you want for a threadgroup. Each JMeterThread +object implements Runnable and it is used to sample the test entries. +
+
+

+

+ +

Samplers

+ Samplers are simple - they are the objects that know the protocol of that which + you wish to sample. The HTTPSampler knows how to request a URL from a web server, for + instance. + + The interface for Sampler is as follows: +
+
public SampleResult sample(Entry e)
+
JMeterThread implementations will loop through all the test samples given + to them, and call the sample method on the Sampler (also given to them) for each + test entry. SampleResult is essentially a Map containing information about + the sampling (timing data is included, as well as the test response from the url). +
+
+

+

+ +

TestSample

+ TestSamples are objects that collect information from users about each test sample + the user wants to test. The TestSample object is also responsible for serving + up its test entries. The interface: +
+
public java.awt.Container getGUI()
+
Returns the GUI used to collect information from the user.
+
public Entry[] getEntries()
+
Gets a list of entries to be sampled from the TestSample object
+
public String[] getThreadGroups()
+
Get all the thread groups the user selected for this TestSample
+
public void setThreadGroups(String[] threadGroups)
+
Set the thread groups the user may choose from
+
public String getName()
+
Get a name for this TestSample
+
public void setName(String name)
+
Set the name for this TestSample
+
public void reset()
+
inform the test sample that a sampling run is starting
+
+

+
+ +
diff --git a/xdocs/extending/JMeter Extension Scenario.xml b/xdocs/extending/JMeter Extension Scenario.xml new file mode 100644 index 00000000000..c4ad287e596 --- /dev/null +++ b/xdocs/extending/JMeter Extension Scenario.xml @@ -0,0 +1,202 @@ + + + + + + + Extending JMeter + + + + + +
+

The purpose of this tutorial is to +describe the general steps involved in a JMeter extension scenerio. The +JMeter documentation describes what must be done on a microscopic level but does +not provide an overall idea of the process. That is the intent of this brief +article. The JMeter extension documentation should be consulted for details.

+

The high level procedure followed these steps. +

+
    +
  1. Planning
  2. +
  3. Code the configuration + object
  4. +
  5. Code the configuration + GUI object
  6. +
  7. Code the controller + object
  8. +
  9. Code the controller GUI + object
  10. +
  11. Code the Sampler object
  12. + +
  13. Install your + extension
  14. +
  15. Tips
+

Planning

I've found planning a JMeter extension to +involve three aspects: + +
    +
  1. What you want the sampler to do
  2. +
  3. What information is needed for the sampler to work
  4. +
  5. How the information is to be acquired from the user
+

You'll notice that the coding steps are somewhat backwards from the planning +steps (the sampler is coded last). The coding order was determined by which +classes could be tested earliest. The config/gui can be tested in isolation. The +controller can be tested with the config element. Neither of these requires a +Sampler to be present initially.

+

Configuration Object

The role of the configuration +object is to supply parameters to the Sampler that can vary from sample to +sample. In the case of the UrlConfig object, this would be information +such as the host name, port, GET or PUT and various parameters. +

The configuration object usually inherits from +org.apache.jmeter.config.AbstractConfigElement. It implements many of +the methods of org.apache.jmeter.gui.JMeterComponentModel that are +needed to effectively interact with JMeter. +

+
    +
  1. Constructor - In the constructor you should at least define the + name of your configuration element. This is best delegated to the base class's + setName method.
  2. +
  3. Property Name Strings - You should define a static final string for + each property you wish to define. These strings will serve as keys into a hash + table maintained by AbstractConfigElement. For example:
     public static final HOST_NAME = "hostname";
    would define a property + in the hash table for storing a host name.
  4. +
  5. Getters/Setters - For each property name you define in the previous + step, define the appropriate accessor methods. The implementation of these + accessors should usually delegate to AbstractConfigElement. For + example:
       public void setHostname(String hostname)
    +    { setProperty(HOST_NAME, hostname); }
    +
    +    public String getHostname()
    +    { return (String)getProperty(HOST_NAME); }
    +    
    Some accessor implementations may be more complex. See the + UrlConfig object for a more involved example.
  6. +
  7. String getClassLabel() - This is the label that will + display in the drop-down menu for adding your configuration element.
  8. +
  9. clone() - Your configuration element is expected to be + cloneable.
  10. +
  11. addConfigElement(ConfigElement) - A typical implementation + of this method looks like
       public void addConfigElement(ConfigElement config) {
    +        if (config instanceof MyConfig)
    +            updatePropertyIfAbsent((MyConfig)config);
    +    }
    where updatePropertyIfAbsent is handled by the super class.
  12. +
  13. getGuiClass - return the name of the this class's corresponding GUI class. +
+

+

Configuration GUI

Each configuration element you +define can have a companion GUI class. It helps to have a little knowledge of +Swing for this. Extend Swing's JPanel class and implement JMeter's +org.apache.jmeter.gui.ModelSupported interface. Remember that you can +review the UrlConfigGui example for hints if you get stuck. +

+
    +
  1. Data Members - You should possess at least two data members: a + reference to your partner configuration element and a reference to a + org.apache.jmeter.gui.NamePanel. You will likely have several others + depending on how sophisticated your GUI is.
  2. +
  3. Add Panels - The layout manager used for many of the panels used in + JMeter is org.apache.jmeter.gui.VerticalLayout. As the name implies, + it supports arranging other panels in a vertical fashion. You can define each + of your panels in a get method and add them to the configuration GUI + in a method called init. Once again, refer to + UrlConfigGui for an example.
  4. +
  5. Implement Listeners - Implement listeners for your GUI components. + The UrlConfigGui serves as a satisfactory example.
  6. +
  7. setModel - Use this method to have the model data member + set on your GUI instance. Run init from inside this method also.
  8. +
  9. updateGui - Use this method to set the GUI fields from the + model.
+ +

Generative Controller

A generative controller is a +controller that generates an Entry object for use by a Sampler. + +
    +
  1. createEntry - This method is the raison d'etre of the + org.apache.jmeter.control.SamplerController interface. The general + idea is to construct an Entry object and populate it with config + objects.
  2. +
  3. clone - After you perform you cloning duties, be sure to + pass the cloned instance to the standardCloneProc method so that base + class cloning activities can complete.
  4. +
  5. getClassLabel - This is the label displayed by the + drop-down menu for the controller.
  6. +
  7. getGuiClass - This should return a Class object for the associated GUI class.
+

Generative Controller GUI

A generative +controller GUI class should extend JPanel and implement +ModelSupported. If your controller GUI doesn't involve anything beyond +the configuration GUI, you might be able to get away with inheriting from the +configuration gui class you created a couple steps ago. If you do this, you need +to at least override the setModel method to make sure that the correct +model is set on the class. You'll be passed a controller object but you'll want +to extract the config element from the controller to be used as the model for +your base class (the config gui). +

Sampler

The sampler is responsible for actually +performing the work using the information provided in the configuration element. +The method of importance is
public SampleResult sample(Entry e)
It is here that you extract +configuration elements from the entry object you are passed. Then use these +configuration elements to perform the task you extension is suppose to do. +

Installation

Follow these steps to install your +extension. + +
    +
  1. Package the class files into a JAR file.
  2. +
  3. Place the JAR file into the ext subdirectory of the JMeter root + install directroy.
  4. +
  5. Edit the bin/jmeter.properties file of the JMeter installation. + Find the search_paths entry and add your JAR to the list. It should + look like
    search_paths=ApacheJMeter.jar;classes;../ext/YourJar.jar
  6. +
  7. Run JMeter and watch the magic.
+ +

Tips

+
    +
  1. You might consider using log4j as your logging utility since that's what + JMeter uses. It's helpful for figuring out what's going on. Not all JMeter + classes have been fully outfitted with logging statements. If things get + nasty, you might have to add your own to JMeter and recompile it to see what + is happening. +

    If you do decide to use log4j and you set the priority (or level, as it + will soon be called) to debug, you will probably see way more than you need to + know. You can filter the JMeter stuff by making the following modifications to + log4j.conf in the JMeter's bin directory. The bold + text is added/modified

        # Set the appenders for the categories
    +    log4j.rootCategory=info,Root_Appender
    +    log4j.category.com.myfirm.jmeter=debug,
    +    log4j.category.org.apache.jmeter.control=debug
    +    log4j.category.org.apache.jmeter.gui.tree.NonGuiTree=INFO,File_Appender
    +    
    Note that the root (default) debugging has been set to info. + This eliminates most log4j output from JMeter. The new line specifies the name + of the package containing JMeter extensions. (com.yourfirm.jmeter) in + this example. Note that it is not necessary to specify a particular class + name. Also, note that no appenders are specified - just the trailing + comma. If you specify Root_Appender here you'll see your message appear twice + (because you specified the same appender twice). All you really want to do is + override the priority.

  2. +
  3. Implement clone carefully. This is an often overlooked method for + a lot of folks. JMeter makes heavy use of cloning. Check out some of the + JMeter coniguration elements and controllers to see how they do it. Notice + that in most cases, a special method is usually invoked to perform base class + cloning activities. For configuration elements, this is + configureClone. For controllers, it is standardCloneProc. +
+
+
+ +
diff --git a/xdocs/extending/index.xml b/xdocs/extending/index.xml new file mode 100644 index 00000000000..b9350dde216 --- /dev/null +++ b/xdocs/extending/index.xml @@ -0,0 +1,861 @@ + + + + + + + Extending JMeter + + + + + +
+ + + +

Extending JMeter

: + +

There are several ways to extend JMeter and add functionality. JMeter is designed + +to make this task easier. + +

+ + + +
+ + + +

Creating your own Timer

+ +

The timer interface:

+ +
+
+      public long delay();
+
+
+ +

Not too complicated. Your delay method must, each time it is called, return a + +long representing the number of milliseconds to delay. The constant timer returns the + +same number every time it's called. A random timer returns a different number each time. + +

+ +
+ +
+ +

Creating your own SampleListener

+ +

The SampleListener interface:

+ +
+
+      public void sampleOccurred(SampleEvent e);
+
+      public void sampleStarted(SampleEvent e);
+
+      public void sampleStopped(SampleEvent e);
+
+
+ +

sampleOccurred is the method called when a sample is completed, and the data has been + +collected. The SampleEvent object should contain all the information gathered + +from the sample. If your sample listener is primarily concerned with collecting the + +data from a test run, you can implement this method - the other two are for other purposes and + +can be ignored (though the methods have to be there for your class to compile). + +

+ +

sampleStarted and sampleStopped are used to indicate the state of the sampling thread. + +This is useful for visualizers that show the user the state of all running threads + +(ie, they are running and waiting for response, or they're stopped and waiting + +to begin again). + +

+ +
+ +
+ +

Creating your own Config Element

+ +

The ConfigElement interface:

+ +
+
+      public void addConfigElement(ConfigElement config);
+
+      public boolean expectsModification();
+
+      public Object clone();
+
+
+ +

The ConfigElement interface is sparse. All ConfigElements are expected to implement + +a public clone() method. The reason for this is that config elements will be cloned + +for each different sampling thread, and most will be cloned for each sample.

+ +

If your config element expects to be modified in the process of a test run, + +and you want those modifications to carry over from sample to sample (as in + +a cookie manager - you want to save all cookies that gets set throughout + +the test), then return true for the expectsModification() method. Your config element will not be + +cloned for each sample. If your config elements are more static in nature, + +return false. If in doubt, return false.

+ +

addConfigElement() is required so that config elements can be layered. For + +instance, let's say a user creates a URL entry that contains default values - + +they might use this to specify a server. Then, all their test samples configure + +individual test cases, but leave out the server field. This information is combined + +via the addConfigElement() method. Your custom config elements should do the right + +thing when this method is called. Normally, this involves ignoring such calls unless + +the passed in ConfigElement is of the same type as yours, and then only merging in + +values that are not already set in the object receiving the call (ie you probably + +don't want to overwrite any values). + +

+ +

You may have noticed there's no specification on how to get the config information + +out of a ConfigElement. This raises the question, who is going to use it? + +At the end of the line, there will be a Sampler that will need the information held + +in your config element. The sampler that uses your config element needs to know more + +about the class than the rest of JMeter - that information is not part of this interface. + +

+ +

If at all possible, extend AbstractConfigElement when creating your own. By doing so, + +and by following some simple rules, you will get cloning and saving to XML of your + +config element for free (as in, you don't have to do anything!). AbstractConfigElement + +stores all its values in a Map, and provides getProperty and putProperty methods. Your + +config element can provide getXXX() and setXXX() methods, but these should delegate + +to getProperty() and setProperty(), probably using static Strings as keys in the Map. + +
You can store any type of object, provided the objects are clonable and Saveable + +(Strings, Integer, Long, Double, Float are all good in this regard). + +

+ +

One caveat - if your config element has been restored from file, all the values + +held in the Map will be String objects (except for elements that implement Saveable + +on their own), and you may have to do casting and parsing. Example: an Integer will + +have to be converted from a String to an int, so your getXXX() method should check + +for this possibility to avoid exceptions. + +

+ +
+ +
+ +

Creating your own logic SamplerController

+ +

The SamplerController interface looks as follows:

+ +
+
+      Entry nextEntry();
+
+      Collection getListeners();
+
+      void addSamplerController(SamplerController controller);
+
+      void addConfigElement(ConfigElement config);
+
+      Object clone();
+
+
+ +

Again, clone() is a method that must be implemented to all SamplerControllers to avoid + +contamination between sampling threads.

+ +

The nextEntry() method is the essential job of a SamplerController - to deliver + +Entry objects to be sampled. An Entry object encapsulates all the information needed + +by a Sampler to do its job. The nextEntry() method should work like an iterator and + +continuously return new Entry objects. + +

+ +

There are two boundary conditions that need to be handled. If the Controller has no + +more Entries to give, for the rest of the test, it should return null. Therefore, + +if your Controller has sub-controllers it is receiving Entries from, it should remove + +them from its list of controllers to get Entries from. The other condition is when + +your controller reaches the end of its list of Entries, and it needs to start over + +from the beginning. The parent Controller needs to know this so that it can move + +on to its next controller in its list. Therefore, at the end of each iteration, + +your SamplerController needs to return a CycleEntry object instead of a normal Entry. + +Conversely, this means that if your Controller receives a CycleEntry object, it should + +move on to the next Controller in its list.

+ +

A logic controller does not generate Entries on its own, but simply regulates + +the flow of Entries from its sub-controllers. A logic controller might provide + +looping logic, or it might modify the Entries that pass through it, or whatever. + +GenericController provides an implementation that does absolutely nothing but + +pass Entries on from its sub-controllers. This class is useful both for reference + +purposes and to extend, since it provides a lot of methods you're likely to find + +useful + +

+ +

getListeners() is an odd member of this Class. It's there to serve those who + +want their controller to receive sample data. This would be useful for a controller + +that modified Entry objects based on previous sample results (like an HTML spider + +that dynamically reacted to previously sampled webpages for links and forms). The + +responsibility of the controller implementer is to collect all potential listeners + +from the sub-controller list, and add themselves if desired. Most SamplerControllers + +that extend GenericController don't have to do anything.

+ +

addSamplerController(SamplerController controller) is the method used to + +add sub controllers to your SamplerController.

+ +

addConfigElement(ConfigElement config) Your SamplerController should also + +be capable of holding configuration elements and adding them to Entries as they + +pass through your controller. Again, see GenericController for reference. Essentially, + +all Entry objects that get returned by nextEntry() are handed all the ConfigElements + +of the controller. + +

+ +
+ +
+ +

Creating your own test sample SamplerController

+ +

A SamplerController that generates Entry objects is just like a logic controller + +except that it creates its own Entry objects instead of gathering them from + +sub-controllers (although, to be fully correct, your test sample SamplerController + +should handle both possibilities). Your test sample SamplerController can also + +benefit from extending GenericController. By doing so, most of your cloning and + +saving needs are handled (but probably not entirely). See HttpTestSample as + +reference.

+ +
+ +
+ +

Creating your own Sampler

+ +

The Sampler interface:

+ +
+
+      public SampleResult sample(Entry e)
+
+
+ +

Your Sampler has two responsibilities. Of lesser importance, it should do whatever + +it is you want to do, given an Entry object that hopefully contains information + +about what is to be sampled. Of greater importance, your sampler should return + +a SampleResult object that holds information about the sampling. Information such + +as how long the sample took, the text response from the sample (if appropriate), and + +a string that describes the location of what was sampled. The SampleResult interface + +is essentially a Map with public static Strings as keys.

+ +
+ +
+ +

Making your custom elements play nice as a JMeter UI component

+ +

In order to take part in the JMeter UI, your component needs to implement the + +JMeterComponentModel interface:

+ +
+
+      Class getGuiClass();
+
+      public String getName();
+
+      public void setName(String name);
+
+      public Collection getAddList();
+
+      public String getClassLabel();
+
+      public void uncompile();
+
+
+ +

Most of this stuff is easy, boring, and tedious. getName(), setName() is a simple + +String property that is the name of the object. getClassLabel() should return + +a String that describes the class. This string will be displayed to the user and + +so should be short but meaningful. getGuiClass() should return a Class object for + +the class that will be used as a GUI component. This class should be a subclass + +of java.awt.Container, and preferably a subclass of javax.swing.JComponent.

+ +

getAddList() should return a list of either Strings or JMenus. These Strings + +represent the Classes that can be added to your SamplerController. Each String + +should correspond to the target class's getClassLabel() String. MenuFactory is + +a class that will return some preset menu lists (such as all available SamplerControllers, + +all available ConfigElements, etc).

+ +

uncompile() is a cleanup method used between sampling runs. When the user + +hits "Start", JMeter "compiles" the objects in the tree. Child nodes are added + +to their parent objects recursively until there is one TestPlan object, which is + +then submitted for testing. Afterward, these elements have to un-added from their + +parent objects, or uncompiled. To uncompile your class, simply clear all your + +data structures that are holding sub-elements. For your SamplerController, this + +will be the list of sub-controllers and the list of ConfigElements.

+ +

That's it, except for your GUI class. If your SamplerController has no + +configuration needs, just return org.apache.jmeter.gui.NamePanel, and the user will + +at least be able to change the name of your component. Otherwise, create a gui class + +that implements the ModelSupported interface:

+ +
+
+      void setModel(Object model);
+
+      public void updateGui();
+
+
+ +

setModel() is used to hand your JMeterModelComponent class to the GUI class when + +it is instantiated. It is your responsibility for providing the means by which + +the Gui class updates the values in the model class. For updating in the other + +direction, there is updateGui(), which the model class can call if necessary. + +Note, normally, this call is made for you automatically whenever the Gui is brought + +to the screen. If you are creating a Visualizer, then you may need to use updateGui(). + +For reference, refer to UrlConfigGui (in org.apache.jmeter.protocol.http.config.gui).

+ +

If you have done all this correctly, there's just one more step. If you compile + +your classes into the ApacheJMeter.jar file, then you're done. Your classes will + +be automatically found and used. Otherwise, you will need to modify jmeter.properties. + +The search_paths property should be modified to include the path where your + +classes are. This does not obviate the need for your classes to be in the JVM's + +CLASSPATH - it is an additional requirement. Otherwise, your classes will not be + +detected, and the Gui will not make them available to the user.

+ +
+ +
+ +

Making your custom elements saveable and loadable from within JMeter

+ +

The Saveable interface has just one method:

+ +
+
+      public Class getTagHandlerClass()
+
+
+ +

This method simply returns the Class object that represents the Class that handles + +the saving and loading of your component.

+ +

To write this SaveHandler, make a class that extends TagHandler + +(from org.apache.jmeter.save.xml). Note, if your component extends AbstractConfigElement, + +it is already fully Saveable - provided you only have information stored in + +the Map from AbstractConfigElement.

+ +

To write your own TagHandler, you will have to implement the following methods:

+ +
+
+      public abstract void setAtts(Attributes atts) throws Exception
+
+      public String getPrimaryTagName()
+
+      public void save(Saveable objectToSave,Writer out) throws IOException
+
+
+ +

getPrimaryTagName() should return the String that is the XML tagname that your + +class handles. When you save your object, it should all be contained within an + +XML tag of the same name. This will ensure that when JMeter's parser hits that tag, + +your class will be called upon to handle the data.

+ +

setAtts(Attributes atts) is called when the parser first hits your tag. + +If this primary tag has any attributes, this method represents your chance to save + +the information.

+ +

save(Saveable objectToSave,Writer out) - when the user selects "Save", + +JMeter will call this method and hand the Saveable object to be saved (it will be + +the object that specified your TagHandler as the class responsible for saving it). + +This method should use the given Writer object to print all the XML necessary to + +save the current state of the objectToSave.

+ +

There's more you have to do to handle creating a new Object when JMeter parses + +an XML file. However, there's no standard interface you need to implement, but rather, + +JMeter uses reflection to generate method calls into your class. When JMeter hits + +a tag that corresponds to your PrimaryTagName, an instance of your TagHandler will + +be created, and its setAtts() method will get called. Thereafter, methods are called + +depending on subsequent tags and character data. For every tag, JMeter calls + +<tag-name>TagStart(Attributes atts), and for every end tag, JMeter calls + +<tag-name>TagEnd().

+ +

Additionally, JMeter will call a method that corresponds to all tags that are + +current. So, for instance, if JMeter runs into a tag name "foo", then + +foo(Attributes atts) will be called. If JMeter then parses character data, + +then foo(String data) will be called. If JMeter parses a tag within foo, called + +"nestedFoo", then JMeter will call foo_nestedFoo(Attributes atts) and + +foo_nestedFoo(String data). And so on. + +

+ +

An annotated example:

+ +
+
+public class AbstractConfigElementHandler extends TagHandler
+
+{
+
+    private AbstractConfigElement config;
+
+    private String currentProperty;
+
+
+
+    public AbstractConfigElementHandler()
+
+    {
+
+    }
+
+
+
+    /**
+
+     * Returns the AbstractConfigElement object parsed from the XML.  This method
+
+     * is required to fulfill the SaveHandler interface.  It is used by the XML
+
+     * routines to gather all the saved objects.
+
+     */
+
+    public Object getModel()
+
+    {
+
+        return config;
+
+    }
+
+
+
+    /**
+
+     * This is called when a tag is first encountered for this handler class to handle.
+
+     * The attributes of the tag are passed, and the SaveHandler object is expected
+
+     * to instantiate a new object.
+
+     */
+
+    public void setAtts(Attributes atts) throws Exception
+
+    {
+
+        String className = atts.getValue("type");
+
+        config = (AbstractConfigElement)Class.forName(className).newInstance();
+
+    }
+
+
+
+    /**
+
+     * Called by reflection when a <property> tag is encountered.  Again, the
+
+     * attributes are passed.
+
+     */
+
+    public void property(Attributes atts)
+
+    {
+
+        currentProperty = atts.getValue("name");
+
+    }
+
+
+
+    /**
+
+     * Called by reflection when text between the begin and end <property>
+
+     * tag is encountered.
+
+     */
+
+    public void property(String data)
+
+    {
+
+
+
+        if(data != null && data.trim().length() > 0)
+
+        {
+
+            config.putProperty(currentProperty,data);
+
+            currentProperty = null;
+
+        }
+
+    }
+
+
+
+    /**
+
+     * Called by reflection when the <property> tag is ended.
+
+     */
+
+    public void propertyTagEnd()
+
+    {
+
+        // Here's a tricky bit.  See below for explanation.
+
+        List children = xmlParent.takeChildObjects(this);
+
+        if(children.size() == 1)
+
+        {
+
+            config.putProperty(currentProperty,((TagHandler)children.get(0)).getModel());
+
+        }
+
+    }
+
+
+
+
+
+      /**
+
+    * Gets the tag name that will trigger the use of this object's TagHandler.
+
+    */
+
+    public String getPrimaryTagName()
+
+    {
+
+        return "ConfigElement";
+
+    }
+
+
+
+  /**
+
+    * Tells the object to save itself to the given output stream.
+
+    */
+
+    public void save(Saveable obj,Writer out) throws IOException
+
+    {
+
+        AbstractConfigElement saved = (AbstractConfigElement)obj;
+
+        out.write("<ConfigElement type=\"");
+
+        out.write(saved.getClass().getName());
+
+        out.write("\">\n");
+
+        Iterator iter = saved.getPropertyNames().iterator();
+
+        while (iter.hasNext())
+
+        {
+
+            String key = (String)iter.next();
+
+            Object value = saved.getProperty(key);
+
+            writeProperty(out,key,value);
+
+        }
+
+        out.write(</ConfigElement>");
+
+    }
+
+
+
+    /**
+
+     * Routine to write each property to xml.
+
+     */
+
+    private void writeProperty(Writer out,String key,Object value) throws IOException
+
+    {
+
+        out.write("<property name=\"");
+
+        out.write(key);
+
+        out.write("\">\n");
+
+        JMeterHandler.writeObject(value,out);
+
+        out.write("\n</property>\n");
+
+    }
+
+
+ +

+ +In the propertyTagEnd() method, takeChildObjects() is called on the xmlParent + +instance variable. xmlParent is inherited from TagHandler - the DocumentHandler + +object that is running the show. xmlParent takes an XML file that represents a portion of + +the test configuration tree, and recreates a tree-like data structure. When it is + +done, it will convert its tree-like data structure into the test configuration tree + +structure. + +

+ +

However, sometimes, a tree element has sub objects that you do not want represented + +in the tree - rather, they are part of your object. But, they may + +be complicated enough to warrant their own SaveHandler class, and thus, the xmlParent + +picks them up as part of its tree. When the tag is done, and you know that there are + +child objects you want to grab, you can call the takeChildObjects() method and get a + +List object containing them all. This will remove them from the tree, and you can add + +them to your object that you're creating. + +

+ +

+ +UrlConfig is good example. It extends AbstractConfigElement, so it uses exactly the + +code above to save and reload itself from XML. However, one of the pieces of data + +that UrlConfig stores is an Arguments object. Arguments is too complicated to save + +to file as a simple string, so it has its own Handler object (ArgumentsHandler). In + +the above code, when the call to JMeterHandler.writeObject(value,out) is made, the + +writeObject method detects whether the object implements Saveable, and if so, calls + +the object's SaveHandler class to deal with it. This means, however, that when + +reading that XML file, the Argument object will show up as a separate entity in + +the data tree, whereas it originally was just part of the data of the UrlConfig + +object. In order to preserve that relationship, it's necessary for the + +AbstractConfigElementHandler to check after each property tag is done for child + +objects in the tree, and take them for its own use. + +

+ +

+ +Study the other SaveHandler objects and the TagHandler class to learn more + +about how saving is accomplished. Once you understand the design, writing your + +own SaveHandler is very easy. + +

+ + + +
+ + + +
diff --git a/xdocs/extending/jmeter_tutorial.pdf b/xdocs/extending/jmeter_tutorial.pdf new file mode 100644 index 00000000000..f3c0910b426 Binary files /dev/null and b/xdocs/extending/jmeter_tutorial.pdf differ diff --git a/xdocs/extending/jmeter_tutorial_mike.pdf b/xdocs/extending/jmeter_tutorial_mike.pdf new file mode 100644 index 00000000000..0de1862b4fc Binary files /dev/null and b/xdocs/extending/jmeter_tutorial_mike.pdf differ diff --git a/xdocs/extending/jmeter_tutorial_mike.sxw b/xdocs/extending/jmeter_tutorial_mike.sxw new file mode 100644 index 00000000000..113c8c41698 Binary files /dev/null and b/xdocs/extending/jmeter_tutorial_mike.sxw differ diff --git a/xdocs/extending/notes_on_extending.txt b/xdocs/extending/notes_on_extending.txt new file mode 100644 index 00000000000..bafc7717ecf --- /dev/null +++ b/xdocs/extending/notes_on_extending.txt @@ -0,0 +1,122 @@ +# 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. + + Making a TestBean Plugin For JMeter + +This component will be a CSV file reading element that will let users easily vary their input +data using csv files. + +1. Pick a package and make three files: + - [ComponentName].java (org.apache.jmeter.config.CSVDataSet.java) + - [ComponentName]BeanInfo.java (org.apache.jmeter.config.CSVDataSetBeanInfo.java) + - [ComponentName]Resources.properties (org.apache.jmeter.config.CSVDataSetResources.properties) + +2. CSVDataSet.java must implement the TestBean interface. In addition, it will extend +ConfigTestElement, and implement LoopIterationListener. + - TestBean is a marker interface, so there are no methods to implement. + - Extending ConfigTestElement will make our component a Config element in a test plan. By + extending different abstract classes, you can control the type of element your component will + be (ie AbstractSampler, AbstractVisualizer, GenericController, etc - though you can also make + different types of elements just by instantiating the right interfaces, the abstract classes can + make your life easier). + +3. CSVDataSetBeanInfo.java should extend org.apache.jmeter.testbeans.BeanInfoSupport + - create a zero-parameter constructor in which we call super(CSVDataSet.class); + - we'll come back to this. + +4. CSVDataSetResources.properties - blank for now + +5. Implement your special logic for you plugin class. + - The CSVDataSet will read a single CSV file and will store the values it finds into + JMeter's running context. The user will define the file, define the variable names for + each "column". The CSVDataSet will open the file when the test starts, and close it + when the test ends (thus we implement TestListener). The CSVDataSet will update the + contents of the variables for every test thread, and for each iteration through its + parent controller, by reading new lines in the file. When we reach the end of the file, + we'll start again at the beginning. + + - When implementing a TestBean, pay careful attention to your properties. These + properties will become the basis of a gui form by which users will configure the CSVDataSet + element. + + - Your element will be cloned by JMeter when the test starts. Each thread will get it's own instance. However, you will + have a chance to control how the cloning is done - we'll be taking advantage of this for CSVDataSet (since we don't want to open the file X number of times from X number of threads). + + a. Properties: filename, variableNames. With public getters and setters. + - filename is self-explanatory, it will hold the name of the CSV file we'll read + - variableNames is a String which will allow a user to enter the names of + the variables we'll assign values to. Why a String? Why not a Collection - surely + users will need to enter multiple (and unknown number) variable names? True, but + if we used a List or Collection, we'd have to write a gui component to handle + collections, and I just want to do this quickly. Instead, we'll let users input + comma-delimited list of variable names. + + b. I then implemented the IterationStart method of the LoopIterationListener interface. The point + of this "event" is that your component is notified of when the test has entered it's parent + controller. For our purposes, every time the CSVDataSet's parent controller is entered, we will + read a new line of the data file and set the variables. Thus, for a regular controller, each + loop through the test will result in a new set of values being read. For a loop controller, each + iteration will do likewise. Every test thread will get different values as well. + +6. Setting up your gui elements in CSVDataSetBeanInfo: + - You can create groupings for your component's properties. Each grouping you create needs + a label and a list of property names to include in that grouping. Ie: + + createPropertyGroup("csv_data",new String[]{"filename","variableNames"}); + + Creates a grouping called "csv_data" that will include gui input elements for the + "filename" and "variableNames" properties of CSVDataSet. Then, we need to define what kind of + properties we want these to be: + + p = property("filename"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p.setValue(NOT_EXPRESSION,Boolean.TRUE); + p = property("variableNames"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p.setValue(NOT_EXPRESSION,Boolean.TRUE); + + This essentially creates two properties whose value is not allowed to be null, and whose default + values are "". There are several such attributes that can be set for each property. Here is a + rundown: + + NOT_UNDEFINED : The property will not be left null. + DEFAULT : A default values must be given if NOT_UNDEFINED is true. + NOT_EXPRESSION : The value will not be parsed for functions if this is true. + NOT_OTHER : This is not a free form entry field - a list of values has to be provided. + TAGS : with a String[] as the value, this sets up a predefined list of acceptable values, and JMeter will create a dropdown select. + + Additionally, a custom property editor can be specified for a property: + + p.setPropertyEditorClass(FileEditor.class); + + This will create a text input plus browse button that opens a dialog for finding a file. + + Usually, complex property settings are not needed, as now. For a more complex example, look + at org.apache.jmeter.protocol.http.sampler.AccessLogSamplerBeanInfo + +7. Defining your resource strings. In CSVDataSetResources.properties we have to define all our string + resources. To provide translations, one would create additional files such as CSVDataSetResources_ja.properties, and + CSVDataSetResources_de.properties. For our component, we must define the following resources: + + displayName - This will provide a name for the element that will appear in menus. + csv_data.displayName - we create a property grouping called "csv_data", so we have to provide a label for the grouping + filename.displayName - a label for the filename input element. + filename.shortDescription - a tool-tip-like help text blurb. + variableNames.displayName - a label for the variable name input element. + variableNames.shortDescription - tool tip for the variableNames input element. + +8. Debug your component. \ No newline at end of file diff --git a/xdocs/images/asf-logo.gif b/xdocs/images/asf-logo.gif new file mode 100644 index 00000000000..22eb9d7358e Binary files /dev/null and b/xdocs/images/asf-logo.gif differ diff --git a/xdocs/images/asf-logo.png b/xdocs/images/asf-logo.png new file mode 100644 index 00000000000..61b0f836f70 Binary files /dev/null and b/xdocs/images/asf-logo.png differ diff --git a/xdocs/images/jakarta-logo.gif b/xdocs/images/jakarta-logo.gif new file mode 100644 index 00000000000..049cf822952 Binary files /dev/null and b/xdocs/images/jakarta-logo.gif differ diff --git a/xdocs/images/logo-small.jpg b/xdocs/images/logo-small.jpg new file mode 100644 index 00000000000..1590774139a Binary files /dev/null and b/xdocs/images/logo-small.jpg differ diff --git a/xdocs/images/logo.jpg b/xdocs/images/logo.jpg new file mode 100644 index 00000000000..82e2f0e442e Binary files /dev/null and b/xdocs/images/logo.jpg differ diff --git a/xdocs/images/logo.svg b/xdocs/images/logo.svg new file mode 100644 index 00000000000..9d6fa73159e --- /dev/null +++ b/xdocs/images/logo.svg @@ -0,0 +1,15642 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Meter + J + Apache + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Meter + J + Apache + TM + + + + diff --git a/xdocs/images/logo.xcf b/xdocs/images/logo.xcf new file mode 100644 index 00000000000..5bf3cc179b7 Binary files /dev/null and b/xdocs/images/logo.xcf differ diff --git a/xdocs/images/screenshots/accesslogsampler.png b/xdocs/images/screenshots/accesslogsampler.png new file mode 100644 index 00000000000..3da8efc782d Binary files /dev/null and b/xdocs/images/screenshots/accesslogsampler.png differ diff --git a/xdocs/images/screenshots/aggregate_graph.png b/xdocs/images/screenshots/aggregate_graph.png new file mode 100644 index 00000000000..c3a8340de3d Binary files /dev/null and b/xdocs/images/screenshots/aggregate_graph.png differ diff --git a/xdocs/images/screenshots/aggregate_graph_settings.png b/xdocs/images/screenshots/aggregate_graph_settings.png new file mode 100644 index 00000000000..0e9ef47d2a3 Binary files /dev/null and b/xdocs/images/screenshots/aggregate_graph_settings.png differ diff --git a/xdocs/images/screenshots/aggregate_report.png b/xdocs/images/screenshots/aggregate_report.png new file mode 100644 index 00000000000..f186d86d4d7 Binary files /dev/null and b/xdocs/images/screenshots/aggregate_report.png differ diff --git a/xdocs/images/screenshots/aggregate_report_grouped.png b/xdocs/images/screenshots/aggregate_report_grouped.png new file mode 100644 index 00000000000..3ef7a8bfa44 Binary files /dev/null and b/xdocs/images/screenshots/aggregate_report_grouped.png differ diff --git a/xdocs/images/screenshots/assertion/HTMLAssertion.png b/xdocs/images/screenshots/assertion/HTMLAssertion.png new file mode 100644 index 00000000000..927ff8222df Binary files /dev/null and b/xdocs/images/screenshots/assertion/HTMLAssertion.png differ diff --git a/xdocs/images/screenshots/assertion/MD5HexAssertion.png b/xdocs/images/screenshots/assertion/MD5HexAssertion.png new file mode 100644 index 00000000000..f1bde1703a4 Binary files /dev/null and b/xdocs/images/screenshots/assertion/MD5HexAssertion.png differ diff --git a/xdocs/images/screenshots/assertion/XMLSchemaAssertion.png b/xdocs/images/screenshots/assertion/XMLSchemaAssertion.png new file mode 100644 index 00000000000..1a790f070df Binary files /dev/null and b/xdocs/images/screenshots/assertion/XMLSchemaAssertion.png differ diff --git a/xdocs/images/screenshots/assertion/assertion.png b/xdocs/images/screenshots/assertion/assertion.png new file mode 100644 index 00000000000..0a07d7f9397 Binary files /dev/null and b/xdocs/images/screenshots/assertion/assertion.png differ diff --git a/xdocs/images/screenshots/assertion/assertionscope.png b/xdocs/images/screenshots/assertion/assertionscope.png new file mode 100644 index 00000000000..6efb1f9f7eb Binary files /dev/null and b/xdocs/images/screenshots/assertion/assertionscope.png differ diff --git a/xdocs/images/screenshots/assertion/assertionscopevar.png b/xdocs/images/screenshots/assertion/assertionscopevar.png new file mode 100644 index 00000000000..aff8c75d98c Binary files /dev/null and b/xdocs/images/screenshots/assertion/assertionscopevar.png differ diff --git a/xdocs/images/screenshots/assertion/compare.png b/xdocs/images/screenshots/assertion/compare.png new file mode 100644 index 00000000000..03c595432e2 Binary files /dev/null and b/xdocs/images/screenshots/assertion/compare.png differ diff --git a/xdocs/images/screenshots/assertion/example1a.png b/xdocs/images/screenshots/assertion/example1a.png new file mode 100644 index 00000000000..9951367bfb2 Binary files /dev/null and b/xdocs/images/screenshots/assertion/example1a.png differ diff --git a/xdocs/images/screenshots/assertion/example1b.png b/xdocs/images/screenshots/assertion/example1b.png new file mode 100644 index 00000000000..354c3c1dbe1 Binary files /dev/null and b/xdocs/images/screenshots/assertion/example1b.png differ diff --git a/xdocs/images/screenshots/assertion/example1c-fail.png b/xdocs/images/screenshots/assertion/example1c-fail.png new file mode 100644 index 00000000000..84b484e65ae Binary files /dev/null and b/xdocs/images/screenshots/assertion/example1c-fail.png differ diff --git a/xdocs/images/screenshots/assertion/example1c-pass.png b/xdocs/images/screenshots/assertion/example1c-pass.png new file mode 100644 index 00000000000..2488b3c6978 Binary files /dev/null and b/xdocs/images/screenshots/assertion/example1c-pass.png differ diff --git a/xdocs/images/screenshots/assertion/smime.png b/xdocs/images/screenshots/assertion/smime.png new file mode 100644 index 00000000000..65e8ecff708 Binary files /dev/null and b/xdocs/images/screenshots/assertion/smime.png differ diff --git a/xdocs/images/screenshots/assertion_results.png b/xdocs/images/screenshots/assertion_results.png new file mode 100644 index 00000000000..99c32c38810 Binary files /dev/null and b/xdocs/images/screenshots/assertion_results.png differ diff --git a/xdocs/images/screenshots/backend_listener.png b/xdocs/images/screenshots/backend_listener.png new file mode 100644 index 00000000000..91ffdc7fffe Binary files /dev/null and b/xdocs/images/screenshots/backend_listener.png differ diff --git a/xdocs/images/screenshots/beanshell_assertion.png b/xdocs/images/screenshots/beanshell_assertion.png new file mode 100644 index 00000000000..e954082f942 Binary files /dev/null and b/xdocs/images/screenshots/beanshell_assertion.png differ diff --git a/xdocs/images/screenshots/beanshell_listener.png b/xdocs/images/screenshots/beanshell_listener.png new file mode 100644 index 00000000000..43b6a247040 Binary files /dev/null and b/xdocs/images/screenshots/beanshell_listener.png differ diff --git a/xdocs/images/screenshots/beanshell_postprocessor.png b/xdocs/images/screenshots/beanshell_postprocessor.png new file mode 100644 index 00000000000..07f7ff03244 Binary files /dev/null and b/xdocs/images/screenshots/beanshell_postprocessor.png differ diff --git a/xdocs/images/screenshots/beanshell_preprocessor.png b/xdocs/images/screenshots/beanshell_preprocessor.png new file mode 100644 index 00000000000..747b32e9fc6 Binary files /dev/null and b/xdocs/images/screenshots/beanshell_preprocessor.png differ diff --git a/xdocs/images/screenshots/beanshellsampler.png b/xdocs/images/screenshots/beanshellsampler.png new file mode 100644 index 00000000000..5b7697bbe39 Binary files /dev/null and b/xdocs/images/screenshots/beanshellsampler.png differ diff --git a/xdocs/images/screenshots/bsf_assertion.png b/xdocs/images/screenshots/bsf_assertion.png new file mode 100644 index 00000000000..70e76abc13c Binary files /dev/null and b/xdocs/images/screenshots/bsf_assertion.png differ diff --git a/xdocs/images/screenshots/bsf_listener.png b/xdocs/images/screenshots/bsf_listener.png new file mode 100644 index 00000000000..de557671532 Binary files /dev/null and b/xdocs/images/screenshots/bsf_listener.png differ diff --git a/xdocs/images/screenshots/bsf_postprocessor.png b/xdocs/images/screenshots/bsf_postprocessor.png new file mode 100644 index 00000000000..3047a54dc01 Binary files /dev/null and b/xdocs/images/screenshots/bsf_postprocessor.png differ diff --git a/xdocs/images/screenshots/bsf_preprocessor.png b/xdocs/images/screenshots/bsf_preprocessor.png new file mode 100644 index 00000000000..e7b12ea4e94 Binary files /dev/null and b/xdocs/images/screenshots/bsf_preprocessor.png differ diff --git a/xdocs/images/screenshots/bsfsampler.png b/xdocs/images/screenshots/bsfsampler.png new file mode 100644 index 00000000000..83e995a5766 Binary files /dev/null and b/xdocs/images/screenshots/bsfsampler.png differ diff --git a/xdocs/images/screenshots/bsh_assertion.png b/xdocs/images/screenshots/bsh_assertion.png new file mode 100644 index 00000000000..70e76abc13c Binary files /dev/null and b/xdocs/images/screenshots/bsh_assertion.png differ diff --git a/xdocs/images/screenshots/changes/2.10/01_css_jquery_tester.png b/xdocs/images/screenshots/changes/2.10/01_css_jquery_tester.png new file mode 100644 index 00000000000..8cb54272dee Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/01_css_jquery_tester.png differ diff --git a/xdocs/images/screenshots/changes/2.10/02_mongodb_source_config.png b/xdocs/images/screenshots/changes/2.10/02_mongodb_source_config.png new file mode 100644 index 00000000000..4e5ae817691 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/02_mongodb_source_config.png differ diff --git a/xdocs/images/screenshots/changes/2.10/03_mongodb_script_alpha.png b/xdocs/images/screenshots/changes/2.10/03_mongodb_script_alpha.png new file mode 100644 index 00000000000..73001784fb5 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/03_mongodb_script_alpha.png differ diff --git a/xdocs/images/screenshots/changes/2.10/04_jdbc_request_timeout.png b/xdocs/images/screenshots/changes/2.10/04_jdbc_request_timeout.png new file mode 100644 index 00000000000..f37743aca7b Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/04_jdbc_request_timeout.png differ diff --git a/xdocs/images/screenshots/changes/2.10/05_urlencode_function.png b/xdocs/images/screenshots/changes/2.10/05_urlencode_function.png new file mode 100644 index 00000000000..33e10d545a3 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/05_urlencode_function.png differ diff --git a/xdocs/images/screenshots/changes/2.10/06_http_request_delete_method.png b/xdocs/images/screenshots/changes/2.10/06_http_request_delete_method.png new file mode 100644 index 00000000000..34f6e11123a Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/06_http_request_delete_method.png differ diff --git a/xdocs/images/screenshots/changes/2.10/07_jmeter_templates_icon.png b/xdocs/images/screenshots/changes/2.10/07_jmeter_templates_icon.png new file mode 100644 index 00000000000..ae6af99cc0c Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/07_jmeter_templates_icon.png differ diff --git a/xdocs/images/screenshots/changes/2.10/08_jmeter_templates_box.png b/xdocs/images/screenshots/changes/2.10/08_jmeter_templates_box.png new file mode 100644 index 00000000000..12f058fdd20 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/08_jmeter_templates_box.png differ diff --git a/xdocs/images/screenshots/changes/2.10/09_save_workbench.png b/xdocs/images/screenshots/changes/2.10/09_save_workbench.png new file mode 100644 index 00000000000..c651cdfc540 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/09_save_workbench.png differ diff --git a/xdocs/images/screenshots/changes/2.10/10_color_syntax_bsf_sampler.png b/xdocs/images/screenshots/changes/2.10/10_color_syntax_bsf_sampler.png new file mode 100644 index 00000000000..971c5500ee9 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/10_color_syntax_bsf_sampler.png differ diff --git a/xdocs/images/screenshots/changes/2.10/11_color_syntax_jsr223_preprocessor.png b/xdocs/images/screenshots/changes/2.10/11_color_syntax_jsr223_preprocessor.png new file mode 100644 index 00000000000..d90f2fcd7fd Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/11_color_syntax_jsr223_preprocessor.png differ diff --git a/xdocs/images/screenshots/changes/2.10/12_drap_n-drop_multiple.png b/xdocs/images/screenshots/changes/2.10/12_drap_n-drop_multiple.png new file mode 100644 index 00000000000..160a4d929db Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/12_drap_n-drop_multiple.png differ diff --git a/xdocs/images/screenshots/changes/2.10/13_response_time_graph_y_scale.png b/xdocs/images/screenshots/changes/2.10/13_response_time_graph_y_scale.png new file mode 100644 index 00000000000..ed7576d9535 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/13_response_time_graph_y_scale.png differ diff --git a/xdocs/images/screenshots/changes/2.10/14_mongodb_jsr223.png b/xdocs/images/screenshots/changes/2.10/14_mongodb_jsr223.png new file mode 100644 index 00000000000..36bceca6b16 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/14_mongodb_jsr223.png differ diff --git a/xdocs/images/screenshots/changes/2.10/15_kerberos.png b/xdocs/images/screenshots/changes/2.10/15_kerberos.png new file mode 100644 index 00000000000..0beeb7aa735 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/15_kerberos.png differ diff --git a/xdocs/images/screenshots/changes/2.10/16_device.png b/xdocs/images/screenshots/changes/2.10/16_device.png new file mode 100644 index 00000000000..d20ce976cf4 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/16_device.png differ diff --git a/xdocs/images/screenshots/changes/2.10/17_os_process_timeout.png b/xdocs/images/screenshots/changes/2.10/17_os_process_timeout.png new file mode 100644 index 00000000000..3857651d466 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/17_os_process_timeout.png differ diff --git a/xdocs/images/screenshots/changes/2.10/17_threads_gui.png b/xdocs/images/screenshots/changes/2.10/17_threads_gui.png new file mode 100644 index 00000000000..e178d099683 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/17_threads_gui.png differ diff --git a/xdocs/images/screenshots/changes/2.10/17_threads_summariser.png b/xdocs/images/screenshots/changes/2.10/17_threads_summariser.png new file mode 100644 index 00000000000..fa0b94ba8c1 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/17_threads_summariser.png differ diff --git a/xdocs/images/screenshots/changes/2.10/18_https_test_script_recorder.png b/xdocs/images/screenshots/changes/2.10/18_https_test_script_recorder.png new file mode 100644 index 00000000000..9ab1bfc5935 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.10/18_https_test_script_recorder.png differ diff --git a/xdocs/images/screenshots/changes/2.11/01_jms_properties_typed_values.png b/xdocs/images/screenshots/changes/2.11/01_jms_properties_typed_values.png new file mode 100644 index 00000000000..0ad62a0df9e Binary files /dev/null and b/xdocs/images/screenshots/changes/2.11/01_jms_properties_typed_values.png differ diff --git a/xdocs/images/screenshots/changes/2.11/02_transaction_controller.png b/xdocs/images/screenshots/changes/2.11/02_transaction_controller.png new file mode 100644 index 00000000000..ec06ed23876 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.11/02_transaction_controller.png differ diff --git a/xdocs/images/screenshots/changes/2.11/03_xpath_tester.png b/xdocs/images/screenshots/changes/2.11/03_xpath_tester.png new file mode 100644 index 00000000000..182cc643ccc Binary files /dev/null and b/xdocs/images/screenshots/changes/2.11/03_xpath_tester.png differ diff --git a/xdocs/images/screenshots/changes/2.11/05_save_as_fragement.png b/xdocs/images/screenshots/changes/2.11/05_save_as_fragement.png new file mode 100644 index 00000000000..5215e142b20 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.11/05_save_as_fragement.png differ diff --git a/xdocs/images/screenshots/changes/2.11/06_summariser.png b/xdocs/images/screenshots/changes/2.11/06_summariser.png new file mode 100644 index 00000000000..8246f31ccd3 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.11/06_summariser.png differ diff --git a/xdocs/images/screenshots/changes/2.11/07_keystore_config.png b/xdocs/images/screenshots/changes/2.11/07_keystore_config.png new file mode 100644 index 00000000000..7afabc09510 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.11/07_keystore_config.png differ diff --git a/xdocs/images/screenshots/changes/2.12/01_critical_section_controller.png b/xdocs/images/screenshots/changes/2.12/01_critical_section_controller.png new file mode 100644 index 00000000000..13dd13b1b34 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/01_critical_section_controller.png differ diff --git a/xdocs/images/screenshots/changes/2.12/02_dns_cache_manager.png b/xdocs/images/screenshots/changes/2.12/02_dns_cache_manager.png new file mode 100644 index 00000000000..d9642f694fc Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/02_dns_cache_manager.png differ diff --git a/xdocs/images/screenshots/changes/2.12/03_mail_reader_sampler.png b/xdocs/images/screenshots/changes/2.12/03_mail_reader_sampler.png new file mode 100644 index 00000000000..f8878a68423 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/03_mail_reader_sampler.png differ diff --git a/xdocs/images/screenshots/changes/2.12/04_jms_publisher.png b/xdocs/images/screenshots/changes/2.12/04_jms_publisher.png new file mode 100644 index 00000000000..65f2b18653a Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/04_jms_publisher.png differ diff --git a/xdocs/images/screenshots/changes/2.12/05_jms_point_to_point.png b/xdocs/images/screenshots/changes/2.12/05_jms_point_to_point.png new file mode 100644 index 00000000000..ff89e680dfc Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/05_jms_point_to_point.png differ diff --git a/xdocs/images/screenshots/changes/2.12/06_smtp_sampler.png b/xdocs/images/screenshots/changes/2.12/06_smtp_sampler.png new file mode 100644 index 00000000000..f923580db9d Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/06_smtp_sampler.png differ diff --git a/xdocs/images/screenshots/changes/2.12/07_view_results_tree.png b/xdocs/images/screenshots/changes/2.12/07_view_results_tree.png new file mode 100644 index 00000000000..ceda7289057 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/07_view_results_tree.png differ diff --git a/xdocs/images/screenshots/changes/2.12/08_response_time_graph.png b/xdocs/images/screenshots/changes/2.12/08_response_time_graph.png new file mode 100644 index 00000000000..217a42b97ca Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/08_response_time_graph.png differ diff --git a/xdocs/images/screenshots/changes/2.12/09_synchronizing_timer.png b/xdocs/images/screenshots/changes/2.12/09_synchronizing_timer.png new file mode 100644 index 00000000000..3fdfbb0e5fd Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/09_synchronizing_timer.png differ diff --git a/xdocs/images/screenshots/changes/2.12/10_undo_redo.png b/xdocs/images/screenshots/changes/2.12/10_undo_redo.png new file mode 100644 index 00000000000..370daa1c378 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/10_undo_redo.png differ diff --git a/xdocs/images/screenshots/changes/2.12/11_log_viewer.png b/xdocs/images/screenshots/changes/2.12/11_log_viewer.png new file mode 100644 index 00000000000..c7ddf3aea35 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/11_log_viewer.png differ diff --git a/xdocs/images/screenshots/changes/2.12/12_cache_resource_mode.png b/xdocs/images/screenshots/changes/2.12/12_cache_resource_mode.png new file mode 100644 index 00000000000..a281b33c91d Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/12_cache_resource_mode.png differ diff --git a/xdocs/images/screenshots/changes/2.12/13_webdav.png b/xdocs/images/screenshots/changes/2.12/13_webdav.png new file mode 100644 index 00000000000..6c95e33f03c Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/13_webdav.png differ diff --git a/xdocs/images/screenshots/changes/2.12/14_recorder_filter.png b/xdocs/images/screenshots/changes/2.12/14_recorder_filter.png new file mode 100644 index 00000000000..b7d293d4665 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.12/14_recorder_filter.png differ diff --git a/xdocs/images/screenshots/changes/2.13/aggregate_graph_new_percentile.png b/xdocs/images/screenshots/changes/2.13/aggregate_graph_new_percentile.png new file mode 100644 index 00000000000..72f6ba9f7bd Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/aggregate_graph_new_percentile.png differ diff --git a/xdocs/images/screenshots/changes/2.13/backend_listener_graphite.png b/xdocs/images/screenshots/changes/2.13/backend_listener_graphite.png new file mode 100644 index 00000000000..73228c1c2f4 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/backend_listener_graphite.png differ diff --git a/xdocs/images/screenshots/changes/2.13/connect_time_table.png b/xdocs/images/screenshots/changes/2.13/connect_time_table.png new file mode 100644 index 00000000000..96967224f22 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/connect_time_table.png differ diff --git a/xdocs/images/screenshots/changes/2.13/connect_time_tree.png b/xdocs/images/screenshots/changes/2.13/connect_time_tree.png new file mode 100644 index 00000000000..4c0515c5358 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/connect_time_tree.png differ diff --git a/xdocs/images/screenshots/changes/2.13/distributed_retry.png b/xdocs/images/screenshots/changes/2.13/distributed_retry.png new file mode 100644 index 00000000000..c8a31d41f7e Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/distributed_retry.png differ diff --git a/xdocs/images/screenshots/changes/2.13/jdbc_resultset_handler.png b/xdocs/images/screenshots/changes/2.13/jdbc_resultset_handler.png new file mode 100644 index 00000000000..dd8fad8d1bd Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/jdbc_resultset_handler.png differ diff --git a/xdocs/images/screenshots/changes/2.13/module_controller_tree_view.png b/xdocs/images/screenshots/changes/2.13/module_controller_tree_view.png new file mode 100644 index 00000000000..5eed7f499b0 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/module_controller_tree_view.png differ diff --git a/xdocs/images/screenshots/changes/2.13/new_methods_caldav.png b/xdocs/images/screenshots/changes/2.13/new_methods_caldav.png new file mode 100644 index 00000000000..7d0e5e4c88b Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/new_methods_caldav.png differ diff --git a/xdocs/images/screenshots/changes/2.13/toolbar_22x22.png b/xdocs/images/screenshots/changes/2.13/toolbar_22x22.png new file mode 100644 index 00000000000..d5aa794e5e6 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/toolbar_22x22.png differ diff --git a/xdocs/images/screenshots/changes/2.13/toolbar_32x32.png b/xdocs/images/screenshots/changes/2.13/toolbar_32x32.png new file mode 100644 index 00000000000..410460d93d7 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/toolbar_32x32.png differ diff --git a/xdocs/images/screenshots/changes/2.13/toolbar_48x48.png b/xdocs/images/screenshots/changes/2.13/toolbar_48x48.png new file mode 100644 index 00000000000..e8fdbc113f9 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/toolbar_48x48.png differ diff --git a/xdocs/images/screenshots/changes/2.13/warning_message_proxy.png b/xdocs/images/screenshots/changes/2.13/warning_message_proxy.png new file mode 100644 index 00000000000..89141393fea Binary files /dev/null and b/xdocs/images/screenshots/changes/2.13/warning_message_proxy.png differ diff --git a/xdocs/images/screenshots/changes/2.6/01_toolbar.png b/xdocs/images/screenshots/changes/2.6/01_toolbar.png new file mode 100644 index 00000000000..b855de1ed2c Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/01_toolbar.png differ diff --git a/xdocs/images/screenshots/changes/2.6/02_ignore_pause_timers.png b/xdocs/images/screenshots/changes/2.6/02_ignore_pause_timers.png new file mode 100644 index 00000000000..43698e2bfd0 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/02_ignore_pause_timers.png differ diff --git a/xdocs/images/screenshots/changes/2.6/03_look_and_feel.png b/xdocs/images/screenshots/changes/2.6/03_look_and_feel.png new file mode 100644 index 00000000000..f57faaa5b71 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/03_look_and_feel.png differ diff --git a/xdocs/images/screenshots/changes/2.6/04_duplicate_context_menu.png b/xdocs/images/screenshots/changes/2.6/04_duplicate_context_menu.png new file mode 100644 index 00000000000..61fbdab7196 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/04_duplicate_context_menu.png differ diff --git a/xdocs/images/screenshots/changes/2.6/05_search_tree.png b/xdocs/images/screenshots/changes/2.6/05_search_tree.png new file mode 100644 index 00000000000..81a51845b0e Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/05_search_tree.png differ diff --git a/xdocs/images/screenshots/changes/2.6/06_post_data.png b/xdocs/images/screenshots/changes/2.6/06_post_data.png new file mode 100644 index 00000000000..6e9af4e6887 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/06_post_data.png differ diff --git a/xdocs/images/screenshots/changes/2.6/07_multiple_selection_params.png b/xdocs/images/screenshots/changes/2.6/07_multiple_selection_params.png new file mode 100644 index 00000000000..b3e97a09313 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/07_multiple_selection_params.png differ diff --git a/xdocs/images/screenshots/changes/2.6/08_file_protocol.png b/xdocs/images/screenshots/changes/2.6/08_file_protocol.png new file mode 100644 index 00000000000..04520ec991c Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/08_file_protocol.png differ diff --git a/xdocs/images/screenshots/changes/2.6/09_file_protocol_embedded.png b/xdocs/images/screenshots/changes/2.6/09_file_protocol_embedded.png new file mode 100644 index 00000000000..67f582f08b1 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/09_file_protocol_embedded.png differ diff --git a/xdocs/images/screenshots/changes/2.6/10_child_sampler.png b/xdocs/images/screenshots/changes/2.6/10_child_sampler.png new file mode 100644 index 00000000000..cff10a8cbce Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/10_child_sampler.png differ diff --git a/xdocs/images/screenshots/changes/2.6/11_jks_keystore.png b/xdocs/images/screenshots/changes/2.6/11_jks_keystore.png new file mode 100644 index 00000000000..db3375709b3 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/11_jks_keystore.png differ diff --git a/xdocs/images/screenshots/changes/2.6/12_aggregate_graph_settings.png b/xdocs/images/screenshots/changes/2.6/12_aggregate_graph_settings.png new file mode 100644 index 00000000000..4bb4d37c82b Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/12_aggregate_graph_settings.png differ diff --git a/xdocs/images/screenshots/changes/2.6/13_aggregate_graph_bar.png b/xdocs/images/screenshots/changes/2.6/13_aggregate_graph_bar.png new file mode 100644 index 00000000000..def4e8d3c75 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/13_aggregate_graph_bar.png differ diff --git a/xdocs/images/screenshots/changes/2.6/14_reset_counter.png b/xdocs/images/screenshots/changes/2.6/14_reset_counter.png new file mode 100644 index 00000000000..1b622b3d9bb Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/14_reset_counter.png differ diff --git a/xdocs/images/screenshots/changes/2.6/15_random_string.png b/xdocs/images/screenshots/changes/2.6/15_random_string.png new file mode 100644 index 00000000000..a85f5d2df76 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/15_random_string.png differ diff --git a/xdocs/images/screenshots/changes/2.6/16_udv_comments.png b/xdocs/images/screenshots/changes/2.6/16_udv_comments.png new file mode 100644 index 00000000000..e014bdb25a3 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/16_udv_comments.png differ diff --git a/xdocs/images/screenshots/changes/2.6/17_vrt_max_size_display.png b/xdocs/images/screenshots/changes/2.6/17_vrt_max_size_display.png new file mode 100644 index 00000000000..505302fcc07 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/17_vrt_max_size_display.png differ diff --git a/xdocs/images/screenshots/changes/2.6/18_change_ctl_type.png b/xdocs/images/screenshots/changes/2.6/18_change_ctl_type.png new file mode 100644 index 00000000000..bea7c315ebd Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/18_change_ctl_type.png differ diff --git a/xdocs/images/screenshots/changes/2.6/19_jdbc_pre_post_proc.png b/xdocs/images/screenshots/changes/2.6/19_jdbc_pre_post_proc.png new file mode 100644 index 00000000000..232e2b4a1fe Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/19_jdbc_pre_post_proc.png differ diff --git a/xdocs/images/screenshots/changes/2.6/20_jdbc_trans_isolation.png b/xdocs/images/screenshots/changes/2.6/20_jdbc_trans_isolation.png new file mode 100644 index 00000000000..7122a593f4a Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/20_jdbc_trans_isolation.png differ diff --git a/xdocs/images/screenshots/changes/2.6/21_poisson_timer.png b/xdocs/images/screenshots/changes/2.6/21_poisson_timer.png new file mode 100644 index 00000000000..8bba1d1b65a Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/21_poisson_timer.png differ diff --git a/xdocs/images/screenshots/changes/2.6/22_drag_and_drop.png b/xdocs/images/screenshots/changes/2.6/22_drag_and_drop.png new file mode 100644 index 00000000000..562889169d4 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/22_drag_and_drop.png differ diff --git a/xdocs/images/screenshots/changes/2.6/23_confirm_remove.png b/xdocs/images/screenshots/changes/2.6/23_confirm_remove.png new file mode 100644 index 00000000000..8d6a05bdbd1 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/23_confirm_remove.png differ diff --git a/xdocs/images/screenshots/changes/2.6/24_diskstore.png b/xdocs/images/screenshots/changes/2.6/24_diskstore.png new file mode 100644 index 00000000000..03024b2a1fb Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/24_diskstore.png differ diff --git a/xdocs/images/screenshots/changes/2.6/25_selector.png b/xdocs/images/screenshots/changes/2.6/25_selector.png new file mode 100644 index 00000000000..986e06ec6d7 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/25_selector.png differ diff --git a/xdocs/images/screenshots/changes/2.6/26_ignore_child_failed.png b/xdocs/images/screenshots/changes/2.6/26_ignore_child_failed.png new file mode 100644 index 00000000000..966fc41e46d Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/26_ignore_child_failed.png differ diff --git a/xdocs/images/screenshots/changes/2.6/27_succes_with_child_failed.png b/xdocs/images/screenshots/changes/2.6/27_succes_with_child_failed.png new file mode 100644 index 00000000000..097b04b8cb1 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/27_succes_with_child_failed.png differ diff --git a/xdocs/images/screenshots/changes/2.6/28_loggerpanel.png b/xdocs/images/screenshots/changes/2.6/28_loggerpanel.png new file mode 100644 index 00000000000..2f889b0609e Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/28_loggerpanel.png differ diff --git a/xdocs/images/screenshots/changes/2.6/28_loggerpanel_option.png b/xdocs/images/screenshots/changes/2.6/28_loggerpanel_option.png new file mode 100644 index 00000000000..e7afb8954b2 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.6/28_loggerpanel_option.png differ diff --git a/xdocs/images/screenshots/changes/2.7/01_os_process_sampler.png b/xdocs/images/screenshots/changes/2.7/01_os_process_sampler.png new file mode 100644 index 00000000000..920e8cd0a4f Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/01_os_process_sampler.png differ diff --git a/xdocs/images/screenshots/changes/2.7/02_os_process_example_results.png b/xdocs/images/screenshots/changes/2.7/02_os_process_example_results.png new file mode 100644 index 00000000000..f9727843ab8 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/02_os_process_example_results.png differ diff --git a/xdocs/images/screenshots/changes/2.7/03_aggregate_graph_with_new_cols.png b/xdocs/images/screenshots/changes/2.7/03_aggregate_graph_with_new_cols.png new file mode 100644 index 00000000000..94e74eb5df7 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/03_aggregate_graph_with_new_cols.png differ diff --git a/xdocs/images/screenshots/changes/2.7/04_aggregate_graph_parameters.png b/xdocs/images/screenshots/changes/2.7/04_aggregate_graph_parameters.png new file mode 100644 index 00000000000..a121e7d2d55 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/04_aggregate_graph_parameters.png differ diff --git a/xdocs/images/screenshots/changes/2.7/05_jmeter_ant_task_report_success.png b/xdocs/images/screenshots/changes/2.7/05_jmeter_ant_task_report_success.png new file mode 100644 index 00000000000..8e8b5a5cbcd Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/05_jmeter_ant_task_report_success.png differ diff --git a/xdocs/images/screenshots/changes/2.7/06_jmeter_ant_task_report_errors.png b/xdocs/images/screenshots/changes/2.7/06_jmeter_ant_task_report_errors.png new file mode 100644 index 00000000000..4f5b7c5ddce Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/06_jmeter_ant_task_report_errors.png differ diff --git a/xdocs/images/screenshots/changes/2.7/07_test_action_next_iter.png b/xdocs/images/screenshots/changes/2.7/07_test_action_next_iter.png new file mode 100644 index 00000000000..8db01e4ad8f Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/07_test_action_next_iter.png differ diff --git a/xdocs/images/screenshots/changes/2.7/08_param_button_detail.png b/xdocs/images/screenshots/changes/2.7/08_param_button_detail.png new file mode 100644 index 00000000000..f3498fca5c2 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/08_param_button_detail.png differ diff --git a/xdocs/images/screenshots/changes/2.7/09_detail_box.png b/xdocs/images/screenshots/changes/2.7/09_detail_box.png new file mode 100644 index 00000000000..ae7d232c960 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/09_detail_box.png differ diff --git a/xdocs/images/screenshots/changes/2.7/10_mailer_visualizer_gui.png b/xdocs/images/screenshots/changes/2.7/10_mailer_visualizer_gui.png new file mode 100644 index 00000000000..2fb226f1b6f Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/10_mailer_visualizer_gui.png differ diff --git a/xdocs/images/screenshots/changes/2.7/11_jms_non_persistent_delivery_mode.png b/xdocs/images/screenshots/changes/2.7/11_jms_non_persistent_delivery_mode.png new file mode 100644 index 00000000000..04fd6f1a9f8 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/11_jms_non_persistent_delivery_mode.png differ diff --git a/xdocs/images/screenshots/changes/2.7/12_jms_sending_objects.png b/xdocs/images/screenshots/changes/2.7/12_jms_sending_objects.png new file mode 100644 index 00000000000..8d82dbdff37 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/12_jms_sending_objects.png differ diff --git a/xdocs/images/screenshots/changes/2.7/13_jms_properties.png b/xdocs/images/screenshots/changes/2.7/13_jms_properties.png new file mode 100644 index 00000000000..252535907c7 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/13_jms_properties.png differ diff --git a/xdocs/images/screenshots/changes/2.7/14_ws_document_cache.png b/xdocs/images/screenshots/changes/2.7/14_ws_document_cache.png new file mode 100644 index 00000000000..de56edf5d4a Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/14_ws_document_cache.png differ diff --git a/xdocs/images/screenshots/changes/2.7/15_ws_maintain_session.png b/xdocs/images/screenshots/changes/2.7/15_ws_maintain_session.png new file mode 100644 index 00000000000..84a400539d2 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/15_ws_maintain_session.png differ diff --git a/xdocs/images/screenshots/changes/2.7/16_log_errors_counter.png b/xdocs/images/screenshots/changes/2.7/16_log_errors_counter.png new file mode 100644 index 00000000000..e56f555de55 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.7/16_log_errors_counter.png differ diff --git a/xdocs/images/screenshots/changes/2.8/01_http_patch_verb.png b/xdocs/images/screenshots/changes/2.8/01_http_patch_verb.png new file mode 100644 index 00000000000..a868114e2d6 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/01_http_patch_verb.png differ diff --git a/xdocs/images/screenshots/changes/2.8/02_http_default_hc4.png b/xdocs/images/screenshots/changes/2.8/02_http_default_hc4.png new file mode 100644 index 00000000000..dccfc8de77e Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/02_http_default_hc4.png differ diff --git a/xdocs/images/screenshots/changes/2.8/03_remove_https_spoofing1.png b/xdocs/images/screenshots/changes/2.8/03_remove_https_spoofing1.png new file mode 100644 index 00000000000..24c0442db06 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/03_remove_https_spoofing1.png differ diff --git a/xdocs/images/screenshots/changes/2.8/05_http_defaults_url_filter.png b/xdocs/images/screenshots/changes/2.8/05_http_defaults_url_filter.png new file mode 100644 index 00000000000..82cb4986e1f Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/05_http_defaults_url_filter.png differ diff --git a/xdocs/images/screenshots/changes/2.8/06_os_sampler_stdout-err-in.png b/xdocs/images/screenshots/changes/2.8/06_os_sampler_stdout-err-in.png new file mode 100644 index 00000000000..c09a46a603a Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/06_os_sampler_stdout-err-in.png differ diff --git a/xdocs/images/screenshots/changes/2.8/07_aggregate_graph_legend_left_right.png b/xdocs/images/screenshots/changes/2.8/07_aggregate_graph_legend_left_right.png new file mode 100644 index 00000000000..c14ba44d971 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/07_aggregate_graph_legend_left_right.png differ diff --git a/xdocs/images/screenshots/changes/2.8/08_resp_time_graph_settings.png b/xdocs/images/screenshots/changes/2.8/08_resp_time_graph_settings.png new file mode 100644 index 00000000000..c1531568e6a Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/08_resp_time_graph_settings.png differ diff --git a/xdocs/images/screenshots/changes/2.8/09_resp_time_graph.png b/xdocs/images/screenshots/changes/2.8/09_resp_time_graph.png new file mode 100644 index 00000000000..53bb0b4e7d5 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/09_resp_time_graph.png differ diff --git a/xdocs/images/screenshots/changes/2.8/10_latency_view_results_table.png b/xdocs/images/screenshots/changes/2.8/10_latency_view_results_table.png new file mode 100644 index 00000000000..f53bb881095 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/10_latency_view_results_table.png differ diff --git a/xdocs/images/screenshots/changes/2.8/11_hc4_cookie.png b/xdocs/images/screenshots/changes/2.8/11_hc4_cookie.png new file mode 100644 index 00000000000..2eb3f861273 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/11_hc4_cookie.png differ diff --git a/xdocs/images/screenshots/changes/2.8/12_delay_thread_creation.png b/xdocs/images/screenshots/changes/2.8/12_delay_thread_creation.png new file mode 100644 index 00000000000..9c2aab228d9 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/12_delay_thread_creation.png differ diff --git a/xdocs/images/screenshots/changes/2.8/13_gnome3_title.png b/xdocs/images/screenshots/changes/2.8/13_gnome3_title.png new file mode 100644 index 00000000000..04c52e4d56d Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/13_gnome3_title.png differ diff --git a/xdocs/images/screenshots/changes/2.8/14_ctrl_F_shortcut.png b/xdocs/images/screenshots/changes/2.8/14_ctrl_F_shortcut.png new file mode 100644 index 00000000000..03ff977b8c3 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/14_ctrl_F_shortcut.png differ diff --git a/xdocs/images/screenshots/changes/2.8/15_add_from_clipboard_filter.png b/xdocs/images/screenshots/changes/2.8/15_add_from_clipboard_filter.png new file mode 100644 index 00000000000..e414e1ef59a Binary files /dev/null and b/xdocs/images/screenshots/changes/2.8/15_add_from_clipboard_filter.png differ diff --git a/xdocs/images/screenshots/changes/2.9/01_css_jquery_extractor.png b/xdocs/images/screenshots/changes/2.9/01_css_jquery_extractor.png new file mode 100644 index 00000000000..84194037522 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/01_css_jquery_extractor.png differ diff --git a/xdocs/images/screenshots/changes/2.9/01_css_jquery_extractor_resul.png b/xdocs/images/screenshots/changes/2.9/01_css_jquery_extractor_resul.png new file mode 100644 index 00000000000..a1de1745273 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/01_css_jquery_extractor_resul.png differ diff --git a/xdocs/images/screenshots/changes/2.9/02_document_render_view_results_tree.png b/xdocs/images/screenshots/changes/2.9/02_document_render_view_results_tree.png new file mode 100644 index 00000000000..4f74b59e919 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/02_document_render_view_results_tree.png differ diff --git a/xdocs/images/screenshots/changes/2.9/03_new_options_tcp_sampler.png b/xdocs/images/screenshots/changes/2.9/03_new_options_tcp_sampler.png new file mode 100644 index 00000000000..1348cb97850 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/03_new_options_tcp_sampler.png differ diff --git a/xdocs/images/screenshots/changes/2.9/04_for_each_new_fields.png b/xdocs/images/screenshots/changes/2.9/04_for_each_new_fields.png new file mode 100644 index 00000000000..743ee1a7cf3 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/04_for_each_new_fields.png differ diff --git a/xdocs/images/screenshots/changes/2.9/05_result_status_action_handler.png b/xdocs/images/screenshots/changes/2.9/05_result_status_action_handler.png new file mode 100644 index 00000000000..8f826ec7608 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/05_result_status_action_handler.png differ diff --git a/xdocs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter1.png b/xdocs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter1.png new file mode 100644 index 00000000000..79014a6dc05 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter1.png differ diff --git a/xdocs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter2.png b/xdocs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter2.png new file mode 100644 index 00000000000..d121899239d Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/06_copy_paste_between_2_jmeter2.png differ diff --git a/xdocs/images/screenshots/changes/2.9/07_header_panel_add_from_clipboard.png b/xdocs/images/screenshots/changes/2.9/07_header_panel_add_from_clipboard.png new file mode 100644 index 00000000000..313f3a881cf Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/07_header_panel_add_from_clipboard.png differ diff --git a/xdocs/images/screenshots/changes/2.9/08_module_controller_improvements.png b/xdocs/images/screenshots/changes/2.9/08_module_controller_improvements.png new file mode 100644 index 00000000000..d1b5837bba1 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/08_module_controller_improvements.png differ diff --git a/xdocs/images/screenshots/changes/2.9/09_proxy_excludes_suggested.png b/xdocs/images/screenshots/changes/2.9/09_proxy_excludes_suggested.png new file mode 100644 index 00000000000..9a599032c57 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/09_proxy_excludes_suggested.png differ diff --git a/xdocs/images/screenshots/changes/2.9/10_http_proxy_dont_force_http_type.png b/xdocs/images/screenshots/changes/2.9/10_http_proxy_dont_force_http_type.png new file mode 100644 index 00000000000..18aa4a0f47e Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/10_http_proxy_dont_force_http_type.png differ diff --git a/xdocs/images/screenshots/changes/2.9/11_jms_publisher_bytes.png b/xdocs/images/screenshots/changes/2.9/11_jms_publisher_bytes.png new file mode 100644 index 00000000000..e2fd63f99b7 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/11_jms_publisher_bytes.png differ diff --git a/xdocs/images/screenshots/changes/2.9/12_jsr223_sampler.png b/xdocs/images/screenshots/changes/2.9/12_jsr223_sampler.png new file mode 100644 index 00000000000..8d0fd64bd33 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/12_jsr223_sampler.png differ diff --git a/xdocs/images/screenshots/changes/2.9/13_regex_user_params.png b/xdocs/images/screenshots/changes/2.9/13_regex_user_params.png new file mode 100644 index 00000000000..c500a6dcfbf Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/13_regex_user_params.png differ diff --git a/xdocs/images/screenshots/changes/2.9/14_xpath_assertion.png b/xdocs/images/screenshots/changes/2.9/14_xpath_assertion.png new file mode 100644 index 00000000000..149a37fc0d2 Binary files /dev/null and b/xdocs/images/screenshots/changes/2.9/14_xpath_assertion.png differ diff --git a/xdocs/images/screenshots/class_diagram.gif b/xdocs/images/screenshots/class_diagram.gif new file mode 100644 index 00000000000..7b594564f60 Binary files /dev/null and b/xdocs/images/screenshots/class_diagram.gif differ diff --git a/xdocs/images/screenshots/comparison_assertion_visualizer.png b/xdocs/images/screenshots/comparison_assertion_visualizer.png new file mode 100644 index 00000000000..68058a38711 Binary files /dev/null and b/xdocs/images/screenshots/comparison_assertion_visualizer.png differ diff --git a/xdocs/images/screenshots/counter.png b/xdocs/images/screenshots/counter.png new file mode 100644 index 00000000000..6b1e77d7f02 Binary files /dev/null and b/xdocs/images/screenshots/counter.png differ diff --git a/xdocs/images/screenshots/css_extractor_attr.png b/xdocs/images/screenshots/css_extractor_attr.png new file mode 100644 index 00000000000..72b37b9e063 Binary files /dev/null and b/xdocs/images/screenshots/css_extractor_attr.png differ diff --git a/xdocs/images/screenshots/css_extractor_noattr.png b/xdocs/images/screenshots/css_extractor_noattr.png new file mode 100644 index 00000000000..17af38be853 Binary files /dev/null and b/xdocs/images/screenshots/css_extractor_noattr.png differ diff --git a/xdocs/images/screenshots/csvdatasetconfig.png b/xdocs/images/screenshots/csvdatasetconfig.png new file mode 100644 index 00000000000..b05ee5f700a Binary files /dev/null and b/xdocs/images/screenshots/csvdatasetconfig.png differ diff --git a/xdocs/images/screenshots/debug_postprocessor.png b/xdocs/images/screenshots/debug_postprocessor.png new file mode 100644 index 00000000000..20bdfac9e6b Binary files /dev/null and b/xdocs/images/screenshots/debug_postprocessor.png differ diff --git a/xdocs/images/screenshots/debug_sampler.png b/xdocs/images/screenshots/debug_sampler.png new file mode 100644 index 00000000000..b2f8f43a758 Binary files /dev/null and b/xdocs/images/screenshots/debug_sampler.png differ diff --git a/xdocs/images/screenshots/distribution_graph.png b/xdocs/images/screenshots/distribution_graph.png new file mode 100644 index 00000000000..eae11e04476 Binary files /dev/null and b/xdocs/images/screenshots/distribution_graph.png differ diff --git a/xdocs/images/screenshots/dns-cache-manager.png b/xdocs/images/screenshots/dns-cache-manager.png new file mode 100644 index 00000000000..008c6fd30eb Binary files /dev/null and b/xdocs/images/screenshots/dns-cache-manager.png differ diff --git a/xdocs/images/screenshots/duration_assertion.png b/xdocs/images/screenshots/duration_assertion.png new file mode 100644 index 00000000000..ac72297b4cf Binary files /dev/null and b/xdocs/images/screenshots/duration_assertion.png differ diff --git a/xdocs/images/screenshots/ftp-config/ftp-request-defaults.png b/xdocs/images/screenshots/ftp-config/ftp-request-defaults.png new file mode 100644 index 00000000000..a453e9af39d Binary files /dev/null and b/xdocs/images/screenshots/ftp-config/ftp-request-defaults.png differ diff --git a/xdocs/images/screenshots/ftptest/ftp-defaults.png b/xdocs/images/screenshots/ftptest/ftp-defaults.png new file mode 100644 index 00000000000..644664dc141 Binary files /dev/null and b/xdocs/images/screenshots/ftptest/ftp-defaults.png differ diff --git a/xdocs/images/screenshots/ftptest/ftp-defaults2.png b/xdocs/images/screenshots/ftptest/ftp-defaults2.png new file mode 100644 index 00000000000..66f7e14d4ec Binary files /dev/null and b/xdocs/images/screenshots/ftptest/ftp-defaults2.png differ diff --git a/xdocs/images/screenshots/ftptest/ftp-request.png b/xdocs/images/screenshots/ftptest/ftp-request.png new file mode 100644 index 00000000000..14ace11a310 Binary files /dev/null and b/xdocs/images/screenshots/ftptest/ftp-request.png differ diff --git a/xdocs/images/screenshots/ftptest/ftp-request2.png b/xdocs/images/screenshots/ftptest/ftp-request2.png new file mode 100644 index 00000000000..adf298935f3 Binary files /dev/null and b/xdocs/images/screenshots/ftptest/ftp-request2.png differ diff --git a/xdocs/images/screenshots/ftptest/ftp-results.png b/xdocs/images/screenshots/ftptest/ftp-results.png new file mode 100644 index 00000000000..8b489c773f3 Binary files /dev/null and b/xdocs/images/screenshots/ftptest/ftp-results.png differ diff --git a/xdocs/images/screenshots/ftptest/threadgroup2.png b/xdocs/images/screenshots/ftptest/threadgroup2.png new file mode 100644 index 00000000000..543b5d039bc Binary files /dev/null and b/xdocs/images/screenshots/ftptest/threadgroup2.png differ diff --git a/xdocs/images/screenshots/function_helper_dialog.png b/xdocs/images/screenshots/function_helper_dialog.png new file mode 100644 index 00000000000..68cd9f77bd3 Binary files /dev/null and b/xdocs/images/screenshots/function_helper_dialog.png differ diff --git a/xdocs/images/screenshots/grafana_dashboard.png b/xdocs/images/screenshots/grafana_dashboard.png new file mode 100644 index 00000000000..28b3b373574 Binary files /dev/null and b/xdocs/images/screenshots/grafana_dashboard.png differ diff --git a/xdocs/images/screenshots/graph_results.png b/xdocs/images/screenshots/graph_results.png new file mode 100644 index 00000000000..f895b9b7e97 Binary files /dev/null and b/xdocs/images/screenshots/graph_results.png differ diff --git a/xdocs/images/screenshots/graphfullresults.png b/xdocs/images/screenshots/graphfullresults.png new file mode 100644 index 00000000000..462775697e4 Binary files /dev/null and b/xdocs/images/screenshots/graphfullresults.png differ diff --git a/xdocs/images/screenshots/html_link_parser.png b/xdocs/images/screenshots/html_link_parser.png new file mode 100644 index 00000000000..d8886575fc3 Binary files /dev/null and b/xdocs/images/screenshots/html_link_parser.png differ diff --git a/xdocs/images/screenshots/http-config/auth-manager-example1a.gif b/xdocs/images/screenshots/http-config/auth-manager-example1a.gif new file mode 100644 index 00000000000..013474d5171 Binary files /dev/null and b/xdocs/images/screenshots/http-config/auth-manager-example1a.gif differ diff --git a/xdocs/images/screenshots/http-config/auth-manager-example1b.png b/xdocs/images/screenshots/http-config/auth-manager-example1b.png new file mode 100644 index 00000000000..c045ed18f7d Binary files /dev/null and b/xdocs/images/screenshots/http-config/auth-manager-example1b.png differ diff --git a/xdocs/images/screenshots/http-config/header-manager-example1a.gif b/xdocs/images/screenshots/http-config/header-manager-example1a.gif new file mode 100644 index 00000000000..fdf5dc3a51d Binary files /dev/null and b/xdocs/images/screenshots/http-config/header-manager-example1a.gif differ diff --git a/xdocs/images/screenshots/http-config/header-manager-example1b.png b/xdocs/images/screenshots/http-config/header-manager-example1b.png new file mode 100644 index 00000000000..02e5a8387fa Binary files /dev/null and b/xdocs/images/screenshots/http-config/header-manager-example1b.png differ diff --git a/xdocs/images/screenshots/http-config/http-auth-manager.png b/xdocs/images/screenshots/http-config/http-auth-manager.png new file mode 100644 index 00000000000..96989f56da3 Binary files /dev/null and b/xdocs/images/screenshots/http-config/http-auth-manager.png differ diff --git a/xdocs/images/screenshots/http-config/http-cache-manager.png b/xdocs/images/screenshots/http-config/http-cache-manager.png new file mode 100644 index 00000000000..1ad53f2b662 Binary files /dev/null and b/xdocs/images/screenshots/http-config/http-cache-manager.png differ diff --git a/xdocs/images/screenshots/http-config/http-config-example.png b/xdocs/images/screenshots/http-config/http-config-example.png new file mode 100644 index 00000000000..bd292e8242c Binary files /dev/null and b/xdocs/images/screenshots/http-config/http-config-example.png differ diff --git a/xdocs/images/screenshots/http-config/http-cookie-manager.png b/xdocs/images/screenshots/http-config/http-cookie-manager.png new file mode 100644 index 00000000000..4b763a53a18 Binary files /dev/null and b/xdocs/images/screenshots/http-config/http-cookie-manager.png differ diff --git a/xdocs/images/screenshots/http-config/http-header-manager.png b/xdocs/images/screenshots/http-config/http-header-manager.png new file mode 100644 index 00000000000..bb311a816e0 Binary files /dev/null and b/xdocs/images/screenshots/http-config/http-header-manager.png differ diff --git a/xdocs/images/screenshots/http-config/http-request-defaults.png b/xdocs/images/screenshots/http-config/http-request-defaults.png new file mode 100644 index 00000000000..1a6c0e0accf Binary files /dev/null and b/xdocs/images/screenshots/http-config/http-request-defaults.png differ diff --git a/xdocs/images/screenshots/http-request-confirm-raw-body.png b/xdocs/images/screenshots/http-request-confirm-raw-body.png new file mode 100644 index 00000000000..ff4f7c0cb03 Binary files /dev/null and b/xdocs/images/screenshots/http-request-confirm-raw-body.png differ diff --git a/xdocs/images/screenshots/http-request-raw-body.png b/xdocs/images/screenshots/http-request-raw-body.png new file mode 100644 index 00000000000..09cae548f3c Binary files /dev/null and b/xdocs/images/screenshots/http-request-raw-body.png differ diff --git a/xdocs/images/screenshots/http-request-raw-single-parameter.png b/xdocs/images/screenshots/http-request-raw-single-parameter.png new file mode 100644 index 00000000000..f5816e5813b Binary files /dev/null and b/xdocs/images/screenshots/http-request-raw-single-parameter.png differ diff --git a/xdocs/images/screenshots/http-request.png b/xdocs/images/screenshots/http-request.png new file mode 100644 index 00000000000..1fef8cdbb13 Binary files /dev/null and b/xdocs/images/screenshots/http-request.png differ diff --git a/xdocs/images/screenshots/icons-22x22.jpg b/xdocs/images/screenshots/icons-22x22.jpg new file mode 100644 index 00000000000..f15cfb56c5a Binary files /dev/null and b/xdocs/images/screenshots/icons-22x22.jpg differ diff --git a/xdocs/images/screenshots/icons-32x32.jpg b/xdocs/images/screenshots/icons-32x32.jpg new file mode 100644 index 00000000000..0ad10246b8b Binary files /dev/null and b/xdocs/images/screenshots/icons-32x32.jpg differ diff --git a/xdocs/images/screenshots/icons-48x48.jpg b/xdocs/images/screenshots/icons-48x48.jpg new file mode 100644 index 00000000000..be82792ad3c Binary files /dev/null and b/xdocs/images/screenshots/icons-48x48.jpg differ diff --git a/xdocs/images/screenshots/ifcontroller.png b/xdocs/images/screenshots/ifcontroller.png new file mode 100644 index 00000000000..aa1db8aecfb Binary files /dev/null and b/xdocs/images/screenshots/ifcontroller.png differ diff --git a/xdocs/images/screenshots/includecontroller.png b/xdocs/images/screenshots/includecontroller.png new file mode 100644 index 00000000000..1430873d97a Binary files /dev/null and b/xdocs/images/screenshots/includecontroller.png differ diff --git a/xdocs/images/screenshots/java_defaults.png b/xdocs/images/screenshots/java_defaults.png new file mode 100644 index 00000000000..99781413fcc Binary files /dev/null and b/xdocs/images/screenshots/java_defaults.png differ diff --git a/xdocs/images/screenshots/java_request.png b/xdocs/images/screenshots/java_request.png new file mode 100644 index 00000000000..495a95546f5 Binary files /dev/null and b/xdocs/images/screenshots/java_request.png differ diff --git a/xdocs/images/screenshots/jdbc-config/jdbc-conn-config.png b/xdocs/images/screenshots/jdbc-config/jdbc-conn-config.png new file mode 100644 index 00000000000..b5461855c17 Binary files /dev/null and b/xdocs/images/screenshots/jdbc-config/jdbc-conn-config.png differ diff --git a/xdocs/images/screenshots/jdbc-config/jdbc-sql-query.png b/xdocs/images/screenshots/jdbc-config/jdbc-sql-query.png new file mode 100644 index 00000000000..45844059ae8 Binary files /dev/null and b/xdocs/images/screenshots/jdbc-config/jdbc-sql-query.png differ diff --git a/xdocs/images/screenshots/jdbc-post-processor.png b/xdocs/images/screenshots/jdbc-post-processor.png new file mode 100644 index 00000000000..6aa8da66090 Binary files /dev/null and b/xdocs/images/screenshots/jdbc-post-processor.png differ diff --git a/xdocs/images/screenshots/jdbc-pre-processor.png b/xdocs/images/screenshots/jdbc-pre-processor.png new file mode 100644 index 00000000000..3f520e3c62f Binary files /dev/null and b/xdocs/images/screenshots/jdbc-pre-processor.png differ diff --git a/xdocs/images/screenshots/jdbctest/JDBCRequest.png b/xdocs/images/screenshots/jdbctest/JDBCRequest.png new file mode 100644 index 00000000000..b578804f7c8 Binary files /dev/null and b/xdocs/images/screenshots/jdbctest/JDBCRequest.png differ diff --git a/xdocs/images/screenshots/jdbctest/JDBCRequest2.png b/xdocs/images/screenshots/jdbctest/JDBCRequest2.png new file mode 100644 index 00000000000..9181a802a92 Binary files /dev/null and b/xdocs/images/screenshots/jdbctest/JDBCRequest2.png differ diff --git a/xdocs/images/screenshots/jdbctest/JDBCRequest3.png b/xdocs/images/screenshots/jdbctest/JDBCRequest3.png new file mode 100644 index 00000000000..a8b00a9f5d4 Binary files /dev/null and b/xdocs/images/screenshots/jdbctest/JDBCRequest3.png differ diff --git a/xdocs/images/screenshots/jdbctest/jdbc-config.png b/xdocs/images/screenshots/jdbctest/jdbc-config.png new file mode 100644 index 00000000000..75ecf8aa9a5 Binary files /dev/null and b/xdocs/images/screenshots/jdbctest/jdbc-config.png differ diff --git a/xdocs/images/screenshots/jdbctest/jdbc-request.png b/xdocs/images/screenshots/jdbctest/jdbc-request.png new file mode 100644 index 00000000000..b4f5e5aef38 Binary files /dev/null and b/xdocs/images/screenshots/jdbctest/jdbc-request.png differ diff --git a/xdocs/images/screenshots/jdbctest/jdbc-results.png b/xdocs/images/screenshots/jdbctest/jdbc-results.png new file mode 100644 index 00000000000..9d5ca02a1ad Binary files /dev/null and b/xdocs/images/screenshots/jdbctest/jdbc-results.png differ diff --git a/xdocs/images/screenshots/jdbctest/threadgroup1.png b/xdocs/images/screenshots/jdbctest/threadgroup1.png new file mode 100644 index 00000000000..f6f66b0eb5d Binary files /dev/null and b/xdocs/images/screenshots/jdbctest/threadgroup1.png differ diff --git a/xdocs/images/screenshots/jdbctest/threadgroup2.png b/xdocs/images/screenshots/jdbctest/threadgroup2.png new file mode 100644 index 00000000000..fbcdea0f277 Binary files /dev/null and b/xdocs/images/screenshots/jdbctest/threadgroup2.png differ diff --git a/xdocs/images/screenshots/jms/JMS_Point-to-Point.png b/xdocs/images/screenshots/jms/JMS_Point-to-Point.png new file mode 100644 index 00000000000..8674c6fd2df Binary files /dev/null and b/xdocs/images/screenshots/jms/JMS_Point-to-Point.png differ diff --git a/xdocs/images/screenshots/jms/jms_config.png b/xdocs/images/screenshots/jms/jms_config.png new file mode 100644 index 00000000000..c46979c70f8 Binary files /dev/null and b/xdocs/images/screenshots/jms/jms_config.png differ diff --git a/xdocs/images/screenshots/jms/jms_messaging.png b/xdocs/images/screenshots/jms/jms_messaging.png new file mode 100644 index 00000000000..b8d08b148d1 Binary files /dev/null and b/xdocs/images/screenshots/jms/jms_messaging.png differ diff --git a/xdocs/images/screenshots/jms/jms_pub.png b/xdocs/images/screenshots/jms/jms_pub.png new file mode 100644 index 00000000000..8ae48daddc2 Binary files /dev/null and b/xdocs/images/screenshots/jms/jms_pub.png differ diff --git a/xdocs/images/screenshots/jms/jms_sub.png b/xdocs/images/screenshots/jms/jms_sub.png new file mode 100644 index 00000000000..533fd2292fe Binary files /dev/null and b/xdocs/images/screenshots/jms/jms_sub.png differ diff --git a/xdocs/images/screenshots/jmspublisher.png b/xdocs/images/screenshots/jmspublisher.png new file mode 100644 index 00000000000..0476282b4bb Binary files /dev/null and b/xdocs/images/screenshots/jmspublisher.png differ diff --git a/xdocs/images/screenshots/jmssubscriber.png b/xdocs/images/screenshots/jmssubscriber.png new file mode 100644 index 00000000000..916dfded867 Binary files /dev/null and b/xdocs/images/screenshots/jmssubscriber.png differ diff --git a/xdocs/images/screenshots/jsr223-sampler.png b/xdocs/images/screenshots/jsr223-sampler.png new file mode 100644 index 00000000000..41552c4c57d Binary files /dev/null and b/xdocs/images/screenshots/jsr223-sampler.png differ diff --git a/xdocs/images/screenshots/junit_sampler.png b/xdocs/images/screenshots/junit_sampler.png new file mode 100644 index 00000000000..dc441cec7eb Binary files /dev/null and b/xdocs/images/screenshots/junit_sampler.png differ diff --git a/xdocs/images/screenshots/keystore_config.png b/xdocs/images/screenshots/keystore_config.png new file mode 100644 index 00000000000..05fec53c885 Binary files /dev/null and b/xdocs/images/screenshots/keystore_config.png differ diff --git a/xdocs/images/screenshots/ldap_defaults.png b/xdocs/images/screenshots/ldap_defaults.png new file mode 100644 index 00000000000..56dd18740b5 Binary files /dev/null and b/xdocs/images/screenshots/ldap_defaults.png differ diff --git a/xdocs/images/screenshots/ldap_request.png b/xdocs/images/screenshots/ldap_request.png new file mode 100644 index 00000000000..9d791a5d81b Binary files /dev/null and b/xdocs/images/screenshots/ldap_request.png differ diff --git a/xdocs/images/screenshots/ldapext_defaults.png b/xdocs/images/screenshots/ldapext_defaults.png new file mode 100644 index 00000000000..b9877fcf23b Binary files /dev/null and b/xdocs/images/screenshots/ldapext_defaults.png differ diff --git a/xdocs/images/screenshots/ldapext_request.png b/xdocs/images/screenshots/ldapext_request.png new file mode 100644 index 00000000000..29b8e248939 Binary files /dev/null and b/xdocs/images/screenshots/ldapext_request.png differ diff --git a/xdocs/images/screenshots/ldaptest/add.png b/xdocs/images/screenshots/ldaptest/add.png new file mode 100644 index 00000000000..7da9d1e0e43 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/add.png differ diff --git a/xdocs/images/screenshots/ldaptest/delete.png b/xdocs/images/screenshots/ldaptest/delete.png new file mode 100644 index 00000000000..c7e12b7eefa Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/delete.png differ diff --git a/xdocs/images/screenshots/ldaptest/extadd.png b/xdocs/images/screenshots/ldaptest/extadd.png new file mode 100644 index 00000000000..40c1cd12c63 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extadd.png differ diff --git a/xdocs/images/screenshots/ldaptest/extcompare.png b/xdocs/images/screenshots/ldaptest/extcompare.png new file mode 100644 index 00000000000..19912435906 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extcompare.png differ diff --git a/xdocs/images/screenshots/ldaptest/extdel.png b/xdocs/images/screenshots/ldaptest/extdel.png new file mode 100644 index 00000000000..685882ad8b5 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extdel.png differ diff --git a/xdocs/images/screenshots/ldaptest/extmod.png b/xdocs/images/screenshots/ldaptest/extmod.png new file mode 100644 index 00000000000..1797467a636 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extmod.png differ diff --git a/xdocs/images/screenshots/ldaptest/extmoddn.png b/xdocs/images/screenshots/ldaptest/extmoddn.png new file mode 100644 index 00000000000..b7f6529d71e Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extmoddn.png differ diff --git a/xdocs/images/screenshots/ldaptest/extrequestdefaults.png b/xdocs/images/screenshots/ldaptest/extrequestdefaults.png new file mode 100644 index 00000000000..101a9b01425 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extrequestdefaults.png differ diff --git a/xdocs/images/screenshots/ldaptest/extsbind.png b/xdocs/images/screenshots/ldaptest/extsbind.png new file mode 100644 index 00000000000..5879637995e Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extsbind.png differ diff --git a/xdocs/images/screenshots/ldaptest/extsearch.png b/xdocs/images/screenshots/ldaptest/extsearch.png new file mode 100644 index 00000000000..13b4d51d8ab Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extsearch.png differ diff --git a/xdocs/images/screenshots/ldaptest/extthreadbind.png b/xdocs/images/screenshots/ldaptest/extthreadbind.png new file mode 100644 index 00000000000..be5abae50b3 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extthreadbind.png differ diff --git a/xdocs/images/screenshots/ldaptest/extthreadgroup.png b/xdocs/images/screenshots/ldaptest/extthreadgroup.png new file mode 100644 index 00000000000..1e496d5ee5e Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extthreadgroup.png differ diff --git a/xdocs/images/screenshots/ldaptest/extthreadunbind.png b/xdocs/images/screenshots/ldaptest/extthreadunbind.png new file mode 100644 index 00000000000..2e7b172d7f0 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extthreadunbind.png differ diff --git a/xdocs/images/screenshots/ldaptest/extviewtree.png b/xdocs/images/screenshots/ldaptest/extviewtree.png new file mode 100644 index 00000000000..3427f83b1bd Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/extviewtree.png differ diff --git a/xdocs/images/screenshots/ldaptest/login-config-element.png b/xdocs/images/screenshots/ldaptest/login-config-element.png new file mode 100644 index 00000000000..f39053906c4 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/login-config-element.png differ diff --git a/xdocs/images/screenshots/ldaptest/modify.png b/xdocs/images/screenshots/ldaptest/modify.png new file mode 100644 index 00000000000..a21a367c5f9 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/modify.png differ diff --git a/xdocs/images/screenshots/ldaptest/requestdefaults.png b/xdocs/images/screenshots/ldaptest/requestdefaults.png new file mode 100644 index 00000000000..2f4826fb83a Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/requestdefaults.png differ diff --git a/xdocs/images/screenshots/ldaptest/responseassertion.png b/xdocs/images/screenshots/ldaptest/responseassertion.png new file mode 100644 index 00000000000..a3c9117eb14 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/responseassertion.png differ diff --git a/xdocs/images/screenshots/ldaptest/search.png b/xdocs/images/screenshots/ldaptest/search.png new file mode 100644 index 00000000000..eeb77a8b778 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/search.png differ diff --git a/xdocs/images/screenshots/ldaptest/threadgroup.png b/xdocs/images/screenshots/ldaptest/threadgroup.png new file mode 100644 index 00000000000..0b0f1266228 Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/threadgroup.png differ diff --git a/xdocs/images/screenshots/ldaptest/viewtable.png b/xdocs/images/screenshots/ldaptest/viewtable.png new file mode 100644 index 00000000000..78028e7899f Binary files /dev/null and b/xdocs/images/screenshots/ldaptest/viewtable.png differ diff --git a/xdocs/images/screenshots/log_errors_counter.png b/xdocs/images/screenshots/log_errors_counter.png new file mode 100644 index 00000000000..83de4757dd5 Binary files /dev/null and b/xdocs/images/screenshots/log_errors_counter.png differ diff --git a/xdocs/images/screenshots/logic-controller/critical-section-controller-tp.png b/xdocs/images/screenshots/logic-controller/critical-section-controller-tp.png new file mode 100644 index 00000000000..6ba1ccde18b Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/critical-section-controller-tp.png differ diff --git a/xdocs/images/screenshots/logic-controller/critical-section-controller.png b/xdocs/images/screenshots/logic-controller/critical-section-controller.png new file mode 100644 index 00000000000..e2c0de5f15b Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/critical-section-controller.png differ diff --git a/xdocs/images/screenshots/logic-controller/foreach-controller.png b/xdocs/images/screenshots/logic-controller/foreach-controller.png new file mode 100644 index 00000000000..ac21768c485 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/foreach-controller.png differ diff --git a/xdocs/images/screenshots/logic-controller/foreach-example.png b/xdocs/images/screenshots/logic-controller/foreach-example.png new file mode 100644 index 00000000000..0a80fbd8367 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/foreach-example.png differ diff --git a/xdocs/images/screenshots/logic-controller/foreach-example2.png b/xdocs/images/screenshots/logic-controller/foreach-example2.png new file mode 100644 index 00000000000..b3d2fb7af24 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/foreach-example2.png differ diff --git a/xdocs/images/screenshots/logic-controller/interleave-controller.png b/xdocs/images/screenshots/logic-controller/interleave-controller.png new file mode 100644 index 00000000000..19d3d9d265b Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/interleave-controller.png differ diff --git a/xdocs/images/screenshots/logic-controller/interleave.png b/xdocs/images/screenshots/logic-controller/interleave.png new file mode 100644 index 00000000000..6f8c9e873c4 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/interleave.png differ diff --git a/xdocs/images/screenshots/logic-controller/interleave2.png b/xdocs/images/screenshots/logic-controller/interleave2.png new file mode 100644 index 00000000000..b57e908d6a9 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/interleave2.png differ diff --git a/xdocs/images/screenshots/logic-controller/interleave3.png b/xdocs/images/screenshots/logic-controller/interleave3.png new file mode 100644 index 00000000000..05010350626 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/interleave3.png differ diff --git a/xdocs/images/screenshots/logic-controller/loop-controller.png b/xdocs/images/screenshots/logic-controller/loop-controller.png new file mode 100644 index 00000000000..bc745b41a57 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/loop-controller.png differ diff --git a/xdocs/images/screenshots/logic-controller/loop-example.png b/xdocs/images/screenshots/logic-controller/loop-example.png new file mode 100644 index 00000000000..7a9efa8d92f Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/loop-example.png differ diff --git a/xdocs/images/screenshots/logic-controller/once-only-controller.png b/xdocs/images/screenshots/logic-controller/once-only-controller.png new file mode 100644 index 00000000000..827779e9e0b Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/once-only-controller.png differ diff --git a/xdocs/images/screenshots/logic-controller/once-only-example.png b/xdocs/images/screenshots/logic-controller/once-only-example.png new file mode 100644 index 00000000000..d233c0dbcfd Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/once-only-example.png differ diff --git a/xdocs/images/screenshots/logic-controller/random-controller.png b/xdocs/images/screenshots/logic-controller/random-controller.png new file mode 100644 index 00000000000..b155c07dc17 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/random-controller.png differ diff --git a/xdocs/images/screenshots/logic-controller/recording-controller.png b/xdocs/images/screenshots/logic-controller/recording-controller.png new file mode 100644 index 00000000000..4c856a3a094 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/recording-controller.png differ diff --git a/xdocs/images/screenshots/logic-controller/simple-controller.png b/xdocs/images/screenshots/logic-controller/simple-controller.png new file mode 100644 index 00000000000..ed64774ca76 Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/simple-controller.png differ diff --git a/xdocs/images/screenshots/logic-controller/simple-example.png b/xdocs/images/screenshots/logic-controller/simple-example.png new file mode 100644 index 00000000000..23abbc1612b Binary files /dev/null and b/xdocs/images/screenshots/logic-controller/simple-example.png differ diff --git a/xdocs/images/screenshots/login-config.png b/xdocs/images/screenshots/login-config.png new file mode 100644 index 00000000000..3a1d42b5315 Binary files /dev/null and b/xdocs/images/screenshots/login-config.png differ diff --git a/xdocs/images/screenshots/mailervisualizer.png b/xdocs/images/screenshots/mailervisualizer.png new file mode 100644 index 00000000000..9d2b293cb4f Binary files /dev/null and b/xdocs/images/screenshots/mailervisualizer.png differ diff --git a/xdocs/images/screenshots/mailreader_sampler.png b/xdocs/images/screenshots/mailreader_sampler.png new file mode 100644 index 00000000000..befee6f4199 Binary files /dev/null and b/xdocs/images/screenshots/mailreader_sampler.png differ diff --git a/xdocs/images/screenshots/mirrorserver.png b/xdocs/images/screenshots/mirrorserver.png new file mode 100644 index 00000000000..628c04148b8 Binary files /dev/null and b/xdocs/images/screenshots/mirrorserver.png differ diff --git a/xdocs/images/screenshots/modification.png b/xdocs/images/screenshots/modification.png new file mode 100644 index 00000000000..69c863491ca Binary files /dev/null and b/xdocs/images/screenshots/modification.png differ diff --git a/xdocs/images/screenshots/module_controller.png b/xdocs/images/screenshots/module_controller.png new file mode 100644 index 00000000000..25f7682fd03 Binary files /dev/null and b/xdocs/images/screenshots/module_controller.png differ diff --git a/xdocs/images/screenshots/mongodb-script.png b/xdocs/images/screenshots/mongodb-script.png new file mode 100644 index 00000000000..6185025a1b3 Binary files /dev/null and b/xdocs/images/screenshots/mongodb-script.png differ diff --git a/xdocs/images/screenshots/mongodb-source-config.png b/xdocs/images/screenshots/mongodb-source-config.png new file mode 100644 index 00000000000..45b1c495ff2 Binary files /dev/null and b/xdocs/images/screenshots/mongodb-source-config.png differ diff --git a/xdocs/images/screenshots/monitor_health.png b/xdocs/images/screenshots/monitor_health.png new file mode 100644 index 00000000000..bf529949f29 Binary files /dev/null and b/xdocs/images/screenshots/monitor_health.png differ diff --git a/xdocs/images/screenshots/monitor_screencap.png b/xdocs/images/screenshots/monitor_screencap.png new file mode 100644 index 00000000000..9a756913da9 Binary files /dev/null and b/xdocs/images/screenshots/monitor_screencap.png differ diff --git a/xdocs/images/screenshots/os_process_sampler.png b/xdocs/images/screenshots/os_process_sampler.png new file mode 100644 index 00000000000..ce7da764533 Binary files /dev/null and b/xdocs/images/screenshots/os_process_sampler.png differ diff --git a/xdocs/images/screenshots/parameter_mask.png b/xdocs/images/screenshots/parameter_mask.png new file mode 100644 index 00000000000..43cff90ec56 Binary files /dev/null and b/xdocs/images/screenshots/parameter_mask.png differ diff --git a/xdocs/images/screenshots/property_display.png b/xdocs/images/screenshots/property_display.png new file mode 100644 index 00000000000..f2bd258f583 Binary files /dev/null and b/xdocs/images/screenshots/property_display.png differ diff --git a/xdocs/images/screenshots/proxy_control.png b/xdocs/images/screenshots/proxy_control.png new file mode 100644 index 00000000000..abe66eac448 Binary files /dev/null and b/xdocs/images/screenshots/proxy_control.png differ diff --git a/xdocs/images/screenshots/random_variable.png b/xdocs/images/screenshots/random_variable.png new file mode 100644 index 00000000000..ce2ce14dd92 Binary files /dev/null and b/xdocs/images/screenshots/random_variable.png differ diff --git a/xdocs/images/screenshots/randomordercontroller.png b/xdocs/images/screenshots/randomordercontroller.png new file mode 100644 index 00000000000..ed977f79af9 Binary files /dev/null and b/xdocs/images/screenshots/randomordercontroller.png differ diff --git a/xdocs/images/screenshots/regex_extractor.png b/xdocs/images/screenshots/regex_extractor.png new file mode 100644 index 00000000000..8ab6d33835a Binary files /dev/null and b/xdocs/images/screenshots/regex_extractor.png differ diff --git a/xdocs/images/screenshots/regex_user_params.png b/xdocs/images/screenshots/regex_user_params.png new file mode 100644 index 00000000000..5149736923a Binary files /dev/null and b/xdocs/images/screenshots/regex_user_params.png differ diff --git a/xdocs/images/screenshots/remote/run-menu00.gif b/xdocs/images/screenshots/remote/run-menu00.gif new file mode 100644 index 00000000000..3a5effe2d21 Binary files /dev/null and b/xdocs/images/screenshots/remote/run-menu00.gif differ diff --git a/xdocs/images/screenshots/response_time_graph.png b/xdocs/images/screenshots/response_time_graph.png new file mode 100644 index 00000000000..667267247dd Binary files /dev/null and b/xdocs/images/screenshots/response_time_graph.png differ diff --git a/xdocs/images/screenshots/response_time_graph_settings.png b/xdocs/images/screenshots/response_time_graph_settings.png new file mode 100644 index 00000000000..cf06ec3c1b1 Binary files /dev/null and b/xdocs/images/screenshots/response_time_graph_settings.png differ diff --git a/xdocs/images/screenshots/resultstatusactionhandler.png b/xdocs/images/screenshots/resultstatusactionhandler.png new file mode 100644 index 00000000000..8a793297435 Binary files /dev/null and b/xdocs/images/screenshots/resultstatusactionhandler.png differ diff --git a/xdocs/images/screenshots/runtimecontroller.png b/xdocs/images/screenshots/runtimecontroller.png new file mode 100644 index 00000000000..2ce8839afe6 Binary files /dev/null and b/xdocs/images/screenshots/runtimecontroller.png differ diff --git a/xdocs/images/screenshots/sample_result_config.png b/xdocs/images/screenshots/sample_result_config.png new file mode 100644 index 00000000000..6ee98d8aa8b Binary files /dev/null and b/xdocs/images/screenshots/sample_result_config.png differ diff --git a/xdocs/images/screenshots/save_image.png b/xdocs/images/screenshots/save_image.png new file mode 100644 index 00000000000..5e53322d8e6 Binary files /dev/null and b/xdocs/images/screenshots/save_image.png differ diff --git a/xdocs/images/screenshots/savetofile.png b/xdocs/images/screenshots/savetofile.png new file mode 100644 index 00000000000..9c260d39f78 Binary files /dev/null and b/xdocs/images/screenshots/savetofile.png differ diff --git a/xdocs/images/screenshots/scoping1.png b/xdocs/images/screenshots/scoping1.png new file mode 100644 index 00000000000..0a58fdc5a17 Binary files /dev/null and b/xdocs/images/screenshots/scoping1.png differ diff --git a/xdocs/images/screenshots/scoping2.png b/xdocs/images/screenshots/scoping2.png new file mode 100644 index 00000000000..4c5a78a3fd0 Binary files /dev/null and b/xdocs/images/screenshots/scoping2.png differ diff --git a/xdocs/images/screenshots/scoping3.png b/xdocs/images/screenshots/scoping3.png new file mode 100644 index 00000000000..4ada01e061a Binary files /dev/null and b/xdocs/images/screenshots/scoping3.png differ diff --git a/xdocs/images/screenshots/searching/raw-search-result.png b/xdocs/images/screenshots/searching/raw-search-result.png new file mode 100644 index 00000000000..786ec5f9996 Binary files /dev/null and b/xdocs/images/screenshots/searching/raw-search-result.png differ diff --git a/xdocs/images/screenshots/searching/raw-search.png b/xdocs/images/screenshots/searching/raw-search.png new file mode 100644 index 00000000000..1fc250837e7 Binary files /dev/null and b/xdocs/images/screenshots/searching/raw-search.png differ diff --git a/xdocs/images/screenshots/searching/regexp-search-result.png b/xdocs/images/screenshots/searching/regexp-search-result.png new file mode 100644 index 00000000000..193597f2883 Binary files /dev/null and b/xdocs/images/screenshots/searching/regexp-search-result.png differ diff --git a/xdocs/images/screenshots/searching/regexp-search.png b/xdocs/images/screenshots/searching/regexp-search.png new file mode 100644 index 00000000000..d9e99de83d9 Binary files /dev/null and b/xdocs/images/screenshots/searching/regexp-search.png differ diff --git a/xdocs/images/screenshots/setup_thread_group.png b/xdocs/images/screenshots/setup_thread_group.png new file mode 100644 index 00000000000..97d3be6e55e Binary files /dev/null and b/xdocs/images/screenshots/setup_thread_group.png differ diff --git a/xdocs/images/screenshots/simple_config_element.png b/xdocs/images/screenshots/simple_config_element.png new file mode 100644 index 00000000000..7d88917c714 Binary files /dev/null and b/xdocs/images/screenshots/simple_config_element.png differ diff --git a/xdocs/images/screenshots/simpledatawriter.png b/xdocs/images/screenshots/simpledatawriter.png new file mode 100644 index 00000000000..060e67eca2e Binary files /dev/null and b/xdocs/images/screenshots/simpledatawriter.png differ diff --git a/xdocs/images/screenshots/size_assertion.png b/xdocs/images/screenshots/size_assertion.png new file mode 100644 index 00000000000..860d4165389 Binary files /dev/null and b/xdocs/images/screenshots/size_assertion.png differ diff --git a/xdocs/images/screenshots/smtp_sampler.png b/xdocs/images/screenshots/smtp_sampler.png new file mode 100644 index 00000000000..9f1821785b4 Binary files /dev/null and b/xdocs/images/screenshots/smtp_sampler.png differ diff --git a/xdocs/images/screenshots/soap_sampler.png b/xdocs/images/screenshots/soap_sampler.png new file mode 100644 index 00000000000..01285630c18 Binary files /dev/null and b/xdocs/images/screenshots/soap_sampler.png differ diff --git a/xdocs/images/screenshots/spline_visualizer.png b/xdocs/images/screenshots/spline_visualizer.png new file mode 100644 index 00000000000..5e19a96f677 Binary files /dev/null and b/xdocs/images/screenshots/spline_visualizer.png differ diff --git a/xdocs/images/screenshots/summary.png b/xdocs/images/screenshots/summary.png new file mode 100644 index 00000000000..03ca2f00e74 Binary files /dev/null and b/xdocs/images/screenshots/summary.png differ diff --git a/xdocs/images/screenshots/summary_report.png b/xdocs/images/screenshots/summary_report.png new file mode 100644 index 00000000000..32925461089 Binary files /dev/null and b/xdocs/images/screenshots/summary_report.png differ diff --git a/xdocs/images/screenshots/summary_report_grouped.png b/xdocs/images/screenshots/summary_report_grouped.png new file mode 100644 index 00000000000..850e2e7372d Binary files /dev/null and b/xdocs/images/screenshots/summary_report_grouped.png differ diff --git a/xdocs/images/screenshots/switchcontroller.png b/xdocs/images/screenshots/switchcontroller.png new file mode 100644 index 00000000000..1762bd2514d Binary files /dev/null and b/xdocs/images/screenshots/switchcontroller.png differ diff --git a/xdocs/images/screenshots/table_results.png b/xdocs/images/screenshots/table_results.png new file mode 100644 index 00000000000..5a95814d9e5 Binary files /dev/null and b/xdocs/images/screenshots/table_results.png differ diff --git a/xdocs/images/screenshots/tcpsampler.png b/xdocs/images/screenshots/tcpsampler.png new file mode 100644 index 00000000000..619baa8d1f0 Binary files /dev/null and b/xdocs/images/screenshots/tcpsampler.png differ diff --git a/xdocs/images/screenshots/tcpsamplerconfig.png b/xdocs/images/screenshots/tcpsamplerconfig.png new file mode 100644 index 00000000000..874dce32828 Binary files /dev/null and b/xdocs/images/screenshots/tcpsamplerconfig.png differ diff --git a/xdocs/images/screenshots/tear_down_on_shutdown.png b/xdocs/images/screenshots/tear_down_on_shutdown.png new file mode 100644 index 00000000000..6708201a1ae Binary files /dev/null and b/xdocs/images/screenshots/tear_down_on_shutdown.png differ diff --git a/xdocs/images/screenshots/teardown_thread_group.png b/xdocs/images/screenshots/teardown_thread_group.png new file mode 100644 index 00000000000..8a5f66f787d Binary files /dev/null and b/xdocs/images/screenshots/teardown_thread_group.png differ diff --git a/xdocs/images/screenshots/template_menu.png b/xdocs/images/screenshots/template_menu.png new file mode 100644 index 00000000000..85952872bfc Binary files /dev/null and b/xdocs/images/screenshots/template_menu.png differ diff --git a/xdocs/images/screenshots/template_wizard.png b/xdocs/images/screenshots/template_wizard.png new file mode 100644 index 00000000000..50a39c7b794 Binary files /dev/null and b/xdocs/images/screenshots/template_wizard.png differ diff --git a/xdocs/images/screenshots/test_action.png b/xdocs/images/screenshots/test_action.png new file mode 100644 index 00000000000..ef250c636d8 Binary files /dev/null and b/xdocs/images/screenshots/test_action.png differ diff --git a/xdocs/images/screenshots/test_fragment.png b/xdocs/images/screenshots/test_fragment.png new file mode 100644 index 00000000000..898c925d923 Binary files /dev/null and b/xdocs/images/screenshots/test_fragment.png differ diff --git a/xdocs/images/screenshots/testplan.png b/xdocs/images/screenshots/testplan.png new file mode 100644 index 00000000000..48f2dceaf43 Binary files /dev/null and b/xdocs/images/screenshots/testplan.png differ diff --git a/xdocs/images/screenshots/threadgroup.png b/xdocs/images/screenshots/threadgroup.png new file mode 100644 index 00000000000..a577ad31b02 Binary files /dev/null and b/xdocs/images/screenshots/threadgroup.png differ diff --git a/xdocs/images/screenshots/throughput_controller.png b/xdocs/images/screenshots/throughput_controller.png new file mode 100644 index 00000000000..5a4a2a1a4a6 Binary files /dev/null and b/xdocs/images/screenshots/throughput_controller.png differ diff --git a/xdocs/images/screenshots/timers/beanshell_timer.png b/xdocs/images/screenshots/timers/beanshell_timer.png new file mode 100644 index 00000000000..89bef46cba5 Binary files /dev/null and b/xdocs/images/screenshots/timers/beanshell_timer.png differ diff --git a/xdocs/images/screenshots/timers/bsf_timer.png b/xdocs/images/screenshots/timers/bsf_timer.png new file mode 100644 index 00000000000..2795881ab95 Binary files /dev/null and b/xdocs/images/screenshots/timers/bsf_timer.png differ diff --git a/xdocs/images/screenshots/timers/constant_throughput_timer.png b/xdocs/images/screenshots/timers/constant_throughput_timer.png new file mode 100644 index 00000000000..3fa5cdd69f3 Binary files /dev/null and b/xdocs/images/screenshots/timers/constant_throughput_timer.png differ diff --git a/xdocs/images/screenshots/timers/constant_timer.png b/xdocs/images/screenshots/timers/constant_timer.png new file mode 100644 index 00000000000..709a33b1f08 Binary files /dev/null and b/xdocs/images/screenshots/timers/constant_timer.png differ diff --git a/xdocs/images/screenshots/timers/gauss_random_timer.png b/xdocs/images/screenshots/timers/gauss_random_timer.png new file mode 100644 index 00000000000..f12b2cb4081 Binary files /dev/null and b/xdocs/images/screenshots/timers/gauss_random_timer.png differ diff --git a/xdocs/images/screenshots/timers/poisson_random_timer.png b/xdocs/images/screenshots/timers/poisson_random_timer.png new file mode 100644 index 00000000000..ba08e90d0d8 Binary files /dev/null and b/xdocs/images/screenshots/timers/poisson_random_timer.png differ diff --git a/xdocs/images/screenshots/timers/sync_timer.png b/xdocs/images/screenshots/timers/sync_timer.png new file mode 100644 index 00000000000..0918539a35a Binary files /dev/null and b/xdocs/images/screenshots/timers/sync_timer.png differ diff --git a/xdocs/images/screenshots/timers/uniform_random_timer.png b/xdocs/images/screenshots/timers/uniform_random_timer.png new file mode 100644 index 00000000000..bb41346b875 Binary files /dev/null and b/xdocs/images/screenshots/timers/uniform_random_timer.png differ diff --git a/xdocs/images/screenshots/transactioncontroller.png b/xdocs/images/screenshots/transactioncontroller.png new file mode 100644 index 00000000000..0448e100aee Binary files /dev/null and b/xdocs/images/screenshots/transactioncontroller.png differ diff --git a/xdocs/images/screenshots/url_rewrite_example_a.png b/xdocs/images/screenshots/url_rewrite_example_a.png new file mode 100644 index 00000000000..aed0fa51d72 Binary files /dev/null and b/xdocs/images/screenshots/url_rewrite_example_a.png differ diff --git a/xdocs/images/screenshots/url_rewrite_example_b.gif b/xdocs/images/screenshots/url_rewrite_example_b.gif new file mode 100644 index 00000000000..b4115e5cfe6 Binary files /dev/null and b/xdocs/images/screenshots/url_rewrite_example_b.gif differ diff --git a/xdocs/images/screenshots/url_rewrite_example_b.png b/xdocs/images/screenshots/url_rewrite_example_b.png new file mode 100644 index 00000000000..f5e12584560 Binary files /dev/null and b/xdocs/images/screenshots/url_rewrite_example_b.png differ diff --git a/xdocs/images/screenshots/url_rewriter.png b/xdocs/images/screenshots/url_rewriter.png new file mode 100644 index 00000000000..ecf6dde2f5b Binary files /dev/null and b/xdocs/images/screenshots/url_rewriter.png differ diff --git a/xdocs/images/screenshots/user_defined_variables.png b/xdocs/images/screenshots/user_defined_variables.png new file mode 100644 index 00000000000..cb5c9f5c32f Binary files /dev/null and b/xdocs/images/screenshots/user_defined_variables.png differ diff --git a/xdocs/images/screenshots/user_param_modifier.gif b/xdocs/images/screenshots/user_param_modifier.gif new file mode 100644 index 00000000000..782ccf79165 Binary files /dev/null and b/xdocs/images/screenshots/user_param_modifier.gif differ diff --git a/xdocs/images/screenshots/user_params.png b/xdocs/images/screenshots/user_params.png new file mode 100644 index 00000000000..1be8b825200 Binary files /dev/null and b/xdocs/images/screenshots/user_params.png differ diff --git a/xdocs/images/screenshots/view_results_tree.png b/xdocs/images/screenshots/view_results_tree.png new file mode 100644 index 00000000000..18c7f7db87a Binary files /dev/null and b/xdocs/images/screenshots/view_results_tree.png differ diff --git a/xdocs/images/screenshots/view_results_tree_document.png b/xdocs/images/screenshots/view_results_tree_document.png new file mode 100644 index 00000000000..d34009fdb48 Binary files /dev/null and b/xdocs/images/screenshots/view_results_tree_document.png differ diff --git a/xdocs/images/screenshots/view_results_tree_regex.png b/xdocs/images/screenshots/view_results_tree_regex.png new file mode 100644 index 00000000000..ede666ef3ea Binary files /dev/null and b/xdocs/images/screenshots/view_results_tree_regex.png differ diff --git a/xdocs/images/screenshots/view_results_tree_xml.png b/xdocs/images/screenshots/view_results_tree_xml.png new file mode 100644 index 00000000000..4e4d682db12 Binary files /dev/null and b/xdocs/images/screenshots/view_results_tree_xml.png differ diff --git a/xdocs/images/screenshots/webservice_sampler.png b/xdocs/images/screenshots/webservice_sampler.png new file mode 100644 index 00000000000..8f85196e020 Binary files /dev/null and b/xdocs/images/screenshots/webservice_sampler.png differ diff --git a/xdocs/images/screenshots/webtest/http-defaults1.png b/xdocs/images/screenshots/webtest/http-defaults1.png new file mode 100644 index 00000000000..9372f0e3168 Binary files /dev/null and b/xdocs/images/screenshots/webtest/http-defaults1.png differ diff --git a/xdocs/images/screenshots/webtest/http-defaults2.png b/xdocs/images/screenshots/webtest/http-defaults2.png new file mode 100644 index 00000000000..9880fa8af05 Binary files /dev/null and b/xdocs/images/screenshots/webtest/http-defaults2.png differ diff --git a/xdocs/images/screenshots/webtest/http-request1.png b/xdocs/images/screenshots/webtest/http-request1.png new file mode 100644 index 00000000000..7d9c3fae0bd Binary files /dev/null and b/xdocs/images/screenshots/webtest/http-request1.png differ diff --git a/xdocs/images/screenshots/webtest/http-request2.png b/xdocs/images/screenshots/webtest/http-request2.png new file mode 100644 index 00000000000..e17f33beb17 Binary files /dev/null and b/xdocs/images/screenshots/webtest/http-request2.png differ diff --git a/xdocs/images/screenshots/webtest/http_login.png b/xdocs/images/screenshots/webtest/http_login.png new file mode 100644 index 00000000000..fb2e7e88bc0 Binary files /dev/null and b/xdocs/images/screenshots/webtest/http_login.png differ diff --git a/xdocs/images/screenshots/webtest/threadgroup.png b/xdocs/images/screenshots/webtest/threadgroup.png new file mode 100644 index 00000000000..6c8d821091d Binary files /dev/null and b/xdocs/images/screenshots/webtest/threadgroup.png differ diff --git a/xdocs/images/screenshots/webtest/threadgroup2.png b/xdocs/images/screenshots/webtest/threadgroup2.png new file mode 100644 index 00000000000..57c72268a84 Binary files /dev/null and b/xdocs/images/screenshots/webtest/threadgroup2.png differ diff --git a/xdocs/images/screenshots/whilecontroller.png b/xdocs/images/screenshots/whilecontroller.png new file mode 100644 index 00000000000..76b5aa49276 Binary files /dev/null and b/xdocs/images/screenshots/whilecontroller.png differ diff --git a/xdocs/images/screenshots/workbench.png b/xdocs/images/screenshots/workbench.png new file mode 100644 index 00000000000..67e4d87f6cf Binary files /dev/null and b/xdocs/images/screenshots/workbench.png differ diff --git a/xdocs/images/screenshots/ws_header.png b/xdocs/images/screenshots/ws_header.png new file mode 100644 index 00000000000..c2c131ee1e6 Binary files /dev/null and b/xdocs/images/screenshots/ws_header.png differ diff --git a/xdocs/images/screenshots/ws_http_request.png b/xdocs/images/screenshots/ws_http_request.png new file mode 100644 index 00000000000..2178bc3fa59 Binary files /dev/null and b/xdocs/images/screenshots/ws_http_request.png differ diff --git a/xdocs/images/screenshots/ws_listener.png b/xdocs/images/screenshots/ws_listener.png new file mode 100644 index 00000000000..0953af64d97 Binary files /dev/null and b/xdocs/images/screenshots/ws_listener.png differ diff --git a/xdocs/images/screenshots/ws_template.png b/xdocs/images/screenshots/ws_template.png new file mode 100644 index 00000000000..f547f373649 Binary files /dev/null and b/xdocs/images/screenshots/ws_template.png differ diff --git a/xdocs/images/screenshots/xml_assertion.png b/xdocs/images/screenshots/xml_assertion.png new file mode 100644 index 00000000000..f7656323358 Binary files /dev/null and b/xdocs/images/screenshots/xml_assertion.png differ diff --git a/xdocs/images/screenshots/xpath_assertion.png b/xdocs/images/screenshots/xpath_assertion.png new file mode 100644 index 00000000000..fe27e8c522f Binary files /dev/null and b/xdocs/images/screenshots/xpath_assertion.png differ diff --git a/xdocs/images/screenshots/xpath_extractor.png b/xdocs/images/screenshots/xpath_extractor.png new file mode 100644 index 00000000000..a9735e37288 Binary files /dev/null and b/xdocs/images/screenshots/xpath_extractor.png differ diff --git a/xdocs/images/twitter.png b/xdocs/images/twitter.png new file mode 100644 index 00000000000..8256d044a0a Binary files /dev/null and b/xdocs/images/twitter.png differ diff --git a/xdocs/index.xml b/xdocs/index.xml new file mode 100644 index 00000000000..cc05ba7c1d2 --- /dev/null +++ b/xdocs/index.xml @@ -0,0 +1,113 @@ + + + + + + Apache JMeter&trade; + + +
+

+ The Apache JMeter&trade; application is open source software, + a 100% pure Java application designed + to load test functional behavior and measure performance. It was + originally designed for testing Web Applications but has + since expanded to other test functions. +

+

What can I do with it?

+

+ Apache JMeter may be used to test performance both on static and dynamic + resources (Webservices (SOAP/REST), Web dynamic languages - PHP, Java, ASP.NET, Files, etc. -, Java Objects, Data Bases and + Queries, FTP Servers and more). It can be used to simulate a heavy +load on a server, group of servers, network or object to test its strength or to analyze +overall performance under different load types. You can use it to make a +graphical analysis of performance or to test your server/script/object +behavior under heavy concurrent load. +

+

What does it do?

+

Apache JMeter features include:

+
    +
  • Ability to load and performance test many different server/protocol types: +
      +
    • Web - HTTP, HTTPS
    • +
    • SOAP / REST
    • +
    • FTP
    • +
    • Database via JDBC
    • +
    • LDAP
    • +
    • Message-oriented middleware (MOM) via JMS
    • +
    • Mail - SMTP(S), POP3(S) and IMAP(S)
    • +
    • MongoDB (NoSQL)
    • +
    • Native commands or shell scripts
    • +
    • TCP
    • +
    +
  • +
  • Complete portability and 100% Java purity.
  • +
  • Full multithreading framework allows concurrent sampling by many threads and + simultaneous sampling of different functions by separate thread groups.
  • +
  • Careful GUI design allows faster Test Plan building and debugging.
  • +
  • Caching and offline analysis/replaying of test results.
  • +
  • Highly Extensible core: +
      +
    • Pluggable Samplers allow unlimited testing capabilities.
    • +
    • Several load statistics may be chosen with pluggable timers.
    • +
    • Data analysis and visualization plugins allow great extensibility + as well as personalization.
    • +
    • Functions can be used to provide dynamic input to a test or provide data manipulation.
    • +
    • Scriptable Samplers (BeanShell, BSF-compatible languages and JSR223-compatible languages)
    • +
    +
  • +
+

JMeter is not a browser

+

+JMeter is not a browser. +As far as web-services and remote services are concerned, JMeter looks like a browser (or rather, multiple browsers); +however JMeter does not perform all the actions supported by browsers. +In particular, JMeter does not execute the Javascript found in HTML pages. +Nor does it render the HTML pages as a browser does +(it's possible to view the response as HTML etc, but the timings are not included in any samples, and only one sample in one thread is ever viewed at a time). +

+ +

How do I do it?

+ +

Tutorials (PDF)

+ +

Further Information About JMeter

+ +
+ +
diff --git a/xdocs/issues.xml b/xdocs/issues.xml new file mode 100644 index 00000000000..66949589aa9 --- /dev/null +++ b/xdocs/issues.xml @@ -0,0 +1,115 @@ + + + + + Issues + + +
+

+JMeter uses Bugzilla for issue tracking, i.e. for reporting bugs and requesting enhancements. +

+

+Before creating a new issue, please check whether the issue has already been reported by searching Bugzilla. +It's also worth checking first on the JMeter user mailing list; others may already have a solution. +

+
+
+ +
+
+

+In most cases it is worth starting a discussion on the mailing list first. +Bugzilla is good for tracking progress and supplying patches, but is unwieldy for longer discussions. +

+

+If you have not already done so, you need to register an account first, using the "New Account" link at the top of the +main Bugzilla page: https://bz.apache.org/bugzilla/. +

+

+Make sure you read and understand the information on the account creation page before signing up. +

+

+Once logged in, click "File a bug" and select JMeter from the list +Please set the severity to "enhancement". +

+

+Please make sure that you describe the enhancement in sufficient detail. If necessary provide an example use-case. +

+

+If you are providing a code patch, also provide a test case, and documentation on how to use the new feature (ideally as a documentation patch). +

+
+
+

+First check that the issue has not already been reported. +If reporting a bug, are you sure it really is a bug in JMeter, not just a misunderstanding of how JMeter works? +

+

+If you have not already done so, you need to register an account first, using the "New Account" link at the top of the +main Bugzilla page: https://bz.apache.org/bugzilla/. +

+

+Make sure you read and understand the information on the account creation page before signing up. +

+

+Once logged in, click "File a bug" and select JMeter from the list. +

+
+
+

+Please make sure you provide sufficient information for others to be able to make use of the report effectively. +Use the checklist below to guide you. +

+
    +
  • JMeter version
  • +
  • Java version (output from java -version)
  • +
  • OS version
  • +
  • jmeter.log file (unlikely to contain sensitive information, but check before uploading)
  • +
  • JMX file if relevant (redact any sensitive information first), providing a simplified Test Plan (using ) will ensure BUG is fixed much more rapidely than without it
  • +
  • JTL file if relevant (may need to redact sensitive information)
  • +
  • For a suspected bug, describe what you did, what happened, and how this differs from what you expected to happen. +Does it happen every time? +
  • +
  • Add yourself in CC List to be notified when JMeter Team requires more information (in this case bug will be marked as NEEDINFO)
  • +
  • Select accurately the IMPORTANCE level, ENHANCEMENT means it's not a BUG while others mean it's a BUG
  • +
  • If you are providing a patch to fix a bug, please ensure it is in unified diff format. +If using Eclipse, please set the patch root to "Project", not the default "Workspace" which is harder to apply.
  • +
  • New source files can be provided as is; please ensure they have the standard Apache License header (as per other JMeter files). +Please do not use @author tags (credit will be given in the changes file). +
  • +
  • In the case of patches for new features, please also provide documentation patches if at all possible. +Components are documented in xdocs/usermanual/component_reference.xml.
  • +
+

See also the following Bug writing guidelines, +also the terms and conditions noted on the Bugzilla account creation page.

+
+ +
diff --git a/xdocs/jmeter_irc.xml b/xdocs/jmeter_irc.xml new file mode 100644 index 00000000000..26d5f0670cd --- /dev/null +++ b/xdocs/jmeter_irc.xml @@ -0,0 +1,31 @@ + + + + + JMeter on IRC + + +
+

JMeter developers often hang out on IRC to chat about development issues. +Users are also welcome to stop by and ask questions, offer feature suggestions, +or just chit-chat.

+

IRC Server: irc.us.freenode.net
+Room: #jmeter

+
+ +
diff --git a/xdocs/localising/index.xml b/xdocs/localising/index.xml new file mode 100644 index 00000000000..7f6cc09c324 --- /dev/null +++ b/xdocs/localising/index.xml @@ -0,0 +1,156 @@ + + + + + + + Jordi Salvat i Alabart + + JMeter Localisation (Translator's Guide) + + + + + +
+ +

This document describes the process of creating and maintaining translated texts for JMeter in languages +other than English. English has been tacitly chosen as the project's primary (or "default") language -- despite its +obvious inadequacy for reasonably unambiguous communication -- as a tribute to the Power of the Empire :-)
+The metropolitan language texts are thus maintained by the software developers, while other project contributors +(called "translators" in this document) take care of maintaining the texts in the languages of the +provinces. The process of producing and maintaining the later is called "translation" in this document.

+ +

This document assumes you'll be using i18nEdit as your tool to edit properties files, and instructions will +be specific to this software, but this is not mandatory: the process should mostly work also if you prefer to use +another tool, such as or vi or Emacs. +

+ +

This document describes 6 processes:

+
    +
  1. Obtaining the current texts [translators].
  2. +
  3. Providing the current texts to translators [developers].
  4. +
  5. Downloading and running i18nEdit [everyone].
  6. +
  7. Translating [translators].
  8. +
  9. Submitting your translations to the project [translators].
  10. +
  11. Merging in new translations [committers].
  12. +
+ +
+ +
+ +

If you want to help with JMeter's translation process, start by reading this document. Then +send a message to dev@jmeter.apache.org +stating your intention. The files you need (*.properties and *.metaprop) are included in the source archive. +But if you are having any difficulty, one of the project contributors will be able to grab the current texts +from SVN and send them to you. You'll receive a jar, zip, tar or tgz file that you'll need to unpack in your +local disk.

+

If you are familiar with SVN or you're brave, feel free to anonymously connect to the Apache SVN server +and obtain the JMeter source yourself, as described in +http://jmeter.apache.org/svnindex.html +-- the files necessary to the translation process are all under the jmeter/src directory. +

+

Once you've unpacked or checked out the files, make sure to find file src/i18nedit.properties in there: +you'll need to know where it is to start working with i18nEdit.

+ +
+ +
+ +

If you have access to JMeter's SVN repository and you want to pack the files necessary for localisation +for sending to a translator, just go to the directory above the project root and issue the following command: +

+ +tar czf jmeter-localisation.tgz `find jmeter/src -name "*.properties" -o -name "*.metaprops"` + +

+Of course you could also send the translator the whole jmeter directory, but this will make his life easier. +

+ +
+ +
+ +

The runtime for i18nEdit can be obtained from +http://www.cantamen.com/i18nedit.php. +Download the binary distribution (i18nedit-1.0.0.jar) and save it locally.

+

To run i18nEdit, just make sure to have a reasonably modern Java Runtime Environment in your PATH, change +to the directory where you saved i18nedit-1.0.0.jar, then issue the following command:

+ +java -jar i18nedit-1.0.0.jar + + +Then: +
    +
  1. If you've never run i18nEdit before, choose a language. The rest of this document assumes you chose UK English.
  2. +
  3. Select the "Projects" menu, then "Open project...".
  4. +
  5. Navigate to jmeter/src/, select i18nedit.properties, and press the "Open" button.
  6. +
  7. In the window that opens, select the "Project" menu, then "Project settings". Check that your target language +appears in the list in field "Additional locales (ISO codes)". Otherwise, add it now. Press "Save".
  8. +
+You're now ready to start translating. + +
+ +
+ +

Before you start translating, select the "Project" menu, then "Translation settings". Choose work mode +"Directed translation (source to target)". Enter "en" (without the quotes) in the "Source localization" field. Enter +the ISO code of your target language in the "Target localization field".

+ +

Click on one of the editable fields in the right panel ("Comment" or "Content" for your language). Press F2. +i18nEdit will bring you to the first property that requires your attention, either because a translation does not yet +exist for it or because the English text has changed since the translation was provided. Enter or fix the text if +necessary, then press F2 again to repeat the process.

+ +

i18nEdit's on-line help is excellent: read through it for more information and tips.

+ +
+ +
+ +

Once you're done translating, just pack up the whole set of files in jmeter/src in a jar, zip, tar, +tgz, or alike and attach them to a JMeter bug report +(follow link to "Known bugs" in JMeter's home page for that).

+ +
+ +
+ +If you're a committer receiving text files from a translator, follow this steps to merge them into +the project: +
    +
  1. Unpack the files submitted by the translator in a separate directory.
  2. +
  3. Start i18nEdit as described in Downloading and running i18nEdit above.
  4. +
  5. If the translator worked in a new language, make sure it is listed in the Additional locales field in the Project Settings.
  6. +
  7. Open the "Team" menu and select "Merge changes as integrator".
  8. +
  9. Enter the path to the src directory in the files submitted by the translator.
  10. +
  11. Select the translator's target language.
  12. +
  13. Press "Perform merge".
  14. +
  15. Close i18nEdit and commit to SVN as usual (remember to Refresh your project if you're using Eclipse).
  16. +
+ +
+ + + +
diff --git a/xdocs/mail.xml b/xdocs/mail.xml new file mode 100644 index 00000000000..7b24d26e269 --- /dev/null +++ b/xdocs/mail.xml @@ -0,0 +1,203 @@ + + + + + + Apache JMeter Project + Mailing Lists + + + + +
+ +

A mailing list is an electronic discussion forum that anyone can +subscribe to. When someone sends an email message to the mailing list, +a copy of that message is broadcast to everyone who is subscribed to +that mailing list. Mailing lists provide a simple and effective +communication mechanism. With potentially thousands of subscribers, +there is a common set of etiquette guidelines that you should observe. +Please keep on reading. +

+ +

+Please note that usage of these mailing lists is subject to the +Public Forum Archive Policy. +

+ +

+ + Respect the mailing list type +
+ There are generally two types of lists. +

+ +

+

    +
  • + The "User" lists where you can send questions and comments about + configuration, setup, usage and other "user" types of questions. +
  • +
  • + The "Developer" lists where you can send questions and + comments about the actual software source code and general + "development" types of questions. +
  • +
+

+ +

Some questions are appropriate for posting on both the "user" and +the "developer" lists. In this case, pick one and only one. Do not +cross post.

+ +

Asking a configuration question on the developers list is frowned +upon because developers' time is as precious as yours. By contacting +them directly instead of the user base you are abusing resources. In +fact, it is unlikely that you will get a quicker answer, if at +all.

+ +

+ + Join the lists that are appropriate for your discussion. +
+Please make sure that you are joining the list that is appropriate for the +topic or product that you would like to discuss. For example, +please do not join the Regexp mailing list and ask questions about Tomcat. +Instead, you should join the Tomcat User list and ask your questions +there. +

+ +

+ + Ask smart questions.
+ +Every volunteer project obtains its strength from the people involved +in it. You are welcome to join any of our mailing lists. You can +choose to lurk, or actively participate; it's up to you. The level of +community responsiveness to specific questions is generally directly +proportional to the amount of effort you spend formulating your +question. Eric Raymond and Rick Moen have even written an essay entitled "Asking +Smart Questions" precisely on this topic. Although somewhat +militant, it is definitely worth reading.
+Note: Please do NOT send your Java problems to the two authors. They welcome feedback on the FAQ's contents, but are simply not a Java help resource. Follow the essay's advice and choose your forum carefully. +

+ +

+ + Give feedback when you get a good answer.
+ +If an answer given to you helped you solve your problem then send a mail saying so and don't forget to say THANKS. +If you fixed the problem yourself then contribute to the mailing list by writing how you solved your issue. +Giving feedback is useful to people who faced/will face same problems as you and will be your way +to contribute to the project. Don't forget that people answering your questions are volunteers +doing so on their personal time.
+

+ +

+ + Keep your email short and to the point; use a suitable subject line. +
+If your email is more than about a page of text, chances are that it +won't get read by very many people. It is much better to try to pack a +lot of informative information (see above about asking smart questions) +into as small of an email as possible. If you are replying to a previous +email, it is a good idea to only quote the parts that you are replying +to and to remove the unnecessary bits. This makes it easier for people +to follow a thread as well as making the email archives easier to search +and read. +

+ +

+ + Start a new thread for a new topic +
+When asing a new question, please start a new thread with an appropriate new subject line. +This makes it easier to read, and to find later in the archives. +

+ +

+ + Do your best to ensure that you are not sending HTML or + "Stylelized" email to the list. +
+If you are using Outlook or Outlook Express or Eudora, chances are that +you are sending HTML email by default. There is usually a setting that +will allow you to send "Plain Text" email. If you are using Microsoft +products to send email, there are several bugs in the software that +prevent you from turning off the sending of HTML email. +

+ +

+ +Please don't send attachments or include large chunks of code
+Attachments can be difficult to read and are rarely needed by all recipients. +Some mailing lists are set up to drop them. +If you need to send more than a few lines of code, ask first. +Note that code is often mangled by word-wrapping, so it is better to provide a link to a downloadable file. +If necessary, arrange with the person(s) responding to the posting how best to give access to the data, +should it prove necessary. +

+ +

+ + Watch where you are sending email. +
+The majority of our mailing lists have set the Reply-To to go back to the +list. That means that when you Reply to a message, it will go to the list +and not to the original author directly. The reason is because it helps +facilitate discussion on the list for everyone to benefit from. Be careful +of this as sometimes you may intend to reply to a message directly to someone +instead of the entire list. + +The appropriate contents of the Reply-To header is an age-old debate that +should not be brought up on the mailing lists. You can +examine opposing points of view +condemning +our convention and + +condoning +it. Bringing this up for debate on a mailing list will add nothing +new and is considered off-topic. + +

+ +

+ + Do not cross post messages. +
+In other words, pick a mailing list and send your messages to that mailing +list only. Do not send your messages to multiple mailing lists. The reason is +that people may be subscribed to one list and not to the other. Therefore, +some people will only see part of the conversation. +

+
+ +
+

+Now that you have read the guidelines above, here is the page that gives +you a listing of the different mailing lists that you can join. If you +managed to find this without reading the above information, chances +are you will be sent back here. You might as well read it now and save +yourself the embarrassment. +

+
+ + +
diff --git a/xdocs/mail2.xml b/xdocs/mail2.xml new file mode 100644 index 00000000000..256a01a676e --- /dev/null +++ b/xdocs/mail2.xml @@ -0,0 +1,150 @@ + + + + + + + Apache JMeter Project + Mailing Lists + + + + +
+

+Before subscribing to any of the mailing lists, please make sure that you have +read and understand the guidelines. +

+ +

+Please note that usage of these mailing lists is subject to the +Public Forum Archive Policy. +

+ +

+For details of how to subscribe/unsubscribe please read +Subscribing and Unsubscribing +

+ +
+ +
+The following mailing lists are available: + +

+This is the list where users of Apache JMeter meet and discuss issues. +

+

+Developers are also expected to be lurking on this list to offer support to users of JMeter. +

+

+This list starts part-way through Nov 2011, when JMeter became an independent Apache project. +For earlier postings, please see the Jakarta JMeter User list, below. +

+ +
+ +This is the old JMeter user list from when JMeter was a sub-project of Apache Jakarta. + + + +

+This is the list where participating developers meet and discuss issues, code changes/additions etc. +

+Please do not send usage questions to this list, see user list above. +

+This list also collects Wiki update messages. +

+

+This list starts part-way through Nov 2011, when JMeter became an independent Apache project. +For earlier postings, please see below. +

+ +
+ +

+SVN commit messages are sent here. +

+Prior to Nov 2011, they were sent to the Jakarta Notifications list, see below. + +
+ +

+Bugzilla messages are sent here. +

+

+Prior to Nov 2011, they were sent to the Jakarta Notifications list, see below. +

+ +
+ +

Combined Jakarta developer list, April 2010 to November 2011

+ +
+ +Historical list, up to April 2010. + + + +Combined Jakarta notifications to November 2011. +Includes Bugzilla, SVN and Wiki commit mails for JMeter. + + +
+ +
+

+There are several 3rd party sites that archive and provide searching for our mailing lists: +

+

+

+

+
+ + +
diff --git a/xdocs/nightly.xml b/xdocs/nightly.xml new file mode 100644 index 00000000000..929084a8ac7 --- /dev/null +++ b/xdocs/nightly.xml @@ -0,0 +1,82 @@ + + + + + Nightly builds for developers + + +
+

+

What are the nightly builds?

+

+ The nightly builds are interim builds that are untested and unsupported. + Use at your own risk! +
+ These unreleased builds may not even load, may have undocumented features, + known defects, and any number of other issues. +
+ They are intended for use by developers and others wishing to help with resolving JMeter bugs. +

+ These builds should not be used in production. +

Where are the nightly builds?

+

JMeter CI builds are currently run by Jenkins and Buildbot

+

These are located at: +

+

+

What do they consist of?

+

+JMeter is distributed as a set of zip (or tar-gz) archive files. + +The files are called: +

    +
  • apache-jmeter-{version}_bin.zip - JMeter binaries
  • +
  • apache-jmeter-{version}_lib.zip - 3rd party jar files (rarely changes)
  • +
  • apache-jmeter-{version}_src.zip - JMeter source
  • +
  • apache-jmeter-{version}_api.zip - JMeter Javadoc (if available)
  • +
+

Installing JMeter runtime

+Download the _bin and _lib files +
+Unpack the archives into the same directory structure +
+The other archives are not needed to run JMeter. + +

Building JMeter

+Download the _src, _bin and _lib files +
+Unpack all the archives into the same directory structure. +
+

+ +

Warning - please note!

+ + + The nightly builds may or may not work properly - or at all. + + +

+ If there is a problem with a particular version, + it may be worth reporting this on the JMeter-dev mailing list and/or trying again in a day or two. +

+
+ +
diff --git a/xdocs/overview.html b/xdocs/overview.html new file mode 100644 index 00000000000..eba1d0ca2ed --- /dev/null +++ b/xdocs/overview.html @@ -0,0 +1,24 @@ + + + + + +This is the documentation for Apache JMeter version 2.13 API. +@version 2.13 + + \ No newline at end of file diff --git a/xdocs/presentation/jmeter_presentation.sxi b/xdocs/presentation/jmeter_presentation.sxi new file mode 100644 index 00000000000..3794ae2ca82 Binary files /dev/null and b/xdocs/presentation/jmeter_presentation.sxi differ diff --git a/xdocs/presentation/jmeter_presentation_part2.sxi b/xdocs/presentation/jmeter_presentation_part2.sxi new file mode 100644 index 00000000000..ba17d0104a2 Binary files /dev/null and b/xdocs/presentation/jmeter_presentation_part2.sxi differ diff --git a/xdocs/stylesheets/printable_project.xml b/xdocs/stylesheets/printable_project.xml new file mode 100644 index 00000000000..90d6180db7a --- /dev/null +++ b/xdocs/stylesheets/printable_project.xml @@ -0,0 +1,26 @@ + + + + + Apache JMeter + Apache JMeter + + + + diff --git a/xdocs/stylesheets/project.xml b/xdocs/stylesheets/project.xml new file mode 100644 index 00000000000..fa617dee5bf --- /dev/null +++ b/xdocs/stylesheets/project.xml @@ -0,0 +1,68 @@ + + + + + Apache JMeter + + Apache JMeter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xdocs/stylesheets/site.vsl b/xdocs/stylesheets/site.vsl new file mode 100644 index 00000000000..59ee54f46fd --- /dev/null +++ b/xdocs/stylesheets/site.vsl @@ -0,0 +1,605 @@ + + + + + +## Defined variables +#set ($bodybg = "#ffffff") +#set ($bodyfg = "#000000") +#set ($bodylink = "#525D76") +#set ($bannerbg = "#525D76") +#set ($bannerfg = "#ffffff") +#set ($subbannerbg = "#828DA6") +#set ($subbannerfg = "#ffffff") +#set ($tablethbg = "#039acc") +#set ($tabletdbg = "#a0ddf0") +#set ($notebackground = "#bbbb00") +#set ($space = " ") +#set ($space = $space.charAt(0)) +#set ($udsc = "_") +#set ($udsc = $udsc.charAt(0)) +#set ($year = $date.getYear()+1900) +## +## Website settings +#set ($imgdir = "$relativePath/images") +#set ($sshotdir = "$imgdir/screenshots" ) +#set ($cssdir = "$relativePath/css") + + +#document() + + +## This is where the macros live + +#macro ( sectionlink $anchor) +#if($anchor)#end +#end + +#macro ( table $table) + +#foreach ( $items in $table.getChildren() ) +#if ($items.getName().equals("tr")) +#tr ($items) +#end +#end +
+#end + +#macro ( tr $tr) + +#foreach ( $items in $tr.getChildren() ) +#if ($items.getName().equals("td")) +#td ($items) +#elseif ($items.getName().equals("th")) +#th ($items) +#end +#end + +#end + +#macro ( td $value) +#if ($value.getAttributeValue("colspan")) +#set ($colspan = $value.getAttributeValue("colspan")) +#end +#if ($value.getAttributeValue("rowspan")) +#set ($rowspan = $value.getAttributeValue("rowspan")) +#end + + +#if ($value.getText().length() != 0 || $value.getChildren().size() > 0) +$value.content +#else +  +#end + + +#end + +#macro ( th $value) +#set ($colspan = $value.getAttributeValue("colspan")) +#set ($rowspan = $value.getAttributeValue("rowspan")) + + +#if ($value.getText().length() != 0 || $value.getChildren().size() > 0) +$value.content +#else +  +#end + + +#end + +#macro ( projectanchor $name $value ) +#if ($value.startsWith("http://")) +$name +#elseif ($value.startsWith("/site")) +$name +#else +$name +#end +#end + +#macro ( metaauthor $author $email ) + + +#end + +#macro ( image $value ) +#if ($value.getAttributeValue("width")) +#set ($width=$value.getAttributeValue("width")) +#end +#if ($value.getAttributeValue("height")) +#set ($height=$value.getAttributeValue("height")) +#end +#if ($value.getAttributeValue("align")) +#set ($align=$value.getAttributeValue("align")) +#end + +#end + +#macro ( source $value) +
+ + + + + + + + + + + + + + + + +
$escape.getText($value.getText())
+
+#end + +#macro (properties $properties) +

+Parameters +#if ($properties.getParent().getName() == 'component') +#set ($name = $properties.getParent().getAttributeValue("name").replace($space,$udsc)) +#set ($suff = "_parms") + +#sectionlink ("$name$suff") +#end + + +#foreach ($items in $properties.getChildren("property")) + + + + + +#end +
AttributeDescriptionRequired
$items.getAttributeValue("name")#runloop($items) +#if("$!items.getAttributeValue('required')" != "") +$items.getAttributeValue("required") +#else +No +#end +
+

+#end + +#macro (seeAlso $seeAlso) +

See Also: +

+

+#end + +#macro (figure $figure) +#set ($width = "") +#set ($width = $figure.getAttributeValue('width') ) +#set ($height = "") +#set ($height = $figure.getAttributeValue('height') ) +#set ($dim= "") +#if ("$!width" != "") +#set ($dim = "width='$width' height='$height'") +#end +


+#runloop($figure)

+#end + +#macro (example $example) + +#sectionlink ($example.getAttributeValue("anchor")) +

$example.getAttributeValue("title")

+#runloop($example) +#end + +#macro (note $note) +

+ + +
#runloop($note)
+

+#end + +#macro (scope $scope) +#if ($scope.getText() == "") +
scope +#else +$scope.getText() +#end +#end +## +#macro ( bugzilla $id) +Bug $id.getText() +#end + +## Shorthand - automatically adds " - " before remaining text +#macro ( bug $id) +#bugzilla($id) - ## +#end + +#macro ( contributor $id) +Contributed by $id.getAttributeValue("name") <$id.getAttributeValue("mail")> +#end + +#macro ( ch_section $section) +

+
$section.getText()

+#end + +#macro ( ch_category $category) +

+
$category.getText()

+#end + +#macro ( ch_title $title) +
+
$title.getText()
+#end + +#macro (unknown $node) +#if($node.getName() == "note") +#note($node) +#elseif($node.getName() == "complink") +#complink($node) +#elseif($node.getName() == "figure") +#figure($node) +#elseif ($node.getName() == "links") +#seeAlso ($node) +#elseif ($node.getName() == "properties") +#properties ($node) +#elseif ($node.getName() == "example") +#example ($node) +#elseif ($node.getName().equals("source")) +#source ($node) +#elseif ($node.getName().equals("table")) +#table ($node) +#elseif ($node.getName().equals("component")) +#component($node) +#elseif ($node.getName().equals("subsection")) +#subsection ($node) +#elseif ($node.getName().equals("scope")) +#scope ($node) +#elseif ($node.getName().equals("bugzilla")) +#bugzilla ($node) +#elseif ($node.getName().equals("bug")) +#bug ($node) +#elseif ($node.getName().equals("contributor")) +#contributor ($node) +#elseif ($node.getName().equals("ch_section")) +#ch_section ($node) +#elseif ($node.getName().equals("ch_category")) +#ch_category ($node) +#elseif ($node.getName().equals("ch_title")) +#ch_title ($node) +#else +#outputTag($node) +#runloop($node) +#outputEndTag($node) +#end +#end + +#macro (complink $complink) +$complink.getAttributeValue("name") +#end + +#macro (outputTag $tag) +<$tag.getName()#getAtts($tag)> +#end + +#macro (getAtts $tag) +#foreach ($att in $tag.getAttributes()) $att.getName()="$att.getValue()"#end +#end + +#macro (outputEndTag $tag) + +#end + +#macro (runloop $itemToLoop) +#foreach ($node in $itemToLoop.getContent()) +#if($node.getClass().getName().indexOf("Element") > -1) +#unknown($node) +#else +$node.getText() +#end +#end +#end + +#macro ( component $component) +#set ($width = "") +#set ($width = $component.getAttributeValue('width') ) +#set ($height = "") +#set ($height = $component.getAttributeValue('height') ) +#set ($dim= "") +#if ("$!width" != "") +#set ($dim = "width='$width' height='$height'") +#end +#set ($screenshot = "") +#set ($screenshot = $component.getAttributeValue('screenshot') ) + + +#if($component.getAttribute("useinstead")) + +#end + + +
+ +#set ($was = "") +#set ($was = $component.getAttributeValue("was")) +#if ("$!was" != "") + +#set ($was = " (was: $was)") +#end +

+$!component.getAttributeValue("index") $component.getAttributeValue("name")$!was +#sectionlink ($component.getAttributeValue("name")) +

+
+
*** This element is deprecated. Use $component.getAttributeValue("useinstead") instead ***
+#foreach ( $items in $component.getChildren() ) +#if ($items.getName().equals("description")) +#runloop($items) +#if ("$!screenshot" != "") +

Control Panel

+
+#end +#else +#unknown($items) +#end +#end +

+
+#end + +#macro ( subsection $subsection) + + + + +
+ +$subsection.getAttributeValue("name") +#sectionlink ($subsection.getAttributeValue("anchor")) + +
+
+#foreach ( $items in $subsection.getChildren() ) +#if ($items.getName().equals("img")) +#image ($items) +#else +#unknown($items) +#end +#end +
+

+#end + +#macro (pagelinks) +#if (("$!next" != "") || ("$!prev" != "")) + + + +#if ("$!next" != "") + +#end +#if ("$!prev" != "") + +#end + +
+ + + + + +
+#end +#end + +#macro ( section $section) + + + + +
+ +#set ($anchor = $section.getAttributeValue("anchor")) +#if($anchor)#end$section.getAttributeValue("name")#if($anchor)#sectionlink ($anchor)#end + +
+
+#foreach ( $items in $section.getChildren() ) +#if ($items.getName().equals("img")) +#image ($items) +#else +#unknown($items) +#end +#end +
+

+

+#end + +#macro ( makeProject ) + + + +
+#set ($menus = $project.getChild("body").getChildren("menu")) +#foreach ( $menu in $menus ) +

$menu.getAttributeValue("name")

+
    +#foreach ( $item in $menu.getChildren() ) +#set ($icon = $item.getAttributeValue("icon")) +#set ($name = $item.getAttributeValue("name")) +
  • #projectanchor($name $item.getAttributeValue("href"))#if($icon)#end
  • +#end +
+#end +
+ +
+#end + +#macro (makeIndex $subsections) + +#end + +#macro (getProjectImage) +#if ($project.getChild("logo")) +#set ( $logoString = $project.getChild("logo").getAttributeValue("href") ) +#set ( $logoHeight = $project.getChild("logo").getAttributeValue("height") ) +#set ( $logoWidth = $project.getChild("logo").getAttributeValue("width") ) + + + + +      +#if ( $logoString.startsWith("/") ) +$project.getChild( +#else +$project.getChild( +#end + +#else + + + +#end +#end + +#macro (document) +## ====================================================================== +## Main Page Section --> +## ====================================================================== + + + + +#set ($authors = $root.getChild("properties").getChildren("author")) +#foreach ( $au in $authors ) +#metaauthor ( $au.getText() $au.getAttributeValue("email") ) +#end +#set ($next = "") +#set ($next = $root.getAttributeValue("next")) +#set ($prev = "") +#set ($prev = $root.getAttributeValue("prev")) +#set ($id = "") +#set ($id = $root.getAttributeValue("id")) + +$project.getChild("title").getText() - $root.getChild("properties").getChild("title").getText() + + + + + + + +
+ +## + +#getProjectImage() + +
+
+ + + + + +
Tweet + + Follow + +
+
+ + + + + + + + + + + +
+
+
+#makeProject() + +#pagelinks() +
+#if ($root.getAttributeValue("index") == "yes") +#makeIndex($root.getChild("body").getChildren("section")) +#end +#set ($allSections = $root.getChild("body").getChildren("section")) +#foreach ( $section in $allSections ) +#section ($section) +#end +
+#pagelinks() +
+
+
+
+Copyright © 1999-$year, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + +#end + diff --git a/xdocs/stylesheets/site.xsl b/xdocs/stylesheets/site.xsl new file mode 100644 index 00000000000..cbec0c8a02d --- /dev/null +++ b/xdocs/stylesheets/site.xsl @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:value-of select="$project/title"/> - <xsl:value-of select="properties/title"/> + + + + + + + + + + + + + + + + + PAGE HEADER + + + HEADER SEPARATOR + + + + + + + LEFT SIDE NAVIGATION + + + RIGHT SIDE MAIN BODY + + + + + FOOTER SEPARATOR + + + + + PAGE FOOTER + + +
+ + JAKARTA LOGO + + The Jakarta Project + + + + + + + + + + + + + PROJECT LOGO + + {$alt} + + + +
+
+
+ + + +
+
+
+
+ Copyright © 1999-2001, Apache Software Foundation +
+
+ + + +
+ + + + +

+
    + +
+
+ + + + + + + +
  • +
    + + + + + + + + + + + + +
    + + + +
    + +
    +
    + + + + + + + + + + + + +
    + + + +
    + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + +
    + + + + + +
    + +
    +                
    +             
    + +
    + + + + + +
    +
    +
    + + + + + + + + + +
    diff --git a/xdocs/stylesheets/site_printable.vsl b/xdocs/stylesheets/site_printable.vsl new file mode 100644 index 00000000000..378b83b9883 --- /dev/null +++ b/xdocs/stylesheets/site_printable.vsl @@ -0,0 +1,596 @@ + + +## +## Content Stylesheet for Printable docs +## +## Defined variables +#set ($bodybg = "#ffffff") +#set ($bodyfg = "#000000") +#set ($bodylink = "#525D76") +#set ($bannerbg = "#525D76") +#set ($bannerfg = "#ffffff") +#set ($subbannerbg = "#828DA6") +#set ($subbannerfg = "#ffffff") +#set ($tablethbg = "#039acc") +#set ($tabletdbg = "#a0ddf0") +#set ($notebackground = "#bbbb00") +#set ($space = " ") +#set ($space = $space.charAt(0)) +#set ($udsc = "_") +#set ($udsc = $udsc.charAt(0)) +#set ($year = $date.getYear()+1900) +## +## Printable document settings +#set ($imgdir = "$relativePath/../docs/images") +#set ($cssdir = "$relativePath/../docs/css") +#set ($sshotdir = "$imgdir/screenshots") + + +#document() + + +## This is where the macros live + +#macro ( sectionlink $anchor) +#if($anchor)#end +#end + +#macro ( table $table) + +#foreach ( $items in $table.getChildren() ) +#if ($items.getName().equals("tr")) +#tr ($items) +#end +#end +
    +#end + +#macro ( tr $tr) + +#foreach ( $items in $tr.getChildren() ) +#if ($items.getName().equals("td")) +#td ($items) +#elseif ($items.getName().equals("th")) +#th ($items) +#end +#end + +#end + +#macro ( td $value) +#if ($value.getAttributeValue("colspan")) +#set ($colspan = $value.getAttributeValue("colspan")) +#end +#if ($value.getAttributeValue("rowspan")) +#set ($rowspan = $value.getAttributeValue("rowspan")) +#end + + +#if ($value.getText().length() != 0 || $value.getChildren().size() > 0) +$value.content +#else +  +#end + + +#end + +#macro ( th $value) +#set ($colspan = $value.getAttributeValue("colspan")) +#set ($rowspan = $value.getAttributeValue("rowspan")) + + +#if ($value.getText().length() != 0 || $value.getChildren().size() > 0) +$value.content +#else +  +#end + + +#end + +#macro ( projectanchor $name $value ) +#if ($value.startsWith("http://")) +$name +#elseif ($value.startsWith("/site")) +$name +#else +$name +#end +#end + +#macro ( metaauthor $author $email ) + + +#end + +#macro ( image $value ) +#if ($value.getAttributeValue("width")) +#set ($width=$value.getAttributeValue("width")) +#end +#if ($value.getAttributeValue("height")) +#set ($height=$value.getAttributeValue("height")) +#end +#if ($value.getAttributeValue("align")) +#set ($align=$value.getAttributeValue("align")) +#end + +#end + +#macro ( source $value) +
    + + + + + + + + + + + + + + + + +
    $escape.getText($value.getText())
    +
    +#end + +#macro (properties $properties) +

    +Parameters + + +#foreach ($items in $properties.getChildren("property")) + + + + + +#end +
    AttributeDescriptionRequired
    $items.getAttributeValue("name")#runloop($items) +#if("$!items.getAttributeValue('required')" != "") +$items.getAttributeValue("required") +#else +No +#end +
    +

    +#end + +#macro (seeAlso $seeAlso) +

    See Also: +

      +#foreach ($items in $seeAlso.getChildren()) +#if($items.getName() == "link") +
    • $xmlout.outputString($items,true)
    • +#elseif($items.getName() == "complink") +
    • #complink($items)
    • +#end +#end +
    +

    +#end + +#macro (figure $figure) +#set ($width = "") +#set ($width = $figure.getAttributeValue('width') ) +#set ($height = "") +#set ($height = $figure.getAttributeValue('height') ) +#set ($dim= "") +#if ("$!width" != "") +#set ($dim = "width='$width' height='$height'") +#end +


    +#runloop($figure)

    +#end + +#macro (example $example) + +

    $example.getAttributeValue("title")

    +#runloop($example) +#end + +#macro (note $note) +

    + + +
    #runloop($note)
    +

    +#end + +#macro (scope $scope) +#if ($scope.getText() == "") +
    scope +#else +$scope.getText() +#end +#end +## +#macro ( bugzilla $id) +Bug $id.getText() +#end + +## Shorthand - automatically adds " - " before remaining text +#macro ( bug $id) +#bugzilla($id) - ## +#end + +#macro ( contributor $id) +Contributed by $id.getAttributeValue("name") <$id.getAttributeValue("mail")> +#end + +#macro ( ch_section $section) +

    +
    $section.getText()

    +#end + +#macro ( ch_category $category) +

    +
    $category.getText()

    +#end + +#macro ( ch_title $title) +
    +
    $title.getText()
    +#end + +#macro (unknown $node) +#if($node.getName() == "note") +#note($node) +#elseif($node.getName() == "complink") +#complink($node) +#elseif($node.getName() == "figure") +#figure($node) +#elseif ($node.getName() == "links") +#seeAlso ($node) +#elseif ($node.getName() == "properties") +#properties ($node) +#elseif ($node.getName() == "example") +#example ($node) +#elseif ($node.getName().equals("source")) +#source ($node) +#elseif ($node.getName().equals("table")) +#table ($node) +#elseif ($node.getName().equals("component")) +#component($node) +#elseif ($node.getName().equals("subsection")) +#subsection ($node) +#elseif ($node.getName().equals("scope")) +#scope ($node) +#elseif ($node.getName().equals("bugzilla")) +#bugzilla ($node) +#elseif ($node.getName().equals("bug")) +#bug ($node) +#elseif ($node.getName().equals("contributor")) +#contributor ($node) +#elseif ($node.getName().equals("ch_section")) +#ch_section ($node) +#elseif ($node.getName().equals("ch_category")) +#ch_category ($node) +#elseif ($node.getName().equals("ch_title")) +#ch_title ($node) +#else +#outputTag($node) +#runloop($node) +#outputEndTag($node) +#end +#end + +#macro (complink $complink) +$complink.getAttributeValue("name") +#end + +#macro (outputTag $tag) +<$tag.getName()#getAtts($tag)> +#end + +#macro (getAtts $tag) +#foreach ($att in $tag.getAttributes()) $att.getName()="$att.getValue()"#end +#end + +#macro (outputEndTag $tag) + +#end + +#macro (runloop $itemToLoop) +#foreach ($node in $itemToLoop.getContent()) +#if($node.getClass().getName().indexOf("Element") > -1) +#unknown($node) +#else +$node.getText() +#end +#end +#end + +#macro ( component $component) +#set ($width = "") +#set ($width = $component.getAttributeValue('width') ) +#set ($height = "") +#set ($height = $component.getAttributeValue('height') ) +#set ($dim= "") +#if ("$!width" != "") +#set ($dim = "width='$width' height='$height'") +#end +#set ($screenshot = "") +#set ($screenshot = $component.getAttributeValue('screenshot') ) + + +#if($component.getAttribute("useinstead")) + +#end + + +
    + +#set ($was = "") +#set ($was = $component.getAttributeValue("was")) +#if ("$!was" != "") + +#set ($was = " (was: $was)") +#end +

    +$!component.getAttributeValue("index") $component.getAttributeValue("name")$!was +

    +
    +
    *** This element is deprecated. Use $component.getAttributeValue("useinstead") instead ***
    +#foreach ( $items in $component.getChildren() ) +#if ($items.getName().equals("description")) +#runloop($items) +#if ("$!screenshot" != "") +

    Control Panel

    +
    +#end +#else +#unknown($items) +#end +#end +

    +
    +#end + +#macro ( subsection $subsection) + + + + +
    + +$subsection.getAttributeValue("name") + +
    +
    +#foreach ( $items in $subsection.getChildren() ) +#if ($items.getName().equals("img")) +#image ($items) +#else +#unknown($items) +#end +#end +
    +

    +#end + +#macro (pagelinks) +#if (("$!next" != "") || ("$!prev" != "")) + + + +#if ("$!next" != "") + +#end +#if ("$!prev" != "") + +#end + +
    + + + + + +
    +#end +#end + +#macro ( section $section) + + + + +
    + +#set ($anchor = $section.getAttributeValue("anchor")) +#if($anchor)#end$section.getAttributeValue("name")#if($anchor)#end + +
    +
    +#foreach ( $items in $section.getChildren() ) +#if ($items.getName().equals("img")) +#image ($items) +#else +#unknown($items) +#end +#end +
    +

    +

    +#end + +#macro ( makeProject ) +#set ($menus = $project.getChild("body").getChildren("menu")) +#foreach ( $menu in $menus ) +

    $menu.getAttributeValue("name")

    +
      +#foreach ( $item in $menu.getChildren() ) +#set ($name = $item.getAttributeValue("name")) +
    • #projectanchor($name $item.getAttributeValue("href"))
    • +#end +
    +#end +#end + +#macro (makeIndex $subsections) +#set ($level2 = $root.getAttributeValue("index-level-2")) +## Should we display numbers for index level 2 ? (useful for checking numbering) +#set ($index2 = $root.getAttributeValue("index-numbers")) +#set ($colbreak = $root.getAttributeValue("colbreak")) +#if ("$!colbreak" != "") + + +
    +#end +
      +#foreach ($sect in $subsections) +#if (("$!colbreak" != "") && ($sect.getAttributeValue("name").startsWith("$colbreak"))) +
      +#end +
    • $sect.getAttributeValue("name")
    • +#if ("$!level2" != "no") +
        +#foreach ($comp in $sect.getChildren("component")) +
      • +#if ("$!index2" == "yes") +$comp.getAttributeValue("index") +#end +#set ($was = $comp.getAttributeValue("was")) +#if ("$!was" != "") +#set ($was = " (was: $was)") +#end +$comp.getAttributeValue("name")$!was
      • +#end +
      +#end +#end +
    +#if ("$!colbreak" != "") +
    +#end +#end + +#macro (getProjectImage) +#if ($project.getChild("logo")) +#set ( $logoString = $project.getChild("logo").getAttributeValue("href") ) +#set ( $logoHeight = $project.getChild("logo").getAttributeValue("height") ) +#set ( $logoWidth = $project.getChild("logo").getAttributeValue("width") ) + + + + +#if ( $logoString.startsWith("/") ) +$project.getChild( +#else +$project.getChild( +#end + +#else + + + +#end +#end + +#macro (document) +## ====================================================================== +## Main Page Section --> +## ====================================================================== + + + + +#set ($authors = $root.getChild("properties").getChildren("author")) +#foreach ( $au in $authors ) +#metaauthor ( $au.getText() $au.getAttributeValue("email") ) +#end +#set ($next = "") +#set ($next = $root.getAttributeValue("next")) +#set ($prev = "") +#set ($prev = $root.getAttributeValue("prev")) +#set ($id = "") +#set ($id = $root.getAttributeValue("id")) + +$project.getChild("title").getText() - $root.getChild("properties").getChild("title").getText() + + + + +## + +#getProjectImage() + +
    + + + + + + + + + +
    +
    +
    +#pagelinks() +
    +#if ($root.getAttributeValue("index") == "yes") +#makeIndex($root.getChild("body").getChildren("section")) +#end +#set ($allSections = $root.getChild("body").getChildren("section")) +#foreach ( $section in $allSections ) +#section ($section) +#end +
    +#pagelinks() +
    +
    +
    + + +#if ("$!id" != "") + +#if ("$!id" != "") + +#end + + +
    +#else + +#end + +Copyright © 1999-$year, Apache Software Foundation + + + +$id + +
    +
    +Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
    +
    +
    + + +#end diff --git a/xdocs/stylesheets/website-style.xsl b/xdocs/stylesheets/website-style.xsl new file mode 100644 index 00000000000..84e831dd5f3 --- /dev/null +++ b/xdocs/stylesheets/website-style.xsl @@ -0,0 +1,577 @@ + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + <xsl:value-of select="$project/title" /> + - + <xsl:value-of select="properties/title" /> + + + + + + + + + + + + + + + + + + +
    + + APACHE LOGO + +
    + + Logo ASF + +
    + + + + + + + + + + + + PROJECT LOGO + +
    + + {$alt} + +
    +
    + + +
    + +
    + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + {$alt} + + + + + + + + + + + + + + + + + + + + + + +
  • + + + + {concat('Icon for ', @name)} + + +
  • +
    + + +
    +

    + + + + + + + + + + + +

    + +
    +
    + + +

    + +

    +
    + + +

    + +

    +
    + + +

    + +

    +
    + + +
    +

    + + + + + + + + + + + +

    + +
    +
    + + +
    +      
    +    
    +
    + + + + + + + + +
    + +
    +
    + + +
    +

    + + + + (was: + + ) + + + + + +

    + +
    + *** This element is deprecated. Use + + + + instead *** +
    +
    + +
    + + + + + + + +
    +
    + +
    + ^ +
    +
    +
    + + + + + + + + +
    +

    + + + + + + Parameters + + + + + +

    +
    +
    Attribute
    +
    Description
    +
    Required
    +
    + +
    +
    + + +
    +
    + +
    +
    + +
    +
    + + + + + + No + + +
    +
    +
    + + +
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + +
    + + + + + + + +
    + +
    +
    +
    + + + + Bug + + + + + + + Bug + + + - + + + + + + + +
  • + + + +
  • +
    + + +
    +
    + + + + + + + + + +
    + +
    +
    + + + + +
    +
    + + + + + + + + + + +
    + + + +
    +
    + + + + + + + +
    diff --git a/xdocs/svnindex.xml b/xdocs/svnindex.xml new file mode 100644 index 00000000000..83ea085813a --- /dev/null +++ b/xdocs/svnindex.xml @@ -0,0 +1,73 @@ + + + + + + Apache JMeter Project + Source Repositories + + + + +
    + +

    Most users of the source code probably don't need to have day to +day access to the source code as it changes. For these users we +provide easy to unpack source code downloads via our download page.

    + +
    + +
    + +

    For information on connecting to the ASF Subversion repositories, see the version control +page.

    + + +

    Modules available for access are listed below.

    + + + +

    Subversion is an open-source version control system. The root url of the +ASF Subversion repository is http://svn.apache.org/repos/asf/ for non-committers and https://svn.apache.org/repos/asf/ for committers.

    + +

    NOTE: +When checking out a subproject using Subversion, ensure that you are checking out a tag, a branch or trunk (the main-line) and not all tags and branches to avoid filling up your hard-disk and wasting bandwidth.

    + + + + + + + + + + + + + + +
    Projecthttp (read-only)https (committers)View-SVN
    Apache JMeterhttp://svn.apache.org/repos/asf/jmeter/trunkhttps://svn.apache.org/repos/asf/jmeter/trunkhttp://svn.apache.org/viewcvs.cgi/jmeter/
    + +
    + +
    + + +
    diff --git a/xdocs/usermanual/best-practices.xml b/xdocs/usermanual/best-practices.xml new file mode 100644 index 00000000000..850006179e4 --- /dev/null +++ b/xdocs/usermanual/best-practices.xml @@ -0,0 +1,414 @@ + + + + +]> + + + + + User's Manual: Best Practices + + + + +
    +
    + +
    +

    The performance of JMeter is being constantly improved, so users are highly encouraged to use the most up to date version.
    +Ensure you always read changes list to be aware of new improvements and components. +You should absolutely avoid using versions that are older than 3 versions before the last one. +

    +
    + +
    +

    Your hardware capabilities as well as the Test Plan design will both impact the number of threads you can effectively +run with JMeter. The number will also depend on how fast your server is (a faster server + makes JMeter work harder since it returns a response quicker). As with any Load Testing tool, if you don't correctly size + the number of threads, you will face the "Coordinated Omission" problem which can give you wrong or inaccurate results. + If you need large-scale load testing, consider running multiple non-GUI JMeter instances on multiple machines + using distributed mode (or not). When using distributed mode the result file is combined on the Controller node, if + using multiple autonomous instances, the sample result files can be combined for subsequent analysis. +For testing how JMeter performs on a given platform, the JavaTest sampler can be used. +It does not require any network access so can give some idea as to the maximum throughput achievable. +

    +

    +JMeter versions since 2.8 have an option to delay thread creation until the thread +starts sampling, i.e. after any thread group delay and the ramp-up time for the thread itself. +This allows for a very large total number of threads, provided that not too many are active concurrently. +

    +
    + +
    +

    See Building a Web Test +for information.

    +
    + +
    +

    See Building an Advanced +Web Test for information.

    +
    + +
    +

    Refer to for details on setting up the +recorder. The most important thing to do is filter out all requests you aren't +interested in. For instance, there's no point in recording image requests (JMeter can +be instructed to download all images on a page - see ). +These will just clutter your test plan. Most likely, there is an extension all your files +share, such as .jsp, .asp, .php, .html or the like. +These you should "include" by entering ".*\.jsp" as an "Include Pattern".

    +

    Alternatively, you can exclude images by entering ".*\.gif" as an "Exclude Pattern". +Depending on your application, this may or may not be a better way to go. You may +also have to exclude stylesheets, javascript files, and other included files. Test +out your settings to verify you are recording what you want, and then erase and start +fresh.

    + +

    The HTTP(S) Test Script Recorder expects to find a ThreadGroup element with a Recording Controller +under it where it will record HTTP Requests to. This conveniently packages all your samples under one +controller, which can be given a name that describes the test case.

    +

    Now, go through the steps of a Test Case. If you have no pre-defined test cases, use +JMeter to record your actions to define your test cases. Once you have finished a +definite series of steps, save the entire test case in an appropriately named file. Then, wipe +clean and start a new test case. By doing this, you can quickly record a large number of +test case "rough drafts".

    +

    One of the most useful features of the HTTP(S) Test Script Recorder is that you can abstract out +certain common elements from the recorded samples. By defining some +user-defined variables at the Test Plan level or in + elements, you can have JMeter automatically +replace values in you recorded samples. For instance, if you are testing an app on +server "xxx.example.com", then you can define a variable called "server" with the value of +"xxx.example.com", and anyplace that value is found in your recorded samples will be replaced +with "${server}". + +Please note that matching is case-sensitive. + +

    +

    +If JMeter does not record any samples, check that the browser really is using the proxy. +If the browser works OK even if JMeter is not running, then the browser cannot be using the proxy. +Some browsers ignore proxy settings for localhost or 127.0.0.1; try using the local hostname or IP instead. +

    +

    +The error "unknown_ca" probably means that you are trying to record HTTPS, and the browser has not accepted the +JMeter Proxy server certificate. +

    + + +
    + +
    +

    +Some test plans need to use different values for different users/threads. +For example, you might want to test a sequence that requires a unique login for each user. +This is easy to achieve with the facilities provided by JMeter. +

    +

    For example:

    +
      +
    • Create a text file containing the user names and passwords, separated by commas. +Put this in the same directory as your test plan. +
    • +
    • +Add a CSV DataSet configuration element to the test plan. +Name the variables USER and PASS. +
    • +
    • +Replace the login name with ${USER} and the password with ${PASS} on the appropriate +samplers +
    • +
    +

    The CSV Data Set element will read a new line for each thread. +

    +
    + +
    +

    +Some suggestions on reducing resource usage. +

    +
      +
    • Use non-GUI mode: jmeter -n -t test.jmx -l test.jtl
    • +
    • Use as few Listeners as possible; if using the -l flag as above they can all be deleted or disabled.
    • +
    • Don't use "View Results Tree" or "View Results in Table" listeners during the load test, use them only during scripting phase to debug your scripts.
    • +
    • Rather than using lots of similar samplers, +use the same sampler in a loop, and use variables (CSV Data Set) to vary the sample. +[The Include Controller does not help here, as it adds all the test elements in the file to the test plan.] +
    • +
    • Don't use functional mode
    • +
    • Use CSV output rather than XML
    • +
    • Only save the data that you need
    • +
    • Use as few Assertions as possible
    • +
    • Use the most performing scripting language (see JSR223 section)
    • +
    +

    +If your test needs large amounts of data - particularly if it needs to be randomised - create the test data in a file +that can be read with CSV Dataset. This avoids wasting resources at run-time. +

    +
    + +
    +

    +The BeanShell interpreter has a very useful feature - it can act as a server, +which is accessible by telnet or http. +

    + +There is no security. Anyone who can connect to the port can issue any BeanShell commands. +These can provide unrestricted access to the JMeter application and the host. +Do not enable the server unless the ports are protected against access, e.g. by a firewall. + +

    +If you do wish to use the server, define the following in jmeter.properties: +

    + +beanshell.server.port=9000 +beanshell.server.file=../extras/startup.bsh + +

    +In the above example, the server will be started, and will listen on ports 9000 and 9001. +Port 9000 will be used for http access. Port 9001 will be used for telnet access. +The startup.bsh file will be processed by the server, and can be used to define various functions and set up variables. +The startup file defines methods for setting and printing JMeter and system properties. +This is what you should see in the JMeter console: +

    + +Startup script running +Startup script completed +Httpd started on port: 9000 +Sessiond started on port: 9001 + +

    +There is a sample script (extras/remote.bsh) you can use to test the server. +[Have a look at it to see how it works.] +
    +When starting it in the JMeter bin directory +(adjust paths as necessary if running from elsewhere) +the output should look like: + +$ java -jar ../lib/bshclient.jar localhost 9000 ../extras/remote.bsh +Connecting to BSH server on localhost:9000 +Reading responses from server ... +BeanShell 2.0b5 - by Pat Niemeyer (pat@pat.net) +bsh % remote.bsh starting +user.home = C:\Documents and Settings\User +user.dir = D:\eclipseworkspaces\main\JMeter_trunk\bin +log_level.jmeter = INFO +log_level.jorphan = INFO +Setting property 'EXAMPLE' to '0'. +Setting property 'EXAMPLE' to '1'. +Setting property 'EXAMPLE' to '2'. +Setting property 'EXAMPLE' to '3'. +Setting property 'EXAMPLE' to '4'. +Setting property 'EXAMPLE' to '5'. +Setting property 'EXAMPLE' to '6'. +Setting property 'EXAMPLE' to '7'. +Setting property 'EXAMPLE' to '8'. +Setting property 'EXAMPLE' to '9'. +EXAMPLE = 9 +remote.bsh ended +bsh % ... disconnected from server. + +

    +

    +As a practical example, assume you have a long-running JMeter test running in non-GUI mode, +and you want to vary the throughput at various times during the test. +The test-plan includes a Constant Throughput Timer which is defined in terms of a property, +e.g. ${__P(throughput)}. +The following BeanShell commands could be used to change the test: +

    + +printprop("throughput"); +curr = Integer.decode(args[0]); // Start value +inc = Integer.decode(args[1]); // Increment +end = Integer.decode(args[2]); // Final value +secs = Integer.decode(args[3]); // Wait between changes +while(curr <= end) { + setprop("throughput",curr.toString()); // Needs to be a string here + Thread.sleep(secs*1000); + curr += inc; +} +printprop("throughput"); + +

    The script can be stored in a file (throughput.bsh, say), and sent to the server using bshclient.jar. +For example: +

    + +java -jar ../lib/bshclient.jar localhost 9000 throughput.bsh 70 5 100 60 + +
    + +
    + +

    +Each BeanShell test element has its own copy of the interpreter (for each thread). +If the test element is repeatedly called, e.g. within a loop, then the interpreter is retained +between invocations unless the "Reset bsh.Interpreter before each call" option is selected. +For intensive load testing, it is recommended to use a JSR223 scripting language whose ScriptingEngine implements Compilable, +see JSR223 section below for more details. +

    +

    +Some long-running tests may cause the interpreter to use lots of memory; if this is the case try using the reset option. +

    +

    +You can test BeanShell scripts outside JMeter by using the command-line interpreter: + +$ java -cp bsh-xxx.jar[;other jars as needed] bsh.Interperter file.bsh [parameters] + +or + +$ java -cp bsh-xxx.jar bsh.Interperter +bsh% source("file.bsh"); +bsh% exit(); // or use EOF key (e.g. ^Z or ^D) + +

    +
    + +

    +Variables can be defined in startup (initialisation) scripts. +These will be retained across invocations of the test element, unless the reset option is used. +

    +

    +Scripts can also access JMeter variables using the get() and put() methods of the "vars" variable, +for example: +vars.get("HOST"); +vars.put("MSG","Successful"); +The get() and put() methods only support variables with String values, +but there are also getObject() and putObject() methods which can be used for arbitrary objects. +JMeter variables are local to a thread, but can be used by all test elements (not just Beanshell). +

    +

    +If you need to share variables between threads, then JMeter properties can be used: + +import org.apache.jmeter.util.JMeterUtils; +String value = JMeterUtils.getPropDefault("name",""); +JMeterUtils.setProperty("name", "value"); + +The sample .bshrc files contain sample definitions of getprop() and setprop() methods. +

    +

    +Another possible method of sharing variables is to use the "bsh.shared" shared namespace. +For example: + +if (bsh.shared.myObj == void){ + // not yet defined, so create it: + myObj = new AnyObject(); +} +bsh.shared.myObj.process(); + +Rather than creating the object in the test element, it can be created in the startup file +defined by the JMeter property "beanshell.init.file". This is only processed once. +

    +
    +
    + +
    +

    +It's quite hard to write and test scripts as functions. +However, JMeter has the JSR223, BSF (and BeanShell) samplers which can be used instead. +

    +

    +Create a simple Test Plan containing the JSR223 or BSF Sampler and Tree View Listener. +Code the script in the sampler script pane, and test it by running the test. +If there are any errors, these will show up in the Tree View. +Also the result of running the script will show up as the response. +

    +

    +Once the script is working properly, it can be stored as a variable on the Test Plan. +The script variable can then be used to create the function call. +For example, suppose a BeanShell script is stored in the variable RANDOM_NAME. +The function call can then be coded as ${__BeanShell(${RANDOM_NAME})}. +There is no need to escape any commas in the script, +because the function call is parsed before the variable's value is interpolated. +

    +
    + +
    +

    +Often it is useful to be able to re-run the same test with different settings. +For example, changing the number of threads or loops, or changing a hostname. +

    +

    +One way to do this is to define a set of variables on the Test Plan, and then use those variables in the test elements. +For example, one could define the variable LOOPS=10, and refer to that in the Thread Group as ${LOOPS}. +To run the test with 20 loops, just change the value of the LOOPS variable on the Test Plan. +

    +

    +This quickly becomes tedious if you want to run lots of tests in non-GUI mode. +One solution to this is to define the Test Plan variable in terms of a property, +for example LOOPS=${__P(loops,10))}. +This uses the value of the property "loops", defaulting to 10 if the property is not found. +The "loops" property can then be defined on the JMeter command-line: +jmeter ... -Jloops=12 ... +If there are a lot of properties that need to be changed together, +then one way to achieve this is to use a set of property files. +The appropriate property file can be passed in to JMeter using the -q command-line option. +

    +
    + +
    +

    +For intensive load testing, the recommended scripting language is one whose ScriptingEngine implements the Compilable interface. +Groovy scripting engine implements Compilable. However neither Beanshell nor Javascript do so as of release date of JMeter 2.13, so it is +recommended to avoid them for intensive load testing. +[Note: Beanshell implements the Compilable interface but it has not been coded - the method just throws an Exception. +JMeter has an explicit work-round for this bug.] + +When using JSR 223 elements, always set caching key to a unique value to ensure the script compilation is cached if underlying language supports it. +Ensure the script does not use any variable using ${varName} as caching would take only first value of ${varName}. Instead use : + +vars.get("varName") + +

    +

    +You can also pass them as Parameters to the script and use them this way. +

    +
    + +
    +

    +Variables are local to a thread; a variable set in one thread cannot be read in another. +This is by design. For variables that can be determined before a test starts, see +Parameterising Tests (above). +If the value is not known until the test starts, there are various options: +

      +
    • Store the variable as a property - properties are global to the JMeter instance
    • +
    • Write variables to a file and re-read them.
    • +
    • Use the bsh.shared namespace - see above
    • +
    • Write your own Java classes
    • +
    +

    +
    + +
    +

    When you need to modify jmeter properties, ensure you don't modify jmeter.properties file, +instead copy the property from jmeter.properties and modify its value in user.properties file.
    +Doing so will ease you migration to the next version of JMeter.
    +Note that in the documentation jmeter.properties is frequently mentioned but this should be understood as +"Copy from jmeter.properties to user.properties the property you want to modify and do so in the latter file".

    +user.properties file superseeds the properties defined in jmeter.properties +
    + + +
    diff --git a/xdocs/usermanual/boss.xml b/xdocs/usermanual/boss.xml new file mode 100644 index 00000000000..6a89f8c9cee --- /dev/null +++ b/xdocs/usermanual/boss.xml @@ -0,0 +1,209 @@ + + + + +]> + + + + + Martin Ramshaw + Philippe Mouawad + User's Manual: My boss wants me to... + + + + +
    +

    This is a fairly open-ended proposition. There are a number of questions to +be asked first, and additionally a number of resources that will be needed. You +will need some hardware to run the benchmarks/load-tests from. A number of +tools will prove useful. There are a number of products to consider. And finally, +why is Java a good choice to implement a load-testing/Benchmarking product. +

    + +

    What is our anticipated average number of users (normal load)? +

    +

    What is our anticipated peak number of users? +

    +

    When is a good time to load-test our application (i.e. off-hours or week-ends), +bearing in mind that this may very well crash one or more of our servers? +

    +

    Does our application have state? If so, how does our application manage it +(cookies, session-rewriting, or some other method)? +

    +

    What is the testing intended to achieve?

    +
    + +

    The following resources will prove very helpful. Bear in mind that if you +cannot locate these resources, you will become these resources. As you +already have your work cut out for you, it is worth knowing who the following +people are, so that you can ask them for help if you need it. +

    + +

    Who knows our network topology? If you run into any firewall or + proxy issues, this will become very important. As well, a private + testing network (which will therefore have very low network latency) + would be a very nice thing. Knowing who can set one up for you + (if you feel that this is necessary) will be very useful. If the + application doesn't scale as expected, who can add additional + hardware? +

    +
    + +

    Who knows how our application functions? The normal sequence is +

      +
    • test (low-volume - can we benchmark our application?)
    • +
    • benchmark (the average number of users)
    • +
    • load-test (the maximum number of users)
    • +
    • test destructively (what is our hard limit?)
    • +
    + The test process may progress from black-box testing to + white-box testing (the difference is that the first requires + no knowledge of the application [it is treated as a "black box"] + while the second requires some knowledge of the application). + It is not uncommon to discover problems with the application + during this process, so be prepared to defend your work.

    +
    +
    + +

    This should be a widely-used piece of hardware, with a standard +(i.e. vanilla) software installation. Remember, if you publish your results, +the first thing your clients will do is hire a graduate student to verify them. +You might as well make it as easy for this person as you possibly can. +

    +

    For Windows, Windows XP Professional should be a minimum (the others +do not multi-thread past 50-60 connections, and you probably anticipate +more users than that). +

    +

    Good free platforms include the linuxes, the BSDs, and Solaris Intel. If +you have a little more money, there are commercial linuxes. +This may be worth it if you need the support. +

    +

    +For non-Windows platforms, investigate "ulimit -n unlimited" with a view to +including it in your user account startup scripts (.bashrc or .cshrc scripts +for the testing account). +

    +

    As you progress to larger-scale benchmarks/load-tests, this platform +will become the limiting factor. So it's worth using the best hardware and +software that you have available. Remember to include the hardware/software +configuration in your published benchmarks. +

    +

    When you need a lot of machines or want to test the network latency, Cloud can help you. +JMeter can easily be installed on Cloud instances as it runs on nearly any architecture available in the Cloud. +JMeter is also supported within Commercial Cloud PAAS if you don't want to manage it yourself. +

    +

    Don't forget JMeter batch (NON-GUI) mode. This mode should be used during load testing for many reasons: +

      +
    • If you have a powerful server that supports Java but perhaps does not have a fast graphics implementation, or where you need to login remotely.
    • +
    • Batch (non-GUI) mode can reduce the network traffic compared with using a remote display or client-server mode.
    • +
    • Java AWT Thread used for GUI mode can alter injection behaviour by blocking sometimes
    • +
    +The batch log file can then be loaded into JMeter on a workstation for analysis, or you can +use CSV output and import the data into a spreadsheet.

    +Remember GUI mode is for Script creation and debugging, not for load testing +
    + +

    The following tools will all prove useful. It is definitely worthwhile to +become familiar with them. This should include trying them out, and reading the +appropriate documentation (man-pages, info-files, application --help messages, +and any supplied documentation). +

    + +

    + This can be used to establish whether or not you can reach your + target site. Options can be specified so that 'ping' provides the + same type of route reporting as 'traceroute'. +

    +
    + +

    + While the user will normally use a human-readable internet + address, you may wish to avoid the overhead of DNS lookups when + performing benchmarking/load-testing. These can be used to determine + the unique address (dotted quad) of your target site. +

    +
    + +

    + If you cannot "ping" your target site, this may be used to determine + the problem (possibly a firewall or a proxy). It can also be used + to estimate the overall network latency (running locally should give + the lowest possible network latency - remember that your users will + be running over a possibly busy internet). Generally, the fewer hops + the better. +

    +
    +
    + +

    There a lot of open-source and commercial plugins that can enhance JMeter, let's mention here the main open-source ones: +

    + +

    This non official project is THE companion to core JMeter.
    + It provides many useful extensions, among which: +

      +
    • Active Threads Over Time Graph Listener
    • +
    • Response Times vs Threads Graph Listener
    • +
    • Transaction Throughput vs Threads Graph Listener
    • +
    • GraphGenerator listener to create graphs at end of a load test
    • +
    • Selenium WebDriver Sampler
    • +
    • ....
    • +
    +

    +
    + +

    This non official plugin allows you to run your automated JMeter tests through Maven.

    +
    + +

    This non official plugin allows you to capture reports from JMeter and JUnit.
    + Jenkins will generate graphic charts with the trend report of performance and robustness. + It includes the feature of setting the final build status as good, unstable or failed, based on the reported error percentage. +

    +
    + +

    This non official plugin automates running Apache JMeter on Amazon EC2

    +
    +
    + +

    Why not Perl or C? +

    +

    Well, Perl might be a very good choice except that the Benchmark package +seems to give fairly fuzzy results. Also, simulating multiple users with +Perl is a tricky proposition (multiple connections can be simulated by forking +many processes from a shell script, but these will not be threads, they will +be processes). However, the Perl community is very large. If you find that +someone has already written something that seems useful, this could be a very +good solution. +

    +

    C, of course, is a very good choice (check out the Apache ab tool). +But be prepared to write all of the custom networking, threading, and state +management code that you will need to benchmark your application. +

    +

    Java gives you (for free) the custom networking, threading, and state +management code that you will need to benchmark your application. Java is +aware of HTTP, FTP, and HTTPS - as well as RMI, IIOP, and JDBC (not to mention +cookies, URL-encoding, and URL-rewriting). In addition Java gives you automatic +garbage-collection, and byte-code level security. +

    +
    +
    + + +
    diff --git a/xdocs/usermanual/build-adv-web-test-plan.xml b/xdocs/usermanual/build-adv-web-test-plan.xml new file mode 100644 index 00000000000..2910012532d --- /dev/null +++ b/xdocs/usermanual/build-adv-web-test-plan.xml @@ -0,0 +1,73 @@ + + + + +]> + + + + + User's Manual: Building an Advanced Web Test Plan + + + + +
    +

    In this section, you will learn how to create advanced +Test Plans to test a Web site.

    + +

    For an example of a basic Test Plan, see +Building a Web Test Plan.

    + +
    +

    If your web application uses URL rewriting rather than cookies to save session information, +then you'll need to do a bit of extra work to test your site.

    +

    To respond correctly to URL rewriting, JMeter needs to parse the HTML +received from the server and retrieve the unique session ID. Use the appropriate +to accomplish this. Simply enter the name of your session ID parameter into the modifier, and it +will find it and add it to each request. If the request already has a value, it will be replaced. +If "Cache Session Id?" is checked, then the last found session id will be saved, +and will be used if the previous HTTP sample does not contain a session id. +

    + + +

    Download this example. In Figure 1 is shown a +test plan using URL rewriting. Note that the URL Re-writing modifier is added to the SimpleController, +thus assuring that it will only affect requests under that SimpleController.

    +
    Figure 1 - Test Tree
    +

    In Figure 2, we see the URL Re-writing modifier GUI, which just has a field for the user to specify +the name of the session ID parameter. There is also a checkbox for indicating that the session ID should +be part of the path (separated by a ";"), rather than a request parameter

    +
    Figure 2 - Request parameters
    +
    +
    + +
    +

    The lets you customize what information +JMeter sends in the HTTP request header. This header includes properties like "User-Agent", +"Pragma", "Referer", etc.

    +

    The , like the , +should probably be added at the Thread Group level, unless for some reason you wish to +specify different headers for the different objects in +your test.

    + +
    + + +
    diff --git a/xdocs/usermanual/build-db-test-plan.xml b/xdocs/usermanual/build-db-test-plan.xml new file mode 100644 index 00000000000..613c1e04539 --- /dev/null +++ b/xdocs/usermanual/build-db-test-plan.xml @@ -0,0 +1,191 @@ + + + + +]> + + + + + User's Manual: Building a Simple Database Test Plan + + + + +
    +

    In this section, you will learn how to create a basic +Test Plan to test a database server. +You will create fifty users that send 2 SQL requests to the database server. +Also, you will tell the users to run their tests 100 times. So, the total number +of requests is (50 users) x (2 requests) x (repeat 100 times) = 10'000 JDBC requests. +To construct the Test Plan, you will use the following elements: +Thread Group, +, .

    + +This example uses the MySQL database driver. +To use this driver, its containing .jar file (ex. mysql-connector-java-X.X.X-bin.jar) must be copied to the JMeter +./lib directory (see JMeter's Classpath +for more details). + +
    + +
    +

    The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group +tells JMeter the number of users you want to simulate, how often the users should +send requests, and the how many requests they should send.

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter JDBC Users.

    + +You will need a valid database, database table, and user-level access to that +table. In the example shown here, the database is 'cloud' and the table name is +'vm_instance'. + +

    Next, increase the number of users to 50.

    + +

    In the next field, the Ramp-Up Period, leave the the value of 10 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 10 seconds, JMeter will +finish starting all of your users by the end of the 10 seconds. So, if we have +50 users and a 10 second Ramp-Up Period, then the delay between starting users +would be 200 milliseconds (10 seconds / 50 users = 0.2 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Finally, enter a value of 100 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + +

    See Figure §-num;.2 for the completed JDBC Users Thread Group.

    + +
    +Figure §-num;.2. JDBC Users Thread Group
    + +
    + +
    +

    Now that we have defined our users, it is time to define the tasks that they +will be performing. In this section, you will specify the JDBC requests to +perform.

    + +

    Begin by selecting the JDBC Users element. Click your right mouse button +to get the wAdd menu, and then select Add --> Config Element --> JDBC Connection Configuration. +Then, select this new element to view its Control Panel (see Figure §-num;.3).

    + +

    Set up the following fields (these assume we will be using a MySQL database called 'cloud'):

    +
      +
    • Variable name (here: myDatabase) bound to pool. This needs to uniquely identify the configuration. It is used by the JDBC Sampler to identify the configuration to be used.
    • +
    • Database URL: jdbc:mysql://ipOfTheServer:3306/cloud
    • +
    • JDBC Driver class: com.mysql.jdbc.Driver
    • +
    • Username: the username of database
    • +
    • Password: password for the username
    • +
    +

    The other fields on the screen can be left as the defaults.

    +

    JMeter creates a database connection pool with the configuration settings as specified in the Control Panel. +The pool is referred to in JDBC Requests in the 'Variable Name' field. +Several different JDBC Configuration elements can be used, but they must have unique names. +Every JDBC Request must refer to a JDBC Configuration pool. +More than one JDBC Request can refer to the same pool. +

    +
    +Figure §-num;.3. JDBC Configuration
    + +

    Selecting the JDBC Users element again. Click your right mouse button +to get the Add menu, and then select Add --> Sampler --> JDBC Request. +Then, select this new element to view its Control Panel (see Figure §-num;.4).

    + +
    +Figure §-num;.4. JDBC Request
    + +

    In our Test Plan, we will make two JDBC requests. The first one is for +select all 'Running' VM instances, and the second is to select 'Expunging' VM instance (obviously you should +change these to examples appropriate for your particular database). These +are illustrated below.

    + +JMeter sends requests in the order that you add them to the tree. + +

    Start by editing the following properties (see Figure §-num;.5): +

      +
    • Change the Name to 'VM Running'.
    • +
    • Enter the Pool Name: 'myDatabase' (same as in the configuration element)
    • +
    • Enter the SQL Query String field.
    • +
    • Enter the Parameter values field with 'Running' value.
    • +
    • Enter the Parameter types with 'VARCHAR'.
    • +
    +

    + +
    +Figure §-num;.5. JDBC Request for the first SQL request
    + +

    Next, add the second JDBC Request and edit the following properties (see +Figure §-num;.6): +

      +
    • Change the Name to 'VM Expunging'.
    • +
    • Change the value of Parameter values to 'Expunging'.
    • +
    +

    + +
    +Figure §-num;.6. JDBC Request for the second request
    + +
    + +
    +

    The final element you need to add to your Test Plan is a +Listener. This element is +responsible for storing all of the results of your JDBC requests in a file +and presenting the results.

    + +

    Select the JDBC Users element and add a +listener (Add --> Listener --> Summary Report).

    + +

    Save the test plan, and run the test with the menu Run --> Start or Ctrl+R

    + +

    The listener shows the results.

    + +
    +Figure §-num;.7. Graph results Listener
    + +
    + + +
    diff --git a/xdocs/usermanual/build-ftp-test-plan.xml b/xdocs/usermanual/build-ftp-test-plan.xml new file mode 100644 index 00000000000..40c2a4d2ca2 --- /dev/null +++ b/xdocs/usermanual/build-ftp-test-plan.xml @@ -0,0 +1,190 @@ + + + + +]> + + + + + User's Manual: Building an FTP Test Plan + + + + +
    +

    In this section, you will learn how to create a basic +Test Plan to test an FTP site. You will +create four users that send requests for two files on a FTP site. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (4 users) x (2 requests) x (repeat 2 times) = 16 FTP requests.

    +

    To construct the Test Plan, you will use the following elements: +Thread Group, +, +, and +.

    + +
    + +
    +

    The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

    + +

    Go ahead and add the Thread Group element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter 'FTP Users'.

    + +

    Next, increase the number of users to 4.

    + +

    In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Finally, enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + +

    See Figure §-num;.2 for the completed FTP Users Thread Group.

    + +
    +Figure §-num;.2. FTP Users Thread Group
    + +
    + +
    +

    Now that we have defined our users, it is time define the tasks that they +will be performing. In this section, you will specify the default settings +for your FTP requests. And then, in section §-num;.3, you will add FTP Request +elements which use some of the default settings you specified here.

    + +

    Begin by selecting the FTP Users element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> FTP Request +Defaults. Then, select this new element to view its Control Panel (see Figure §-num;.3). +

    + +
    +Figure §-num;.3. FTP Request Defaults
    + +

    +Like most JMeter elements, the Control +Panel has a name field that you can modify. In this example, leave this field with +the default value.

    + +

    Skip to the next field, which is the FTP Server's Server Name/IP. For the +Test Plan that you are building, all FTP requests will be sent to the same +FTP server, ftp.domain.com in this case. Enter this domain name into the field. +This is the only field that we will specify a default, so leave the remaining +fields with their default values.

    + +The FTP Request Defaults element does not tell JMeter +to send an FTP request. It simply defines the default values that the +FTP Request elements use. + +

    See Figure §-num;.4 for the completed FTP Request Defaults element

    + +
    +Figure §-num;.4. FTP Defaults for our Test Plan
    + +
    + +
    + +

    In our Test Plan, we need to make two FTP requests.

    + +JMeter sends requests in the order that they appear in the tree. + +

    Start by adding the first +to the FTP Users element (Add --> Sampler --> FTP Request). +Then, select the FTP Request element in the tree and edit the following properties +(see Figure §-num;.5): +

      +
    1. Change the Name to "File1".
    2. +
    3. Change the Remote File field to "/directory/file1.txt".
    4. +
    5. Change the Username field to "anonymous".
    6. +
    7. Change the Password field to "anonymous@test.com".
    8. +
    +

    + +You do not have to set the Server Name field because you already specified +this value in the FTP Request Defaults element. + +
    +Figure §-num;.5. FTP Request for file1
    + +

    Next, add the second FTP Request and edit the following properties (see +Figure §-num;.6: +

      +
    1. Change the Name to "File2".
    2. +
    3. Change the Remote File field to "/directory/file2.txt".
    4. +
    5. Change the Username field to "anonymous".
    6. +
    7. Change the Password field to "anonymous@test.com".
    8. +
    +

    + +
    +Figure §-num;.6. FTP Request for file2
    + +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your FTP requests in a file and presenting +a visual model of the data.

    + +

    Select the FTP Users element and add a +listener (Add --> Listener --> View Results in Table).

    +

    Run your test and view the results.

    + +
    +Figure §-num;.7. View Results in Table Listener
    + +
    + + +
    diff --git a/xdocs/usermanual/build-jms-point-to-point-test-plan.xml b/xdocs/usermanual/build-jms-point-to-point-test-plan.xml new file mode 100644 index 00000000000..ff88d064bf4 --- /dev/null +++ b/xdocs/usermanual/build-jms-point-to-point-test-plan.xml @@ -0,0 +1,216 @@ + + + +]> + + + + + User's Manual: Building a JMS (Java Messaging Service) Point-to-Point Test Plan + + + + + +
    + + + Make sure the required jar files are in JMeter's lib directory. If they are not, shutdown JMeter, + copy the jar files over and restart JMeter. + See Getting Started for details. + + +

    In this section, you will learn how to create a + Test Plan to test a JMS Point-to-Point messaging solution. +The setup of the test is 1 threadgroup with 5 threads sending 4 messages each through a request queue. +A fixed reply queue will be used for monitoring the reply messages. +To construct the Test Plan, you will use the +following elements: + Thread Group, + , and + . +

    + +

    General notes on JMS: There are currently two JMS samplers. One uses JMS topics +and the other uses queues. Topic messages are commonly known as pub/sub messaging. +Topic messaging is generally used in cases where a message is published by a producer and +consumed by multiple subscribers. A JMS sampler needs the JMS implementation jar files; +for example, from Apache ActiveMQ. See here for the list +of jars provided by ActiveMQ 3.0.

    + +
    + +
    +

    The first step you want to do with every JMeter Test Plan is to add a + Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter Point-to-Point.

    + +

    Next, increase the number of users (called threads) to 5.

    + +

    In the next field, the Ramp-Up Period, leave set the value to 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Clear the checkbox labeled "Forever", and enter a value of 4 in the Loop +Count field. This property tells JMeter how many times to repeat your test. +If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + + In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + + +
    + +
    + +

    Start by adding the sampler +to the Point-to-Point element (Add --> Sampler --> JMS Point-to-Point). +Then, select the JMS Point-to-Point sampler element in the tree. + In building the example a configuration will be provided that works with ActiveMQ 3.0. +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameValueDescription
    JMS Resources
    QueueuConnectionFactoryConnectionFactory This is the default JNDI entry for the connection factory within active mq.
    JNDI Name Request QueueQ.REQThis is equal to the JNDI name defined in the JNDI properties.
    JNDI Name Reply QueueQ.RPLThis is equal to the JNDI name defined in the JNDI properties.
    Message Properties
    Communication StyleRequest ResponseThis means that you need at least a service running outside of JMeter and that will respond to the requests. + This service must listen to the Request Queue and send messages to the queue referenced by the message.getJMSReplyTo()
    ContenttestThis is just the content of the message.
    JMS PropertiesNothing needed for active mq.
    JNDI Properties
    InitialContextFactoryorg.apache.activemq.jndi.ActiveMQInitialContextFactoryThe standard InitialContextFactory for Active MQ
    Properties
    queue.Q.REQexample.AThis defines a JNDI name Q.REQ for the request queue that points to the queue example.A
    queue.Q.RPLexample.BThis defines a JNDI name Q.RPL for the reply queue that points to the queue example.B
    Provider URL
    Provider URLtcp://localhost:61616This defines the URL of the active mq messaging system.
    +

    + +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your JMS requests in a file and presenting +a visual model of the data. +

    + +

    Select the Thread Group element and add a + listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename. +

    + +
    +Figure §-num;.2. Graph Results Listener
    + +
    + + +
    diff --git a/xdocs/usermanual/build-jms-topic-test-plan.xml b/xdocs/usermanual/build-jms-topic-test-plan.xml new file mode 100644 index 00000000000..2d1742c47dd --- /dev/null +++ b/xdocs/usermanual/build-jms-topic-test-plan.xml @@ -0,0 +1,199 @@ + + + +]> + + + + User's Manual: Building a JMS (Java Messaging Service) Test Plan + + + + +
    + +JMS requires some optional jars to be downloaded. Please refer to Getting Started for full details. + +

    In this section, you will learn how to create a +Test Plan to test JMS Providers. You will +create five subscribers and one publisher. You will create 2 thread groups and set +each one to 10 iterations. The total messages is (6 threads) x (1 message) x +(repeat 10 times) = 60 messages. To construct the Test Plan, you will use the +following elements: +Thread Group, +, +, and +.

    + +

    General notes on JMS: There are currently two JMS samplers. One uses JMS topics +and the other uses queues. Topic messages are commonly known as pub/sub messaging. +Topic messaging is generally used in cases where a message is published by a producer and +consumed by multiple subscribers. Queue messaging is generally used for transactions +where the sender expects a response. Messaging systems are quite different from +normal HTTP requests. In HTTP, a single user sends a request and gets a response. +Messaging system can work in sychronous and asynchronous mode. A JMS sampler needs +the JMS implementation jar files; for example, from Apache ActiveMQ. +See here for the list of jars provided by ActiveMQ 3.0.

    + +
    + +
    +

    The first step is add a Thread Group + element. The Thread Group tells JMeter the number of users you want to simulate, + how often the users should send requests, and how many requests they should +send.

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter Subscribers.

    + +

    Next, increase the number of users (called threads) to 5.

    + +

    In the next field, the Ramp-Up Period, set the value to 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, JMeter will immediately start all users.

    + +

    Clear the checkbox labeled "Forever", and enter a value of 10 in the Loop +Count field. This property tells JMeter how many times to repeat your test. +If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +

    Repeat the process and add another thread group. For the second thread +group, enter "Publisher" in the name field, set the number of threads to 1, +and set the iteration to 10. +

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + + +
    + +
    + +

    Make sure the required jar files are in JMeter's lib directory. If they are +not, shutdown JMeter, copy the jar files over and restart JMeter.

    + +

    Start by adding the sampler +to the Subscribers element (Add --> Sampler --> JMS Subscriber). +Then, select the JMS Subscriber element in the tree and edit the following properties: + +

      +
    1. Change the Name field to "Sample Subscriber"
    2. +
    3. If the JMS provider uses the jndi.properties file, check the box
    4. +
    5. Enter the name of the InitialContextFactory class. For example, with ActiveMQ 5.4, the value is "org.apache.activemq.jndi.ActiveMQInitialContextFactory"
    6. +
    7. Enter the provider URL. This is the URL for the JNDI server, if there is one. For example, with ActiveMQ 5.4 on local machine with default port, the value is "tcp://localhost:61616"
    8. +
    9. Enter the name of the connection factory. Please refer to the documentation +of the JMS provider for the information. For ActiveMQ, the default is "ConnectionFactory"
    10. +
    11. Enter the name of the message topic. For ActiveMQ Dynamic Topics (create topics dynamically), example value is "dynamicTopics/MyStaticTopic1" +Note: Setup at startup mean that JMeter starting to listen on the Destination at beginning of test without name change possibility. +Setup on Each sample mean that JMeter (re)starting to listen before run each JMS Subscriber sample, +this last option permit to have Destination name with some JMeter variables
    12. +
    13. If the JMS provider requires authentication, check "required" and enter the +username and password. For example, Orion JMS requires authentication, while ActiveMQ +and MQSeries does not
    14. +
    15. Enter 10 in "Number of samples to aggregate". For performance reasons, the sampler +will aggregate messages, since small messages will arrive very quickly. If the sampler +didn't aggregate the messages, JMeter wouldn't be able to keep up.
    16. +
    17. If you want to read the response, check the box
    18. +
    19. There are two client implementations for subscribers. If the JMS provider +exhibits zombie threads with one client, try the other.
    20. +
    +

    + +
    +Figure §-num;.2. JMS Subscriber
    + +

    Next add the sampler +to the Publisher element (Add --> Sampler --> JMS Subscriber). +Then, select the JMS Publisher element in the tree and edit the following properties: +

    + +
      +
    1. Change the Name field to "Sample Publisher".
    2. +
    3. If the JMS provider uses the jndi.properties file, check the box
    4. +
    5. Enter the name of the InitialContextFactory class. For example, with ActiveMQ 5.4, the value is "org.apache.activemq.jndi.ActiveMQInitialContextFactory"
    6. +
    7. Enter the provider URL. This is the URL for the JNDI server, if there is one. For example, with ActiveMQ 5.4 on local machine with default port, the value is "tcp://localhost:61616"
    8. +
    9. Enter the name of the connection factory. Please refer to the documentation +of the JMS provider for the information. For ActiveMQ, the default is "ConnectionFactory"
    10. +
    11. Enter the name of the message topic. For ActiveMQ Dynamic Topics (create topics dynamically), example value is "dynamicTopics/MyStaticTopic1". +Note: Setup at startup mean that JMeter starting connection with the Destination at beginning of test without name change possibility. +Setup on Each sample mean that JMeter (re)starting the connection before run each JMS Publisher sample, +this last option permit to have Destination name with some JMeter variables
    12. +
    13. If the JMS provider requires authentication, check "required" and enter the +username and password. For example, Orion JMS requires authentication, while ActiveMQ +and MQSeries does not
    14. +
    15. Enter 10 in "Number of samples to aggregate". For performance reasons, the sampler +will aggregate messages, since small messages will arrive very quickly. If the sampler +didn't aggregate the messages, JMeter wouldn't be able to keep up.
    16. +
    17. Select the appropriate configuration for getting the message to publish. If you +want the sampler to randomly select the message, place the messages in a directory +and select the directory using browse.
    18. +
    19. Select the message type. If the message is in object format or map message, make sure the +message is generated correctly.
    20. +
    +

    +
    +Figure §-num;.3. JMS Publisher
    + + +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data.

    + +

    Select the Test Plan element and add a listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename.

    + +
    +Figure §-num;.4. Graph Results Listener
    + +
    + + +
    diff --git a/xdocs/usermanual/build-ldap-test-plan.xml b/xdocs/usermanual/build-ldap-test-plan.xml new file mode 100644 index 00000000000..586a3dde1bb --- /dev/null +++ b/xdocs/usermanual/build-ldap-test-plan.xml @@ -0,0 +1,166 @@ + + + + +]> + + + + + + User's Manual: Building an LDAP Test Plan + + + +
    +

    In this section, you will learn how to create a basic Test Plan to test an LDAP server. +You will create four users that send requests for four tests on the LDAP server. Also, you will tell +the users to run their tests 4 times. So, the total number of requests is (4 users) x (4 requests) x +repeat 4 times) = 40 LDAP requests. To construct the Test Plan, you will use the following elements: +Thread Group, +, +, and + +.

    +

    This example assumes that the LDAP Server is available at ldap.test.com.

    +
    +
    +

    The first step you want to do with every JMeter Test Plan is to add a Thread Group element. +The Thread Group tells JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

    +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, clicking your +right mouse button to get the Add menu, and then select Add>ThreadGroup. You should now see the +Thread Group element under Test Plan. If you do not see the element, then "expand" the Test Plan tree by +clicking on the Test Plan element. +

    +Figure §-num;.1. Thread Group and final test tree
    + +

    +
    +
    +

    Begin by selecting the LDAP Users element. Click your right mouse +button to get the Add menu, and then select Add>Config Element>Login Config Element. +Then, select this new element to view its Control Panel.

    +

    Like most JMeter elements, the Login Config Element's Control Panel has a name +field that you can modify. In this example, leave this field with the default value.

    + +
    + Figure §-num;.2 Login Config Element for our Test Plan
    + +

    Enter Username field to "your LDAP Username",
    + The password field to "your LDAP Passowrd"

    + +

    These values will be used by the LDAP Requests.

    +
    + +
    +

    Begin by selecting the LDAP Users element. Click your right mouse button +to get the Add menu, and then select Add>Config Element>LDAP Request Defaults. Then, +select this new element to view its Control Panel.

    +

    Like most JMeter elements, the LDAP Request Defaults Control Panel has a name +field that you can modify. In this example, leave this field with the default value.

    + + +
    + Figure §-num;.3 LDAP Defaults for our Test Plan
    + + Enter DN field to "your LDAP Root Distinguished Name".
    + Enter LDAP Server's Servername field to "ldap.test.com".
    + The port to 389.
    + These values are default for the LDAP Requests.
    +
    + + +
    +

    In our Test Plan, we need to make four LDAP requests.

    +
      +
    1. Inbuilt Add Test
    2. +
    3. Inbuilt Search Test
    4. +
    5. Inbuilt Modify Test
    6. +
    7. Inbuilt Delete Test
    8. + +
    +

    JMeter sends requests in the order that you add them to the tree. +Start by adding the first LDAP Request to the LDAP Users element (Add> +Sampler>LDAP Request). Then, select the LDAP Request element in the tree +and edit the following properties

    +
      +
    1. Rename to "Add" this element
    2. +
    3. Select the Add Test radio button in Test Configuration group
    4. +
    +
    + Figure §-num;.4.1 LDAP Request for Inbuilt Add test
    + + +

    You do not have to set the Server Name field, port field, Username, Password +and DN because you already specified this value in the Login Config Element and +LDAP Request Defaults.

    +

    Next, add the second LDAP Request and edit the following +properties

    +
      +
    1. Rename to "Search" this element
    2. +
    3. Select the Search Test radio button in Test Configuration group
    4. +
    + Next, add the Third LDAP Request and edit the following properties +
    + Figure §-num;.4.2 LDAP Request for Inbuilt Search test
    + +
      +
    1. Rename to "Modify" this element
    2. +
    3. Select the Modify Test radio button in Test Configuration group
    4. +
    + Next, add the fourth LDAP Request and edit the following properties + +
    + Figure §-num;.4.3 LDAP Request for Inbuilt Modify test
    + +
      +
    1. Rename to "Delete" this element
    2. +
    3. Select the Delete Test radio button in Test Configuration group
    4. +
    +
    + Figure §-num;.4.4 LDAP Request for Inbuilt Delete test
    + +
    +
    +

    You can add a Response Assertion element. + This element will check the received response data by verifying if the response text is "successful". + (Add>Assertion>Response Assertion).
    Note: A this position in the tree, + the Response Assertion will be executed for each LDAP Request.

    +
      +
    1. Select Text Response Radio button in Response Field to Test group
    2. +
    3. Select Substring Radio button in Pattern Matching Rules group
    4. +
    5. Click on Add button and add the string "successful" in Pattern to Test field
    6. +
    +
    + Figure §-num;.5 LDAP Response Assertion
    +
    +
    +

    The final element you need to add to your Test Plan is a Listener. + This element is responsible for storing all of the results of your LDAP +requests in a file and presenting a visual model of the data. Select the LDAP +Users element and add a View Results in Table (Add>Listener>View Results in Table)

    +
    + Figure §-num;.6 View Results in Table Listener
    + +
    + + +
    diff --git a/xdocs/usermanual/build-ldapext-test-plan.xml b/xdocs/usermanual/build-ldapext-test-plan.xml new file mode 100644 index 00000000000..d588e8fa47a --- /dev/null +++ b/xdocs/usermanual/build-ldapext-test-plan.xml @@ -0,0 +1,467 @@ + + + + +]> + + + + + + User's Manual: Building an Extended LDAP Test Plan + + + +
    +

    +In this section, you will learn how to create a basic Test Plan to test an LDAP +server.

    +

    +As the Extended LDAP Sampler is highly configurable, this also means that it takes +some time to build a correct testplan. You can however tune it exactly up to your +needs. +

    + +

    +You will create four users that send requests for four tests on the LDAP server. Also, you will tell +the users to run their tests one time. So, the total number of requests is (1 users) x (9 requests) x +repeat 1 time) = 9 LDAP requests. To construct the Test Plan, you will use the following elements:
    +Thread Group,
    +,
    +, and
    + +

    +

    +This example assumes that the LDAP Server is available at ldap.test.com. +

    +

    +For the less experienced LDAP users, I build a small +LDAP tutorial which shortly explains +the several LDAP operations that can be used in building a complex testplan. +

    +

    +Take care when using LDAP special characters in the distinghuished name, in that case (eg, you want to use a + sign in a +distinghuished name) you need to escape the character by adding an "\" sign before that character. +extra exeption: if you want to add a \ character in a distinguished name (in an add or rename operation), you need to use 4 backslashes. +examples: +cn=dolf\+smits to add/search an entry with the name like cn=dolf+smits +cn=dolf \\ smits to search an entry with the name cn=dolf \ smits +cn=c:\\\\log.txt to add an entry with a name like cn=c:\log.txt +

    + + + +

    +The first step you want to do with every JMeter Test Plan is to add a Thread Group element. +The Thread Group tells JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

    +

    +Go ahead and add the Thread Group element by first selecting the Test Plan, clicking your +right mouse button to get the Add menu, and then select Add>Threads (Users)>Thread Group. +You should now see the Thread Group element under Test Plan. If you do not see the element, then "expand" the Test Plan tree by +clicking on the Test Plan element. +

    +

    +

    +Figure §-num;.1. Thread Group with Default Values
    + +

    +
    + +

    +Begin by selecting the LDAP Ext Users element. Click your right mouse button +to get the Add menu, and then select Add >Config Element>LDAP Extended Request Defaults. Then, +select this new element to view its Control Panel. +

    +

    +Like most JMeter elements, the LDAP Extended Request Defaults Control Panel has a name +field that you can modify. In this example, leave this field with the default value. +

    +


    + Figure §-num;.2 LDAP Defaults for our Test Plan
    +

    +

    + For each of the different operations, some default values can be filled in. + In All cases, when a default is filled in, this is used for the LDAP extended requests. + For each requst, you can override the defaults by filling in the values in the LDAP extended request sampler. + When no value is entered which is necesarry for a test, the test will fail in an unpredictable way! +

    + We will not enter any default values here, as we will build a very small testplan, so we will explain all the different fields when we add the LDAP Extended samplers. +
    + +

    +In our Test Plan, we want to use all 9 LDAP requests. +

    +
      +
    1. +Thread bind +
    2. +
    3. +Search Test +
    4. +
    5. +Compare Test +
    6. +
    7. +Single bind/unbind Test +
    8. +
    9. +Add Test +
    10. +
    11. +Modify Test +
    12. +
    13. +Rename entry (moddn) +
    14. +
    15. +Delete Test +
    16. +
    17. +Thread unbind +
    18. +
    +

    +JMeter sends requests in the order that you add them to the tree. +

    +

    +Adding a requests always start by:
    +Adding the LDAP Extended Request to the LDAP Ext Users element (Add> +Sampler>LDAP Ext Request). Then, select the LDAP Ext Request element in the tree +and edit the following properties.

    + + + +

    +

      +
    1. +Rename the element: "1. Thread bind" +
    2. +
    3. +Select the "Thread bind" button. +
    4. +
    5. +Enter the hostname value from the LDAP server in the Servername field +
    6. +
    7. +Enter the portnumber from the LDAP server (636 : ldap over SSL) in the port field +
    8. +
    9. +(Optional) Enter the baseDN in the DN field, this baseDN will be used as thestarting point for searches, add, deletes etc.
      +take care that this must be the uppermost shared level for all your request, eg When all information is stored under ou=Users, dc=test, dc=com, you can use this value in the basedn.
      +
    10. +
    11. +(Optional) Enter the distinghuised name from the user you want to use for authentication. +When this field is kept empty, an anonymous bind will be established. +
    12. +
    13. +(Optional) Enter the password for the user you want to authenticate with, an empty password will also lead to an anonymous bind. +
    14. +
    15. +(Optional) Enter a value for the connection timeout with LDAP +
    16. +
    17. +(Optional) Check the box Use Secure LDAP Protocol if you access with LDAP over SSL (ldaps) +
    18. +
    +

    +

    +

    +Figure §-num;.3.1. Thread Bind example
    +

    +
    + + +

    +

      +
    1. +Rename the element: "2. Search Test" +
    2. +
    3. +Select the "Search Test" button. +
    4. +
    5. +(Optional) enter the searchbase under which you want to perform the search, relative to the basedn, used in the thread bind request.
      +When left empty, the basedn is used as a search base, this files is important if you want to use a "base-entry" or "one-level" search (see below) +
    6. +
    7. +Enter the searchfilter, any decent LDAP serach filter will do, but for now, use something simple, like (sn=Doe) or (cn=*) +
    8. +
    9. +(Optional) Enter the scope in the scope field, it has three options: +
        +
      1. baseobject search
        only the given searchbase is used, only for checking attributes or existence. +
      2. +
      3. onelevel search
        Only search in one level below given searchbase is used +
      4. +
      5. subtree search
        Searches for object at any point below the given basedn +
      +
    10. +
    11. +(Optional) Size limit, specifies the maximun number of returned entries, +
    12. +
    13. +(Optional) Time limit, specifies the maximum number of miliseconds, the SERVER can use for performing the search. it is NOT the maximun time the application will wait.
      +When a very large returnset is returned, from a very fast server, over a very slow line, you may have to wait for ages for the completion of the search request, but this parameter will not influence this. +
    14. +
    15. (Optional) Attributes you want in the search answer. This can be used to limit the size of the answer, especially when an onject has very large attributes (like jpegPhoto). There are three possibilities: +
      1. Leave empty (the default setting must also be empty) This will return all attributes. +
      2. +
      3. Put in one empty value (""), it will request a non-existent attributes, so in reality it returns no attributes +
      4. +
      5. Put in the attributes, seperated by a semi-colon. It will return only the requested attributes +
    16. +
    17. +(Optional) Return object. Checked will return all java-object attributes, it will add these to the requested attributes, as specified above.
      +Unchecked will mean no java-object attributes will be returned. +
    18. +
    19. +(Optional) Dereference aliases. Checked will mean it will follow references, Unchecked says it will not. +
    20. +
    21. +(Optional) Parse the search results?. Checked will mean it gets all results in response data, Unchecked says it will not. +
    22. +
    +

    +

    +

    +Figure §-num;.3.2. search request example
    +

    + + +

    +

      +
    1. +Rename the element: "3. Compare Test" +
    2. +
    3. +Select the "Compare" button. +
    4. +
    5. +enter the entryname form the object on which you want the compare operation to work, relative to the basedn, eg "cn=jdoe,ou=Users" +
    6. +
    7. +Enter the compare filter, this must be in the form "attribute=value", eg "mail=jdoe@test.com" +
    8. +
    +

    +

    +

    +Figure §-num;.3.3. Compare example
    +

    +
    + + +

    +

      +
    1. +Rename the element: "4. Single bind/unbind Test" +
    2. +
    3. +Select the "Single bind/unbind" button. +
    4. +
    5. +Enter the FULL distinghuised name from the user you want to use for authentication.
      +eg. cn=jdoe,ou=Users,dc=test,dc=com +When this field is kept empty, an anonymous bind will be established. +
    6. +
    7. +Enter the password for the user you want to authenticate with, an empty password will also lead to an anonymous bind. +
    8. +
    +Take care: This single bind/unbind is in reality two seperate operations but cannot easily be split! +

    +

    +Figure §-num;.3.4. Single bind/unbind example
    +

    +
    + + +

    +

      +
    1. +Rename the element: "5. Add Test" +
    2. +
    3. +Select the "Add" button. +
    4. +
    5. +Enter the distinghuised name for the object to add, relative to the basedn. +
    6. +
    7. +Add a line in the "add test" table, fill in the attribute and value.
      +When you need the same attribute more than once, just add a new line, add the attribute again, and a different value.
      +All necessary attributes and values must be specified to pass the test, see picture!
      +(sometimes the server adds the attribute "objectClass=top", this might give a problem. +
    8. +
    +

    +

    +

    +Figure §-num;.3.5. Add request example
    +

    +
    + + +

    +

      +
    1. +Rename the element: "6. Modify Test" +
    2. +
    3. +Select the "Modify test" button. +
    4. +
    5. +Enter the distinghuised name for the object to modify, relative to the basedn. +
    6. +
    7. +Add a line in the "modify test" table, with the "add" button. +
    8. +
    9. +You need to enter the attribute you want to modify, (optional) a value, and the opcode. The meaning of this opcode: +
      1. add
        this will mean that the attribute value (not optional in this case) willbe added to the attribute.
        +When the attribute is not existing, it will be created and the value added
        +When it is existing, and defined multi-valued, the new value is added.
        +when it is existing, but single valued, it will fail.
      2. +
      3. replace
        +This will overwrite the attribute with the given new value (not optional here)
        +When the attribute is not existing, it will be created and the value added
        +When it is existing, old values are removed, the new value is added.
      4. +
      5. delete
        +When no value is given, all values will be removed
        +When a value is given, only that value will be removed
        + when the given value is not existing, the test will fail +
      +
    10. +
    11. +(Optional) Add more modifications in the "modify test" table.
      +All modifications which are specified must succeed, to let the modification test pass. When one modification fails, +NO modifications at all will be made and the entry will remain unchanged. +
    12. +
    +

    +

    +

    +Figure §-num;.3.6. Modify example
    +

    +
    + + +

    +

      +
    1. +Rename the element: "7. Rename entry (moddn)" +
    2. +
    3. +Select the "Rename Entry" button. +
    4. +
    5. +Enter the name of the entry, relative to the baseDN, in the "old entry name-Field".
      +that is, if you want to rename "cn=Little John Doe,ou=Users", and you set the baseDN to "dc=test,dc=com", +you need to enter "cn=John Junior Doe,ou=Users" in the old entry name-field. +
    6. +
    7. +Enter the new name of the entry, relative to the baseDN, in the "new distinghuised name-Field".
      +when you only change the RDN, it will simply rename the entry
      +when you also add a different subtree, eg you change from cn=john doe,ou=Users to cn=john doe,ou=oldusers, it will move the entry. +You can also move a complete subtree (If your LDAP server supports this!), eg ou=Users,ou=retired, to ou=oldusers,ou=users, +this will move the complete subtee, plus all retired people in the subtree to the new place in the tree. +
    8. +
    +

    +

    +

    +Figure §-num;.3.8. Rename example
    +

    +
    + + +

    +

      +
    1. +Rename the element: "8. Delete Test" +
    2. +
    3. +Select the "Delete" button. +
    4. +
    5. +Enter the name of the entry, relative to the baseDN, in the Delete-Field.
      +that is, if you want to remove "cn=John Junior Doe,ou=Users,dc=test,dc=com", and you set the baseDN to "dc=test,dc=com", +you need to enter "cn=John Junior Doe,ou=Users" in the Delete-field. +
    6. +
    +

    +

    +

    +Figure §-num;.3.7. Delete example
    +

    +
    + + +

    +

      +
    1. +Rename the element: 9. Thread unbind" +
    2. +
    3. +Select the "Thread unbind" button. +This will be enough as it just closes the current connection. +The information which is needed is already known by the system +
    +

    +

    +

    +Figure §-num;.3.9. Unbind example
    +

    +
    +
    + + +

    +The final element you need to add to your Test Plan is a Listener. + This element is responsible for storing all of the results of your LDAP +requests in a file and presenting a visual model of the data.Select the Thread group +element and add a View Results Tree (Add>Listener>View Results Tree) +

    +

    +

    +Figure §-num;.4. View Result Tree Listener
    +

    +

    +In this listener you have three tabs to view, the sampler result, the request and the response data. +

      +
    1. +The sampler result just contains the response time, the returncode and return message +
    2. +
    3. +The request gives a short description of the request that was made, in practice no relevant information +is contained here. +
    4. +
    5. +The response data contains the full details of the sent request, as well the full details of the received answer, +this is given in a (self defined) xml-style. +The full description can be found here. +
    6. +
    +

    +
    +
    + +
    diff --git a/xdocs/usermanual/build-monitor-test-plan.xml b/xdocs/usermanual/build-monitor-test-plan.xml new file mode 100644 index 00000000000..8e55ffb43f5 --- /dev/null +++ b/xdocs/usermanual/build-monitor-test-plan.xml @@ -0,0 +1,162 @@ + + + +]> + + + + User's Manual: Building a Monitor Test Plan + + + + +
    +

    In this section, you will learn how to create a +Test Plan to monitor webservers. Monitors +are useful for a stress testing and system management. Used with stress +testing, the monitor provides additional information about server performance. +It also makes it easier to see the relationship between server performance +and response time on the client side. As a system administration tool, the +monitor provides an easy way to monitor multiple servers from one console. +The monitor was designed to work with the status servlet in Tomcat 5. In +theory, any servlet container that supports JMX (Java Management Extension) +can port the status servlet to provide the same information.

    +

    For those who want to use the monitor with other servlet or EJB containers, +Tomcat's status servlet should work with other containers for the memory +statistics without any modifications. To get thread information, you will +need to change the MBeanServer lookup to retrieve the correct MBeans.

    + +
    + +
    +

    The first step is to add a Thread Group +element. The Thread Group tells JMeter the number of threads you want. Always use +1, since we are using JMeter as a monitor. This is very important for those not +familiar with server monitors. As a general rule, using multiple threads for a +single server is bad and can create significant stress. +

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, "expand" the Test Plan tree by clicking on the Test Plan element.

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Change the loop count to forever (or some large number) so that enough samples are generated.

    + +
    + +
    +

    Add the to the Thread Group element +(Add --> Config element --> HTTP Authorization Manager). Enter the username +and password for your webserver. Important note: the monitor only works with +Tomcat5 build 5.0.19 and newer. For instructions on how to setup Tomcat, please +refer to tomcat 5 documentation.

    +
      +
    1. leave the base URL blank
    2. +
    3. enter the username
    4. +
    5. enter the password
    6. +
    +
    + +
    + +

    Add the to the Thread Group element +(Add --> Sampler --> HTTP Request). Then, select the HTTP Request element +in the tree and edit the following properties): +

      +
    1. Change the Name field to "Server Status".
    2. +
    3. Enter the IP address or Hostname
    4. +
    5. Enter the port number
    6. +
    7. Set the Path field to "/manager/status" if you're using Tomcat.
    8. +
    9. Add a request parameter named "XML" in uppercase. Give it a value of +"true" in lowercase.
    10. +
    11. Check "Use as Monitor" at the bottom of the sampler
    12. +
    +

    + +
    + +
    + +

    Add a timer to this thread group (Add --> Timer --> Constant Timer). +Enter 5000 milliseconds in the "Thread Delay" box. In general, using intervals shorter +than 5 seconds will add stress to your server. Find out what is an acceptable interval +before you deploy the monitor in your production environment.

    + +
    + +
    +

    If you want to save the raw results from the server, add a simple data + Listener. If you want to save the + calculated statistics, enter a filename in the listener. If you want to save both + the raw data and statistics, make sure you use different filenames.

    + +

    Select the thread group element and add a listener +(Add --> Listener --> Simple Data Writer). Next, you need to specify a directory +and filename of the output file. You can either type it into the filename field, or +select the Browse button and browse to a directory and then enter a filename.

    + +
    + +
    + +

    Add the Listener by selecting the +test plan element (Add --> Listener -- > Monitor Results). +

    +By default, the Listener will select the results from the first connector in the sample response. +The Connector prefix field can be used to select a different connector. +If specified, the Listener will choose the first connector which matches the prefix. +If no match is found, then the first connector is selected. +

    +

    There are two tabs in +the monitor results listener. The first is the "Health", which displays the status of +the last sample the monitor received. The second tab is "Performance", which shows a +historical view of the server's performance. +

    + +
    +

    A quick note about how health is calculated. Typically, a server will crash if +it runs out of memory, or reached the maximum number of threads. In the case of +Tomcat 5, once the threads are maxed out, requests are placed in a queue until a +thread is available. The relative importance of threads vary between containers, so +the current implementation uses 50/50 to be conservative. A container that is more +efficient with thread management might not see any performance degradation, but +the used memory definitely will show an impact.

    +
    +

    The performance graph shows four different lines. The free memory line shows how +much free memory is left in the current allocated block. Tomcat 5 returns the maximum +memory, but it is not graphed. In a well tuned environment, the server should never +reach the maximum memory.

    +

    Note the graph has captions on both sides of the graph. On the left is percent and +the right is dead/healthy. If the memory line spikes up and down rapidly, it could +indicate memory thrashing. In those situations, it is a good idea to profile the +application with Borland OptimizeIt or JProbe. What you want to see is a regular +pattern for load, memory and threads. Any erratic behavior usually indicates poor +performance or a bug of some sort.

    + +
    + + +
    diff --git a/xdocs/usermanual/build-test-plan.xml b/xdocs/usermanual/build-test-plan.xml new file mode 100644 index 00000000000..2b3046ccabf --- /dev/null +++ b/xdocs/usermanual/build-test-plan.xml @@ -0,0 +1,154 @@ + + + + +]> + + + + + User's Manual: Building a Test Plan + + + + +
    +

    A test plan describes a series of steps JMeter will execute when run. A complete +test plan will consist of one or more Thread Groups, logic controllers, sample generating +controllers, listeners, timers, assertions, and configuration elements. +

    + + +

    Adding elements to a test plan can be done by right-clicking on an element in the +tree, and choosing a new element from the "add" list. Alternatively, elements can +be loaded from file and added by choosing the "merge" or "open" option.

    + +

    To remove an element, make sure the element is selected, right-click on the element, +and choose the "remove" option.

    +
    + + +

    To load an element from file, right click on the existing tree elements to which +you want to add the loaded element, and select the "merge" option. Choose the file where +your elements are saved. JMeter will merge the elements into the tree.

    + +

    To save tree elements, right click on an element and choose the "Save Selection As ..." option. +JMeter will save the element selected, plus all child elements beneath it. In this way, +you can save test tree fragments and individual elements for later use.

    + +The workbench is not automatically saved with the test plan, but it can be saved separately as above. +
    + + +

    Any element in the test tree will present controls in JMeter's right-hand frame. These +controls allow you to configure the behavior of that particular test element. What can be +configured for an element depends on what type of element it is.

    + +The Test Tree itself can be manipulated by dragging and dropping components around the test tree. +
    + + +

    Although it is not required, we recommend that you save the Test Plan to a +file before running it. To save the Test Plan, select "Save" or "Save Test Plan As ..." from the +File menu (with the latest release, it is no longer necessary to select the +Test Plan element first).

    + +JMeter allows you to save the entire Test Plan tree or +only a portion of it. To save only the elements located in a particular "branch" +of the Test Plan tree, select the Test Plan element in the tree from which to start +the "branch", and then click your right mouse button to access the "Save Selection As ..." menu item. +Alternatively, select the appropriate Test Plan element and then select "Save Selection As ..." from +the Edit menu. + +
    + + +

    To run your test plan, choose "Start" (Control + r) from the "Run" menu item. +When JMeter is running, it shows a small green box at the right hand end of the section just under the menu bar. +You can also check the "Run" menu. +If "Start" is disabled, and "Stop" is enabled, +then JMeter is running your test plan (or, at least, it thinks it is).

    +

    +The numbers to the left of the green box are the number of active threads / total number of threads. +These only apply to a locally run test; they do not include any threads started on remote systems when using client-server mode. +

    +
    + + +

    +There are two types of stop command available from the menu: +

      +
    • Stop (Control + '.') - stops the threads immediately if possible. +In Versions of JMeter after 2.3.2, many samplers are now Interruptible which means that active samples can be terminated early. +The stop command will check that all threads have stopped within the default timeout, which is 5000 ms = 5 seconds. +[This can be changed using the JMeter property jmeterengine.threadstop.wait] +If the threads have not stopped, then a message is displayed. +The Stop command can be retried, but if it fails, then it is necessary to exit JMeter to clean up. +
    • +
    • Shutdown (Control + ',')- requests the threads to stop at the end of any current work. +Will not interrupt any active samples. +The modal shutdown dialog box will remain active until all threads have stopped.
    • +
    +Versions of JMeter after 2.3.2 allow a Stop to be initiated if Shutdown is taking too long. +Close the Shutdown dialog box and select Run/Stop, or just press Control + '.'. +

    +

    +When running JMeter in non-GUI mode, there is no Menu, and JMeter does not react to keystrokes such as Control + '.'. +So in versions after 2.3.2, JMeter non-GUI mode will listen for commands on a specific port +(default 4445, see the JMeter property jmeterengine.nongui.port). +In versions after 2.4, JMeter supports automatic choice of an alternate port if the default port is being used +(for example by another JMeter instance). In this case, JMeter will try the next higher port, continuing until +it reaches the JMeter property jmeterengine.nongui.maxport) which defaults to 4455. +If maxport is less than or equal to port, port scanning will not take place. +Note that JMeter 2.4 and earlier did not set up the listener for non-GUI clients, only non-GUI standalone tests; +this has been fixed. +

    +The chosen port is displayed in the console window. +
    +The commands currently supported are: +

      +
    • Shutdown - graceful shutdown
    • +
    • StopTestNow - immediate shutdown
    • +
    +These commands can be sent by using the shutdown[.cmd|.sh] or stoptest[.cmd|.sh] script +respectively. The scripts are to be found in the JMeter bin directory. +The commands will only be accepted if the script is run from the same host. +

    +
    + + +

    +JMeter reports warnings and errors to the jmeter.log file, as well as some information on the test run itself. +JMeter shows at the right hand end of its window, the number of warnings/errors found in jmeter.log file next to the warning icon. +Click on the warning icon to show the jmeter.log file at the bottom of JMeter's window. +Just occasionally there may be some errors that JMeter is unable to trap and log; these will appear on the command console. +If a test is not behaving as you expect, please check the log file in case any errors have been reported (e.g. perhaps a syntax error in a function call). +

    +

    +Sampling errors (e.g. HTTP 404 - file not found) are not normally reported in the log file. +Instead these are stored as attributes of the sample result. +The status of a sample result can be seen in the various different Listeners. +

    +
    + +
    + + +
    + diff --git a/xdocs/usermanual/build-web-test-plan.xml b/xdocs/usermanual/build-web-test-plan.xml new file mode 100644 index 00000000000..69d6ded690f --- /dev/null +++ b/xdocs/usermanual/build-web-test-plan.xml @@ -0,0 +1,232 @@ + + + + +]> + + + + + User's Manual: Building a Web Test Plan + + + + +
    +

    In this section, you will learn how to create a basic +Test Plan to test a Web site. You will +create five users that send requests to two pages on the JMeter Web site. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (5 users) x (2 requests) x (repeat 2 times) = 20 HTTP requests. To +construct the Test Plan, you will use the following elements: +Thread Group, +, +, and +.

    + +

    For a more advanced Test Plan, see +Building an Advanced Web Test Plan.

    +
    + + + +
    +

    The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and how many requests they should send.

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter JMeter Users.

    + +

    Next, increase the number of users (called threads) to 5.

    + +

    In the next field, the Ramp-Up Period, leave the the default value of 1 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Finally enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. If you enter a loop count value of 1, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + +

    See Figure §-num;.2 for the completed JMeter Users Thread Group.

    + +
    +Figure §-num;.2. JMeter Users Thread Group
    + +
    + +
    +

    Now that we have defined our users, it is time to define the tasks that they +will be performing. In this section, you will specify the default settings +for your HTTP requests. And then, in section §-num;.3, you will add HTTP Request +elements which use some of the default settings you specified here.

    + +

    Begin by selecting the JMeter Users (Thread Group) element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> HTTP Request +Defaults. Then select this new element to view its Control Panel (see Figure §-num;.3). +

    + +
    +Figure §-num;.3. HTTP Request Defaults
    + +

    +Like most JMeter elements, the Control +Panel has a name field that you can modify. In this example, leave this field with +the default value.

    + +

    Skip to the next field, which is the Web Server's Server Name/IP. For the +Test Plan that you are building, all HTTP requests will be sent to the same +Web server, jmeter.apache.org. Enter this domain name into the field. +This is the only field that we will specify a default, so leave the remaining +fields with their default values.

    + +The HTTP Request Defaults element does not tell JMeter +to send an HTTP request. It simply defines the default values that the +HTTP Request elements use. + +

    See Figure §-num;.4 for the completed HTTP Request Defaults element

    + +
    +Figure §-num;.4. HTTP Defaults for our Test Plan
    + +
    + +
    +

    Nearly all web testing should use cookie support, unless your application +specifically doesn't use cookies. To add cookie support, simply add an + to each Thread +Group in your test plan. This will ensure that each thread gets its own +cookies, but shared across all objects.

    + +

    To add the , simply select the +Thread Group, and choose Add --> +Config Element --> HTTP +Cookie Manager, either from the Edit Menu, or from the right-click pop-up menu.

    +
    + + +
    + +

    In our Test Plan, we need to make two HTTP requests. The first one is for the +JMeter home page (http://jmeter.apache.org/), and the second one is for the +Changes page (http://jmeter.apache.org/changes.html).

    + +JMeter sends requests in the order that they appear in the tree. + +

    Start by adding the first +to the JMeter Users element (Add --> Sampler --> HTTP Request). +Then, select the HTTP Request element in the tree and edit the following properties +(see Figure §-num;.5): +

      +
    1. Change the Name field to "Home Page".
    2. +
    3. Set the Path field to "/". Remember that you do not have to set the Server +Name field because you already specified this value in the HTTP Request Defaults +element.
    4. +
    +

    + +
    +Figure §-num;.5. HTTP Request for JMeter Home Page
    + +

    Next, add the second HTTP Request and edit the following properties (see +Figure §-num;.6: +

      +
    1. Change the Name field to "Changes".
    2. +
    3. Set the Path field to "/changes.html".
    4. +
    +

    + +
    +Figure §-num;.6. HTTP Request for JMeter Changes Page
    + +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data.

    + +

    Select the JMeter Users element and add a listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename.

    + +
    +Figure §-num;.7. Graph Results Listener
    + +
    + +
    +

    +It's not the case here, but some web-sites require you to login before permitting you to perform certain actions. +In a web-browser, the login will be shown as a form for the user name and password, +and a button to submit the form. +The button generates a POST request, passing the values of the form items as parameters. +

    +

    +To do this in JMeter, add an HTTP Request, and set the method to POST. +You'll need to know the names of the fields used by the form, and the target page. +These can be found out by inspecting the code of the login page. +[If this is difficult to do, you can use the JMeter Proxy Recorder to record the login sequence.] +Set the path to the target of the submit button. +Click the Add button twice and enter the username and password details. +Sometimes the login form contains additional hidden fields. +These will need to be added as well. +

    +
    +Figure §-num;.8. Sample HTTP login request
    + +
    + + +
    diff --git a/xdocs/usermanual/build-ws-test-plan.xml b/xdocs/usermanual/build-ws-test-plan.xml new file mode 100644 index 00000000000..f81464d1636 --- /dev/null +++ b/xdocs/usermanual/build-ws-test-plan.xml @@ -0,0 +1,158 @@ + + + + +]> + + + + + User's Manual: Building a SOAP WebService Test Plan + + + + +
    +

    In this section, you will learn how to create a +Test Plan to test a WebService. You will +create five users that send requests to One page. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (5 users) x (1 requests) x (repeat 2 times) = 10 HTTP requests. To +construct the Test Plan, you will use the following elements: +Thread Group, +, and +.

    + +

    If the sampler appears to be getting an error from the webservice, double check the +SOAP message and make sure the format is correct. In particular, make sure the +xmlns attributes are exactly the same as the WSDL. If the xml namespace is +different, the webservice will likely return an error.

    + +
    + +
    + +

    In our Test Plan, we will use a .NET webservice. We won't go into the details of writing a +webservice. If you don't know how to write a webservice, google for +webservice and familiarize yourself with writing webservices for +Java and .NET. It should be noted there is a significant difference +between how .NET and Java implement webservices. The topic is too +broad to cover in the user manual. Please refer to other sources to +get a better idea of the differences.

    + +JMeter sends requests in the order that they appear in the tree. + +

    Start by using menu File > "Templates..." and select template "Building a SOAP Webservice Test Plan". +Then, click "Create" button. + +

    +Figure §-num;.1.0. Webservice Template
    +Change the following: +
      +
    1. In "HTTP Request Defaults" change "Server Name of IP"
    2. +
    3. In "Soap Request", change "Path:" +
      Figure §-num;.1.1 Webservice Path
      +
    4. +
    +

    + +

    Next, select "HTTP Header Manager" and update "SOAPAction" header to match your webservice. +Some webservices may not use SOAPAction in this case remove it.
    +Currently, only .NET uses SOAPAction, so it is normal to have a blank SOAPAction for all other webservices. The list includes JWSDP, Weblogic, Axis, The Mind Electric Glue, and gSoap. +

    +
    Figure §-num;.1.2 Webservice Headers
    + +

    The last step is to paste the SOAP message in the "Body Data" +text area.

    +
    Figure §-num;.1.3 Webservice Body
    + + +
    + +
    +

    The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

    + +

    Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.2 +below)

    + +
    +Figure §-num;.2. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter JMeter Users.

    + +

    Next, increase the number of users (called threads) to 10.

    + +

    In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Finally, clear the checkbox labeled "Forever", and enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + +

    See Figure §-num;.2 for the completed JMeter Users Thread Group.

    + +
    +Figure §-num;.3. JMeter Users Thread Group
    +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data.

    + +

    Select the JMeter Users element and add a listener (Add --> Listener +--> Aggregate Graph). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename.

    + +
    +Figure §-num;.4. Graph Results Listener
    + +
    + +
    +

    Testing a REST Webservice is very similar as you only need to modify in HTTP Request +

      +
    • Method: to select the one you want to test
    • +
    • Body Data: which can be JSON, XML or any custom text
    • +
    +You may also need to modify "HTTP Header Manager" to select the correct "Content-Type" +

    +
    + +
    diff --git a/xdocs/usermanual/component_reference.xml b/xdocs/usermanual/component_reference.xml new file mode 100644 index 00000000000..f0b240f936e --- /dev/null +++ b/xdocs/usermanual/component_reference.xml @@ -0,0 +1,6520 @@ + + + +]> + + + + User's Manual: Component Reference + + + + + +
    + +

    + +

    + + Several test elements use JMeter properties to control their behaviour. + These properties are normally resolved when the class is loaded. + This generally occurs before the test plan starts, so it's not possible to change the settings by using the __setProperty() function. + +

    +

    +
    +
    + +
    + +

    + Samplers perform the actual work of JMeter. + Each sampler (except ) generates one or more sample results. + The sample results have various attributes (success/fail, elapsed time, data size etc) and can be viewed in the various listeners. +

    +
    + + +This controller lets you send an FTP "retrieve file" or "upload file" request to an FTP server. +If you are going to send multiple requests to the same FTP server, consider +using a Configuration +Element so you do not have to enter the same information for each FTP Request Generative +Controller. When downloading a file, it can be stored on disk (Local File) or in the Response Data, or both. +

    +Latency is set to the time it takes to login (versions of JMeter after 2.3.1). +

    +
    + + Descriptive name for this sampler that is shown in the tree. + Domain name or IP address of the FTP server. + Port to use. If this is >0, then this specific port is used, + otherwise JMeter uses the default FTP port. + File to retrieve or name of destination file to upload. + File to upload, or destination for downloads (defaults to remote file name). + Provides the contents for the upload, overrides the Local File property. + Whether to retrieve or upload a file. + Check this to use Binary mode (default Ascii) + + Whether to store contents of retrieved file in response data. + If the mode is Ascii, then the contents will be visible in the . + + FTP account username. + FTP account password. N.B. This will be visible in the test plan. + + + Assertions + + Building an FTP Test Plan + + +
    + + + + +

    This sampler lets you send an HTTP/HTTPS request to a web server. It + also lets you control whether or not JMeter parses HTML files for images and + other embedded resources and sends HTTP requests to retrieve them. + The following types of embedded resource are retrieved:

    +
      +
    • images
    • +
    • applets
    • +
    • stylesheets
    • +
    • external scripts
    • +
    • frames, iframes
    • +
    • background images (body, table, TD, TR)
    • +
    • background sound
    • +
    +

    + The default parser is htmlparser. + This can be changed by using the property "htmlparser.classname" - see jmeter.properties for details. +

    +

    If you are going to send multiple requests to the same web server, consider + using an + Configuration Element so you do not have to enter the same information for each + HTTP Request.

    + +

    Or, instead of manually adding HTTP Requests, you may want to use + JMeter's to create + them. This can save you time if you have a lot of HTTP requests or requests with many + parameters.

    + +

    There are two different test elements used to define the samplers:

    +
      +
    • AJP/1.3 Sampler - uses the Tomcat mod_jk protocol (allows testing of Tomcat in AJP mode without needing Apache httpd) + The AJP Sampler does not support multiple file upload; only the first file will be used. +
    • +
    • HTTP Request - this has an implementation drop-down box, which selects the HTTP protocol implementation to be used: +
      +
      Java
      uses the HTTP implementation provided by the JVM. + This has some limitations in comparison with the HttpClient implementations - see below.
      +
      HTTPClient3.1
      uses Apache Commons HttpClient 3.1. + This is no longer being developed, and support for this may be dropped in a future JMeter release.
      +
      HTTPClient4
      uses Apache HttpComponents HttpClient 4.x.
      +
      Blank Value
      does not set implementation on HTTP Samplers, so relies on HTTP Request Defaults if present or on jmeter.httpsampler property defined in jmeter.properties
      +
      +
    • +
    +

    The Java HTTP implementation has some limitations:

    +
      +
    • There is no control over how connections are re-used. + When a connection is released by JMeter, it may or may not be re-used by the same thread.
    • +
    • The API is best suited to single-threaded usage - various settings + are defined via system properties, and therefore apply to all connections.
    • +
    • There is a bug in the handling of HTTPS via a Proxy (the CONNECT is not handled correctly). + See Java bugs 6226610 and 6208335. +
    • +
    • It does not support virtual hosts.
    • +
    • It does not support the following methods: COPY, LOCK, MKCOL, MOVE, + PATCH, PROPFIND, PROPPATCH, UNLOCK, REPORT, MKCALENDAR.
    • +
    • It does not support client based certificate testing with Keystore Config.
    • +
    +

    Note: the FILE protocol is intended for testing purposes only. + It is handled by the same code regardless of which HTTP Sampler is used.

    +

    If the request requires server or proxy login authorization (i.e. where a browser would create a pop-up dialog box), + you will also have to add an Configuration Element. + For normal logins (i.e. where the user enters login information in a form), you will need to work out what the form submit button does, + and create an HTTP request with the appropriate method (usually POST) + and the appropriate parameters from the form definition. + If the page uses HTTP, you can use the JMeter Proxy to capture the login sequence. +

    +

    + In versions of JMeter up to 2.2, only a single SSL context was used for all threads and samplers. + This did not generate the proper load for multiple users. + A separate SSL context is now used for each thread. + To revert to the original behaviour, set the JMeter property:

    + +https.sessioncontext.shared=true + + By default, the SSL context is retained for the duration of the test. + In versions of JMeter from 2.5.1, the SSL session can be optionally reset for each test iteration. + To enable this, set the JMeter property: + +https.use.cached.ssl.context=false + + + Note: this does not apply to the Java HTTP implementation. + + JMeter defaults to the SSL protocol level TLS. + If the server needs a different level, e.g. SSLv3, change the JMeter property, for example: + +https.default.protocol=SSLv3 + +

    + JMeter also allows one to enable additional protocols, by changing the property https.socket.protocols. +

    +

    If the request uses cookies, then you will also need an + . You can + add either of these elements to the Thread Group or the HTTP Request. If you have + more than one HTTP Request that needs authorizations or cookies, then add the + elements to the Thread Group. That way, all HTTP Request controllers will share the + same Authorization Manager and Cookie Manager elements.

    + +

    If the request uses a technique called "URL Rewriting" to maintain sessions, + then see section + 6.1 Handling User Sessions With URL Rewriting + for additional configuration steps.

    +
    + + Descriptive name for this sampler that is shown in the tree. + + Domain name or IP address of the web server. e.g. www.example.com. [Do not include the http:// prefix.] + Note: in JMeter 2.5 (and later) if the "Host" header is defined in a Header Manager, then this will be used + as the virtual host name. + + Port the web server is listening to. Default: 80 + Connection Timeout. Number of milliseconds to wait for a connection to open. + Response Timeout. Number of milliseconds to wait for a response. + Note that this applies to each wait for a response. If the server response is sent in several chunks, the overall + elapsed time may be longer than the timeout. +

    A can be used to detect responses that take too long to complete.

    +
    + Hostname or IP address of a proxy server to perform request. [Do not include the http:// prefix.] + Port the proxy server is listening to. + (Optional) username for proxy server. + (Optional) password for proxy server. (N.B. this is stored unencrypted in the test plan) + Java, HttpClient3.1, HttpClient4. + If not specified (and not defined by HTTP Request Defaults), the default depends on the value of the JMeter property + jmeter.httpsampler, failing that, the HttpClient4 implementation is used. + HTTP, HTTPS or FILE. Default: HTTP + GET, POST, HEAD, TRACE, + OPTIONS, PUT, DELETE, PATCH (not supported for + JAVA implementation). With HttpClient4, the following methods related to WebDav are + also allowed: COPY, LOCK, MKCOL, MOVE, + PROPFIND, PROPPATCH, UNLOCK, REPORT, MKCALENDAR. + + Content encoding to be used (for POST, PUT, PATCH and FILE). + This the the character encoding to be used, and is not related to the Content-Encoding HTTP header. + + + Sets the underlying http protocol handler to automatically follow redirects, + so they are not seen by JMeter, and thus will not appear as samples. + Should only be used for GET and HEAD requests. + The HttpClient sampler will reject attempts to use it for POST or PUT. + Warning: see below for information on cookie and header handling. + + + This only has any effect if "Redirect Automatically" is not enabled. + If set, the JMeter sampler will check if the response is a redirect and follow it if so. + The initial redirect and further responses will appear as additional samples. + The URL and data fields of the parent sample will be taken from the final (non-redirected) + sample, but the parent byte count and elapsed time include all samples. + The latency is taken from the initial response (versions of JMeter after 2.3.4 - previously it was zero). + Note that the HttpClient sampler may log the following message: + "Redirect requested but followRedirects is disabled" + This can be ignored. +
    + In versions after 2.3.4, JMeter will collapse paths of the form '/../segment' in + both absolute and relative redirect URLs. For example http://host/one/../two will be collapsed into http://host/two. + If necessary, this behaviour can be suppressed by setting the JMeter property + httpsampler.redirect.removeslashdotdot=false +
    + JMeter sets the Connection: keep-alive header. This does not work properly with the default HTTP implementation, as connection re-use is not under user-control. + It does work with the Apache HttpComponents HttpClient implementations. + + Use a multipart/form-data or application/x-www-form-urlencoded post request + + + When using multipart/form-data, this suppresses the Content-Type and + Content-Transfer-Encoding headers; only the Content-Disposition header is sent. + + The path to resource (for example, /servlets/myServlet). If the +resource requires query string parameters, add them below in the +"Send Parameters With the Request" section. + +As a special case, if the path starts with "http://" or "https://" then this is used as the full URL. + +In this case, the server, port and protocol fields are ignored; parameters are also ignored for GET and DELETE methods. +Also please note that the path is not encoded - apart from replacing spaces with %20 - +so unsafe characters may need to be encoded to avoid errors such as URISyntaxException. + + The query string will + be generated from the list of parameters you provide. Each parameter has a name and + value, the options to encode the parameter, and an option to include or exclude an equals sign (some applications + don't expect an equals when the value is the empty string). The query string will be generated in the correct fashion, depending on + the choice of "Method" you made (ie if you chose GET or DELETE, the query string will be + appended to the URL, if POST or PUT, then it will be sent separately). Also, if you are + sending a file using a multipart form, the query string will be created using the + multipart form specifications. + See below for some further information on parameter handling. +

    + Additionally, you can specify whether each parameter should be URL encoded. If you are not sure what this + means, it is probably best to select it. If your values contain characters such as the following then encoding is usually required.: +

    +
      +
    • ASCII Control Chars
    • +
    • Non-ASCII characters
    • +
    • Reserved characters:URLs use some characters for special use in defining their syntax. When these characters are not used in their special role inside a URL, they need to be encoded, example : '$', '&', '+', ',' , '/', ':', ';', '=', '?', '@'
    • +
    • Unsafe characters: Some characters present the possibility of being misunderstood within URLs for various reasons. These characters should also always be encoded, example : ' ', '<', '>', '#', '%', ...
    • +
    +
    + Name of the file to send. If left blank, JMeter + does not send a file, if filled in, JMeter automatically sends the request as + a multipart form request. +

    + If it is a POST or PUT or PATCH request and there is a single file whose 'Parameter name' attribute (below) is omitted, + then the file is sent as the entire body + of the request, i.e. no wrappers are added. This allows arbitrary bodies to be sent. This functionality is present for POST requests + after version 2.2, and also for PUT requests after version 2.3. + See below for some further information on parameter handling. +

    +
    + Value of the "name" web request parameter. + MIME type (for example, text/plain). + If it is a POST or PUT or PATCH request and either the 'name' attribute (below) are omitted or the request body is + constructed from parameter values only, then the value of this field is used as the value of the + content-type request header. + + Tell JMeter to parse the HTML file +and send HTTP/HTTPS requests for all images, Java applets, JavaScript files, CSSs, etc. referenced in the file. + See below for more details. + + For use with the listener. + + If this is selected, then the response is not stored in the sample result. + Instead, the 32 character MD5 hash of the data is calculated and stored instead. + This is intended for testing large amounts of data. + + + If present, this must be a regular expression that is used to match against any embedded URLs found. + So if you only want to download embedded resources from http://example.com/, use the expression: + http://example\.com/.* + + Use a pool of concurrent connections to get embedded resources. + Pool size for concurrent connections used to get embedded resources. + + [Only for HTTP Request with HTTPClient implementation]

    + To distinguish the source address value, select the type of these: +
      +
    • Select IP/Hostname to use a specific IP address or a (local) hostname
    • +
    • Select Device to pick the first available address for that interface which + this may be either IPv4 or IPv6
    • +
    • Select Device IPv4 to select the IPv4 address of the device name (like eth0, lo, em0, etc.)
    • +
    • Select Device IPv6 to select the IPv6 address of the device name (like eth0, lo, em0, etc.)
    • +
    +
    + + [Only for HTTP Request with HTTPClient implementation]

    + This property is used to enable IP Spoofing. + It override the default local IP address for this sample. + The JMeter host must have multiple IP addresses (i.e. IP aliases, network interfaces, devices). + The value can be a host name, IP address, or a network interface device such as "eth0" or "lo" or "wlan0".

    + If the property httpclient.localaddress is defined, that is used for all HttpClient requests. +
    +
    + +When using Automatic Redirection, cookies are only sent for the initial URL. +This can cause unexpected behaviour for web-sites that redirect to a local server. +E.g. if www.example.com redirects to www.example.co.uk. +In this case the server will probably return cookies for both URLs, but JMeter will only see the cookies for the last +host, i.e. www.example.co.uk. If the next request in the test plan uses www.example.com, +rather than www.example.co.uk, it will not get the correct cookies. +Likewise, Headers are sent for the initial request, and won't be sent for the redirect. +This is generally only a problem for manually created test plans, +as a test plan created using a recorder would continue from the redirected URL. + +

    +Parameter Handling:

    +For the POST and PUT method, if there is no file to send, and the name(s) of the parameter(s) are omitted, +then the body is created by concatenating all the value(s) of the parameters. +Note that the values are concatenated without adding any end-of-line characters. +These can be added by using the __char() function in the value fields. +This allows arbitrary bodies to be sent. +The values are encoded if the encoding flag is set (versions of JMeter after 2.3). +See also the MIME Type above how you can control the content-type request header that is sent. +

    +For other methods, if the name of the parameter is missing, +then the parameter is ignored. This allows the use of optional parameters defined by variables. +(versions of JMeter after 2.3) +

    +
    +

    Since JMeter 2.6, you have the option to switch to Post Body when a request has only unnamed parameters +(or no parameters at all). +This option is useful in the following cases (amongst others):

    +
      +
    • GWT RPC HTTP Request
    • +
    • JSON REST HTTP Request
    • +
    • XML REST HTTP Request
    • +
    • SOAP HTTP Request
    • +
    + +Note that once you leave the Tree node, you cannot switch back to the parameter tab unless you clear the Post Body tab of data. + +

    +In Post Body mode, each line will be sent with CRLF appended, apart from the last line. +To send a CRLF after the last line of data, just ensure that there is an empty line following it. +(This cannot be seen, except by noting whether the cursor can be placed on the subsequent line.) +

    +
    Figure 1 - HTTP Request with one unnamed parameter
    +
    Figure 2 - Confirm dialog to switch
    +
    Figure 3 - HTTP Request using Body Data
    + +

    +Method Handling:

    +The POST, PUT and PATCH request methods work similarly, except that the PUT and PATCH methods do not support multipart requests +or file upload. +The PUT and PATCH method body must be provided as one of the following:

    +
      +
    • define the body as a file with empty Parameter name field; in which case the MIME Type is used as the Content-Type
    • +
    • define the body as parameter value(s) with no name
    • +
    • use the Post Body tab
    • +
    +

    +If you define any parameters with a name in either the sampler or HTTP +defaults then nothing is sent. +PUT and PATCH require a Content-Type. +If not using a file, attach a Header Manager to the sampler and define the Content-Type there. +The GET and DELETE request methods work similarly to each other. +

    +

    Upto and including JMeter 2.1.1, only responses with the content-type "text/html" were scanned for +embedded resources. Other content-types were assumed to be something other than HTML. +JMeter 2.1.2 introduces the a new property HTTPResponse.parsers, which is a list of parser ids, + e.g. htmlParser and wmlParser. For each id found, JMeter checks two further properties:

    +
      +
    • id.types - a list of content types
    • +
    • id.className - the parser to be used to extract the embedded resources
    • +
    +

    See jmeter.properties file for the details of the settings. + If the HTTPResponse.parser property is not set, JMeter reverts to the previous behaviour, + i.e. only text/html responses will be scanned

    +Emulating slow connections:

    +HttpClient31, HttpClient4 and Java Sampler support emulation of slow connections; see the following entries in jmeter.properties: + +# Define characters per second > 0 to emulate slow connections +#httpclient.socket.http.cps=0 +#httpclient.socket.https.cps=0 + +

    Response size calculation

    +Optional properties to allow change the method to get response size:

    +

    • Gets the real network size in bytes for the body response +sampleresult.getbytes.body_real_size=true
    • +
    • Add HTTP headers to full response size +sampleresult.getbytes.headers_size=true
    + + +The Java and HttpClient3 inplementations do not include transport overhead such as +chunk headers in the response body size.

    +The HttpClient4 implementation does include the overhead in the response body size, +so the value may be greater than the number of bytes in the response content. +
    + +Versions of JMeter before 2.5 returns only data response size (uncompressed if request uses gzip/deflate mode). +

    To return to settings before version 2.5, set the two properties to false.
    +

    +

    +Retry handling

    +In version 2.5 of JMeter, the HttpClient4 and Commons HttpClient 3.1 samplers used the default retry count, which was 3. +In later versions, the retry count has been set to 1, which is what the Java implementation appears to do. +The retry count can be overridden by setting the relevant JMeter property, for example: + +httpclient4.retrycount=3 +httpclient3.retrycount=3 + +

    +

    +Note: Certificates does not conform to algorithm constraints

    +You may encounter the following error: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints + if you run a HTTPS request on a web site with a SSL certificate (itself or one of SSL certificates in its chain of trust) with a signature + algorithm using MD2 (like md2WithRSAEncryption) or with a SSL certificate with a size lower than 1024 bits. +

    +This error is related to increased security in Java 7 version u16 (MD2) and version u40 (Certificate size lower than 1024 bits), and Java 8 too. +

    +To allow you to perform your HTTPS request, you can downgrade the security of your Java installation by editing +the Java jdk.certpath.disabledAlgorithms property. Remove the MD2 value or the constraint on size, depending on your case. +

    +This property is in this file: +

    JAVA_HOME/jre/lib/security/java.security
    +See 56357 for details. +

    + + Assertion + Building a Web Test Plan + Building an Advanced Web Test Plan + + + + + + + HTTP Requests and Session ID's: URL Rewriting + + +
    + + + +

    This sampler lets you send an JDBC Request (an SQL query) to a database.

    +

    Before using this you need to set up a + Configuration element +

    +

    +If the Variable Names list is provided, then for each row returned by a Select statement, the variables are set up +with the value of the corresponding column (if a variable name is provided), and the count of rows is also set up. +For example, if the Select statement returns 2 rows of 3 columns, and the variable list is A,,C, +then the following variables will be set up: +

    +A_#=2 (number of rows)
    +A_1=column 1, row 1
    +A_2=column 1, row 2
    +C_#=2 (number of rows)
    +C_1=column 3, row 1
    +C_2=column 3, row 2
    +
    +If the Select statement returns zero rows, then the A_# and C_# variables would be set to 0, and no other variables would be set. +

    +

    +Old variables are cleared if necessary - e.g. if the first select retrieves six rows and a second select returns only three rows, +the additional variables for rows four, five and six will be removed. +

    +The latency time is set from the time it took to acquire a connection. +
    + + + Descriptive name for this sampler that is shown in the tree. + + Name of the JMeter variable that the connection pool is bound to. + This must agree with the 'Variable Name' field of a . + + Set this according to the statement type: +
      +
    • Select Statement
    • +
    • Update Statement - use this for Inserts and Deletes as well
    • +
    • Callable Statement
    • +
    • Prepared Select Statement
    • +
    • Prepared Update Statement - use this for Inserts and Deletes as well
    • +
    • Commit
    • +
    • Rollback
    • +
    • Autocommit(false)
    • +
    • Autocommit(true)
    • +
    • Edit - this should be a variable reference that evaluates to one of the above
    • +
    + + When "Prepared Select Statement", "Prepared Update Statement" or "Callable Statement" types are selected, a Statement Cache per connection is used by JDBC Request. + It stores by default up to 100 PreparedStatements per connection, this can impact your database (Open Cursors). + +
    + + SQL query. + Do not enter a trailing semi-colon. + There is generally no need to use { and } to enclose Callable statements; + however they may be used if the database uses a non-standard syntax. + [The JDBC driver automatically converts the statement if necessary when it is enclosed in {}]. + For example: +
      +
    • select * from t_customers where id=23
    • +
    • CALL SYSCS_UTIL.SYSCS_EXPORT_TABLE (null,?, ?, null, null, null) +
        +
      • Parameter values: tablename,filename
      • +
      • Parameter types: VARCHAR,VARCHAR
      • +
      +
    • + The second example assumes you are using Apache Derby. +
    +
    + + Comma-separated list of parameter values. Use ]NULL[ to indicate a NULL parameter. + (If required, the null string can be changed by defining the property "jdbcsampler.nullmarker".) +

    + The list must be enclosed in double-quotes if any of the values contain a comma or double-quote, + and any embedded double-quotes must be doubled-up, for example: + "Dbl-Quote: "" and Comma: ," + There must be as many values as there are placeholders in the statement even if your parameters are OUT ones, be sure to set a value even if the value will not be used (for example in a CallableStatement). +
    + + Comma-separated list of SQL parameter types (e.g. INTEGER, DATE, VARCHAR, DOUBLE) or integer values of Constants when for example you use custom database types proposed by driver (-10 for OracleTypes.CURSOR for example).
    + These are defined as fields in the class java.sql.Types, see for example:
    + Javadoc for java.sql.Types.
    + [Note: JMeter will use whatever types are defined by the runtime JVM, + so if you are running on a different JVM, be sure to check the appropriate document]
    + If the callable statement has INOUT or OUT parameters, then these must be indicated by prefixing the + appropriate parameter types, e.g. instead of "INTEGER", use "INOUT INTEGER".
    + If not specified, "IN" is assumed, i.e. "DATE" is the same as "IN DATE". +

    + If the type is not one of the fields found in java.sql.Types, versions of JMeter after 2.3.2 also + accept the corresponding integer number, e.g. since OracleTypes.CURSOR == -10, you can use "INOUT -10". +

    + There must be as many types as there are placeholders in the statement. +
    + Comma-separated list of variable names to hold values returned by Select statements, Prepared Select Statements or CallableStatement. + Note that when used with CallableStatement, list of variables must be in the same sequence as the OUT parameters returned by the call. + If there are less variable names than OUT parameters only as many results shall be stored in the thread-context variables as variable names were supplied. + If more variable names than OUT parameters exist, the additional variables will be ignored + + If specified, this will create an Object variable containing a list of row maps. + Each map contains the column name as the key and the column data as the value. Usage:

    + columnValue = vars.getObject("resultObject").get(0).get("Column Name"); +
    + Defines how ResultSet returned from callable statements be handled: +
      +
    • Store As String (default) - All variables on Variable Names list are stored as strings, will not iterate through a ResultSet when present on the list.
    • +
    • Store As Object - Variables of ResultSet type on Variables Names list will be stored as Object and can be accessed in subsequent tests/scripts and iterated, will not iterate through the ResultSet
    • +
    • Count Records - Variables of ResultSet types will be iterated through showing the count of records as result. Variables will be stored as Strings.
    • +
    +
    +
    + + + Building a Database Test Plan + + +Versions of JMeter after 2.3.2 use UTF-8 as the character encoding. Previously the platform default was used. +Ensure Variable Name is unique accross Test Plan. +
    + + + +

    This sampler lets you control a java class that implements the +org.apache.jmeter.protocol.java.sampler.JavaSamplerClient interface. +By writing your own implementation of this interface, +you can use JMeter to harness multiple threads, input parameter control, and +data collection.

    +

    The pull-down menu provides the list of all such implementations found by +JMeter in its classpath. The parameters can then be specified in the +table below - as defined by your implementation. Two simple examples (JavaTest and SleepTest) are provided. +

    +

    +The JavaTest example sampler can be useful for checking test plans, because it allows one to set +values in almost all the fields. These can then be used by Assertions, etc. +The fields allow variables to be used, so the values of these can readily be seen. +

    +
    + +Since JMeter 2.8, if the method teardownTest is not overriden by a subclass of AbstractJavaSamplerClient, its teardownTest method will not be called. +This reduces JMeter memory requirements. +This will not have any impact on existing Test plans. + +The Add/Delete buttons don't serve any purpose at present. + + + Descriptive name for this sampler + that is shown in the tree. + The specific implementation of + the JavaSamplerClient interface to be sampled. + A list of + arguments that will be passed to the sampled class. All arguments + are sent as Strings. See below for specific settings. + + +

    The following parameters apply to the SleepTest and JavaTest implementations:

    + + + How long to sleep for (ms) + How much "randomness" to add:

    + The sleep time is calculated as follows: + totalSleepTime = SleepTime + (System.currentTimeMillis() % SleepMask) +
    +
    + +

    The following parameters apply additionaly to the JavaTest implementation:

    + + + The label to use. If provided, overrides Name + If provided, sets the SampleResult ResponseCode. + If provided, sets the SampleResult ResponseMessage. + If provided, sets the SampleResult Status. If this equals "OK" (ignoring case) then the status is set to success, otherwise the sample is marked as failed. + If provided, sets the SampleResult SamplerData. + If provided, sets the SampleResult ResultData. + +
    + + + +See Building a WebService Test Plan for up to date way of test SOAP and REST Webservices + +

    This sampler lets you send a SOAP request to a webservice. It can also be +used to send XML-RPC over HTTP. It creates an HTTP POST request, with the specified XML as the +POST content. +To change the "Content-type" from the default of "text/xml", use a . +Note that the sampler will use all the headers from the . +If a SOAP action is specified, that will override any SOAPaction in the . +The primary difference between the soap sampler and +webservice sampler, is the soap sampler uses raw post and does not require conformance to +SOAP 1.1.

    +For versions of JMeter later than 2.2, the sampler no longer uses chunked encoding by default.
    +For screen input, it now always uses the size of the data.
    +File input uses the file length as determined by Java.
    +On some OSes this may not work for all files, in which case add a child +with Content-Length set to the actual length of the file.
    +Or set Content-Length to -1 to force chunked encoding. +
    +
    + + + Descriptive name for this sampler + that is shown in the tree. + The URL to direct the SOAP request to. + Send a SOAP action header? (overrides the ) + If set, sends Connection: keep-alive, else sends Connection: close + The Soap XML message, or XML-RPC instructions. + Not used if the filename is provided. + + If specified, then the contents of the file are sent, and the Data field is ignored + + +
    + + + +See Building a WebService Test Plan for up to date way of test SOAP and REST Webservices + +

    This sampler has been tested with IIS Webservice running .NET 1.0 and .NET 1.1. + It has been tested with SUN JWSDP, IBM webservices, Axis and gSoap toolkit for C/C++. + The sampler uses Apache SOAP driver to serialize the message and set the header + with the correct SOAPAction. Right now the sampler doesn't support automatic WSDL + handling, since Apache SOAP currently does not provide support for it. Both IBM + and SUN provide WSDL drivers. There are 3 options for the post data: text area, + external file, or directory. If you want the sampler to randomly select a message, + use the directory. Otherwise, use the text area or a file. The if either the + file or path are set, it will not use the message in the text area. If you need + to test a soap service that uses different encoding, use the file or path. If you + paste the message in to text area, it will not retain the encoding and will result + in errors. Save your message to a file with the proper encoding, and the sampler + will read it as java.io.FileInputStream.

    +

    An important note on the sampler is it will automatically use the proxy host + and port passed to JMeter from command line, if those fields in the sampler are + left blank. If a sampler has values in the proxy host and port text field, it + will use the ones provided by the user. This behavior may not be what users + expect.

    +

    By default, the webservice sampler sets SOAPHTTPConnection.setMaintainSession + (true). If you need to maintain the session, add a blank Header Manager. The + sampler uses the Header Manager to store the SOAPHTTPConnection object, since + the version of apache soap does not provide a easy way to get and set the cookies.

    +

    Note: If you are using CSVDataSet, do not check "Memory Cache". If memory + cache is checked, it will not iterate to the next value. That means all the requests + will use the first value.

    +

    Make sure you use &lt;soap:Envelope rather than &lt;Envelope. For example:

    +
    +&lt;?xml version="1.0" encoding="utf-8"?>
    +&lt;soap:Envelope 
    +xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    +xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    +xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    +&lt;soap:Body>
    +&lt;foo xmlns="http://clients-xlmns"/>
    +&lt;/soap:Body>
    +&lt;/soap:Envelope>
    +
    +The SOAP library that is used does not support SOAP 1.2, only SOAP 1.1. +Also the library does not provide access to the HTTP response code (e.g. 200) or message (e.g. OK). +To get round this, versions of JMeter after 2.3.2 check the returned message length. +If this is zero, then the request is marked as failed. + +
    + + + Descriptive name for this sampler + that is shown in the tree. + The WSDL URL with the service description. + Versions of JMeter after 2.3.1 support the file: protocol for local WSDL files. + + Will be populated from the WSDL when the Load WSDL button is pressed. + Select one of the methods and press the Configure button to populate the Protocol, Server, Port, Path and SOAPAction fields. + + HTTP or HTTPS are acceptable protocol. + The hostname or IP address. + Port Number. + Connection timeout. + Path for the webservice. + The SOAPAction defined in the webservice description or WSDL. + The Soap XML message + File containing soap message + Folder containing soap files. Files are choose randomly during test. + + When using external files, setting this causes the file to be processed once and caches the result. + This may use a lot of memory if there are many different large files. + + Read the SOAP reponse (consumes performance). Permit to have assertions or post-processors + Check box if http proxy should be used + Proxy hostname + Proxy host port + + + +Webservice Soap Sampler assumes that empty response means failure. + +
    + + + This Sampler lets you send a different Ldap request(Add, Modify, Delete and Search) to an LDAP server. +

    If you are going to send multiple requests to the same LDAP server, consider + using an + Configuration Element so you do not have to enter the same information for each + LDAP Request.

    The same way the also using for Login and password. +
    + +

    There are two ways to create test cases for testing an LDAP Server.

    +
    1. Inbuilt Test cases.
    2. +
    3. User defined Test cases.
    + +

    There are four test scenarios of testing LDAP. The tests are given below:

    +
      +
    1. Add Test
    2. +
      1. Inbuilt test : +

        This will add a pre-defined entry in the LDAP Server and calculate + the execution time. After execution of the test, the created entry will be + deleted from the LDAP + Server.

      2. +
      3. User defined test : +

        This will add the entry in the LDAP Server. User has to enter all the + attributes in the table.The entries are collected from the table to add. The + execution time is calculated. The created entry will not be deleted after the + test.

      + +
    3. Modify Test
    4. +
      1. Inbuilt test : +

        This will create a pre-defined entry first, then will modify the + created entry in the LDAP Server.And calculate the execution time. After + execution + of the test, the created entry will be deleted from the LDAP Server.

      2. +
      3. User defined test +

        This will modify the entry in the LDAP Server. User has to enter all the + attributes in the table. The entries are collected from the table to modify. + The execution time is calculated. The entry will not be deleted from the LDAP + Server.

      + +
    5. Search Test
    6. +
      1. Inbuilt test : +

        This will create the entry first, then will search if the attributes + are available. It calculates the execution time of the search query. At the + end of the execution,created entry will be deleted from the LDAP Server.

      2. +
      3. User defined test +

        This will search the user defined entry(Search filter) in the Search + base (again, defined by the user). The entries should be available in the LDAP + Server. The execution time is calculated.

      + +
    7. Delete Test
    8. +
      1. Inbuilt test : +

        This will create a pre-defined entry first, then it will be deleted + from the LDAP Server. The execution time is calculated.

      2. + +
      3. User defined test +

        This will delete the user-defined entry in the LDAP Server. The entries + should be available in the LDAP Server. The execution time is calculated.

    + + Descriptive name for this sampler that is shown in the tree. + Domain name or IP address of the LDAP server. + JMeter assumes the LDAP server is listening on the default port(389). + default port(389). + DN for the server to communicate + LDAP server username. + LDAP server password. (N.B. this is stored unencrypted in the test plan) + the name of the context to create or Modify; may not be empty Example: do you want to add cn=apache,ou=test + you have to add in table name=cn, value=apache + + the name of the context to Delete; may not be empty + the name of the context or object to search + the filter expression to use for the search; may not be null + this name, value pair to added in the given context object + this name, value pair to add or modify in the given context object + + + + Building an Ldap Test Plan + + + +
    + + + This Sampler can send all 8 different LDAP request to an LDAP server. It is an extended version of the LDAP sampler, + therefore it is harder to configure, but can be made much closer resembling a real LDAP session. +

    If you are going to send multiple requests to the same LDAP server, consider + using an + Configuration Element so you do not have to enter the same information for each + LDAP Request.

    + +

    There are nine test operations defined. These operations are given below:

    +
      +
    1. Thread bind
    2. +

      Any LDAP request is part of an LDAP session, so the first thing that should be done is starting a session to the LDAP server. + For starting this session a thread bind is used, which is equal to the LDAP "bind" operation. + The user is requested to give a username (Distinguished name) and password, + which will be used to initiate a session. + When no password, or the wrong password is specified, an anonymous session is started. Take care, + omitting the password will not fail this test, a wrong password will. + (N.B. this is stored unencrypted in the test plan)

      + + Descriptive name for this sampler that is shown in the tree. + The name (or IP-address) of the LDAP server. + The port number that the LDAP server is listening to. If this is omitted + JMeter assumes the LDAP server is listening on the default port(389). + The distinguished name of the base object that will be used for any subsequent operation. + It can be used as a starting point for all operations. You cannot start any operation on a higher level than this DN! + Full distinguished name of the user as which you want to bind. + Password for the above user. If omitted it will result in an anonymous bind. + If is is incorrect, the sampler will return an error and revert to an anonymous bind. (N.B. this is stored unencrypted in the test plan) + +
      +
    3. Thread unbind
    4. +

      This is simply the operation to end a session. + It is equal to the LDAP "unbind" operation.

      + + Descriptive name for this sampler that is shown in the tree. + + +
      +
    5. Single bind/unbind
    6. +

      This is a combination of the LDAP "bind" and "unbind" operations. + It can be used for an authentication request/password check for any user. It will open an new session, just to + check the validity of the user/password combination, and end the session again.

      + + Descriptive name for this sampler that is shown in the tree. + Full distinguished name of the user as which you want to bind. + Password for the above user. If omitted it will result in an anonymous bind. + If is is incorrect, the sampler will return an error. (N.B. this is stored unencrypted in the test plan) + + +
      +
    7. Rename entry
    8. +

      This is the LDAP "moddn" operation. It can be used to rename an entry, but + also for moving an entry or a complete subtree to a different place in + the LDAP tree.

      + + Descriptive name for this sampler that is shown in the tree. + The current distinguished name of the object you want to rename or move, + relative to the given DN in the thread bind operation. + The new distinguished name of the object you want to rename or move, + relative to the given DN in the thread bind operation. + + +
      +
    9. Add test
    10. +

      This is the ldap "add" operation. It can be used to add any kind of + object to the LDAP server.

      + + Descriptive name for this sampler that is shown in the tree. + Distinguished name of the object you want to add, relative to the given DN in the thread bind operation. + A list of attributes and their values you want to use for the object. + If you need to add a multiple value attribute, you need to add the same attribute with their respective + values several times to the list. + + +
      +
    11. Delete test
    12. +

      This is the LDAP "delete" operation, it can be used to delete an + object from the LDAP tree

      + + Descriptive name for this sampler that is shown in the tree. + Distinguished name of the object you want to delete, relative to the given DN in the thread bind operation. + + +
      +
    13. Search test
    14. +

      This is the LDAP "search" operation, and will be used for defining searches.

      + + Descriptive name for this sampler that is shown in the tree. + Distinguished name of the subtree you want your + search to look in, relative to the given DN in the thread bind operation. + searchfilter, must be specified in LDAP syntax. + Use 0 for baseobject-, 1 for onelevel- and 2 for a subtree search. (Default=0) + Specify the maximum number of results you want back from the server. (default=0, which means no limit.) When the sampler hits the maximum number of results, it will fail with errorcode 4 + Specify the maximum amount of (cpu)time (in miliseconds) that the server can spend on your search. Take care, this does not say anything about the responsetime. (default is 0, which means no limit) + Specify the attributes you want to have returned, seperated by a semicolon. An empty field will return all attributes + Whether the object will be returned (true) or not (false). Default=false + If true, it will dereference aliases, if false, it will not follow them (default=false) + + +
      +
    15. Modification test
    16. +

      This is the LDAP "modify" operation. It can be used to modify an object. It + can be used to add, delete or replace values of an attribute.

      + + Descriptive name for this sampler that is shown in the tree. + Distinguished name of the object you want to modify, relative + to the given DN in the thread bind operation + The attribute-value-opCode triples. The opCode can be any + valid LDAP operationCode (add, delete/remove or replace). If you don't specify a value with a delete operation, + all values of the given attribute will be deleted. If you do specify a value in a delete operation, only + the given value will be deleted. If this value is non-existent, the sampler will fail the test. + + +
      +
    17. Compare
    18. +

      This is the LDAP "compare" operation. It can be used to compare the value + of a given attribute with some already known value. In reality this is mostly + used to check whether a given person is a member of some group. In such a case + you can compare the DN of the user as a given value, with the values in the + attribute "member" of an object of the type groupOfNames. + If the compare operation fails, this test fails with errorcode 49.

      + + Descriptive name for this sampler that is shown in the tree. + The current distinguished name of the object of + which you want to compare an attribute, relative to the given DN in the thread bind operation. + In the form "attribute=value" + +
    + + + Building an LDAP Test Plan + + + +
    + + + + + +

    (Beta Code)

    +

    AccessLogSampler was designed to read access logs and generate http requests. +For those not familiar with the access log, it is the log the webserver maintains of every +request it accepted. This means every image, css file, javascript file, html file.... +The current implementation is complete, but some features have not been enabled. +There is a filter for the access log parser, but I haven't figured out how to link to the pre-processor. +Once I do, changes to the sampler will be made to enable that functionality.

    +

    Tomcat uses the common format for access logs. This means any webserver that uses the +common log format can use the AccessLogSampler. Server that use common log format include: +Tomcat, Resin, Weblogic, and SunOne. Common log format looks +like this:

    +

    127.0.0.1 - - [21/Oct/2003:05:37:21 -0500] "GET /index.jsp?%2Findex.jsp= HTTP/1.1" 200 8343

    +The current implementation of the parser only looks at the text within the quotes that contains one of the HTTP protocol methods (GET, PUT, POST, DELETE...). +Everything else is stripped out and ignored. For example, the response code is completely +ignored by the parser. +

    For the future, it might be nice to filter out entries that +do not have a response code of 200. Extending the sampler should be fairly simple. There +are two interfaces you have to implement:

    +
      +
    • org.apache.jmeter.protocol.http.util.accesslog.LogParser
    • +
    • org.apache.jmeter.protocol.http.util.accesslog.Generator
    • +
    +

    The current implementation of AccessLogSampler uses the generator to create a new +HTTPSampler. The servername, port and get images are set by AccessLogSampler. Next, +the parser is called with integer 1, telling it to parse one entry. After that, +HTTPSampler.sample() is called to make the request. + +samp = (HTTPSampler) GENERATOR.generateRequest(); +samp.setDomain(this.getDomain()); +samp.setPort(this.getPort()); +samp.setImageParser(this.isImageParser()); +PARSER.parse(1); +res = samp.sample(); +res.setSampleLabel(samp.toString()); + +The required methods in LogParser are: +

      +
    • setGenerator(Generator)
    • +
    • parse(int)
    • +
    +Classes implementing Generator interface should provide concrete implementation +for all the methods. For an example of how to implement either interface, refer to +StandardGenerator and TCLogParser. +

    +
    + + + Descriptive name for this sampler that is shown in the tree. + Domain name or IP address of the web server. + Port the web server is listening to. + The log parser class is responsible for parsing the logs. + The filter class is used to filter out certain lines. + The location of the access log file. + +

    +The TCLogParser processes the access log independently for each thread. +The SharedTCLogParser and OrderPreservingLogParser share access to the file, +i.e. each thread gets the next entry in the log. +

    +

    +The SessionFilter is intended to handle Cookies across threads. +It does not filter out any entries, but modifies the cookie manager so that the cookies for a given IP are +processed by a single thread at a time. If two threads try to process samples from the same client IP address, +then one will be forced to wait until the other has completed. +

    +

    +The LogFilter is intended to allow access log entries to be filtered by filename and regex, +as well as allowing for the replacement of file extensions. However, it is not currently possible +to configure this via the GUI, so it cannot really be used. +

    +
    + + +

    This sampler allows you to write a sampler using the BeanShell scripting language. +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener interface methods. +These must be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +

    +From JMeter version 2.5.1, the BeanShell sampler also supports the Interruptible interface. +The interrupt() method can be defined in the script or the init file. +

    +
    + + Descriptive name for this sampler that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + This is intended for use with script files; for scripts defined in the GUI, you can use whatever + variable and function references you need within the script itself. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The BeanShell script to run. + The return value (if not null) is stored as the sampler result. +
    +

    +N.B. Each Sampler instance has its own BeanShell interpeter, +and Samplers are only called from a single thread +

    +If the property "beanshell.sampler.init" is defined, it is passed to the Interpreter +as the name of a sourced file. +This can be used to define common methods and variables. +There is a sample init file in the bin directory: BeanShellSampler.bshrc. +

    +If a script file is supplied, that will be used, otherwise the script will be used.

    + +JMeter processes function and variable references before passing the script field to the interpreter, +so the references will only be resolved once. +Variable and function references in script files will be passed +verbatim to the interpreter, which is likely to cause a syntax error. +In order to use runtime variables, please use the appropriate props methods, +e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
    +BeanShell does not currently support Java 5 syntax such as generics and the enhanced for loop. +
    +

    Before invoking the script, some variables are set up in the BeanShell interpreter: +

    +

    The contents of the Parameters field is put into the variable "Parameters". + The string is also split into separate tokens using a single space as the separator, and the resulting list + is stored in the String array bsh.args.

    +

    The full list of BeanShell variables that is set up is as follows:

    +
      +
    • log - the Logger
    • +
    • Label - the Sampler label
    • +
    • FileName - the file name, if any
    • +
    • Parameters - text from the Parameters field
    • +
    • bsh.args - the parameters, split as described above
    • +
    • SampleResult - pointer to the current SampleResult
    • +
    • ResponseCode = 200
    • +
    • ResponseMessage = "OK"
    • +
    • IsSuccess = true
    • +
    • ctx - JMeterContext
    • +
    • vars - JMeterVariables - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.remove("VAR3"); vars.putObject("OBJ1",new Object());
    • +
    • props - JMeterProperties (class java.util.Properties)- e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    +

    When the script completes, control is returned to the Sampler, and it copies the contents + of the following script variables into the corresponding variables in the SampleResult:

    +
      +
    • ResponseCode - for example 200
    • +
    • ResponseMessage - for example "OK"
    • +
    • IsSuccess - true/false
    • +
    +

    The SampleResult ResponseData is set from the return value of the script. + Since version 2.1.2, if the script returns null, it can set the response directly, by using the method + SampleResult.setResponseData(data), where data is either a String or a byte array. + The data type defaults to "text", but can be set to binary by using the method + SampleResult.setDataType(SampleResult.BINARY). +

    +

    The SampleResult variable gives the script full access to all the fields and + methods in the SampleResult. For example, the script has access to the methods + setStopThread(boolean) and setStopTest(boolean). + + Here is a simple (not very useful!) example script:

    + +
    +if (bsh.args[0].equalsIgnoreCase("StopThread")) {
    +    log.info("Stop Thread detected!");
    +    SampleResult.setStopThread(true);
    +}
    +return "Data from sample with Label "+Label;
    +//or, since version 2.1.2
    +SampleResult.setResponseData("My data");
    +return null;
    +
    +

    Another example:

    ensure that the property beanshell.sampler.init=BeanShellSampler.bshrc is defined in jmeter.properties. +The following script will show the values of all the variables in the ResponseData field: +

    +
    +return getVariables();
    +
    +

    +For details on the methods available for the various classes (JMeterVariables, SampleResult etc) please check the Javadoc or the source code. +Beware however that misuse of any methods can cause subtle faults that may be difficult to find ... +

    +
    + + + +

    This sampler allows you to write a sampler using a BSF scripting language.

    + See the Apache Bean Scripting Framework + website for details of the languages supported. + You may need to download the appropriate jars for the language; they should be put in the JMeter lib directory. +

    + + The BSF API has been largely superseded by JSR-223, which is included in Java 6 onwards. + Most scripting languages now include support for JSR-223; please use the JSR223 Sampler instead. + The BSF Sampler should only be needed for supporting legacy languages/test scripts. + +

    By default, JMeter supports the following languages:

    +
      +
    • javascript
    • +
    • jexl (JMeter version 2.3.2 and later)
    • +
    • xslt
    • +
    + Unlike the BeanShell sampler, the interpreter is not saved between invocations. +
    + + Descriptive name for this sampler that is shown in the tree. + Name of the BSF scripting language to be used. + N.B. Not all the languages in the drop-down list are supported by default. + The following are supported: jexl, javascript, xslt. + Others may be available if the appropriate jar is installed in the JMeter lib directory. + + Name of a file to be used as a BSF script, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property + List of parameters to be passed to the script file or the script. + Script to be passed to BSF language + +

    +If a script file is supplied, that will be used, otherwise the script will be used.

    + +JMeter processes function and variable references before passing the script field to the interpreter, +so the references will only be resolved once. +Variable and function references in script files will be passed +verbatim to the interpreter, which is likely to cause a syntax error. +In order to use runtime variables, please use the appropriate props methods, +e.g. props.get("START.HMS"); props.put("PROP1","1234"); + +

    +Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. +

    +
      +
    • log - the Logger
    • +
    • Label - the Sampler label
    • +
    • FileName - the file name, if any
    • +
    • Parameters - text from the Parameters field
    • +
    • args - the parameters, split as described above
    • +
    • SampleResult - pointer to the current SampleResult
    • +
    • sampler - pointer to current Sampler
    • +
    • ctx - JMeterContext
    • +
    • vars - JMeterVariables - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.remove("VAR3"); vars.putObject("OBJ1",new Object());
    • +
    • props - JMeterProperties (class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    +The SampleResult ResponseData is set from the return value of the script. +If the script returns null, it can set the response directly, by using the method +SampleResult.setResponseData(data), where data is either a String or a byte array. +The data type defaults to "text", but can be set to binary by using the method +SampleResult.setDataType(SampleResult.BINARY). +

    +

    +The SampleResult variable gives the script full access to all the fields and +methods in the SampleResult. For example, the script has access to the methods +setStopThread(boolean) and setStopTest(boolean). +

    +

    +Unlike the BeanShell Sampler, the BSF Sampler does not set the ResponseCode, ResponseMessage and sample status via script variables. +Currently the only way to changes these is via the SampleResult methods: +

      +
    • SampleResult.setSuccessful(true/false)
    • +
    • SampleResult.setResponseCode("code")
    • +
    • SampleResult.setResponseMessage("message")
    • +
    +

    +
    + + + +

    +The JSR223 Sampler allows JSR223 script code to be used to perform a sample. +

    +

    +The JSR223 test elements have a feature (compilation) that can significantly increase performance. +To benefit from this feature: +

      +
    • Use Script files instead of inlining them. This will make JMeter compile them if this feature is available on ScriptEngine and cache them.
    • +
    • Or Use Script Text and fill in script cache key property, ensure it is unique across Test Plan as JMeter will use it to cache result of compilation. + When using this feature, ensure you script code does not use JMeter variables directly in script code as caching would only cache first replacement. Instead use script parameters. + To benefit fomr Caching and compilation, language engine used for scripting must implement JSR223 Compilable interface (Groovy is one of these, java, beanshell and javascript are not) +
    • +
    +Cache size is controlled by the following jmeter property (jmeter.properties): +
      +
    • jsr223.compiled_scripts_cache_size=100
    • +
    +For details, see . +

    +Unlike the BeanShell sampler, the interpreter is not saved between invocations. + +Since JMeter 2.8, JSR223 Test Elements using Script file or Script text + cache key are now Compiled if ScriptEngine supports this feature, this enables great performance enhancements. + +
    + +JMeter processes function and variable references before passing the script field to the interpreter, +so the references will only be resolved once. +Variable and function references in script files will be passed +verbatim to the interpreter, which is likely to cause a syntax error. +In order to use runtime variables, please use the appropriate props methods, +e.g. props.get("START.HMS"); props.put("PROP1","1234"); + +
    + + + +

    + The TCP Sampler opens a TCP/IP connection to the specified server. + It then sends the text, and waits for a response. +

    + If "Re-use connection" is selected, connections are shared between Samplers in the same thread, + provided that the exact same host name string and port are used. + Different hosts/port combinations will use different connections, as will different threads. + If both of "Re-use connection" and "Close connection" are selected, the socket will be closed after running the sampler. + On the next sampler, another socket will be created. You may want to close a socket at the end of each thread loop. +

    + If an error is detected - or "Re-use connection" is not selected - the socket is closed. + Another socket will be reopened on the next sample. +

    + The following properties can be used to control its operation: +

    +
      +
    • tcp.status.prefix - text that precedes a status number
    • +
    • tcp.status.suffix - text that follows a status number
    • +
    • tcp.status.properties - name of property file to convert status codes to messages
    • +
    • tcp.handler - Name of TCP Handler class (default TCPClientImpl) - only used if not specified on the GUI
    • +
    + The class that handles the connection is defined by the GUI, failing that the property tcp.handler. + If not found, the class is then searched for in the package org.apache.jmeter.protocol.tcp.sampler. +

    + Users can provide their own implementation. + The class must extend org.apache.jmeter.protocol.tcp.sampler.TCPClient. +

    +

    + The following implementations are currently provided. +

      +
    • TCPClientImpl
    • +
    • BinaryTCPClientImpl
    • +
    • LengthPrefixedBinaryTCPClientImpl
    • +
    + The implementations behave as follows: +

    +

    TCPClientImpl

    + This implementation is fairly basic. + When reading the response, it reads until the end of line byte, if this is defined + by setting the property tcp.eolByte, otherwise until the end of the input stream. + You can control charset encoding by setting tcp.charset, which will default to Platform default encoding. +

    +

    BinaryTCPClientImpl

    + This implementation converts the GUI input, which must be a hex-encoded string, into binary, + and performs the reverse when reading the response. + When reading the response, it reads until the end of message byte, if this is defined + by setting the property tcp.BinaryTCPClient.eomByte, otherwise until the end of the input stream. +

    +

    LengthPrefixedBinaryTCPClientImpl

    + This implementation extends BinaryTCPClientImpl by prefixing the binary message data with a binary length byte. + The length prefix defaults to 2 bytes. + This can be changed by setting the property tcp.binarylength.prefix.length. +

    +

    Timeout handling + If the timeout is set, the read will be terminated when this expires. + So if you are using an eolByte/eomByte, make sure the timeout is sufficiently long, + otherwise the read will be terminated early. +

    +

    Response handling +

    + If tcp.status.prefix is defined, then the response message is searched for the text following + that up to the suffix. If any such text is found, it is used to set the response code. + The response message is then fetched from the properties file (if provided). +

    + For example, if the prefix = "[" and the suffix = "]", then the following repsonse: +

    + [J28] XI123,23,GBP,CR +

    + would have the response code J28. +

    + Response codes in the range "400"-"499" and "500"-"599" are currently regarded as failures; + all others are successful. [This needs to be made configurable!] +

    +The login name/password are not used by the supplied TCP implementations. +

    + Sockets are disconnected at the end of a test run. +
    + + Descriptive name for this element that is shown in the tree. + Name of the TCPClient class. Defaults to the property tcp.handler, failing that TCPClientImpl. + Name or IP of TCP server + Port to be used + If selected, the connection is kept open. Otherwise it is closed when the data has been read. + If selected, the connection will be closed after running the sampler. + Enable/disable SO_LINGER with the specified linger time in seconds when a socket is created. If you set "SO_LINGER" value as 0, you may prevent large numbers of sockets sitting around with a TIME_WAIT status. + Byte value for end of line, set this to a value outside the range -128 to +127 to skip eol checking. You may set this in jmeter.properties file as well with eolByte property. If you set this in TCP Sampler Config and in jmeter.properties file at the same time, the setting value in the TCP Sampler Config will be used. + Connect Timeout (milliseconds, 0 disables). + Response Timeout (milliseconds, 0 disables). + See java.net.Socket.setTcpNoDelay(). + If selected, this will disable Nagle's algorithm, otherwise Nagle's algorithm will be used. + Text to be sent + User Name - not used by default implementation + Password - not used by default implementation (N.B. this is stored unencrypted in the test plan) + +
    + + +BETA CODE - the code is still subject to change + +

    + JMS Publisher will publish messages to a given destination (topic/queue). For those not + familiar with JMS, it is the J2EE specification for messaging. There are + numerous JMS servers on the market and several open source options. +

    +

    +JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory +
    + + Descriptive name for this element that is shown in the tree. + use jndi.properties. + Note that the file must be on the classpath - e.g. by updating the user.classpath JMeter property. + If this option is not selected, JMeter uses the "JNDI Initial Context Factory" and "Provider URL" fields + to create the connection. + + Name of the context factory + The URL for the jms provider + The message destination (topic or queue name) + The destination setup type. With At startup, the destination name is static (i.e. always same name during the test), with Each sample, the destination name is dynamic and is evaluate at each sample (i.e. the destination name may be a variable) + Authentication requirement for the JMS provider + User Name + Password (N.B. this is stored unencrypted in the test plan) + + The expiration time (in milliseconds) of the message before it becomes obsolete. + If you do not specify an expiration time, the default value is 0 (never expires). + + + The priority level of the message. There are ten priority levels from 0 (lowest) to 9 (highest). + If you do not specify a priority level, the default level is 4. + + Number of samples to aggregate + Where to obtain the message: +
      +
    • From File : means the referenced file will be read and reused by all samples
    • +
    • Random File from folder specified below : means a random file will be selected from folder specified below, this folder must contain either files with extension .dat for Bytes Messages, or files with extension .txt or .obj for Object or Text messages
    • +
    • Text area : The Message to use either for Text or Object message
    • +
    +
    + Text, Map, Object message or Bytes Message + + Whether to set DeliveryMode.NON_PERSISTENT (defaults to false) + + + The JMS Properties are properties specific for the underlying messaging system. + You can setup the name, the value and the class (type) of value. Default type is String. + For example: for WebSphere 5.1 web services you will need to set the JMS Property targetService to test + webservices through JMS. + + +
    +

    +For the MapMessage type, JMeter reads the source as lines of text. +Each line must have 3 fields, delimited by commas. +The fields are: +

      +
    • Name of entry
    • +
    • Object class name, e.g. "String" (assumes java.lang package if not specified)
    • +
    • Object string value
    • +
    +For each entry, JMeter adds an Object with the given name. +The value is derived by creating an instance of the class, and using the valueOf(String) method to convert the value if necessary. +For example: +
    +name,String,Example
    +size,Integer,1234
    +
    +This is a very simple implementation; it is not intended to support all possible object types. +

    +

    + +The Object message is implemented since 2.7 and works as follow: +

      +
    • Put the JAR that contains your object and its dependencies in jmeter_home/lib/ folder
    • +
    • Serialize your object as XML using XStream
    • +
    • Either put result in a file suffixed with .txt or .obj or put XML content direclty in Text Area
    • +
    +Note that if message is in a file, replacement of properties will not occur while it will if you use Text Area. + + +

    +

    +The following table shows some values which may be useful when configuring JMS: + + + + + + + + + + + + + + +
    Apache ActiveMQValue(s)Comment
    Context Factoryorg.apache.activemq.jndi.ActiveMQInitialContextFactory.
    Provider URLvm://localhost
    Provider URLvm:(broker:(vm://localhost)?persistent=false)Disable persistence
    Queue ReferencedynamicQueues/QUEUENAMEDynamically define the QUEUENAME to JNDI
    Topic ReferencedynamicTopics/TOPICNAMEDynamically define the TOPICNAME to JNDI
    +

    +
    + + +BETA CODE - the code is still subject to change + +

    + JMS Publisher will subscribe to messages in a given destination (topic or queue). For those not + familiar with JMS, it is the J2EE specification for messaging. There are + numerous JMS servers on the market and several open source options. +

    +

    +JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory +
    + + Descriptive name for this element that is shown in the tree. + use jndi.properties. + Note that the file must be on the classpath - e.g. by updating the user.classpath JMeter property. + If this option is not selected, JMeter uses the "JNDI Initial Context Factory" and "Provider URL" fields + to create the connection. + + Name of the context factory + The URL for the jms provider + the message destination (topic or queue name) + The ID to use for a durable subscription. On first + use the respective queue will automatically be generated by the JMS provider if it does not exist yet. + The Client ID to use when you you use a durable subscription. + Be sure to add a variable like ${__threadNum} when you have more than one Thread. + Message Selector as defined by JMS specification to extract only + messages that respect the Selector condition. Syntax uses subpart of SQL 92. + The destination setup type. With At startup, the destination name is static (i.e. always same name during the test), with Each sample, the destination name is dynamic and is evaluate at each sample (i.e. the destination name may be a variable) + Authentication requirement for the JMS provider + User Name + Password (N.B. this is stored unencrypted in the test plan) + number of samples to aggregate + should the sampler read the response. If not, only the response length is returned. + Specify the timeout to be applied, in milliseconds. 0=none. + This is the overall aggregate timeout, not per sample. + Which client implementation to use. + Both of them create connections which can read messages. However they use a different strategy, as described below: +
      +
    • MessageConsumer.receive() - calls receive() for every requested message. + Retains the connection between samples, but does not fetch messages unless the sampler is active. + This is best suited to Queue subscriptions. +
    • +
    • MessageListener.onMessage() - establishes a Listener that stores all incoming messages on a queue. + The listener remains active after the sampler completes. + This is best suited to Topic subscriptions.
    • +
    +
    + + If selected, then JMeter calls Connection.stop() at the end of each sample (and calls start() before each sample). + This may be useful in some cases where multiple samples/threads have connections to the same queue. + If not selected, JMeter calls Connection.start() at the start of the thread, and does not call stop() until the end of the thread. + + + Separator used to separate messages when there is more than one (related to setting Number of samples to aggregate). + Note that \n, \r, \t are accepted. + +
    +

    +NOTE: JMeter 2.3.4 and earlier used a different strategy for the MessageConsumer.receive() client. +Previously this started a background thread which polled for messages. This thread continued when the sampler +completed, so the net effect was similar to the MessageListener.onMessage() strategy. +

    +
    + + +BETA CODE - the code is still subject to change + +

    + This sampler sends and optionally receives JMS Messages through point-to-point connections (queues). + It is different from pub/sub messages and is generally used for handling transactions. +

    +

    + Request Only will typically used to put load on a JMS System.

    + Request Response will be used when you want to test response time of a JMS service that processes messages sent to the Request Queue as this mode will wait for the response on the Reply queue sent by this service.

    +

    +

    + Versions of JMeter after 2.3.2 use the properties java.naming.security.[principal|credentials] - if present - + when creating the Queue Connection. If this behaviour is not desired, set the JMeter property + JMSSampler.useSecurity.properties=false +

    +

    +JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory +
    + + Descriptive name for this element that is shown in the tree. + + The JNDI name of the queue connection factory to use for connecting to the messaging system. + + + This is the JNDI name of the queue to which the messages are sent. + + + The JNDI name of the receiving queue. If a value is provided here and the communication style is Request Response + this queue will be monitored for responses to the requests sent. + + + Message Selector as defined by JMS specification to extract only + messages that respect the Selector condition. Syntax uses subpart of SQL 92. + + + The Communication style can be Request Only (also known as Fire and Forget) or Request Response: +
      +
    • Request Only will only send messages and will not monitor replies. As such it can be used to put load on a system.
    • +
    • Request Response will send messages and monitor the replies it receives. Behaviour depends on the value of the JNDI Name Reply Queue. + If JNDI Name Reply Queue has a value, this queue is used to monitor the results. Matching of request and reply is done with + the message id of the request and the correlation id of the reply. If the JNDI Name Reply Queue is empty, then + temporary queues will be used for the communication between the requestor and the server. + This is very different from the fixed reply queue. With temporary queues the sending thread will block until the reply message has been received. + With Request Response mode, you need to have a Server that listens to messages sent to Request Queue and sends replies to + queue referenced by message.getJMSReplyTo().
    • +
    +
    + + These check-boxes select the fields which will be used for matching the response message with the original request. +
      +
    • Use Request Message Id - if selected, the request JMSMessageID will be used, + otherwise the request JMSCorrelationID will be used. + In the latter case the correlation id must be specified in the request.
    • +
    • Use Response Message Id - if selected, the response JMSMessageID will be used, + otherwise the response JMSCorrelationID will be used. +
    • +
    + There are two frequently used JMS Correlation patterns: +
      +
    • JMS Correlation ID Pattern - + i.e. match request and response on their correlation Ids + => deselect both checkboxes, and provide a correlation id.
    • +
    • JMS Message ID Pattern - + i.e. match request message id with response correlation id + => select "Use Request Message Id" only. +
    • +
    + In both cases the JMS application is responsible for populating the correlation ID as necessary. + if the same queue is used to send and receive messages, + then the response message will be the same as the request message. + In which case, either provide a correlation id and clear both checkboxes; + or select both checkboxes to use the message Id for correlation. + This can be useful for checking raw JMS throughput. +
    + + The timeout in milliseconds for the reply-messages. If a reply has not been received within the specified + time, the specific testcase failes and the specific reply message received after the timeout is discarded. + Default value is 2000 ms. + + + The expiration time (in milliseconds) of the message before it becomes obsolete. + If you do not specify an expiration time, the default value is 0 (never expires). + + + The priority level of the message. There are ten priority levels from 0 (lowest) to 9 (highest). + If you do not specify a priority level, the default level is 4. + + + Whether to set DeliveryMode.NON_PERSISTENT. + + + The content of the message. + + + The JMS Properties are properties specific for the underlying messaging system. + You can setup the name, the value and the class (type) of value. Default type is String. + For example: for WebSphere 5.1 web services you will need to set the JMS Property targetService to test + webservices through JMS. + + + The Initial Context Factory is the factory to be used to look up the JMS Resources. + + + The JNDI Properties are the specific properties for the underlying JNDI implementation. + + + The URL for the jms provider. + +
    +
    + + + + + +The current implementation supports standard Junit convention and extensions. It also +includes extensions like oneTimeSetUp and oneTimeTearDown. The sampler works like the +JavaSampler with some differences. +
      +
    • rather than use Jmeter's test interface, it scans the jar files for classes extending junit's TestCase class. That includes any class or subclass.
    • +
    • Junit test jar files should be placed in jmeter/lib/junit instead of /lib directory. +In versions of JMeter after 2.3.1, you can also use the "user.classpath" property to specify where to look for TestCase classes.
    • +
    • Junit sampler does not use name/value pairs for configuration like the JavaSampler. The sampler assumes setUp and tearDown will configure the test correctly.
    • +
    • The sampler measures the elapsed time only for the test method and does not include setUp and tearDown.
    • +
    • Each time the test method is called, Jmeter will pass the result to the listeners.
    • +
    • Support for oneTimeSetUp and oneTimeTearDown is done as a method. Since Jmeter is multi-threaded, we cannot call oneTimeSetUp/oneTimeTearDown the same way Maven does it.
    • +
    • The sampler reports unexpected exceptions as errors. +There are some important differences between standard JUnit test runners and JMeter's +implementation. Rather than make a new instance of the class for each test, JMeter +creates 1 instance per sampler and reuses it. +This can be changed with checkbox "Create a new instance per sample".
    • +
    +The current implementation of the sampler will try to create an instance using the string constructor first. If the test class does not declare a string constructor, the sampler will look for an empty constructor. Example below: + +Empty Constructor: +
    +public class myTestCase {
    +  public myTestCase() {}
    +}
    +
    +String Constructor: +
    +public class myTestCase {
    +  public myTestCase(String text) {
    +    super(text);
    +  }
    +}
    +
    +
    +By default, Jmeter will provide some default values for the success/failure code and message. Users should define a set of unique success and failure codes and use them uniformly across all tests. + +

    General Guidelines

    +If you use setUp and tearDown, make sure the methods are declared public. If you do not, the test may not run properly. +

    +Here are some general guidelines for writing Junit tests so they work well with Jmeter. Since Jmeter runs multi-threaded, it is important to keep certain things in mind. +
      +
    • Write the setUp and tearDown methods so they are thread safe. This generally means avoid using static memebers.
    • +
    • Make the test methods discrete units of work and not long sequences of actions. By keeping the test method to a descrete operation, it makes it easier to combine test methods to create new test plans.
    • +
    • Avoid making test methods depend on each other. Since Jmeter allows arbitrary sequencing of test methods, the runtime behavior is different than the default Junit behavior.
    • +
    • If a test method is configurable, be careful about where the properties are stored. Reading the properties from the Jar file is recommended.
    • +
    • Each sampler creates an instance of the test class, so write your test so the setup happens in oneTimeSetUp and oneTimeTearDown.
    • +
    +
    +
    + + Descriptive name for this element that is shown in the tree. + Select this to search for JUnit 4 tests (@Test annotations) + Comma separated list of packages to show. Example, org.apache.jmeter,junit.framework. + Fully qualified name of the JUnit test class. + String pass to the string constructor. If a string is set, the sampler will use the + string constructor instead of the empty constructor. + The method to test. + A descriptive message indicating what success means. + An unique code indicating the test was successful. + A descriptive message indicating what failure means. + An unique code indicating the test failed. + A description for errors. + Some code for errors. Does not need to be unique. + Set the sampler not to call setUp and tearDown. + By default, setUp and tearDown should be called. Not calling those methods could affect the test and make it inaccurate. + This option should only be used with calling oneTimeSetUp and oneTimeTearDown. If the selected method is oneTimeSetUp or oneTimeTearDown, + this option should be checked. + Whether or not to append assertion errors to the response message. + Whether or not to append runtime exceptions to the response message. Only applies if "Append assertion errors" is not selected. + Whether or not to create a new JUnit instance for each sample. Defaults to false, meaning JUnit TestCase is created one and reused. + +

    +The following JUnit4 annotations are recognised: +

      +
    • @Test - used to find test methods and classes. The "expected" and "timeout" attributes are supported.
    • +
    • @Before - treated the same as setUp() in JUnit3
    • +
    • @After - treated the same as tearDown() in JUnit3
    • +
    • @BeforeClass, @AfterClass - treated as test methods so they can be run independently as required
    • +
    +

    +

    +Note that JMeter currently runs the test methods directly, rather than leaving it to JUnit. +This is to allow the setUp/tearDown methods to be excluded from the sample time. +

    +
    + + + +

    +The Mail Reader Sampler can read (and optionally delete) mail messages using POP3(S) or IMAP(S) protocols. +

    +
    + +Descriptive name for this element that is shown in the tree. +The protocol used by the provider: e.g. pop3, pop3s, imap, imaps. +or another string representing the server protocol. +For example file for use with the read-only mail file provider. +The actual provider names for POP3 and IMAP are pop3 and imap + +Hostname or IP address of the server. See below for use with file protocol. +Port to be used to connect to the server (optional) +User login name +User login password (N.B. this is stored unencrypted in the test plan) +The IMAP(S) folder to use. See below for use with file protocol. +Set this to retrieve all or some messages +If selected, only the message headers will be retrieved. +If set, messages will be deleted after retrieval +Whether to store the message as MIME. +If so, then the entire raw message is stored in the Response Data; the headers are not stored as they are available in the data. +If not, the message headers are stored as Response Headers. +A few headers are stored (Date, To, From, Subject) in the body. + +Indicates that the connection to the server does not use any security protocol. +Indicates that the connection to the server must use the SSL protocol. +Indicates that the connection to the server should attempt to start the TLS protocol. +If the server does not start the TLS protocol the connection will be terminated. +When selected it will accept all certificates independent of the CA. +When selected it will only accept certificates that are locally trusted. +Path to file containing the trusted certificates. +Relative paths are resolved against the current directory. +
    Failing that, against the directory containing the test script (JMX file). +
    +
    +

    +Messages are stored as subsamples of the main sampler. +In versions of JMeter after 2.3.4, multipart message parts are stored as subsamples of the message. +

    +

    +Special handling for "file" protocol:

    +The file JavaMail provider can be used to read raw messages from files. +The server field is used to specify the path to the parent of the folder. +Individual message files should be stored with the name n.msg, +where n is the message number. +Alternatively, the server field can be the name of a file which contains a single message. +The current implementation is quite basic, and is mainly intended for debugging purposes. +

    +
    + + + +The Test Action sampler is a sampler that is intended for use in a conditional controller. +Rather than generate a sample, the test element eithers pauses or stops the selected target. +

    This sampler can also be useful in conjunction with the Transaction Controller, as it allows +pauses to be included without needing to generate a sample. +For variable delays, set the pause time to zero, and add a Timer as a child.

    +

    +The "Stop" action stops the thread or test after completing any samples that are in progress. +The "Stop Now" action stops the test without waiting for samples to complete; it will interrupt any active samples. +If some threads fail to stop within the 5 second time-limit, a message will be displayed in GUI mode. +You can try using the Stop command to see if this will stop the threads, but if not, you should exit JMeter. +In non-GUI mode, JMeter will exit if some threads fail to stop within the 5 second time limit. +[This can be changed using the JMeter property jmeterengine.threadstop.wait] +

    +
    + + Descriptive name for this element that is shown in the tree. + Current Thread / All Threads (ignored for Pause) + Pause / Stop / Stop Now / Go to next loop iteration + How long to pause for (milliseconds) + +
    + + + + +

    +The SMTP Sampler can send mail messages using SMTP/SMTPS protocol. +It is possible to set security propocols for the connection (SSL and TLS), as well as user authentication. +If a security protocol is used a verification on the server certificate will occur.

    +Two alternatives to handle this verification are available:

    +

      +
    • Trust all certificates. This will ignore certificate chain verification
    • +
    • Use a local truststore. With this option the certificate chain will be validated against the local truststore file.
    • +
    +

    +
    + +Hostname or IP address of the server. See below for use with file protocol. +Port to be used to connect to the server. +Defaults are: SMTP=25, SSL=465, StartTLS=587 + +Connection timeout value in milliseconds (socket level). Default is infinite timeout. +Read timeout value in milliseconds (socket level). Default is infinite timeout. +The from address that will appear in the e-mail +The destination e-mail address (multiple values separated by ";") +Carbon copy destinations e-mail address (multiple values separated by ";") +Blind carbon copy destinations e-mail address (multiple values separated by ";") +Alternate Reply-To address (multiple values separated by ";") +Indicates if the SMTP server requires user authentication +User login name +User login password (N.B. this is stored unencrypted in the test plan) +Indicates that the connection to the SMTP server does not use any security protocol. +Indicates that the connection to the SMTP server must use the SSL protocol. +Indicates that the connection to the SMTP server should attempt to start the TLS protocol. +If the server does not start the TLS protocol the connection will be terminated. +When selected it will accept all certificates independent of the CA. +When selected it will only accept certificates that are locally trusted. +Path to file containing the trusted certificates. +Relative paths are resolved against the current directory. +
    Failing that, against the directory containing the test script (JMX file). +
    +The e-mail message subject. +If selected, the "Subject:" header is omitted from the mail that is sent. +This is different from sending an empty "Subject:" header, though some e-mail clients may display it identically. +Includes the System.currentTimemilis() in the subject line. +Additional headers can be defined using this button. +The message body. + +If selected, then send the body as a plain message, i.e. not multipart/mixed, if possible. +If the message body is empty and there is a single file, then send the file contents as the message body. +Note: If the message body is not empty, and there is at least one attached file, then the body is sent as multipart/mixed. + +Files to be attached to the message. +If set, the .eml file will be sent instead of the entries in the Subject, Message, and Attached files +Calculates the message size and stores it in the sample result. +If set, then the "mail.debug" property is set to "true" +
    +
    + + + +

    +The OS Process Sampler is a sampler that can be used to execute commands on the local machine.

    +It should allow execution of any command that can be run from the command line.

    +Validation of the return code can be enabled, and the expected return code can be specified.

    +

    +

    +Note that OS shells generally provide command-line parsing. +This varies between OSes, but generally the shell will split parameters on white-space. +Some shells expand wild-card file names; some don't. +The quoting mechanism also varies between OSes. +The sampler deliberately does not do any parsing or quote handling. +The command and its parameters must be provided in the form expected by the executable. +This means that the sampler settings will not be portable between OSes. +

    +

    +Many OSes have some built-in commands which are not provided as separate executables. +For example the Windows DIR command is part of the command interpreter (CMD.EXE). +These built-ins cannot be run as independent programs, but have to be provided as arguments to the appropriate command interpreter. +

    +

    +For example, the Windows command-line: DIR C:\TEMP needs to be specified as follows: +

    +command:   CMD
    +Param 1:   /C
    +Param 2:   DIR
    +Param 3:   C:\TEMP
    +
    +

    +
    + +The program name to execute. +Directory from which command will be executed, defaults to folder referenced by "user.dir" System property +Parameters passed to the program name. +Key/Value pairs added to environment when running command. +Name of file from which input is to be taken (STDIN). +Name of output file for standard output (STDOUT). +If omitted, output is captured and returned as the response data. +Name of output file for standard error (STDERR). +If omitted, output is captured and returned as the response data. +If checked, sampler will compare return code with Expected Return Code. +Expected return code for System Call, required if "Check Return Code" is checked. +Timeout for command in milliseconds, defaults to 0, which means NO timeout. +If the timeout expires before the command finishes, JMeter will attempt to kill the OS process. + + +
    + + +

    This sampler lets you send an Request to a MongoDB.

    +

    Before using this you need to set up a + Configuration element +

    +This Element currently uses com.mongodb.DB#eval which takes a global write lock causing a performance impact on the database, see db.eval(). +So it is better to avoid using this element for load testing and use JSR223+Groovy scripting using MongoDBHolder instead. +MongoDB Script is more suitable for functionnal testing or test setup (setup/teardown threads) +
    + + + Descriptive name for this sampler that is shown in the tree. + + Name of the JMeter variable that the MongoDB connection is bound to. + This must agree with the 'MongoDB Source' field of a MongoDB Source Config. + + Database Name, will be used in your script + + + + + + + Mongo script as it would be used in MongoDB shell + + + + + + +Ensure Variable Name is unique accross Test Plan. +
    + +^ + +
    + +
    + +
    Logic Controllers determine the order in which Samplers are processed.
    +
    + + + +

    The Simple Logic Controller lets you organize your Samplers and other +Logic Controllers. Unlike other Logic Controllers, this controller provides no functionality beyond that of a +storage device.

    +
    + + Descriptive name for this controller that is shown in the tree. + + + +

    Download this example (see Figure 6). +In this example, we created a Test Plan that sends two Ant HTTP requests and two +Log4J HTTP requests. We grouped the Ant and Log4J requests by placing them inside +Simple Logic Controllers. Remember, the Simple Logic Controller has no effect on how JMeter +processes the controller(s) you add to it. So, in this example, JMeter sends the requests in the +following order: Ant Home Page, Ant News Page, Log4J Home Page, Log4J History Page. +Note, the File Reporter +is configured to store the results in a file named "simple-test.dat" in the current directory.

    +
    Figure 6 Simple Controller Example
    + +
    +
    + + +

    If you add Generative or Logic Controllers to a Loop Controller, JMeter will +loop through them a certain number of times, in addition to the loop value you +specified for the Thread Group. For example, if you add one HTTP Request to a +Loop Controller with a loop count of two, and configure the Thread Group loop +count to three, JMeter will send a total of 2 * 3 = 6 HTTP Requests. +

    + + + Descriptive name for this controller that is shown in the tree. + + The number of times the subelements of this controller will be iterated each time + through a test run. +

    Special Case: The Loop Controller embedded in the Thread Group + element behaves slightly differently. Unless set to forever, it stops the test after + the given number of iterations have been done.

    + When using a function in this field, be aware it may be evaluated multiple times. + Example using __Random will evaluate it to a different value for each child samplers of Loop Controller and result into unwanted behaviour.
    +
    + + + +

    Download this example (see Figure 4). +In this example, we created a Test Plan that sends a particular HTTP Request +only once and sends another HTTP Request five times.

    + +
    Figure 4 - Loop Controller Example
    + +

    We configured the Thread Group for a single thread and a loop count value of +one. Instead of letting the Thread Group control the looping, we used a Loop +Controller. You can see that we added one HTTP Request to the Thread Group and +another HTTP Request to a Loop Controller. We configured the Loop Controller +with a loop count value of five.

    +

    JMeter will send the requests in the following order: Home Page, News Page, +News Page, News Page, News Page, and News Page. Note, the File Reporter +is configured to store the results in a file named "loop-test.dat" in the current directory.

    + +
    + +
    + + + +

    The Once Only Logic Controller tells JMeter to process the controller(s) inside it only once per Thread, and pass over any requests under it +during further iterations through the test plan.

    + +

    The Once Only Controller will now execute always during the first iteration of any looping parent controller. +Thus, if the Once Only Controller is placed under a Loop Controller specified to loop 5 times, then the Once Only Controller will execute only on the first iteration through the Loop Controller +(ie, every 5 times). +Note this means the Once Only Controller will still behave as previously expected if put under a Thread Group (runs only once per test per Thread), +but now the user has more flexibility in the use of the Once Only Controller.

    + +

    For testing that requires a login, consider placing the login request in this controller since each thread only needs +to login once to establish a session.

    +
    + + Descriptive name for this controller that is shown in the tree. + + + +

    Download this example (see Figure 5). +In this example, we created a Test Plan that has two threads that send HTTP request. +Each thread sends one request to the Home Page, followed by three requests to the Bug Page. +Although we configured the Thread Group to iterate three times, each JMeter thread only +sends one request to the Home Page because this request lives inside a Once Only Controller.

    +
    Figure 5. Once Only Controller Example
    +

    Each JMeter thread will send the requests in the following order: Home Page, Bug Page, +Bug Page, Bug Page. Note, the File Reporter is configured to store the results in a file named "loop-test.dat" in the current directory.

    + +
    +The behaviour of the Once Only controller under anything other than the +Thread Group or a Loop Controller is not currently defined. Odd things may happen. +
    + + +

    If you add Generative or Logic Controllers to an Interleave Controller, JMeter will alternate among each of the +other controllers for each loop iteration.

    +
    + + Descriptive name for this controller that is shown in the tree. + If checked, the interleave controller will treat sub-controllers like single request elements and only allow one request per controller at a time. + + + + + +

    Download this example (see Figure 1). In this example, +we configured the Thread Group to have two threads and a loop count of five, for a total of ten +requests per thread. See the table below for the sequence JMeter sends the HTTP Requests.

    + +
    Figure 1 - Interleave Controller Example 1
    + + + + + + + + + + + + + +
    Loop IterationEach JMeter Thread Sends These HTTP Requests
    1News Page
    1Log Page
    2FAQ Page
    2Log Page
    3Gump Page
    3Log Page
    4Because there are no more requests in the controller,

    JMeter starts over and sends the first HTTP Request, which is the News Page.
    4Log Page
    5FAQ Page
    5Log Page
    + + +
    + + + +

    Download another example (see Figure 2). In this +example, we configured the Thread Group +to have a single thread and a loop count of eight. Notice that the Test Plan has an outer Interleave Controller with +two Interleave Controllers inside of it.

    + +
    + Figure 2 - Interleave Controller Example 2 +
    + +

    The outer Interleave Controller alternates between the +two inner ones. Then, each inner Interleave Controller alternates between each of the HTTP Requests. Each JMeter +thread will send the requests in the following order: Home Page, Interleaved, Bug Page, Interleaved, CVS Page, Interleaved, and FAQ Page, Interleaved. +Note, the File Reporter is configured to store the results in a file named "interleave-test2.dat" in the current directory.

    + +
    + Figure 3 - Interleave Controller Example 3 +
    +

    If the two interleave controllers under the main interleave controller were instead simple controllers, then the order would be: Home Page, CVS Page, Interleaved, Bug Page, FAQ Page, Interleaved. However, if "ignore sub-controller blocks" was checked on the main interleave controller, then the order would be: Home Page, Interleaved, Bug Page, Interleaved, CVS Page, Interleaved, and FAQ Page, Interleaved.

    +
    +
    + + + +

    The Random Logic Controller acts similarly to the Interleave Controller, except that +instead of going in order through its sub-controllers and samplers, it picks one +at random at each pass.

    +Interactions between multiple controllers can yield complex behavior. +This is particularly true of the Random Controller. Experiment before you assume +what results any given interaction will give +
    + + Descriptive name for this controller that is shown in the tree. + + +
    + + + + + +

    The Random Order Controller is much like a Simple Controller in that it will execute each child + element at most once, but the order of execution of the nodes will be random.

    +
    + + Descriptive name for this controller that is shown in the tree. + +
    + + + +

    +This controller is badly named, as it does not control throughput. +Please refer to the for an element that can be used to adjust the throughput. +

    +

    The Throughput Controller allows the user to control how often it is executed. There are two modes - percent execution and total executions. Percent executions causes the controller to execute a certain percentage of the iterations through the test plan. Total +executions causes the controller to stop executing after a certain number of executions have occurred. Like the Once Only Controller, this +setting is reset when a parent Loop Controller restarts. +

    +
    +The Throughput Controller can yield very complex behavior when combined with other controllers - in particular with interleave or random controllers as parents (also very useful). + + Descriptive name for this controller that is shown in the tree. + Whether the controller will run in percent executions or total executions mode. + A number. for percent execution mode, a number from 0-100 that indicates the percentage of times the controller will execute. "50" means the controller will execute during half the iterations throught the test plan. for total execution mode, the number indicates the total number of times the controller will execute. + If checked, per user will cause the controller to calculate whether it should execute on a per user (per thread) basis. if unchecked, then the calculation will be global for all users. for example, if using total execution mode, and uncheck "per user", then the number given for throughput will be the total number of executions made. if "per user" is checked, then the total number of executions would be the number of users times the number given for throughput. + + +
    + + + +

    The Runtime Controller controls how long its children are allowed to run. +

    +
    + + Descriptive name for this controller that is shown in the tree, and used to name the transaction. + Desired runtime in seconds + +
    + + + +

    The If Controller allows the user to control whether the test elements below it (its children) are run or not.

    +

    + Prior to JMeter 2.3RC3, the condition was evaluated for every runnable element contained in the controller. + This sometimes caused unexpected behaviour, so 2.3RC3 was changed to evaluate the condition only once on initial entry. + However, the original behaviour is also useful, so versions of JMeter after 2.3RC4 have an additional + option to select the original behaviour. +

    +

    + Versions of JMeter after 2.3.2 allow the script to be processed as a variable expression, rather than requiring Javascript. + It was always possible to use functions and variables in the Javascript condition, so long as they evaluated to "true" or "false"; + now this can be done without the overhead of using Javascript as well. For example, previously one could use the condition: + ${__jexl(${VAR} == 23)} and this would be evaluated as true/false, the result would then be passed to Javascript + which would then return true/false. If the Variable Expression option is selected, then the expression is evaluated + and compared with "true", without needing to use Javascript. + Also, variable expressions can return any value, whereas the + Javascript condition must return "true"/"false" or an error is logged. +

    + + No variables are made available to the script when the condition is interpreted as Javascript. + If you need access to such variables, then select "Interpret Condition as Variable Expression?" and use + a __javaScript() function call. You can then use the objects "vars", "log", "ctx" etc. in the script. + + + To test if a variable is undefined (or null) do the following, suppose var is named myVar, expression will be: +

    + "${myVar}" == "\${myVar}" +

    + Or use: +

    + "${myVar}" != "\${myVar}" +

    + to test if a variable is defined and is not null. +
    +
    + + Descriptive name for this controller that is shown in the tree. + By default the condition is interpreted as Javascript code that returns "true" or "false", + but this can be overriden (see below) + If this is selected, then the condition must be an expression that evaluates to "true" (case is ignored). + For example, ${FOUND} or ${__jexl(${VAR} > 100)}. + Unlike the Javascript case, the condition is only checked to see if it matches "true" (case is ignored). + + + Should condition be evaluated for all children? + If not checked, then the condition is only evaluated on entry. + + +

    Examples (Javascript): +

      +
    • ${COUNT} < 10
    • +
    • "${VAR}" == "abcd"
    • +
    • ${JMeterThread.last_sample_ok} (check if last sample succeeded)
    • +
    + If there is an error interpreting the code, the condition is assumed to be false, and a message is logged in jmeter.log. +

    +

    Examples (Variable Expression): +

      +
    • ${__jexl(${COUNT} < 10)}
    • +
    • ${RESULT}
    • +
    +

    +
    + + + + + + + + + + +

    +The While Controller runs its children until the condition is "false". +

    + +

    Possible condition values:

    +
      +
    • blank - exit loop when last sample in loop fails
    • +
    • LAST - exit loop when last sample in loop fails. +If the last sample just before the loop failed, don't enter loop.
    • +
    • Otherwise - exit (or don't enter) the loop when the condition is equal to the string "false"
    • +
    + +The condition can be any variable or function that eventually evaluates to the string "false". +This allows the use of JavaScript, BeanShell, properties or variables as needed. + +

    + +Note that the is evaluated twice, once before starting sampling children and once at end of children sampling, so putting +non idempotent functions in Condition (like __counter) can introduce issues. + +

    +For example: +
      +
    • ${VAR} - where VAR is set to false by some other test element
    • +
    • ${__javaScript(${C}==10)}
    • +
    • ${__javaScript("${VAR2}"=="abcd")}
    • +
    • ${_P(property)} - where property is set to "false" somewhere else
    • +
    +
    + + Descriptive name for this controller that is shown in the tree, and used to name the transaction. + blank, LAST, or variable/function + +
    + + + +

    +The Switch Controller acts like the +in that it runs one of the subordinate elements on each iteration, but rather than +run them in sequence, the controller runs the element defined by the switch value. +

    +

    +Note: In versions of JMeter after 2.3.1, the switch value can also be a name. +

    +

    If the switch value is out of range, it will run the zeroth element, +which therefore acts as the default for the numeric case. +It also runs the zeroth element if the value is the empty string.

    +

    +If the value is non-numeric (and non-empty), then the Switch Controller looks for the +element with the same name (case is significant). +If none of the names match, then the element named "default" (case not significant) is selected. +If there is no default, then no element is selected, and the controller will not run anything. +

    +
    + + Descriptive name for this controller that is shown in the tree, and used to name the transaction. + The number (or name) of the subordinate element to be invoked. Elements are numbered from 0. + +
    + + +

    A ForEach controller loops through the values of a set of related variables. +When you add samplers (or controllers) to a ForEach controller, every sample sample (or controller) +is executed one or more times, where during every loop the variable has a new value. +The input should consist of several variables, each extended with an underscore and a number. +Each such variable must have a value. +So for example when the input variable has the name inputVar, the following variables should have been defined: +

      +
    • inputVar_1 = wendy
    • +
    • inputVar_2 = charles
    • +
    • inputVar_3 = peter
    • +
    • inputVar_4 = john
    • +
    +

    Note: the "_" separator is now optional.

    +When the return variable is given as "returnVar", the collection of samplers and controllers under the ForEach controller will be executed 4 consecutive times, +with the return variable having the respective above values, which can then be used in the samplers. +

    +

    +It is especially suited for running with the regular expression post-processor. +This can "create" the necessary input variables out of the result data of a previous request. +By omitting the "_" separator, the ForEach Controller can be used to loop through the groups by using +the input variable refName_g, and can also loop through all the groups in all the matches +by using an input variable of the form refName_${C}_g, where C is a counter variable. +

    +The ForEach Controller does not run any samples if inputVar_1 is null. +This would be the case if the Regular Expression returned no matches. +
    + + + Descriptive name for this controller that is shown in the tree. + Prefix for the variable names to be used as input. + Start index (exclusive) for loop over variables (first element is at start index + 1) + End index (inclusive) for loop over variables + + The name of the variable which can be used in the loop for replacement in the samplers + If not checked, the "_" separator is omitted. + + + + +

    Download this example (see Figure 7). +In this example, we created a Test Plan that sends a particular HTTP Request +only once and sends another HTTP Request to every link that can be found on the page.

    + +
    Figure 7 - ForEach Controller Example
    + +

    We configured the Thread Group for a single thread and a loop count value of +one. You can see that we added one HTTP Request to the Thread Group and +another HTTP Request to the ForEach Controller.

    +

    After the first HTTP request, a regular expression extractor is added, which extracts all the html links +out of the return page and puts them in the inputVar variable

    +

    In the ForEach loop, a HTTP sampler is added which requests all the links that were extracted from the first returned HTML page. +

    + +

    Here is another example you can download. +This has two Regular Expressions and ForEach Controllers. +The first RE matches, but the second does not match, +so no samples are run by the second ForEach Controller

    +
    Figure 8 - ForEach Controller Example 2
    +

    The Thread Group has a single thread and a loop count of two. +

    +Sample 1 uses the JavaTest Sampler to return the string "a b c d". +

    The Regex Extractor uses the expression (\w)\s which matches a letter followed by a space, +and returns the letter (not the space). Any matches are prefixed with the string "inputVar". +

    The ForEach Controller extracts all variables with the prefix "inputVar_", and executes its +sample, passing the value in the variable "returnVar". In this case it will set the variable to the values "a" "b" and "c" in turn. +

    The For 1 Sampler is another Java Sampler which uses the return variable "returnVar" as part of the sample Label +and as the sampler Data. +

    Sample 2, Regex 2 and For 2 are almost identical, except that the Regex has been changed to "(\w)\sx", +which clearly won't match. Thus the For 2 Sampler will not be run. +

    +
    +
    + + + +

    +The Module Controller provides a mechanism for substituting test plan fragments into the current test plan at run-time. +

    +

    +A test plan fragment consists of a Controller and all the test elements (samplers etc) contained in it. +The fragment can be located in any Thread Group, or on the . +If the fragment is located in a Thread Group, then its Controller can be disabled to prevent the fragment being run +except by the Module Controller. +Or you can store the fragments in a dummy Thread Group, and disable the entire Thread Group. +

    +

    +There can be multiple fragments, each with a different series of +samplers under them. The module controller can then be used to easily switch between these multiple test cases simply by choosing +the appropriate controller in its drop down box. This provides convenience for running many alternate test plans quickly and easily. +

    +

    +A fragment name is made up of the Controller name and all its parent names. +For example: +

    +Test Plan / Protocol: JDBC / Control / Interleave Controller (Module1)
    +
    +Any fragments used by the Module Controller must have a unique name, +as the name is used to find the target controller when a test plan is reloaded. +For this reason it is best to ensure that the Controller name is changed from the default +- as shown in the example above - +otherwise a duplicate may be accidentally created when new elements are added to the test plan. +

    +
    +The Module Controller should not be used with remote testing or non-gui testing in conjunction with Workbench components since the Workbench test elements are not part of test plan .jmx files. Any such test will fail. + + Descriptive name for this controller that is shown in the tree. + The module controller provides a list of all controllers loaded into the gui. Select + the one you want to substitute in at runtime. + +
    + + + +

    +The include controller is designed to use an external jmx file. To use it, create a Test Fragment +underneath the Test Plan and add any desired samplers, controllers etc. below it. +Then save the Test Plan. The file is now ready to be included as part of other Test Plans. +

    +

    +For convenience, a Thread Group can also be added in the external JMX file for debugging purposes. +A Module Controller can be used to reference the Test Fragment. The Thread Group will be ignored during the +include process. +

    +

    +If the test uses a Cookie Manager or User Defined Variables, these should be placed in the top-level +test plan, not the included file, otherwise they are not guaranteed to work. +

    + +This element does not support variables/functions in the filename field.

    +However, if the property includecontroller.prefix is defined, +the contents are used to prefix the pathname. +
    + +When using IncludeController and including the same JMX file, ensure you name the IncludeController differently to avoid facing known issue 50898. + +

    +If the file cannot be found at the location given by prefix+filename, then the controller +attempts to open the fileName relative to the JMX launch directory (versions of JMeter after 2.3.4). +

    +
    + + The file to include. + +
    + + + +

    + The Transaction Controller generates an additional + sample which measures the overall time taken to perform the nested test elements. +

    + + Note: when the check box "Include duration of timer and pre-post processors in generated sample" is checked, + the time includes all processing within the controller scope, not just the samples. + +

    + For JMeter versions after 2.3, there are two modes of operation +

      +
    • additional sample is added after the nested samples
    • +
    • additional sample is added as a parent of the nested samples
    • +
    +

    +

    + The generated sample time includes all the times for the nested samplers, and any timers etc. + Depending on the clock resolution, it may be slightly longer than the sum of the individual samplers plus timers. + The clock might tick after the controller recorded the start time but before the first sample starts. + Similarly at the end. +

    +

    The generated sample is only regarded as successful if all its sub-samples are successful.

    +

    + In parent mode, the individual samples can still be seen in the Tree View Listener, + but no longer appear as separate entries in other Listeners. + Also, the sub-samples do not appear in CSV log files, but they can be saved to XML files. +

    + + In parent mode, Assertions (etc) can be added to the Transaction Controller. + However by default they will be applied to both the individual samples and the overall transaction sample. + To limit the scope of the Assertions, use a Simple Controller to contain the samples, and add the Assertions + to the Simple Controller. + Parent mode controllers do not currently properly support nested transaction controllers of either type. + +
    + + Descriptive name for this controller that is shown in the tree, and used to name the transaction. + + If checked, then the sample is generated as a parent of the other samples, + otherwise the sample is generated as an independent sample. + + + Whether to include timer, pre- and post-processing delays in the generated sample. + Default is false (since JMeter 2.11, in previous versions the default value is true). + + +
    + + + +

    The Recording Controller is a place holder indicating where the proxy server should +record samples to. During test run, it has no effect, similar to the Simple Controller. But during +recording using the , all recorded samples will by default +be saved under the Recording Controller.

    + +
    + + Descriptive name for this controller that is shown in the tree. + + +
    + + + +

    The Critical Section Controller ensures that its children elements (samplers/controllers, etc) will be executed +by only one thread as a named lock will be taken before executing children of controller.

    +
    +

    + The figure below shows an example of using Critical Section Controller, in the figure below 2 Critical Section Controllers ensure + that: +

      +
    • DS2-${__threadNum} is executed only by one thread at a time
    • +
    • DS4-${__threadNum} is executed only by one thread at a time
    • +
    +
    Test Plan using Critical Section Controller
    +

    + + + Lock that will be taken by controller, ensure you use different lock names for unrelated sections + + +Critical Section Controller takes locks only within one JVM, so if using Distributed testing ensure your use case does not rely on all threads of all JVMs blocking. + + +
    + +^ + +
    + +
    + +

    +Most of the listeners perform several roles in addition to "listening" +to the test results. +They also provide means to view, save, and read saved test results. +

    Note that Listeners are processed at the end of the scope in which they are found.

    +

    +The saving and reading of test results is generic. The various +listeners have a panel whereby one can specify the file to +which the results will be written (or read from). +By default, the results are stored as XML +files, typically with a ".jtl" extension. +Storing as CSV is the most efficient option, but is less detailed than XML (the other available option). +

    +

    +Listeners do not process sample data in non-GUI mode, but the raw data will be saved if an output +file has been configured. +In order to analyse the data generated by a non-GUI test run, you need to load the file into the appropriate +Listener. +

    + +To read existing results and display them, use the file panel Browse button to open the file. + +

    +Versions of JMeter up to 2.3.2 used to clear any current data before loading the new file.

    +This is no longer done, thus allowing files to be merged. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. +

    +

    Results can be read from XML or CSV format files. +When reading from CSV results files, the header (if present) is used to determine which fields are present. +In order to interpret a header-less CSV file correctly, the appropriate properties must be set in jmeter.properties. +

    + +The file name can contain function and/or variable references. +However variable references do not work in client-server mode (functions work OK). + +

    Listeners can use a lot of memory if there are a lot of samples. +Most of the listeners currently keep a copy of every sample in their scope, apart from: +

    +
      +
    • Simple Data Writer
    • +
    • BeanShell/BSF Listener
    • +
    • Mailer Visualizer
    • +
    • Monitor Results
    • +
    • Summary Report
    • +
    +

    +The following Listeners no longer need to keep copies of every single sample. +Instead, samples with the same elapsed time are aggregated. +Less memory is now needed, especially if most samples only take a second or two at most. +

    +
      +
    • Aggregate Report
    • +
    • Aggregate Graph
    • +
    • Distribution Graph
    • +
    +

    To minimise the amount of memory needed, use the Simple Data Writer, and use the CSV format.

    +

    + +Versions of JMeter after 2.3.1 allow JMeter variables to be saved to the output files. +This can only be specified using a property. +See the Listener Sample Variables for details + +For full details on setting up the default items to be saved +see the Listener Default Configuration documentation. +For details of the contents of the output files, +see the CSV log format or +the XML log format. +

    +The entries in jmeter.properties are used to define the defaults; +these can be overriden for individual listeners by using the Configure button, +as shown below. +The settings in jmeter.properties also apply to the listener that is added +by using the -l command-line flag. + +

    + The figure below shows an example of the result file configuration panel +

    Result file configuration panel
    +

    + + Name of the file containing sample results. + The file name can be specified using either a relative or an absolute path name. + Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). + Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). + If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), + then the path is assumed to be relative to the JMX file location. + + File Browse Button + Select this to write/read only results with errors + Select this to write/read only results without errors. + If neither Errors nor Successes is selected, then all results are processed. + Configure Button, see below + +
    + + + +

    +Listeners can be configured to save different items to the result log files (JTL) by using the Config popup as shown below. +The defaults are defined as described in the Listener Default Configuration documentation. +Items with (CSV) after the name only apply to the CSV format; items with (XML) only apply to XML format. +CSV format cannot currently be used to save any items that include line-breaks. +

    +

    +Note that cookies, method and the query string are saved as part of the "Sampler Data" option. +

    +
    +
    + + + + +Graph Results MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. + +

    The Graph Results listener generates a simple graph that plots all sample times. Along +the bottom of the graph, the current sample (black), the current average of all samples(blue), the +current standard deviation (red), and the current throughput rate (green) are displayed in milliseconds.

    +

    The throughput number represents the actual number of requests/minute the server handled. This calculation +includes any delays you added to your test and JMeter's own internal processing time. The advantage +of doing the calculation like this is that this number represents something +real - your server in fact handled that many requests per minute, and you can increase the number of threads +and/or decrease the delays to discover your server's maximum throughput. Whereas if you made calculations +that factored out delays and JMeter's processing, it would be unclear what you could conclude from that +number.

    +

    The following table briefly describes the items on the graph. +Further details on the precise meaning of the statistical terms can be found on the web + - e.g. Wikipedia - or by consulting a book on statistics. +

    +
      +
    • Data - plot the actual data values
    • +
    • Average - plot the Average
    • +
    • Median - plot the Median (midway value)
    • +
    • Deviation - plot the Standard Deviation (a measure of the variation)
    • +
    • Throughput - plot the number of samples per unit of time
    • +
    +

    The individual figures at the bottom of the display are the current values. + "Latest Sample" is the current elapsed sample time, shown on the graph as "Data".

    +

    The value displayed on the top left of graph is the max of 90th percentile of response time.

    +
    + + + +Spline Visualizer MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. + + +

    +The Spline Visualizer provides a view of all sample times from the start +of the test till the end, regardless of how many samples have been taken. The spline +has 10 points, each representing 10% of the samples, and connected using spline +logic to show a single continuous line. +

    +

    +The graph is automatically scaled to fit within the window. +This needs to be borne in mind when comparing graphs. +

    +
    +
    + + + + +Assertion Results MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. + +

    The Assertion Results visualizer shows the Label of each sample taken. +It also reports failures of any Assertions that +are part of the test plan.

    + + + + +
    + + + + +View Results Tree MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. + +The View Results Tree shows a tree of all sample responses, allowing you to view the +response for any sample. In addition to showing the response, you can see the time it took to get +this response, and some response codes. +Note that the Request panel only shows the headers added by JMeter. +It does not show any headers (such as Host) that may be added by the HTTP protocol implementation. +

    +There are several ways to view the response, selectable by a drop-down box at the bottom of the left hand panel.

    + + + + + + + + + + + + + + + + + + + + + +
    RendererDescription
    CSS/JQuery TesterThe CSS/JQuery Tester only works for text responses. It shows the plain text in the upper panel. +The "Test" button allows the user to apply the CSS/JQuery to the upper panel and the results +will be displayed in the lower panel.
    +The engine of CSS/JQuery expression can be JSoup or Jodd, syntax of these 2 implementation differs slightly.
    +For example, the Selector a[class=sectionlink] with attribute href applied to the current JMeter functions page gives the following output: +
    +
    +Match count: 74
    +Match[1]=#functions
    +Match[2]=#what_can_do
    +Match[3]=#where
    +Match[4]=#how
    +Match[5]=#function_helper
    +Match[6]=#functions
    +Match[7]=#__regexFunction
    +Match[8]=#__regexFunction_parms
    +Match[9]=#__counter
    +... and so on ...
    +
    +
    DocumentThe Document view will show the extract text from various type of documents like Microsoft Office +(Word, Excel, PowerPoint 97-2003, 2007-2010 (openxml), Apache OpenOffice (writer, calc, impress), HTML, +gzip, jar/zip files (list of content), and some meta-data on "multimedia" files like mp3, mp4, flv, etc. The complete list of +support format is available on Apache Tika format page. +

    +Note: A requirement to the Document view is to download the +Apache Tika binary package (tika-app-x.x.jar) and put this in JMETER_HOME/lib directory. +

    +If the document is larger than 10 MB, then it won't be displayed. +To change this limit, set the JMeter property document.max_size (unit is byte) or set to 0 to remove the limit. +
    HTMLThe HTML view attempts to render the response as +HTML. The rendered HTML is likely to compare poorly to the view one +would get in any web browser; however, it does provide a quick +approximation that is helpful for initial result evaluation.
    +Images, style-sheets, etc. aren't downloaded. +
    HTML (download resources)If the HTML (download resources) view option is selected, the renderer +may download images, style-sheets, etc. referenced by the HTML code. +
    JSONThe JSON view will show the response in tree style (also handles JSON embedded in JavaScript). +
    Regexp TesterThe Regexp Tester view only works for text responses. It shows the plain text in the upper panel. +The "Test" button allows the user to apply the Regular Expression to the upper panel and the results +will be displayed in the lower panel.
    +The engine of regular expression is the same that the Regular Expression Extractor.
    +For example, the RE (JMeter\w*).* applied to the current JMeter home page gives the following output: +
    +
    +Match count: 26
    +Match[1][0]=JMeter - Apache JMeter&lt;/title>
    +Match[1][1]=JMeter
    +Match[2][0]=JMeter" title="JMeter" border="0"/>&lt;/a>
    +Match[2][1]=JMeter
    +Match[3][0]=JMeterCommitters">Contributors&lt;/a>
    +Match[3][1]=JMeterCommitters
    +... and so on ...
    +
    +
    +The first number in [] is the match number; the second number is the group. +Group [0] is whatever matched the whole RE. +Group [1] is whatever matched the 1st group, i.e. (JMeter\w*) in this case. +See Figure 9b (below). +
    Text +The default Text view shows all of the text contained in the response. +Note that this will only work if the response content-type is considered to be text. +If the content-type begins with any of the following, it is considered as binary, +otherwise it is considered to be text. +
    +image/
    +audio/
    +video/
    +
    +
    XMLThe XML view will show response in tree style. +Any DTD nodes or Prolog nodes will not show up in tree; however, response may contain those nodes. +
    XPath TesterThe XPath Tester only works for text responses. It shows the plain text in the upper panel. +The "Test" button allows the user to apply the XPath query to the upper panel and the results +will be displayed in the lower panel.
    +
    +

    Scroll automatically? option permit to have last node display in tree selection

    +

    +With Search option, most of the views also allow the displayed data to be searched; the result of the search will be high-lighted +in the display above. For example the Control panel screenshot below shows one result of searching for "Java". +Note that the search operates on the visible text, so you may get different results when searching +the Text and HTML views. +
    Note: The regular expression uses the Java engine (not ORO engine like the Regular Expression Extractor or Regexp Tester view). +

    +

    +If there is no content-type provided, then the content +will not be displayed in the any of the Response Data panels. +You can use to save the data in this case. +Note that the response data will still be available in the sample result, +so can still be accessed using Post-Processors. +

    +

    If the response data is larger than 200K, then it won't be displayed. +To change this limit, set the JMeter property view.results.tree.max_size. +You can also use save the entire response to a file using +. +

    +

    +Additional renderers can be created. +The class must implement the interface org.apache.jmeter.visualizers.ResultRenderer +and/or extend the abstract class org.apache.jmeter.visualizers.SamplerResultTab, and the +compiled code must be available to JMeter (e.g. by adding it to the lib/ext directory). +

    +
    +

    + The Control Panel (above) shows an example of an HTML display.
    + Figure 9 (below) shows an example of an XML display.
    + Figure 9a (below) shows an example of an Regexp tester display.
    + Figure 9b (below) shows an example of an Document display.
    +

    +
    Figure 9 Sample XML display
    +
    Figure 9a Sample Regexp Test display
    +
    Figure 9b Sample Document (here PDF) display
    +
    +

    +
    + + +The aggregate report creates a table row for each differently named request in your +test. For each request, it totals the response information and provides request count, min, max, +average, error rate, approximate throughput (request/second) and Kilobytes per second throughput. +Once the test is done, the throughput is the actual through for the duration of the entire test. +

    +The thoughput is calculated from the point of view of the sampler target +(e.g. the remote server in the case of HTTP samples). +JMeter takes into account the total time over which the requests have been generated. +If other samplers and timers are in the same thread, these will increase the total time, +and therefore reduce the throughput value. +So two identical samplers with different names will have half the throughput of two samplers with the same name. +It is important to choose the sampler names correctly to get the best results from +the Aggregate Report. +

    +

    +Calculation of the Median and 90% Line (90th percentile) values requires additional memory. +JMeter now combines samples with the same elapsed time, so far less memory is used. +However, for samples that take more than a few seconds, the probability is that fewer samples will have identical times, +in which case more memory will be needed. +Note you can use this listener afterwards to reload a CSV or XML results file which is the recommended way to avoid performance impacts. +See the for a similar Listener that does not store individual samples and so needs constant memory. +

    + +Starting with JMeter 2.12, you can configure the 3 percentile values you want to compute, this can be done by setting properties: +
      +
    • aggregate_rpt_pct1: defaults to 90th percentile
    • +
    • aggregate_rpt_pct2: defaults to 95th percentile
    • +
    • aggregate_rpt_pct3: defaults to 99th percentile
    • +
    +
    +
      +
    • Label - The label of the sample. +If "Include group name in label?" is selected, then the name of the thread group is added as a prefix. +This allows identical labels from different thread groups to be collated separately if required. +
    • +
    • # Samples - The number of samples with the same label
    • +
    • Average - The average time of a set of results
    • +
    • Median - The median is the time in the middle of a set of results. +50% of the samples took no more than this time; the remainder took at least as long.
    • +
    • 90% Line - 90% of the samples took no more than this time. +The remaining samples took at least as long as this. (90th percentile)
    • +
    • 95% Line - 95% of the samples took no more than this time. +The remaining samples took at least as long as this. (95th percentile)
    • +
    • 99% Line - 99% of the samples took no more than this time. +The remaining samples took at least as long as this. (99th percentile)
    • +
    • Min - The shortest time for the samples with the same label
    • +
    • Max - The longest time for the samples with the same label
    • +
    • Error % - Percent of requests with errors
    • +
    • Throughput - the Throughput is measured in requests per second/minute/hour. +The time unit is chosen so that the displayed rate is at least 1.0. +When the throughput is saved to a CSV file, it is expressed in requests/second, +i.e. 30.0 requests/minute is saved as 0.5. +
    • +
    • Kb/sec - The throughput measured in Kilobytes per second
    • +
    +

    Times are in milliseconds.

    +
    +
    +

    + The figure below shows an example of selecting the "Include group name" checkbox. +

    Sample "Include group name" display
    +

    +
    +
    + + +This visualizer creates a row for every sample result. +Like the , this visualizer uses a lot of memory. +

    +By default, it only displays the main (parent) samples; it does not display the sub-samples (child samples). +Versions of JMeter after 2.5.1 have a "Child Samples?" check-box. +If this is selected, then the sub-samples are displayed instead of the main samples. +

    +
    +
    + + +This listener can record results to a file +but not to the UI. It is meant to provide an efficient means of +recording data by eliminating GUI overhead. +When running in non-GUI mode, the -l flag can be used to create a data file. +The fields to save are defined by JMeter properties. +See the jmeter.properties file for details. + + + + + +

    Monitor Results is a new Visualizer for displaying server +status. It is designed for Tomcat 5, but any servlet container +can port the status servlet and use this monitor. There are two primary +tabs for the monitor. The first is the "Health" tab, which will show the +status of one or more servers. The second tab labled "Performance" shows +the performance for one server for the last 1000 samples. The equations +used for the load calculation is included in the Visualizer.

    +

    Currently, the primary limitation of the monitor is system memory. A +quick benchmark of memory usage indicates a buffer of 1000 data points for +100 servers would take roughly 10Mb of RAM. On a 1.4Ghz centrino +laptop with 1Gb of ram, the monitor should be able to handle several +hundred servers.

    +

    As a general rule, monitoring production systems should take care to +set an appropriate interval. Intervals shorter than 5 seconds are too +aggressive and have a potential of impacting the server. With a buffer of +1000 data points at 5 second intervals, the monitor would check the server +status 12 times a minute or 720 times a hour. This means the buffer shows +the performance history of each machine for the last hour.

    + +The monitor requires Tomcat 5 or above. +Use a browser to check that you can access the Tomcat status servlet OK. + +

    +For a detailed description of how to use the monitor, please refer to +Building a Monitor Test Plan +

    +
    +
    + + + + +Distribution Graph MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. + + +

    The distribution graph will display a bar for every unique response time. Since the +granularity of System.currentTimeMillis() is 10 milliseconds, the 90% threshold should be +within the width of the graph. The graph will draw two threshold lines: 50% and 90%. +What this means is 50% of the response times finished between 0 and the line. The same +is true of 90% line. Several tests with Tomcat were performed using 30 threads for 600K +requests. The graph was able to display the distribution without any problems and both +the 50% and 90% line were within the width of the graph. A performant application will +generally produce results that clump together. A poorly written application that has +memory leaks may result in wild fluctuations. In those situations, the threshold lines +may be beyond the width of the graph. The recommended solution to this specific problem +is fix the webapp so it performs well. If your test plan produces distribution graphs +with no apparent clumping or pattern, it may indicate a memory leak. The only way to +know for sure is to use a profiling tool.

    +
    +
    + + +The aggregate graph is similar to the aggregate report. The primary +difference is the aggregate graph provides an easy way to generate bar graphs and save +the graph as a PNG file. +
    +

    + The figure below shows an example of settings to draw this graph. +

    Aggregate graph settings
    +

    +
    +

    Please note: All this parameters aren't saved in JMeter jmx script.

    + +
      +
    • Columns to display: Choose the column(s) to display in graph.
    • +
    • Rectangles color: Clic on right color rectangle open a popup dialog to choose a custom color for column.
    • +
    • Foreground color Allow to change the value text color.
    • +
    • Value font: Allow to define font settings for the text.
    • +
    • Draw outlines bar? To draw or not the border line on bar chart
    • +
    • Show number grouping? Show or not the number grouping in Y Axis labels.
    • +
    • Value labels vertical? Change orientation for value label. (Default is horizontal)
    • +
    • Column label selection: Filter by result label. A regular expression can be used, example: .*Transaction.* +

      Before display the graph, click on Apply filter button to refresh internal data.
    + Define the graph's title on the head of chart. Empty value is the default value : "Aggregate Graph". + The button Synchronize with name define the title with the label of the listener. And define font settings for graph title + Compute the graph size by the width and height depending of the current JMeter's window size. + Use Width and Height fields to define a custom size. The unit is pixel. + Define the max length of X Axis label (in pixel). + Define a custom maximum value for Y Axis. + Define the placement and font settings for chart legend +
    +
    + + + +The Response Time Graph draws a line chart showing the evolution of response time during the test, for each labelled request. +If many samples exist for the same timestamp, the mean value is displayed. + +
    +

    + The figure below shows an example of settings to draw this graph. +

    Response time graph settings
    +

    +
    +

    Please note: All this parameters are saved in JMeter .jmx file.

    + + The time in milli-seconds for X axis interval. Samples are grouped according to this value. + Before display the graph, click on Apply interval button to refresh internal data. + Filter by result label. A regular expression can be used, ex..*Transaction.*. + Before display the graph, click on Apply filter button to refresh internal data. + Define the graph's title on the head of chart. Empty value is the default value : "Response Time Graph". + The button Synchronize with name define the title with the label of the listener. And define font settings for graph title + Define the width of the line. Define the type of each value point. Choose none to have a line without mark + Compute the graph size by the width and height depending of the current JMeter's window size. + Use Width and Height fields to define a custom size. The unit is pixel. + Customize the date format of X axis label. + The syntax is the Java SimpleDateFormat API. + Define a custom maximum value for Y Axis in milli-seconds. Define the increment for the scale (in ms) Show or not the number grouping in Y Axis labels. + Define the placement and font settings for chart legend + +
    + + +

    The mailer visualizer can be set up to send email if a test run receives too many +failed responses from the server.

    + + Descriptive name for this element that is shown in the tree. + Email address to send messages from. + Email address to send messages to, comma-separated. + Email subject line for success messages. + Once this number of successful responses is exceeded + after previously reaching the failure limit, a success email + is sent. The mailer will thus only send out messages in a sequence of failed-succeeded-failed-succeeded, etc. + Email subject line for fail messages. + Once this number of failed responses is exceeded, a failure + email is sent - i.e. set the count to 0 to send an e-mail on the first failure. + + IP address or host name of SMTP server (email redirector) + server. + Port of SMTP server (defaults to 25). + Login used to authenticate. + Password used to authenticate. + Type of encryption for SMTP authentication (SSL, TLS or none). + + Press this button to send a test mail + A field that keeps a running total of number + of failures so far received. + +
    + + + +

    +The BeanShell Listener allows the use of BeanShell for processing samples for saving etc. +

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The BeanShell script to run. The return value is ignored. +
    +

    Before invoking the script, some variables are set up in the BeanShell interpreter:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • sampleResult, prev - (SampleResult) - gives access to the previous SampleResult
    • +
    • sampleEvent (SampleEvent) gives access to the current sample event
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +

    If the property beanshell.listener.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

    +
    + + +The summary report creates a table row for each differently named request in your +test. This is similar to the , except that it uses less memory. +

    +The thoughput is calculated from the point of view of the sampler target +(e.g. the remote server in the case of HTTP samples). +JMeter takes into account the total time over which the requests have been generated. +If other samplers and timers are in the same thread, these will increase the total time, +and therefore reduce the throughput value. +So two identical samplers with different names will have half the throughput of two samplers with the same name. +It is important to choose the sampler labels correctly to get the best results from +the Report. +

    +
      +
    • Label - The label of the sample. +If "Include group name in label?" is selected, then the name of the thread group is added as a prefix. +This allows identical labels from different thread groups to be collated separately if required. +
    • +
    • # Samples - The number of samples with the same label
    • +
    • Average - The average elapsed time of a set of results
    • +
    • Min - The lowest elapsed time for the samples with the same label
    • +
    • Max - The longest elapsed time for the samples with the same label
    • +
    • Std. Dev. - the Standard Deviation of the sample elapsed time
    • +
    • Error % - Percent of requests with errors
    • +
    • Throughput - the Throughput is measured in requests per second/minute/hour. +The time unit is chosen so that the displayed rate is at least 1.0. +When the throughput is saved to a CSV file, it is expressed in requests/second, +i.e. 30.0 requests/minute is saved as 0.5. +
    • +
    • Kb/sec - The throughput measured in Kilobytes per second
    • +
    • Avg. Bytes - average size of the sample response in bytes. (in JMeter 2.2 it wrongly showed the value in kB)
    • +
    +

    Times are in milliseconds.

    +
    +
    +

    + The figure below shows an example of selecting the "Include group name" checkbox. +

    Sample "Include group name" display
    +

    +
    +
    + + + +

    + This test element can be placed anywhere in the test plan. + For each sample in its scope, it will create a file of the response Data. + The primary use for this is in creating functional tests, but it can also + be useful where the response is too large to be displayed in the + Listener. + The file name is created from the specified prefix, plus a number (unless this is disabled, see below). + The file extension is created from the document type, if known. + If not known, the file extension is set to 'unknown'. + If numbering is disabled, and adding a suffix is disabled, then the file prefix is + taken as the entire file name. This allows a fixed file name to be generated if required. + The generated file name is stored in the sample response, and can be saved + in the test log output file if required. +

    +

    + The current sample is saved first, followed by any sub-samples (child samples). + If a variable name is provided, then the names of the files are saved in the order + that the sub-samples appear. See below. +

    +
    + + Descriptive name for this element that is shown in the tree. + Prefix for the generated file names; this can include a directory name. + Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). + Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). + If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), + then the path is assumed to be relative to the JMX file location. + + + Name of a variable in which to save the generated file name (so it can be used later in the test plan). + If there are sub-samples then a numeric suffix is added to the variable name. + E.g. if the variable name is FILENAME, then the parent sample file name is saved in the variable FILENAME, + and the filenames for the child samplers are saved in FILENAME1, FILENAME2 etc. + + If selected, then only failed responses are saved + If selected, then only successful responses are saved + If selected, then no number is added to the prefix. If you select this option, make sure that the prefix is unique or the file may be overwritten. + If selected, then no suffix is added. If you select this option, make sure that the prefix is unique or the file may be overwritten. + If "Don't add number to prefix" is not checked, then numbers added to prefix will be padded by 0 so that prefix is has size of this value.Defaults to 0. + +
    + + + +

    +The BSF Listener allows BSF script code to be applied to sample results. +

    +
    + + Descriptive name for this element that is shown in the tree. + The BSF language to be used + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    + A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property + The script to run. +
    +

    +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

    +

    +Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. +

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • Label - the String Label
    • +
    • Filename - the script file name (if any)
    • +
    • Parameters - the parameters (as a String)
    • +
    • args[] - the parameters as a String array (split on whitespace)
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • sampleResult, prev - (SampleResult) - gives access to the SampleResult
    • +
    • sampleEvent - (SampleEvent) - gives access to the SampleEvent
    • +
    • sampler - (Sampler)- gives access to the last sampler
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 Listener allows JSR223 script code to be applied to sample results. +For details, see . +

    +
    +
    + + + This test element can be placed anywhere in the test plan. +Generates a summary of the test run so far to the log file and/or +standard output. Both running and differential totals are shown. +Output is generated every n seconds (default 30 seconds) on the appropriate +time boundary, so that multiple test runs on the same time will be synchronised. +See jmeter.properties file for the summariser configuration items: +
    +# Define the following property to automatically start a summariser with that name
    +# (applies to non-GUI mode only)
    +#summariser.name=summary
    +#
    +# interval between summaries (in seconds) default 3 minutes
    +#summariser.interval=30
    +#
    +# Write messages to log file
    +#summariser.log=true
    +#
    +# Write messages to System.out
    +#summariser.out=true
    +
    +This element is mainly intended for batch (non-GUI) runs. +The output looks like the following: +
    +label +   171 in  20.3s =    8.4/s Avg:  1129 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label +   263 in  31.3s =    8.4/s Avg:  1138 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label =   434 in  50.4s =    8.6/s Avg:  1135 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label +   263 in  31.0s =    8.5/s Avg:  1138 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label =   697 in  80.3s =    8.7/s Avg:  1136 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label +   109 in  12.4s =    8.8/s Avg:  1092 Min:    47 Max:  1250 Err:     0 (0.00%)
    +label =   806 in  91.6s =    8.8/s Avg:  1130 Min:    47 Max:  1250 Err:     0 (0.00%)
    +
    +The "label" is the the name of the element. +The "+" means that the line is a delta line, i.e. shows the changes since the last output.
    +The "=" means that the line is a totals line, i.e. it shows the running total.
    +Entries in the jmeter log file also include time-stamps. +The example "806 in 91.6s = 8.8/s" means that there were 806 samples recorded in 91.6 seconds, +and that works out at 8.8 samples per second.
    +The Avg (Average), Min(imum) and Max(imum) times are in milliseconds.
    +"Err" means number of errors (also shown as percentage).
    +The last two lines will appear at the end of a test. +They will not be synchronised to the appropriate time boundary. +Note that the initial and final deltas may be for less than the interval (in the example above this is 30 seconds). +The first delta will generally be lower, as JMeter synchronizes to the interval boundary. +The last delta will be lower, as the test will generally not finish on an exact interval boundary. +

    +The label is used to group sample results together. +So if you have multiple Thread Groups and want to summarize across them all, then use the same label + - or add the summariser to the Test Plan (so all thread groups are in scope). +Different summary groupings can be implemented +by using suitable labels and adding the summarisers to appropriate parts of the test plan. +

    + +In Non-GUI mode by default a Generate Summary Results listener named "summariser" is configured, if you have already added one to your Test Plan, ensure you name it differently +otherwise results will be accumulated under this label (summary) leading to wrong results (sum of total samples + samples located under the Parent of Generate Summary Results listener). +This is not a bug but a design choice allowing to summarize across thread groups. + +
    + + Descriptive name for this element that is shown in the tree. + It appears as the "label" in the output. Details for all elements with the same label will be added together. + + +
    + + + +The Comparison Assertion Visualizer shows the results of any elements. + + + Descriptive name for this element that is shown in the tree. + + + + + + +The backend listener is an Asynchronous listener that enables you to plug custom implementations of BackendListenerClient. +By default, a Graphite implementation is provided. + + + Descriptive name for this element that is shown in the tree. + Class of the BackendListenerClient implementation. + Size of the queue that holds the SampleResults while they are processed asynchronously. + Parameters of the BackendListenerClient implementation. + + + +

    The following parameters apply to the GraphiteBackendListenerClient implementation:

    + + + org.apache.jmeter.visualizers.backend.graphite.TextGraphiteMetricsSender or org.apache.jmeter.visualizers.backend.graphite.PickleGraphiteMetricsSender + Graphite or InfluxDB (with Graphite plugin enabled) server host + Graphite or InfluxDB (with Graphite plugin enabled) server port, defaults to 2003. Note PickleGraphiteMetricsSender (port 2004) can only talk to Graphite server. + Prefix of metrics sent to backend. Defaults to "jmeter." + Only send a summary with no detail. Defaults to true. + Semicolon separated list of samplers for which you want to report metrics to backend. + The percentiles you want to send to backend. List must be semicolon separated. + +

    Read this for more details.

    +
    Grafana dashboard
    +
    + +^ + +
    + +
    + +

    + Configuration elements can be used to set up defaults and variables for later use by samplers. + Note that these elements are processed at the start of the scope in which they are found, + i.e. before any samplers in the same scope. +

    +
    + + + +

    + CSV Data Set Config is used to read lines from a file, and split them into variables. + It is easier to use than the __CSVRead() and _StringFromFile() functions. + It is well suited to handling large numbers of variables, and is also useful for tesing with + "random" and unique values. + Generating unique random values at run-time is expensive in terms of CPU and memory, so just create the data + in advance of the test. If necessary, the "random" data from the file can be used in conjunction with + a run-time parameter to create different sets of values from each run - e.g. using concatenation - which is + much cheaper than generating everything at run-time. +

    +

    + Versions of JMeter after 2.3.1 allow values to be quoted; this allows the value to contain a delimiter. + Previously it was necessary to choose a delimiter that was not used in any values. + If "allow quoted data" is enabled, a value may be enclosed in double-quotes. + These are removed. To include double-quotes within a quoted field, use two double-quotes. + For example:

    +1,"2,3","4""5" =>
    +1
    +2,3
    +4"5
    +
    +

    +

    + Versions of JMeter after 2.3.4 support CSV files which have a header line defining the column names. + To enable this, leave the "Variable Names" field empty. The correct delimiter must be provided. +

    +

    + Versions of JMeter after 2.7 support CSV files with quoted data that includes new-lines. +

    +

    + By default, the file is only opened once, and each thread will use a different line from the file. + However the order in which lines are passed to threads depends on the order in which they execute, + which may vary between iterations. + Lines are read at the start of each test iteration. + The file name and mode are resolved in the first iteration. +

    +

    + See the description of the Share mode below for additional options (JMeter 2.3.2+). + If you want each thread to have its own set of values, then you will need to create a set of files, + one for each thread. For example test1.csv, test2.csv,... testn.csv. Use the filename + test${__threadNum}.csv and set the "Sharing mode" to "Current thread". +

    + CSV Dataset variables are defined at the start of each test iteration. + As this is after configuration processing is completed, + they cannot be used for some configuration items - such as JDBC Config - + that process their contents at configuration time (see 40394) + However the variables do work in the HTTP Auth Manager, as the username etc are processed at run-time. + +

    + As a special case, the string "\t" (without quotes) in the delimiter field is treated as a Tab. +

    +

    + When the end of file (EOF) is reached, and the recycle option is true, reading starts again with the first line of the file. +

    +

    + If the recycle option is false, and stopThread is false, then all the variables are set to &lt;EOF> when the end of file is reached. + This value can be changed by setting the JMeter property csvdataset.eofstring. +

    +

    + If the Recycle option is false, and Stop Thread is true, then reaching EOF will cause the thread to be stopped. +

    +
    + + Descriptive name for this element that is shown in the tree. + Name of the file to be read. + Relative file names are resolved with respect to the path of the active test plan. + For distributed testing, the CSV file must be stored on the server host system in the correct relative directory to where the jmeter server is started. + Absolute file names are also supported, but note that they are unlikely to work in remote mode, + unless the remote server has the same directory structure. + If the same physical file is referenced in two different ways - e.g. csvdata.txt and ./csvdata.txt - + then these are treated as different files. + If the OS does not distinguish between upper and lower case, csvData.TXT would also be opened separately. + + The encoding to be used to read the file, if not the platform default. + List of variable names (comma-delimited). + Versions of JMeter after 2.3.4 support CSV header lines: + if the variable name field empty, then the first line of the file is read and interpreted as the list of column names. + The names must be separated by the delimiter character. They can be quoted using double-quotes. + + Delimiter to be used to split the records in the file. + If there are fewer values on the line than there are variables the remaining variables are not updated - + so they will retain their previous value (if any). + Should the CSV file allow values to be quoted? + If enabled, then values can be enclosed in " - double-quote - allowing values to contain a delimeter. + + Should the file be re-read from the beginning on reaching EOF? (default is true) + Should the thread be stopped on EOF, if Recycle is false? (default is false) + +
      +
    • All threads - (the default) the file is shared between all the threads.
    • +
    • Current thread group - each file is opened once for each thread group in which the element appears
    • +
    • Current thread - each file is opened separately for each thread
    • +
    • Identifier - all threads sharing the same identifier share the same file. + So for example if you have 4 thread groups, you could use a common id for two or more of the groups + to share the file between them. + Or you could use the thread number to share the file between the same thread numbers in different thread groups. +
    • +
    +
    +
    +
    + + + + + + + DNS Cache Manager is designed for using in the root of Thread Group or Test Plan. Do not place it as child element of particular HTTP Sampler + + DNS Cache Manager working only with the HTTP request using HTTPClient4 implementation. +

    The DNS Cache Manager element allows to test applications, which have several servers behind load balancers (CDN, etc), + when user receives content from different IP's. By default JMeter uses JVM DNS cache. That's why + only one server from the cluster receives load. DNS Cache Manager resolves name for each thread separately each iteration and + saves results of resolving to its internal DNS Cache, which independent from both JVM and OS DNS caches. +

    +
    + + Descriptive name for this element that is shown in the tree. + If selected, DNS cache of every Thread is cleared each time new iteration is started. + System DNS resolver will be used. For correct work edit + $JAVA_HOME/jre/lib/security/java.security and add
    networkaddress.cache.ttl=0
    +
    + Custom DNS resolver(from dnsjava library) will be used. + List of DNS servers to use. If empty, network configuration DNS will used. + Add an entry to the DNS servers table. + Delete the currently selected table entry. +
    +
    + + +If there is more than one Authorization Manager in the scope of a Sampler, +there is currently no way to specify which one is to be used. + + +

    The Authorization Manager lets you specify one or more user logins for web pages that are +restricted using server authentication. You see this type of authentication when you use +your browser to access a restricted page, and your browser displays a login dialog box. JMeter +transmits the login information when it encounters this type of page.

    +

    +The Authorisation headers may not be shown in the Tree View Listener "Request" tab. +The Java implementation does pre-emptive authentication, but it does not +return the Authorization header when JMeter fetches the headers. +The Commons HttpClient (3.1) implementation defaults to pre-emptive and the header will be shown. +The HttpComponents implementation does not do pre-emptive auth +(it is supported by the library but JMeter does not enable it) +

    +

    +In versions of JMeter after 2.2, the HttpClient sampler defaults to pre-emptive authentication +if the setting has not been defined. To disable this, set the values as below, in which case +authentication will only be performed in response to a challenge. +

    +jmeter.properties:
    +httpclient.parameters.file=httpclient.parameters
    +
    +httpclient.parameters:
    +http.authentication.preemptive$Boolean=false
    +
    +Note: the above settings only apply to the HttpClient sampler (and the SOAP samplers, which use Httpclient). +

    + +When looking for a match against a URL, JMeter checks each entry in turn, and stops when it finds the first match. +Thus the most specific URLs should appear first in the list, followed by less specific ones. +Duplicate URLs will be ignored. +If you want to use different usernames/passwords for different threads, you can use variables. +These can be set up using a Element (for example). + +
    + + + Descriptive name for this element that is shown in the tree. + Used by Kerberos authentication. If checked, authentication will be done on each iteration of Main Thread Group loop even if it has already been done in a previous one. + This is usually useful if each main thread group iteration represents behaviour of one Virtual User. + + A partial or complete URL that matches one or more HTTP Request URLs. As an example, +say you specify a Base URL of "http://jmeter.apache.org/restricted/" with a username of "jmeter" and +a password of "jmeter". If you send an HTTP request to the URL +"http://jmeter.apache.org/restricted/ant/myPage.html", the Authorization Manager sends the login +information for the user named, "jmeter". + The username to authorize. + The password for the user. (N.B. this is stored unencrypted in the test plan) + The domain to use for NTLM. + The realm to use for NTLM. + Type of authentication to perform. JMeter can perform different types of authentications based on used Http Samplers: + + + + + +
    SamplerAuthentications
    JavaBASIC
    HttpClient 3.1BASIC, DIGEST
    HttpClient 4BASIC, DIGEST and Kerberos
    +
    +
    + +The Realm only applies to the HttpClient sampler. +In JMeter 2.2, the domain and realm did not have separate columns, and were encoded as part of +the user name in the form: [domain\]username[@realm]. +This was an experimental feature and has been removed. + +

    +Kerberos Configuration: +

    To configure Kerberos you need to setup at least two JVM system properties: +

      +
    • -Djava.security.krb5.conf=krb5.conf
    • +
    • -Djava.security.auth.login.config=jaas.conf
    • +
    +You can also configure those two properties in the file bin/system.properties. +Look at the two sample configuration files (krb5.conf and jaas.conf) located in the jmeter bin folder +for references to more documentation, and tweak them to match your Kerberos configuration. +

    +

    +When generating a SPN for Kerberos SPNEGO authentication IE and Firefox will omit the port number +from the url. Chrome has an option (--enable-auth-negotiate-port) to include the port +number if it differs from the standard ones (80 and 443). That behavior +can be emulated by setting the following jmeter property as below. +

    +

    +In jmeter.properties or user.properties, set: +

      +
    • kerberos.spnego.strip_port=false
    • +
    +

    +

    +Controls: +
      +
    • Add Button - Add an entry to the authorization table.
    • +
    • Delete Button - Delete the currently selected table entry.
    • +
    • Load Button - Load a previously saved authorization table and add the entries to the existing +authorization table entries.
    • +
    • Save As Button - Save the current authorization table to a file.
    • +
    + +When you save the Test Plan, JMeter automatically saves all of the authorization +table entries - including any passwords, which are not encrypted. + + + +

    Download this example. In this example, we created a Test Plan on a local server that sends three HTTP requests, two requiring a login and the +other is open to everyone. See figure 10 to see the makeup of our Test Plan. On our server, we have a restricted +directory named, "secret", which contains two files, "index.html" and "index2.html". We created a login id named, "kevin", +which has a password of "spot". So, in our Authorization Manager, we created an entry for the restricted directory and +a username and password (see figure 11). The two HTTP requests named "SecretPage1" and "SecretPage2" make requests +to "/secret/index.html" and "/secret/index2.html". The other HTTP request, named "NoSecretPage" makes a request to +"/index.html".

    + +
    Figure 10 - Test Plan
    +
    Figure 11 - Authorization Manager Control Panel
    + +

    When we run the Test Plan, JMeter looks in the Authorization table for the URL it is requesting. If the Base URL matches +the URL, then JMeter passes this information along with the request.

    + +You can download the Test Plan, but since it is built as a test for our local server, you will not +be able to run it. However, you can use it as a reference in constructing your own Test Plan. +
    + +
    + + + +

    +The HTTP Cache Manager is used to add caching functionality to HTTP requests within its scope to simulate browser cache feature. +Each Virtual User thread has its own Cache. By default, Cache Manager will store up to 5000 items in cache per Virtual User thread, using LRU algorithm. +Use property "maxSize" to modify this value. Note that the more you increase this value the more HTTP Cache Manager will consume memory, so be sure to adapt -Xmx option accordingly. +

    +

    +If a sample is successful (i.e. has response code 2xx) then the Last-Modified and Etag (and Expired if relevant) values are saved for the URL. +Before executing the next sample, the sampler checks to see if there is an entry in the cache, +and if so, the If-Last-Modified and If-None-Match conditional headers are set for the request. +

    +

    +Additionally, if the "Use Cache-Control/Expires header" option is selected, then the Cache-Control/Expires value is checked against the current time. +If the request is a GET request, and the timestamp is in the future, then the sampler returns immediately, +without requesting the URL from the remote server. This is intended to emulate browser behaviour. +Note that if Cache-Control header is "no-cache", the response will be stored in cache as pre-expired, +so will generate a conditional GET request. +If Cache-Control has any other value, +the "max-age" expiry option is processed to compute entry lifetime, if missing then expire header will be used, if also missing entry will be cached +as specified in RFC 2616 section 13.2.4. using Last-Modified time and response Date. +

    +

    +If the requested document has not changed since it was cached, then the response body will be empty. +Likewise if the Expires date is in the future. +This may cause problems for Assertions. +

    + +
    + + Descriptive name for this element that is shown in the tree. + + If selected, then the cache is cleared at the start of the thread. + + See description above. + See description above. + +
    + + + +If there is more than one Cookie Manager in the scope of a Sampler, +there is currently no way to specify which one is to be used. +Also, a cookie stored in one cookie manager is not available to any other manager, +so use multiple Cookie Managers with care. + +

    The Cookie Manager element has two functions:

    +First, it stores and sends cookies just like a web browser. If you have an HTTP Request and +the response contains a cookie, the Cookie Manager automatically stores that cookie and will +use it for all future requests to that particular web site. Each JMeter thread has its own +"cookie storage area". So, if you are testing a web site that uses a cookie for storing +session information, each JMeter thread will have its own session. +Note that such cookies do not appear on the Cookie Manager display, but they can be seen using +the Listener. +

    +

    +JMeter version 2.3.2 and earlier did not check that received cookies were valid for the URL. +This meant that cross-domain cookies were stored, and might be used later. +This has been fixed in later versions. +To revert to the earlier behaviour, define the JMeter property "CookieManager.check.cookies=false". +

    +

    +Received Cookies can be stored as JMeter thread variables +(versions of JMeter after 2.3.2 no longer do this by default). +To save cookies as variables, define the property "CookieManager.save.cookies=true". +Also, cookies names are prefixed with "COOKIE_" before they are stored (this avoids accidental corruption of local variables) +To revert to the original behaviour, define the property "CookieManager.name.prefix= " (one or more spaces). +If enabled, the value of a cookie with the name TEST can be referred to as ${COOKIE_TEST}. +

    +

    Second, you can manually add a cookie to the Cookie Manager. However, if you do this, +the cookie will be shared by all JMeter threads.

    +

    Note that such Cookies are created with an Expiration time far in the future

    +

    +Since version 2.0.3, cookies with null values are ignored by default. +This can be changed by setting the JMeter property: CookieManager.delete_null_cookies=false. +Note that this also applies to manually defined cookies - any such cookies will be removed from the display when it is updated. +Note also that the cookie name must be unique - if a second cookie is defined with the same name, it will replace the first. +

    +
    + + Descriptive name for this element that is shown in the tree. + If selected, all server-defined cookies are cleared each time the main Thread Group loop is executed. + In JMeter versions after 2.3, any cookies defined in the GUI are not cleared. + The cookie policy that will be used to manage the cookies. + "compatibility" is the default, and should work in most cases. + See http://hc.apache.org/httpclient-3.x/cookies.html and + http://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/cookie/CookiePolicy.html + [Note: "ignoreCookies" is equivalent to omitting the CookieManager.] + + HC3CookieHandler (HttpClient 3.1 API) or HC4CookieHandler (HttpClient 4 API). + Default is HC3CookieHandler. +

    + [Note: If you have a website to test with IPv6 address, choose HC4CookieHandler (IPv6 compliant)]
    + This + gives you the opportunity to use hardcoded cookies that will be used by all threads during the test execution. +

    + The "domain" is the hostname of the server (without http://); the port is currently ignored. +
    + Add an entry to the cookie table. + Delete the currently selected table entry. + Load a previously saved cookie table and add the entries to the existing +cookie table entries. + + Save the current cookie table to a file (does not save any cookies extracted from HTTP Responses). + +
    + +
    + + +

    This element lets you set default values that your HTTP Request controllers use. For example, if you are +creating a Test Plan with 25 HTTP Request controllers and all of the requests are being sent to the same server, +you could add a single HTTP Request Defaults element with the "Server Name or IP" field filled in. Then, when +you add the 25 HTTP Request controllers, leave the "Server Name or IP" field empty. The controllers will inherit +this field value from the HTTP Request Defaults element.

    + +In JMeter 2.2 and earlier, port 80 was treated specially - it was ignored if the sampler used the https protocol. +JMeter 2.3 and later treat all port values equally; a sampler that does not specify a port will use the HTTP Request Defaults port, if one is provided. + +
    + + + Descriptive name for this element that is shown in the tree. + Domain name or IP address of the web server. e.g. www.example.com. [Do not include the http:// prefix. + Port the web server is listening to. + Connection Timeout. Number of milliseconds to wait for a connection to open. + Response Timeout. Number of milliseconds to wait for a response. + Java, HttpClient3.1, HttpClient4. + If not specified the default depends on the value of the JMeter property + jmeter.httpsampler, failing that, the Java implementation is used. + HTTP or HTTPS. + HTTP GET or HTTP POST. + The path to resource (for example, /servlets/myServlet). If the + resource requires query string parameters, add them below in the "Send Parameters With the Request" section. + Note that the path is the default for the full path, not a prefix to be applied to paths + specified on the HTTP Request screens. + + The query string will + be generated from the list of parameters you provide. Each parameter has a name and + value. The query string will be generated in the correct fashion, depending on + the choice of "Method" you made (ie if you chose GET, the query string will be + appended to the URL, if POST, then it will be sent separately). Also, if you are + sending a file using a multipart form, the query string will be created using the + multipart form specifications. + Hostname or IP address of a proxy server to perform request. [Do not include the http:// prefix.] + Port the proxy server is listening to. + (Optional) username for proxy server. + (Optional) password for proxy server. (N.B. this is stored unencrypted in the test plan) + Tell JMeter to parse the HTML file +and send HTTP/HTTPS requests for all images, Java applets, JavaScript files, CSSs, etc. referenced in the file. + + Use a pool of concurrent connections to get embedded resources. + Pool size for concurrent connections used to get embedded resources. + + If present, this must be a regular expression that is used to match against any embedded URLs found. + So if you only want to download embedded resources from http://example.com/, use the expression: + http://example\.com/.* + + + +Note: radio buttons only have two states - on or off. +This makes it impossible to override settings consistently +- does off mean off, or does it mean use the current default? +JMeter uses the latter (otherwise defaults would not work at all). +So if the button is off, then a later element can set it on, +but if the button is on, a later element cannot set it off. + +
    + + + +

    The Header Manager lets you add or override HTTP request headers.

    +

    +Versions of JMeter up to 2.3.2 supported only one Header Manager per sampler; +if there were more in scope, then only the last one would be used. +

    +

    +JMeter now supports multiple Header Managers. The header entries are merged to form the list for the sampler. +If an entry to be merged matches an existing header name, it replaces the previous entry, +unless the entry value is empty, in which case any existing entry is removed. +This allows one to set up a default set of headers, and apply adjustments to particular samplers. +

    +
    + + + Descriptive name for this element that is shown in the tree. + Name of the request header. + Two common request headers you may want to experiment with +are "User-Agent" and "Referer". + Request header value. + Add an entry to the header table. + Delete the currently selected table entry. + Load a previously saved header table and add the entries to the existing +header table entries. + Save the current header table to a file. + + + + +

    Download this example. In this example, we created a Test Plan +that tells JMeter to override the default "User-Agent" request header and use a particular Internet Explorer agent string +instead. (see figures 12 and 13).

    + +
    Figure 12 - Test Plan
    +
    Figure 13 - Header Manager Control Panel
    +
    + +
    + + +

    The Java Request Defaults component lets you set default values for Java testing. See the .

    +
    + +
    + + + Creates a database connection (used by Sampler) + from the supplied JDBC Connection settings. The connection may be optionally pooled between threads. + Otherwise each thread gets its own connection. + The connection configuration name is used by the JDBC Sampler to select the appropriate + connection. + + + Descriptive name for the connection configuration that is shown in the tree. + The name of the variable the connection is tied to. + Multiple connections can be used, each tied to a different variable, allowing JDBC Samplers + to select the appropriate connection. + Each name must be different. If there are two configuration elements using the same name, + only one will be saved. JMeter versions after 2.3 log a message if a duplicate name is detected. + + + Maximum number of connections allowed in the pool. + In most cases, set this to zero (0). + This means that each thread will get its own pool with a single connection in it, i.e. + the connections are not shared betweeen threads. +
    + If you really want to use shared pooling (why?), then set the max count to the same as the number of threads + to ensure threads don't wait on each other. +
    + Pool throws an error if the timeout period is exceeded in the + process of trying to retrieve a connection + This is used to specify how long idle connections will be maintained in the pool before being closed. For a complete explanation on how this works, see ResourceLimitingPool.trim() (Defaults to "60000", 1 minute) + Turn auto commit on or off for the connections. + The keep-alive is used enable a mechanism to monitor the health of connections. If a connection has not been used for Max Connection Age (ms) then before returning the connection from a call to getConnection(), the connection is first used to ping the database to make sure that it is still alive. + Setting the age allows the 5 second age to be overridden. Validation Query will be used to test it. + Controls the age mentionned in "Keep-Alive" property. It means connections not used for more than Max Connection Age will be tested + A simple query used to determine if the database is still + responding. + JDBC Connection string for the database. + Fully qualified name of driver class. (Must be in + JMeter's classpath - easiest to copy .jar file into JMeter's /lib directory). + Name of user to connect as. + Password to connect with. (N.B. this is stored unencrypted in the test plan) +
    +

    Different databases and JDBC drivers require different JDBC settings. +The Database URL and JDBC Driver class are defined by the provider of the JDBC implementation.

    +

    Some possible settings are shown below. Please check the exact details in the JDBC driver documentation.

    + +

    +If JMeter reports No suitable driver, then this could mean either: +

      +
    • The driver class was not found. In this case, there will be a log message such as DataSourceElement: Could not load driver: {classname} java.lang.ClassNotFoundException: {classname}
    • +
    • The driver class was found, but the class does not support the connection string. This could be because of a syntax error in the connection string, or because the the wrong classname was used.
    • +
    +If the database server is not running or is not accessible, then JMeter will report a java.net.ConnectException. +

    + + + + + + + + +
    DatabaseDriver classDatabase URL
    MySQLcom.mysql.jdbc.Driverjdbc:mysql://host[:port]/dbname
    PostgreSQLorg.postgresql.Driverjdbc:postgresql:{dbname}
    Oracleoracle.jdbc.OracleDriverjdbc:oracle:thin:@//host:port/service +OR
    jdbc:oracle:thin:@(description=(address=(host={mc-name})(protocol=tcp)(port={port-no}))(connect_data=(sid={sid})))
    Ingres (2006)ingres.jdbc.IngresDriverjdbc:ingres://host:port/db[;attr=value]
    SQL Server (MS JDBC driver)com.microsoft.sqlserver.jdbc.SQLServerDriverjdbc:sqlserver://host:port;DatabaseName=dbname
    Apache Derbyorg.apache.derby.jdbc.ClientDriverjdbc:derby://server[:port]/databaseName[;URLAttributes=value[;...]]
    +The above may not be correct - please check the relevant JDBC driver documentation. +
    + + + +

    The Keystore Config Element lets you configure how Keystore will be loaded and which keys it will use. +This component is typically used in HTTPS scenarios where you don't want to take into account keystore initialization into account in response time.

    +

    To use this element, you need to setup first a Java Key Store with the client certificates you want to test, to do that: +

      +
    1. Create your certificates either with Java keytool utility or through your PKI
    2. +
    3. If created by PKI, import your keys in Java Key Store by converting them to a format acceptable by JKS
    4. +
    5. Then reference the keystore file through the 2 JVM properties (or add them in system.properties): +
        +
      • -Djavax.net.ssl.keyStore=path_to_keystore
      • +
      • -Djavax.net.ssl.keyStorePassword=password_of_keystore
      • +
      +
    6. +
    +

    +
    + +preload.shortDescription=Preload Keystore before test. Setting is to true is usually the best option. +startIndex.displayName=Alias Start index (0-based) +startIndex.shortDescription=First index of Alias in Keystore +endIndex.displayName=Alias End index (0-based) +endIndex.shortDescription=Last index of Alias in Keystore. When using Variable name ensure it is large enough so that all keys are loaded at startup. +clientCertAliasVarName.displayName=Variable name holding certificate alias +clientCertAliasVarName.shortDescription=Variable name that will contain the alias to use for Cert authentication. Var content can come from CSV Data Set. + + + Descriptive name for this element that is shown in the tree. + Wether or not to preload Keystore. Setting is to true is usually the best option. + Variable name that will contain the alias to use for authentication by client certificate. Variable value will be filled from CSV Data Set for example. In the screenshot, "certificat_ssl" will also be a variable in CSV Data Set. + The index of the first key to use in Keystore, 0-based. + The index of the last key to use in Keystore, 0-based. When using "Variable name holding certificate alias" ensure it is large enough so that all keys are loaded at startup. + + +To make JMeter use more than one certificate you need to ensure that: +
      +
    • https.use.cached.ssl.context=false is set in jmeter.properties or user.properties
    • +
    • You use either HTTPClient 3.1 or 4 implementations for HTTP Request
    • +
    +
    +
    + + +

    The Login Config Element lets you add or override username and password settings in samplers that use username and password as part of their setup.

    +
    + + + Descriptive name for this element that is shown in the tree. + The default username to use. + The default password to use. (N.B. this is stored unencrypted in the test plan) + + +
    + + +

    The LDAP Request Defaults component lets you set default values for LDAP testing. See the .

    +
    + +
    + + +

    The LDAP Extended Request Defaults component lets you set default values for extended LDAP testing. See the .

    +
    + +
    + + + +

    + The TCP Sampler Config provides default data for the TCP Sampler +

    +
    + + Descriptive name for this element that is shown in the tree. + Name of the TCPClient class. Defaults to the property tcp.handler, failing that TCPClientImpl. + Name or IP of TCP server + Port to be used + If selected, the connection is kept open. Otherwise it is closed when the data has been read. + If selected, the connection will be closed after running the sampler. + Enable/disable SO_LINGER with the specified linger time in seconds when a socket is created. If you set "SO_LINGER" value as 0, you may prevent large numbers of sockets sitting around with a TIME_WAIT status. + Byte value for end of line, set this to a value outside the range -128 to +127 to skip eol checking. You may set this in jmeter.properties file as well with eolByte property. If you set this in TCP Sampler Config and in jmeter.properties file at the same time, the setting value in the TCP Sampler Config will be used. + Connect Timeout (milliseconds, 0 disables). + Response Timeout (milliseconds, 0 disables). + Should the nodelay property be set? + Text to be sent + +
    + + +

    The User Defined Variables element lets you define an initial set of variables, just as in the . + +Note that all the UDV elements in a test plan - no matter where they are - are processed at the start. + +So you cannot reference variables which are defined as part of a test run, e.g. in a Post-Processor. +

    +

    + +UDVs should not be used with functions that generate different results each time they are called. +Only the result of the first function call will be saved in the variable. + +However, UDVs can be used with functions such as __P(), for example: +

    +HOST      ${__P(host,localhost)} 
    +
    +which would define the variable "HOST" to have the value of the JMeter property "host", defaulting to "localhost" if not defined. +

    +

    +For defining variables during a test run, see . +UDVs are processed in the order they appear in the Plan, from top to bottom. +

    +

    +For simplicity, it is suggested that UDVs are placed only at the start of a Thread Group +(or perhaps under the Test Plan itself). +

    +

    +Once the Test Plan and all UDVs have been processed, the resulting set of variables is +copied to each thread to provide the initial set of variables. +

    +

    +If a runtime element such as a User Parameters Pre-Processor or Regular Expression Extractor defines a variable +with the same name as one of the UDV variables, then this will replace the initial value, and all other test +elements in the thread will see the updated value. +

    +
    + +If you have more than one Thread Group, make sure you use different names for different values, as UDVs are shared between Thread Groups. +Also, the variables are not available for use until after the element has been processed, +so you cannot reference variables that are defined in the same element. +You can reference variables defined in earlier UDVs or on the Test Plan. + + + Descriptive name for this element that is shown in the tree. + Variable name/value pairs. The string under the "Name" + column is what you'll need to place inside the brackets in ${...} constructs to use the variables later on. The + whole ${...} will then be replaced by the string in the "Value" column. + +
    + + + +

    +The Random Variable Config Element is used to generate random numeric strings and store them in variable for use later. +It's simpler than using together with the __Random() function. +

    +

    +The output variable is constructed by using the random number generator, +and then the resulting number is formatted using the format string. +The number is calculated using the formula minimum+Random.nextInt(maximum-minimum+1). +Random.nextInt() requires a positive integer. +This means that maximum-minimum - i.e. the range - must be less than 2147483647, +however the minimum and maximum values can be any long values so long as the range is OK. +

    +
    + + + Descriptive name for this element that is shown in the tree. + The name of the variable in which to store the random string. + The java.text.DecimalFormat format string to be used. + For example "000" which will generate numbers with at least 3 digits, + or "USER_000" which will generate output of the form USER_nnn. + If not specified, the default is to generate the number using Long.toString() + The minimum value (long) of the generated random number. + The maximum value (long) of the generated random number. + The seed for the random number generator. Default is the current time in milliseconds. + If you use the same seed value with Per Thread set to true, you will get the same value for earch Thread as per + Random class. + + If False, the generator is shared between all threads in the thread group. + If True, then each thread has its own random generator. + + +
    + + +

    Allows the user to create a counter that can be referenced anywhere +in the Thread Group. The counter config lets the user configure a starting point, a maximum, +and the increment. The counter will loop from the start to the max, and then start over +with the start, continuing on like that until the test is ended.

    +

    From version 2.1.2, the counter now uses a long to store the value, so the range is from -2^63 to 2^63-1.

    +
    + + Descriptive name for this element that is shown in the tree. + The starting number for the counter. The counter will equal this + number during the first iteration. + How much to increment the counter by after each + iteration. + If the counter exceeds the maximum, then it is reset to the Start value. + For versions after 2.2 the default is Long.MAX_VALUE (previously it was 0). + + Optional format, e.g. 000 will format as 001, 002 etc. + This is passed to DecimalFormat, so any valid formats can be used. + If there is a problem interpreting the format, then it is ignored. + [The default format is generated using Long.toString()] + + This controls how you refer to this value in other elements. Syntax is + as in user-defined values: $(reference_name}. + In other words, is this a global counter, or does each user get their + own counter? If unchecked, the counter is global (ie, user #1 will get value "1", and user #2 will get value "2" on + the first iteration). If checked, each user has an independent counter. + This option is only available when counter is tracked per User, if checked, + counter will be reset to Start value on each Thread Group iteration. This can be useful when Counter is inside a Loop Controller. + +
    + + +

    The Simple Config Element lets you add or override arbitrary values in samplers. You can choose the name of the value +and the value itself. Although some adventurous users might find a use for this element, it's here primarily for developers as a basic +GUI that they can use while developing new JMeter components.

    +
    + + + Descriptive name for this element that is shown in the tree. + The name of each parameter. These values are internal to JMeter's workings and + are not generally documented. Only those familiar with the code will know these values. + The value to apply to that parameter. + + +
    + + + + Creates a MongoDB connection (used by Sampler) + from the supplied Connection settings. Each thread gets its own connection. + The connection configuration name is used by the JDBC Sampler to select the appropriate + connection. + You can then access com.mongodb.DB object in Beanshell or JSR223 Test Elements through the element MongoDBHolder + using this code
    + + +import com.mongodb.DB; +import org.apache.jmeter.protocol.mongodb.config.MongoDBHolder; +DB db = MongoDBHolder.getDBFromSource("value of property MongoDB Source", + "value of property Database Name"); +... + +
    + + Descriptive name for the connection configuration that is shown in the tree. + Mongo DB Servers + The name of the variable the connection is tied to. + Each name must be different. If there are two configuration elements using the same name, only one will be saved. + + + + If true, the driver will keep trying to connect to the same server in case that the socket cannot be established.
    + There is maximum amount of time to keep retrying, which is 15s by default.
    This can be useful to avoid some exceptions being thrown when a server is down temporarily by blocking the operations. +
    It also can be useful to smooth the transition to a new master (so that a new master is elected within the retry time).
    + Note that when using this flag\: +
    - for a replica set, the driver will trying to connect to the old master for that time, instead of failing over to the new one right away +
    - this does not prevent exception from being thrown in read/write operations on the socket, which must be handled by application.
    + Even if this flag is false, the driver already has mechanisms to automatically recreate broken connections and retry the read operations.
    + Default is false. +
    + + + The connection timeout in milliseconds.
    It is used solely when establishing a new connection Socket.connect(java.net.SocketAddress, int)
    Default is 0 and means no timeout. +
    + + The maximum amount of time in MS to spend retrying to open connection to the same server.
    Default is 0, which means to use the default 15s if autoConnectRetry is on. +
    + + The maximum wait time in ms that a thread may wait for a connection to become available.
    Default is 120,000. +
    + + The socket timeout in milliseconds It is used for I/O socket read and write operations Socket.setSoTimeout(int)
    Default is 0 and means no timeout. +
    + + This flag controls the socket keep alive feature that keeps a connection alive through firewalls Socket.setKeepAlive(boolean)
    + Default is false. +
    + + This multiplier, multiplied with the connectionsPerHost setting, gives the maximum number of threads that may be waiting for a connection to become available from the pool.
    + All further threads will get an exception right away.
    + For example if connectionsPerHost is 10 and threadsAllowedToBlockForConnectionMultiplier is 5, then up to 50 threads can wait for a connection.
    + Default is 5. +
    + + If true the driver will use a WriteConcern of WriteConcern.SAFE for all operations.
    + If w, wtimeout, fsync or j are specified, this setting is ignored.
    + Default is false. +
    + + The fsync value of the global WriteConcern.
    + Default is false. +
    + + The j value of the global WriteConcern.
    + Default is false. +
    + + The w value of the global WriteConcern.
    Default is 0. +
    + + The wtimeout value of the global WriteConcern.
    Default is 0. +
    + + If batch inserts should continue after the first error + +
    +
    + +^ + +
    + +
    + +

    + Assertions are used to perform additional checks on samplers, and are processed after every sampler + in the same scope. + To ensure that an Assertion is applied only to a particular sampler, add it as a child of the sampler. +

    +

    + Note: Unless documented otherwise, Assertions are not applied to sub-samples (child samples) - + only to the parent sample. + In the case of BSF and BeanShell Assertions, the script can retrieve sub-samples using the method + prev.getSubResults() which returns an array of SampleResults. + The array will be empty if there are none. +

    +

    + Versions of JMeter after 2.3.2 include the option to apply certain assertions + to either the main sample, the sub-samples or both. + The default is to apply the assertion to the main sample only. + If the Assertion supports this option, then there will be an entry on the GUI which looks like the following: +

    Assertion Scope
    + or the following +
    Assertion Scope
    + If a sub-sampler fails and the main sample is successful, + then the main sample will be set to failed status and an Assertion Result will be added. + If the JMeter variable option is used, it is assumed to relate to the main sample, and + any failure will be applied to the main sample only. +

    + + The variable JMeterThread.last_sample_ok is updated to + "true" or "false" after all assertions for a sampler have been run. + +
    + + +

    The response assertion control panel lets you add pattern strings to be compared against various + fields of the response. + The pattern strings are: +

      +
    • Contains, Matches: Perl5-style regular expressions
    • +
    • Equals, Substring: plain text, case-sensitive
    • +
    +

    +

    + A summary of the pattern matching characters can be found at ORO Perl5 regular expressions. +

    +

    You can also choose whether the strings will be expected +to match the entire response, or if the response is only expected to contain the +pattern. You can attach multiple assertions to any controller for additional flexibility.

    +

    Note that the pattern string should not include the enclosing delimiters, + i.e. use Price: \d+ not /Price: \d+/. +

    +

    + By default, the pattern is in multi-line mode, which means that the "." meta-character does not match newline. + In multi-line mode, "^" and "$" match the start or end of any line anywhere within the string + - not just the start and end of the entire string. Note that \s does match new-line. + Case is also significant. To override these settings, one can use the extended regular expression syntax. + For example: +

    +
    +
    (?i)
    ignore case
    +
    (?s)
    treat target as single line, i.e. "." matches new-line
    +
    (?is)
    both the above
    +
    +These can be used anywhere within the expression and remain in effect until overriden. e.g. +
    +
    (?i)apple(?-i) Pie
    matches "ApPLe Pie", but not "ApPLe pIe"
    +
    (?s)Apple.+?Pie
    matches Apple followed by Pie, which may be on a subsequent line.
    +
    Apple(?s).+?Pie
    same as above, but it's probably clearer to use the (?s) at the start.
    +
    + +
    + + Descriptive name for this element that is shown in the tree. + + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
      +
    • Main sample only - assertion only applies to the main sample
    • +
    • Sub-samples only - assertion only applies to the sub-samples
    • +
    • Main sample and sub-samples - assertion applies to both.
    • +
    • JMeter Variable - assertion is to be applied to the contents of the named variable
    • +
    +
    + Instructs JMeter which field of the Response to test. +
      +
    • Text Response - the response text from the server, i.e. the body, excluding any HTTP headers.
    • +
    • Document (text) - the extract text from various type of documents via Apache Tika (see Document view section).
    • +
    • URL sampled
    • +
    • Response Code - e.g. 200
    • +
    • Response Message - e.g. OK
    • +
    • Response Headers, including Set-Cookie headers (if any)
    • +
    +
    + Instructs JMeter to set the status to success initially. +

    + The overall success of the sample is determined by combining the result of the + assertion with the existing Response status. + When the Ignore Status checkbox is selected, the Response status is forced + to successful before evaluating the Assertion. +

    + HTTP Responses with statuses in the 4xx and 5xx ranges are normally + regarded as unsuccessful. + The "Ignore status" checkbox can be used to set the status successful before performing further checks. + Note that this will have the effect of clearing any previous assertion failures, + so make sure that this is only set on the first assertion. +
    + Indicates how the text being tested + is checked against the pattern. +
      +
    • Contains - true if the text contains the regular expression pattern
    • +
    • Matches - true if the whole text matches the regular expression pattern
    • +
    • Equals - true if the whole text equals the pattern string (case-sensitive)
    • +
    • Substring - true if the text contains the pattern string (case-sensitive)
    • +
    + Equals and Substring patterns are plain strings, not regular expressions. + NOT may also be selected to invert the result of the check.
    + A list of patterns to + be tested. + Each pattern is tested separately. + If a pattern fails, then further patterns are not checked. + There is no difference between setting up + one Assertion with multiple patterns and setting up multiple Assertions with one + pattern each (assuming the other options are the same). + However, when the Ignore Status checkbox is selected, this has the effect of cancelling any + previous assertion failures - so make sure that the Ignore Status checkbox is only used on + the first Assertion. + +
    +

    + The pattern is a Perl5-style regular expression, but without the enclosing brackets. +

    + +
    +
    Figure 14 - Test Plan
    +
    Figure 15 - Assertion Control Panel with Pattern
    +
    Figure 16 - Assertion Listener Results (Pass)
    +
    Figure 17 - Assertion Listener Results (Fail)
    +
    +
    + + +
    + + +

    The Duration Assertion tests that each response was received within a given amount +of time. Any response that takes longer than the given number of milliseconds (specified by the +user) is marked as a failed response.

    + + + Descriptive name for this element that is shown in the tree. + The maximum number of milliseconds + each response is allowed before being marked as failed. + +
    + + +

    The Size Assertion tests that each response contains the right number of bytes in it. You can specify that +the size be equal to, greater than, less than, or not equal to a given number of bytes.

    +Since JMeter 2.3RC3, an empty response is treated as being 0 bytes rather than reported as an error. +
    + + + Descriptive name for this element that is shown in the tree. + + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
      +
    • Main sample only - assertion only applies to the main sample
    • +
    • Sub-samples only - assertion only applies to the sub-samples
    • +
    • Main sample and sub-samples - assertion applies to both.
    • +
    • JMeter Variable - assertion is to be applied to the contents of the named variable
    • +
    +
    + The number of bytes to use in testing the size of the response (or value of the JMeter variable). + Whether to test that the response is equal to, greater than, less than, + or not equal to, the number of bytes specified. + +
    +
    + + +

    The XML Assertion tests that the response data consists of a formally correct XML document. It does not +validate the XML based on a DTD or schema or do any further validation.

    + + +Descriptive name for this element that is shown in the tree. + + +
    + + +

    The BeanShell Assertion allows the user to perform assertion checking using a BeanShell script. +

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +Note that a different Interpreter is used for each independent occurence of the assertion +in each thread in a test script, but the same Interpreter is used for subsequent invocations. +This means that variables persist across calls to the assertion. +

    +

    +All Assertions are called from the same thread as the sampler. +

    +

    +If the property "beanshell.assertion.init" is defined, it is passed to the Interpreter +as the name of a sourced file. This can be used to define common methods and variables. +There is a sample init file in the bin directory: BeanShellAssertion.bshrc +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. This overrides the script. + The file name is stored in the script variable FileName + The BeanShell script to run. The return value is ignored. +
    +

    There's a sample script you can try.

    +

    +Before invoking the script, some variables are set up in the BeanShell interpreter. +These are strings unless otherwise noted: +

      +
    • log - the Logger Object. (e.g.) log.warn("Message"[,Throwable])
    • +
    • SampleResult - the SampleResult Object; read-write
    • +
    • Response - the response Object; read-write
    • +
    • Failure - boolean; read-write; used to set the Assertion status
    • +
    • FailureMessage - String; read-write; used to set the Assertion message
    • +
    • ResponseData - the response body (byte [])
    • +
    • ResponseCode - e.g. 200
    • +
    • ResponseMessage - e.g. OK
    • +
    • ResponseHeaders - contains the HTTP headers
    • +
    • RequestHeaders - contains the HTTP headers sent to the server
    • +
    • SampleLabel
    • +
    • SamplerData - data that was sent to the server
    • +
    • ctx - JMeterContext
    • +
    • vars - JMeterVariables - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.putObject("OBJ1",new Object());
    • +
    • props - JMeterProperties (class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    +

    +

    The following methods of the Response object may be useful: +

      +
    • setStopThread(boolean)
    • +
    • setStopTest(boolean)
    • +
    • String getSampleLabel()
    • +
    • setSampleLabel(String)
    • +

    +
    + + +

    The MD5Hex Assertion allows the user to check the MD5 hash of the response data.

    + + +Descriptive name for this element that is shown in the tree. +32 hex digits representing the MD5 hash (case not significant) + + +
    + + +

    The HTML Assertion allows the user to check the HTML syntax of the response data using JTidy.

    + + +Descriptive name for this element that is shown in the tree. +omit/auto/strict/loose +HTML, XHTML or XML +Only take note of errors? +Number of errors allowed before classing the response as failed +Number of warnings allowed before classing the response as failed +Name of file to which report is written + + +
    + +

    The XPath Assertion tests a document for well formedness, has the option +of validating against a DTD, or putting the document through JTidy and testing for an +XPath. If that XPath exists, the Assertion is true. Using "/" will match any well-formed +document, and is the default XPath Expression. +The assertion also supports boolean expressions, such as "count(//*error)=2". +See http://www.w3.org/TR/xpath for more information +on XPath. +

    +Some sample expressions: +
      +
    • //title[text()='Text to match'] - matches &lt;text&gt;Text to match&lt;/text&gt; anywhere in the response
    • +
    • /title[text()='Text to match'] - matches &lt;text&gt;Text to match&lt;/text&gt; at root level in the response
    • +
    +
    + + +Descriptive name for this element that is shown in the tree. +Use Tidy, i.e. be tolerant of XML/HTML errors +Sets the Tidy Quiet flag +If a Tidy error occurs, then set the Assertion accordingly +Sets the Tidy showWarnings option +Should namespaces be honoured? +Check the document against its schema. +Ignore Element Whitespace. +If selected, external DTDs are fetched. +XPath to match in the document. +True if a XPath expression is not matched + + +The non-tolerant parser can be quite slow, as it may need to download the DTD etc. + + +As a work-round for namespace limitations of the Xalan XPath parser implementation on which JMeter is based, +you can provide a Properties file which contains mappings for the namespace prefixes: +
      +
    • prefix1=Full Namespace 1
    • +
    • prefix2=Full Namespace 2
    • +
    • ...
    • +
    + +You reference this file in jmeter.properties file using the property: +
      +
    • xpath.namespace.config
    • +
    +
    +
    + +

    The XML Schema Assertion allows the user to validate a response against an XML Schema.

    + + +Descriptive name for this element that is shown in the tree. +Specify XML Schema File Name + +
    + + + +

    +The BSF Assertion allows BSF script code to be used to check the status of the previous sample. +

    +
    + + Descriptive name for this element that is shown in the tree. + The BSF language to be used + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    + A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property + The script to run. +
    +

    +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

    +

    The following variables are set up for use by the script:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • Label - the String Label
    • +
    • Filename - the script file name (if any)
    • +
    • Parameters - the parameters (as a String)
    • +
    • args[] - the parameters as a String array (split on whitespace)
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • SampleResult, prev - (SampleResult) - gives access to the previous SampleResult (if any)
    • +
    • sampler - (Sampler)- gives access to the current sampler
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    • AssertionResult - the assertion result
    • +
    +

    +The script can check various aspects of the SampleResult. +If an error is detected, the script should use AssertionResult.setFailureMessage("message") and AssertionResult.setFailure(true). +

    +

    For futher details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 Assertion allows JSR223 script code to be used to check the status of the previous sample. +For details, see . +

    +
    +
    + + + + +Compare Assertion MUST NOT BE USED during load test as it consumes a lot of resources (memory and CPU). Use it only for either functional testing or +during Test Plan debugging and Validation. + + +The Compare Assertion can be used to compare sample results within its scope. +Either the contents or the elapsed time can be compared, and the contents can be filtered before comparison. +The assertion comparisons can be seen in the . + + + Descriptive name for this element that is shown in the tree. + Whether or not to compare the content (response data) + If the value is >=0, then check if the response time difference is no greater than the value. + I.e. if the value is 0, then the response times must be exactly equal. + Filters can be used to remove strings from the content comparison. + For example, if the page has a time-stamp, it might be matched with: "Time: \d\d:\d\d:\d\d" and replaced with a dummy fixed time "Time: HH:MM:SS". + + + + + + +The SMIME Assertion can be used to evaluate the sample results from the Mail Reader Sampler. +This assertion verifies if the body of a mime message is signed or not. The signature can also be verified against a specific signer certificate. +As this is a functionality that is not necessarily needed by most users, additional jars need to be downloaded and added to JMETER_HOME/lib :

    +
      +
    • bcmail-xxx.jar (BouncyCastle SMIME/CMS)
    • +
    • bcprov-xxx.jar (BouncyCastle Provider)
    • +
    +These need to be downloaded from BouncyCastle. +

    +If using the Mail Reader Sampler, +please ensure that you select "Store the message using MIME (raw)" otherwise the Assertion won't be able to process the message correctly. +

    +
    + + Descriptive name for this element that is shown in the tree. + If selected, the asertion will verify if it is a valid signature according to the parameters defined in the Signer Certificate box. + Whether or not to expect a signature in the message + "No Check" means that it wil not perform signature verification. "Check values" is used to verify the signature against the inputs provided. And "Certificate file" will perform the verification against a specific certificate file. + + The Mail sampler can retrieve multiple messages in a single sample. + Use this field to specify which message will be checked. + Messages are numbered from 0, so 0 means the first message. + Negative numbers count from the LAST message; -1 means LAST, -2 means penultimate etc. + + +
    + +^ + +
    + +
    + +

    +

    + Note that timers are processed before each sampler in the scope in which they are found; + if there are several timers in the same scope, all the timers will be processed before + each sampler. +

    + Timers are only processed in conjunction with a sampler. + A timer which is not in the same scope as a sampler will not be processed at all. +

    + To apply a timer to a single sampler, add the timer as a child element of the sampler. + The timer will be applied before the sampler is executed. + To apply a timer after a sampler, either add it to the next sampler, or add it as the + child of a Sampler. +

    +
    + + +

    If you want to have each thread pause for the same amount of time between +requests, use this timer.

    + + + Descriptive name for this timer that is shown in the tree. + Number of milliseconds to pause. + +
    + + + +

    This timer pauses each thread request for a random amount of time, with most +of the time intervals ocurring near a particular value. +The total delay is the sum of the Gaussian distributed value (with mean 0.0 and standard deviation 1.0) times +the deviation value you specify, and the offset value. +Another way to explain it, in Gaussian Random Timer, the variation around constant offset has a gaussian curve distribution. + +

    + + + + Descriptive name for this timer that is shown in the tree + Deviation in milliseconds. + Number of milliseconds to pause in addition +to the random delay. + + +
    + + + +

    This timer pauses each thread request for a random amount of time, with +each time interval having the same probability of occurring. The total delay +is the sum of the random value and the offset value.

    + + + Descriptive name for this timer that is shown in the tree. + Maxium random number of milliseconds to +pause. + Number of milliseconds to pause in addition +to the random delay. + + +
    + + + +

    This timer introduces variable pauses, calculated to keep the total throughput (in terms of samples per minute) as close as possible to a give figure. Of course the throughput will be lower if the server is not capable of handling it, or if other timers or time-consuming test elements prevent it.

    +

    +N.B. although the Timer is called the Constant Throughput timer, the throughput value does not need to be constant. +It can be defined in terms of a variable or function call, and the value can be changed during a test. +The value can be changed in various ways: +

    +
      +
    • using a counter variable
    • +
    • using a JavaScript or BeanShell function to provide a changing value
    • +
    • using the remote BeanShell server to change a JMeter property
    • +
    +

    See Best Practices for further details. +Note that the throughput value should not be changed too often during a test +- it will take a while for the new value to take effect. +

    +
    + + Descriptive name for this timer that is shown in the tree. + Throughput we want the timer to try to generate. + +
      +
    • this thread only - each thread will try to maintain the target throughput. The overall throughput will be proportional to the number of active threads.
    • +
    • all active threads in current thread group - the target throughput is divided amongst all the active threads in the group. + Each thread will delay as needed, based on when it last ran.
    • +
    • all active threads - the target throughput is divided amongst all the active threads in all Thread Groups. + Each thread will delay as needed, based on when it last ran. + In this case, each other Thread Group will need a Constant Throughput timer with the same settings.
    • +
    • all active threads in current thread group (shared) - as above, but each thread is delayed based on when any thread in the group last ran.
    • +
    • all active threads (shared) - as above; each thread is delayed based on when any thread last ran.
    • +
    +
    +

    The shared and non-shared algorithms both aim to generate the desired thoughput, and will produce similar results. + The shared algorithm should generate a more accurate overall transaction rate. + The non-shared algortihm should generate a more even spread of transactions across threads.

    +
    +
    + + + + +

    +The purpose of the SyncTimer is to block threads until X number of threads have been blocked, and +then they are all released at once. A SyncTimer can thus create large instant loads at various +points of the test plan. +

    +
    + + + Descriptive name for this timer that is shown in the tree. + Number of threads to release at once. Setting it to 0 is equivalent to setting it to Number of threads in Thread Group. + If set to 0, Timer will wait for the number of threads to reach the value in "Number of Simultaneous Users to Group", if superior to 0, then timer will wait at max "Timeout in milliseconds" if number of Threads does not reach if ater the timeout interval the number of users waiting is not reached, timer will stop waiting. Defaults to 0 + + +If timeout in milliseconds is set to 0 and number of threads never reaches "Number of Simultaneous Users to Group by" then Test will pause infinitely. +Only a forced stop will stop it. Setting Timeout in milliseconds is an option to consider in this case. + + +Synchronizing timer blocks only within one JVM, so if using Distributed testing ensure you never set "Number of Simultaneous Users to Group by" to a value superior to the number of users +of its containing Thread group considering 1 injector only. + + +
    + + + +

    +The BeanShell Timer can be used to generate a delay. +

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    +
    + + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The return value is used as the number of milliseconds to wait. + + + The BeanShell script. The return value is used as the number of milliseconds to wait. + +
    +

    Before invoking the script, some variables are set up in the BeanShell interpreter:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • prev - (SampleResult) - gives access to the previous SampleResult (if any)
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +

    If the property beanshell.timer.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

    +
    + + + + +

    +The BSF Timer can be used to generate a delay using a BSF scripting language. +

    +
    + + Descriptive name for this element that is shown in the tree. + + The scripting language to be used. + + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    +
    + + A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property + The return value is converted to a long integer and used as the number of milliseconds to wait. + + + The script. The return value is used as the number of milliseconds to wait. + +
    +

    Before invoking the script, some variables are set up in the script interpreter:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • sampler - the current Sampler
    • +
    • Label - the name of the Timer
    • +
    • Filename - the file name (if any)
    • +
    • OUT - System.out
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 Timer can be used to generate a delay using a JSR223 scripting language, +For details, see . +

    +
    +
    + + + +

    This timer pauses each thread request for a random amount of time, with most +of the time intervals ocurring near a particular value. The total delay is the +sum of the Poisson distributed value, and the offset value.

    + + + + Descriptive name for this timer that is shown in the tree + Lambda value in milliseconds. + Number of milliseconds to pause in addition +to the random delay. + + +
    + +^ + +
    + +
    + +

    + Preprocessors are used to modify the Samplers in their scope. +

    +
    + + +

    This modifier parses HTML response from the server and extracts +links and forms. A URL test sample that passes through this modifier will be examined to +see if it "matches" any of the links or forms extracted +from the immediately previous response. It would then replace the values in the URL +test sample with appropriate values from the matching link or form. Perl-type regular +expressions are used to find matches.

    +
    + +Matches are performed using protocol, host, path and parameter names. +The target sampler cannot contain parameters that are not in the response links. + + +If using distributed testing, ensure you switch mode (see jmeter.properties) so that it's not a stripping one, see 56376 + + + +

    Consider a simple example: let's say you wanted JMeter to "spider" through your site, +hitting link after link parsed from the HTML returned from your server (this is not +actually the most useful thing to do, but it serves as a good example). You would create +a , and add the "HTML Link Parser" to it. Then, create an +HTTP Request, and set the domain to ".*", and the path likewise. This will +cause your test sample to match with any link found on the returned pages. If you wanted to +restrict the spidering to a particular domain, then change the domain value +to the one you want. Then, only links to that domain will be followed. +

    +
    + + +

    A more useful example: given a web polling application, you might have a page with +several poll options as radio buttons for the user to select. Let's say the values +of the poll options are very dynamic - maybe user generated. If you wanted JMeter to +test the poll, you could either create test samples with hardcoded values chosen, or you +could let the HTML Link Parser parse the form, and insert a random poll option into +your URL test sample. To do this, follow the above example, except, when configuring +your Web Test controller's URL options, be sure to choose "POST" as the +method. Put in hard-coded values for the domain, path, and any additional form parameters. +Then, for the actual radio button parameter, put in the name (let's say it's called "poll_choice"), +and then ".*" for the value of that parameter. When the modifier examines +this URL test sample, it will find that it "matches" the poll form (and +it shouldn't match any other form, given that you've specified all the other aspects of +the URL test sample), and it will replace your form parameters with the matching +parameters from the form. Since the regular expression ".*" will match with +anything, the modifier will probably have a list of radio buttons to choose from. It +will choose at random, and replace the value in your URL test sample. Each time through +the test, a new random value will be chosen.

    + +
    Figure 18 - Online Poll Example
    + +One important thing to remember is that you must create a test sample immediately +prior that will return an HTML page with the links and forms that are relevant to +your dynamic test sample. +
    + +
    + + +

    This modifier works similarly to the HTML Link Parser, except it has a specific purpose for which +it is easier to use than the HTML Link Parser, and more efficient. For web applications that +use URL Re-writing to store session ids instead of cookies, this element can be attached at the +ThreadGroup level, much like the . Simply give it the name +of the session id parameter, and it will find it on the page and add the argument to every +request of that ThreadGroup.

    +

    Alternatively, this modifier can be attached to select requests and it will modify only them. +Clever users will even determine that this modifier can be used to grab values that elude the +.

    +
    + + + Descriptive name given to this element in the test tree. + The name of the parameter to grab from + previous response. This modifier will find the parameter anywhere it exists on the page, and + grab the value assigned to it, whether it's in an HREF or a form. + Some web apps rewrite URLs by appending + a semi-colon plus the session id parameter. Check this box if that is so. + Some web apps rewrite URLs without using an "=" sign between the parameter name and value (such as Intershop Enfinity). + Prevents the query string to end up in the path extension (such as Intershop Enfinity). + + Should the value of the session Id be saved for later use when the session Id is not present? + + + URL Encode value when writing parameter + + + + +If using distributed testing, ensure you switch mode (see jmeter.properties) so that it's not a stripping one, see 56376. + + +
    + + +

    The HTML Parameter Mask is used to generate unique values for HTML arguments. By +specifying the name of the parameter, a value prefix and suffix, and counter parameters, this +modifier will generate values of the form "name=prefixcountersuffix". Any HTTP +Request that it modifies, it will replace any parameter with the same name or add the appropriate +parameter to the requests list of arguments.

    +The value of the argument in your HTTP Request must be a '*' in order for the HTML Parameter Mask +Modifier to replace it. +

    As an example, the username for a login script could be modified to send a series of values +such as:

    +user_1

    +user_2

    +user_3

    +user_4, etc.

    + + + Descriptive name given to this element in the test tree. + The name of the parameter to + modify or add to the HTTP Request. + A string value to prefix to every generated value. + A number value to start the counter at. + A number value to end the counter, at which point it restarts + with the Lower Bound value. + Value to increment the counter by each time through. + A string value to add as suffix to every generated vaue. + +
    + + +

    Allows the user to specify values for User Variables specific to individual threads.

    +

    User Variables can also be specified in the Test Plan but not specific to individual threads. This panel allows +you to specify a series of values for any User Variable. For each thread, the variable will be assigned one of the values from the series +in sequence. If there are more threads than values, the values get re-used. For example, this can be used to assign a distinct +user id to be used by each thread. User variables can be referenced in any field of any jMeter Component.

    + +

    The variable is specified by clicking the Add Variable button in the bottom of the panel and filling in the Variable name in the 'Name:' column. +To add a new value to the series, click the 'Add User' button and fill in the desired value in the newly added column.

    + +

    Values can be accessed in any test component in the same thread group, using the function syntax: ${variable}.

    +

    See also the element, which is more suitable for large numbers of parameters

    +
    + + + Descriptive name for this element that is shown in the tree. + A flag to indicate whether the User Paramters element + should update its variables only once per iteration. if you embed functions into the UP, then you may need greater + control over how often the values of the variables are updated. Keep this box checked to ensure the values are + updated each time through the UP's parent controller. Uncheck the box, and the UP will update the parameters for + every sample request made within its scope. + + +
    + + + +

    +The BeanShell PreProcessor allows arbitrary code to be applied before taking a sample. +

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The BeanShell script. The return value is ignored. +
    +

    Before invoking the script, some variables are set up in the BeanShell interpreter:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • prev - (SampleResult) - gives access to the previous SampleResult (if any)
    • +
    • sampler - (Sampler)- gives access to the current sampler
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +

    If the property beanshell.preprocessor.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

    +
    + + + +

    +The BSF PreProcessor allows BSF script code to be applied before taking a sample. +

    +
    + + Descriptive name for this element that is shown in the tree. + The BSF language to be used + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    + A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property + The script to run. +
    +

    +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

    +

    The following BSF variables are set up for use by the script:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • Label - the String Label
    • +
    • Filename - the script file name (if any)
    • +
    • Parameters - the parameters (as a String)
    • +
    • args[] - the parameters as a String array (split on whitespace)
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • sampler - (Sampler)- gives access to the current sampler
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 PreProcessor allows JSR223 script code to be applied before taking a sample. +For details, see . +

    +
    +
    + + + +

    +The JDBC PreProcessor enables you to run some SQL statement just before a sample runs. +This can be useful if your JDBC Sample requires some data to be in DataBase and you cannot compute this in a setup Thread group. +For details, see . +

    +

    +See the following Test plan: +

    + + Test Plan using JDBC Pre/Post Processor + +

    +In the linked test plan,"Create Price Cut-Off" JDBC PreProcessor calls a stored procedure to create a Price Cut-Off in Database, +this one will be used by "Calculate Price cut off". + +

    Create Price Cut-Off Preprocessor
    + +

    +
    +
    + + +

    Allows to specify dynamic values for HTTP parameters extracted from another HTTP Request using regular expressions. + RegEx User Parameters are specific to individual threads.

    +

    This component allows you to specify reference name of a regular expression that extracts names and values of HTTP request parameters. + Regular expression group numbers must be specified for parameter's name and also for parameter's value. + Replacement will only occur for parameters in the Sampler that uses this RegEx User Parameters which name matches

    +
    + + + Descriptive name for this element that is shown in the tree. + Name of a reference to a regular expression + Group number of regular expression used to extract parameter names + Group number of regular expression used to extract parameter values + + +

    Example:

    +

    Suppose we have a request which returns a form with 3 input parameters and we want to extract the value of 2 of them to inject them in next request

    +

    1. - Create Post Processor Regular Expression for first HTTP Request

    +
      +
    • refName - set name of a regular expression Ex. (listParams)
    • +
    • regular expression - expression that will extract input names and input values attributes +
      + Ex: input name="([^"]+?)" value="([^"]+?)"
    • +
    • template would be empty
    • +
    • match nr - -1 (in order to iterate through all the possible matches)
    • +
    + +

    2. - Create Pre Processor RegEx User Parameters for second HTTP Request

    +
      +
    • refName - set the same reference name of a regular expression, would be listParams in our example
    • +
    • parameter names group number - group number of regular expression for parameter names, would be 1 in our example
    • +
    • parameter values group number - group number of regular expression for parameter values, would be 2 in our example
    • +
    + +

    See also the element, which is used to extract parametes names and values

    +
    + + Test Plan showing how to use RegEx User Parameters + + +^ + +
    + +
    + +

    + As the name suggests, Post-Processors are applied after samplers. Note that they are + applied to all the samplers in the same scope, so to ensure that a post-processor + is applied only to a particular sampler, add it as a child of the sampler. +

    +

    + Note: Unless documented otherwise, Post-Processors are not applied to sub-samples (child samples) - + only to the parent sample. + In the case of BSF and BeanShell post-processors, the script can retrieve sub-samples using the method + prev.getSubResults() which returns an array of SampleResults. + The array will be empty if there are none. +

    +

    + Post-Processors are run before Assertions, so they do not have access to any Assertion Results, nor will + the sample status reflect the results of any Assertions. If you require access to Assertion Results, try + using a Listener instead. Also note that the variable JMeterThread.last_sample_ok is set to "true" or "false" + after all Assertions have been run. +

    +
    + +

    Allows the user to extract values from a server response using a Perl-type regular expression. As a post-processor, +this element will execute after each Sample request in its scope, applying the regular expression, extracting the requested values, +generate the template string, and store the result into the given variable name.

    + + Descriptive name for this element that is shown in the tree. + + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
      +
    • Main sample only - only applies to the main sample
    • +
    • Sub-samples only - only applies to the sub-samples
    • +
    • Main sample and sub-samples - applies to both.
    • +
    • JMeter Variable - assertion is to be applied to the contents of the named variable
    • +
    + Matching is applied to all qualifying samples in turn. + For example if there is a main sample and 3 sub-samples, each of which contains a single match for the regex, + (i.e. 4 matches in total). + For match number = 3, Sub-samples only, the extractor will match the 3rd sub-sample. + For match number = 3, Main sample and sub-samples, the extractor will match the 2nd sub-sample (1st match is main sample). + For match number = 0 or negative, all qualifying samples will be processed. + For match number > 0, matching will stop as soon as enough matches have been found. +
    + + The following fields can be checked: +
      +
    • Body - the body of the response, e.g. the content of a web-page (excluding headers)
    • +
    • Body (unescaped) - the body of the response, with all Html escape codes replaced. + Note that Html escapes are processed without regard to context, so some incorrect substitutions + may be made. + Note that this option highly impacts performances, so use it only when absolutely necessary and be aware of its impacts +
    • +
    • Body as a Document - the extract text from various type of documents via Apache Tika (see Document view section). + Note that the Body as a Document option can impact performances, so ensure it is Ok for your test +
    • +
    • Request Headers - may not be present for non-HTTP samples
    • +
    • Response Headers - may not be present for non-HTTP samples
    • +
    • URL
    • +
    • Response Code - e.g. 200
    • +
    • Response Message - e.g. OK
    • +
    + Headers can be useful for HTTP samples; it may not be present for other sample types. +
    + The name of the JMeter variable in which to store the result. Also note that each group is stored as [refname]_g#, where [refname] is the string you entered as the reference name, and # is the group number, where group 0 is the entire match, group 1 is the match from the first set of parentheses, etc. + The regular expression used to parse the response data. + This must contain at least one set of parentheses "()" to capture a portion of the string, unless using the group $0$. + Do not enclose the expression in / / - unless of course you want to match these characters as well. + + The template used to create a string from the matches found. This is an arbitrary string + with special elements to refer to groups within the regular expression. The syntax to refer to a group is: '$1$' to refer to + group 1, '$2$' to refer to group 2, etc. $0$ refers to whatever the entire expression matches. + Indicates which match to use. The regular expression may match multiple times. +
      +
    • Use a value of zero to indicate JMeter should choose a match at random.
    • +
    • A positive number N means to select the nth match.
    • +
    • Negative numbers are used in conjunction with the ForEach controller - see below.
    • +
    +
    + + If the regular expression does not match, then the reference variable will be set to the default value. + This is particularly useful for debugging tests. If no default is provided, then it is difficult to tell + whether the regular expression did not match, or the RE element was not processed or maybe the wrong variable + is being used. +

    + However, if you have several test elements that set the same variable, + you may wish to leave the variable unchanged if the expression does not match. + In this case, remove the default value once debugging is complete. +

    +
    +
    +

    + If the match number is set to a non-negative number, and a match occurs, the variables are set as follows: +

      +
    • refName - the value of the template
    • +
    • refName_gn, where n=0,1,2 - the groups for the match
    • +
    • refName_g - the number of groups in the Regex (excluding 0)
    • +
    + If no match occurs, then the refName variable is set to the default (unless this is absent). + Also, the following variables are removed: +
      +
    • refName_g0
    • +
    • refName_g1
    • +
    • refName_g
    • +
    +

    +

    + If the match number is set to a negative number, then all the possible matches in the sampler data are processed. + The variables are set as follows: +

      +
    • refName_matchNr - the number of matches found; could be 0
    • +
    • refName_n, where n = 1,2,3 etc - the strings as generated by the template
    • +
    • refName_n_gm, where m=0,1,2 - the groups for match n
    • +
    • refName - always set to the default value
    • +
    • refName_gn - not set
    • +
    + Note that the refName variable is always set to the default value in this case, + and the associated group variables are not set. +

    See also for some examples of how to specify modifiers, + and for further information on JMeter regular expressions.

    +

    +
    + + +

    Allows the user to extract values from a server response using a CSS/JQuery selector like syntax. As a post-processor, +this element will execute after each Sample request in its scope, applying the CSS/JQuery expression, extracting the requested nodes, +extracting the node as text or attribute value and store the result into the given variable name.

    + + Descriptive name for this element that is shown in the tree. + + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
      +
    • Main sample only - only applies to the main sample
    • +
    • Sub-samples only - only applies to the sub-samples
    • +
    • Main sample and sub-samples - applies to both.
    • +
    • JMeter Variable - assertion is to be applied to the contents of the named variable
    • +
    + Matching is applied to all qualifying samples in turn. + For example if there is a main sample and 3 sub-samples, each of which contains a single match for the regex, + (i.e. 4 matches in total). + For match number = 3, Sub-samples only, the extractor will match the 3rd sub-sample. + For match number = 3, Main sample and sub-samples, the extractor will match the 2nd sub-sample (1st match is main sample). + For match number = 0 or negative, all qualifying samples will be processed. + For match number > 0, matching will stop as soon as enough matches have been found. +
    + + As of JMeter 2.9, 2 implementations for CSS/JQuery based syntax are supported: + + If selector is set to empty, default implementation(JSoup) will be used. + + The name of the JMeter variable in which to store the result. + The CSS/JQuery selector used to select nodes from the response data. + Selector, selectors combination and pseudo-selectors are supported, examples: +
      +
    • E[foo] an E element with a "foo" attribute
    • +
    • ancestor child:child elements that descend from ancestor, e.g. .body p finds p elements anywhere under a block with class "body"
    • +
    • :lt(n): find elements whose sibling index (i.e. its position in the DOM tree relative to its parent) is less than n; e.g. td:lt(3)
    • +
    • :contains(text): find elements that contain the given text. The search is case-insensitive; e.g. p:contains(jsoup)
    • +
    • ...
    • +
    + For more details on syntax, see: + +
    + + Name of attribute (as per HTML syntax) to extract from nodes that matched the selector. If empty, then the combined text of this element and all its children will be returned.
    + This is the equivalent Element#attr(name) function for JSoup if an atttribute is set.
    +
    CSS Extractor with attribute value set

    + If empty this is the equivalent of Element#text() function for JSoup if not value is set for attribute. +
    CSS Extractor with no attribute set
    +
    + Indicates which match to use. The CSS/JQuery selector may match multiple times. +
      +
    • Use a value of zero to indicate JMeter should choose a match at random.
    • +
    • A positive number N means to select the nth match.
    • +
    • Negative numbers are used in conjunction with the ForEach controller - see below.
    • +
    +
    + + If the expression does not match, then the reference variable will be set to the default value. + This is particularly useful for debugging tests. If no default is provided, then it is difficult to tell + whether the expression did not match, or the CSS/JQuery element was not processed or maybe the wrong variable + is being used. +

    + However, if you have several test elements that set the same variable, + you may wish to leave the variable unchanged if the expression does not match. + In this case, remove the default value once debugging is complete. +

    +
    +
    +

    + If the match number is set to a non-negative number, and a match occurs, the variables are set as follows: +

      +
    • refName - the value of the template
    • +
    + If no match occurs, then the refName variable is set to the default (unless this is absent). +

    +

    + If the match number is set to a negative number, then all the possible matches in the sampler data are processed. + The variables are set as follows: +

      +
    • refName_matchNr - the number of matches found; could be 0
    • +
    • refName_n, where n = 1,2,3 etc - the strings as generated by the template
    • +
    • refName - always set to the default value
    • +
    + Note that the refName variable is always set to the default value in this case. +

    +
    + + + This test element allows the user to extract value(s) from + structured response - XML or (X)HTML - using XPath + query language. + + + Descriptive name for this element that is shown in the tree. + + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
      +
    • Main sample only - only applies to the main sample
    • +
    • Sub-samples only - only applies to the sub-samples
    • +
    • Main sample and sub-samples - applies to both.
    • +
    • JMeter Variable - assertion is to be applied to the contents of the named variable
    • +
    + XPath matching is applied to all qualifying samples in turn, and all the matching results will be returned. +
    + If checked use Tidy to parse HTML response into XHTML. +
      +
    • "Use Tidy" should be checked on for HTML response. Such response is converted to valid XHTML (XML compatible HTML) using Tidy
    • +
    • "Use Tidy" should be unchecked for both XHTML or XML response (for example RSS)
    • +
    +
    +Sets the Tidy Quiet flag +If a Tidy error occurs, then set the Assertion accordingly +Sets the Tidy showWarnings option + + If checked, then the XML parser will use namespace resolution. + Note that currently only namespaces declared on the root element will be recognised. + A later version of JMeter may support user-definition of additional workspace names. + Meanwhile, a work-round is to replace: +

    + //mynamespace:tagname +

    + by +

    + //*[local-name()='tagname' and namespace-uri()='uri-for-namespace'] +

    + where "uri-for-namespace" is the uri for the "mynamespace" namespace. + + (not applicable if Tidy is selected) +
    + Check the document against its schema. + Ignore Element Whitespace. + If selected, external DTDs are fetched. + + If selected, the fragment will be returned rather than the text content.

    + For example //title would return "&lt;title>Apache JMeter&lt;/title>" rather than "Apache JMeter".

    + In this case, //title/text() would return "Apache JMeter". +
    + The name of the JMeter variable in which to store the result. + Element query in XPath language. Can return more than one match. + Default value returned when no match found. + It is also returned if the node has no value and the fragment option is not selected. +
    +

    To allow for use in a ForEach Controller, the following variables are set on return:

    +
      +
    • refName - set to first (or only) match; if no match, then set to default
    • +
    • refName_matchNr - set to number of matches (may be 0)
    • +
    • refName_n - n=1,2,3 etc. Set to the 1st, 2nd 3rd match etc. +
    • +
    +

    Note: The next refName_n variable is set to null - e.g. if there are 2 matches, then refName_3 is set to null, + and if there are no matches, then refName_1 is set to null. +

    +

    XPath is query language targeted primarily for XSLT transformations. However it is usefull as generic query language for structured data too. See + XPath Reference or XPath specification for more information. Here are few examples: +

    +
    +
    /html/head/title
    +
    extracts title element from HTML response
    +
    /book/page[2]
    +
    extracts 2nd page from a book
    +
    /book/page
    +
    extracts all pages from a book
    +
    //form[@name='countryForm']//select[@name='country']/option[text()='Czech Republic'])/@value
    +
    extracts value attribute of option element that match text 'Czech Republic' + inside of select element with name attribute 'country' inside of + form with name attribute 'countryForm'
    +
    + When "Use Tidy" is checked on - resulting XML document may slightly differ from original HTML response: +
      +
    • All elements and attribute names are converted to lowercase
    • +
    • Tidy attempts to correct improperly nested elements. For example - original (incorrect) ul/font/li becomes correct ul/li/font
    • +
    + See Tidy homepage for more information. +
    + +
    + + + This test element allows the user to stop the thread or the whole test if the relevant sampler failed. + + + Descriptive name for this element that is shown in the tree. + + Determines what happens if a sampler error occurs, either because the sample itself failed or an assertion failed. + The possible choices are: +
      +
    • Continue - ignore the error and continue with the test
    • +
    • Start next thread loop - does not execute samplers following the sampler in error for the current iteration and restarts the loop on next iteration
    • +
    • Stop Thread - current thread exits
    • +
    • Stop Test - the entire test is stopped at the end of any current samples.
    • +
    • Stop Test Now - the entire test is stopped abruptly. Any current samplers are interrupted if possible.
    • +
    +
    +
    +
    + + + +

    +The BeanShell PreProcessor allows arbitrary code to be applied after taking a sample. +

    +

    For JMeter versions after 2.2 the BeanShell Post-Processor no longer ignores samples with zero-length result data

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The BeanShell script. The return value is ignored. +
    +

    The following BeanShell variables are set up for use by the script:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • prev - (SampleResult) - gives access to the previous SampleResult
    • +
    • data - (byte [])- gives access to the current sample data
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +

    If the property beanshell.postprocessor.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

    +
    + + + +

    +The BSF PostProcessor allows BSF script code to be applied after taking a sample. +

    +
    + + Descriptive name for this element that is shown in the tree. + The BSF language to be used + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    + A file containing the script to run, if a relative file path is used, then it will be relative to directory referenced by "user.dir" System property + The script to run. +
    +

    +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

    +

    +Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. +

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • Label - the String Label
    • +
    • Filename - the script file name (if any)
    • +
    • Parameters - the parameters (as a String)
    • +
    • args[] - the parameters as a String array (split on whitespace)
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • prev - (SampleResult) - gives access to the previous SampleResult (if any)
    • +
    • sampler - (Sampler)- gives access to the current sampler
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 PostProcessor allows JSR223 script code to be applied after taking a sample. +For details, see the . +

    +
    +
    + + + +

    +The JDBC PostProcessor enables you to run some SQL statement just after a sample has run. +This can be useful if your JDBC Sample changes some data and you want to reset state to what it was before the JDBC sample run. +

    +
    +
    + + Test Plan using JDBC Pre/Post Processor + +

    +In the linked test plan,"JDBC PostProcessor" JDBC PostProcessor calls a stored procedure to delete from Database the Price Cut-Off that was created by PreProcessor. +

    JDBC PostProcessor
    +

    +
    + +
    + +

    +
    + + +

    +The Test Plan is where the overall settings for a test are specified. +

    +

    +Static variables can be defined for values that are repeated throughout a test, such as server names. +For example the variable SERVER could be defined as www.example.com, and the rest of the test plan +could refer to it as ${SERVER}. This simplifies changing the name later. +

    +

    +If the same variable name is reused on one of more + Configuration elements, +the value is set to the last definition in the test plan (reading from top to bottom). +Such variables should be used for items that may change between test runs, +but which remain the same during a test run. +

    +

    +Note that the Test Plan cannot refer to variables it defines. +If you need to construct other variables from the Test Plan variables, +use a test element. +

    +

    +Selecting Functional Testing instructs JMeter to save the additional sample information +- Response Data and Sampler Data - to all result files. +This increases the resources needed to run a test, and may adversely impact JMeter performance. +If more data is required for a particular sampler only, then add a Listener to it, and configure the fields as required. +[The option does not affect CSV result files, which cannot currently store such information.] +

    +

    Also, an option exists here to instruct JMeter to run the serially rather than in parallel.

    +

    Run tearDown Thread Groups after shutdown of main threads: +if selected, the tearDown groups (if any) will be run after graceful shutdown of the main threads. +The tearDown threads won't be run if the test is forcibly stopped. +

    +

    +Test plan now provides an easy way to add classpath setting to a specific test plan. +The feature is additive, meaning that you can add jar files or directories, +but removing an entry requires restarting JMeter. +Note that this cannot be used to add JMeter GUI plugins, because they are processed earlier. +However it can be useful for utility jars such as JDBC drivers. The jars are only added to +the search path for the JMeter loader, not for the system class loader. +

    +

    +JMeter properties also provides an entry for loading additional classpaths. +In jmeter.properties, edit "user.classpath" or "plugin_dependency_paths" to include additional libraries. +See JMeter's Classpath and +Configuring JMeter for details. +

    +
    +
    + + + +

    A Thread Group defines a pool of users that will execute a particular test case against your server. In the Thread Group GUI, you can control the number of users simulated (num of threads), the ramp up time (how long it takes to start all the threads), the number of times to perform the test, and optionally, a start and stop time for the test.

    +

    +See also and . +

    +

    +When using the scheduler, JMeter runs the thread group until either the number of loops is reached or the duration/end-time is reached - whichever occurs first. +Note that the condition is only checked between samples; when the end condition is reached, that thread will stop. +JMeter does not interrupt samplers which are waiting for a response, so the end time may be delayed arbitrarily. +

    +
    + + + Descriptive name for this element that is shown in the tree. + + Determines what happens if a sampler error occurs, either because the sample itself failed or an assertion failed. + The possible choices are: +
      +
    • Continue - ignore the error and continue with the test
    • +
    • Start Next Loop - ignore the error, start next loop and continue with the test
    • +
    • Stop Thread - current thread exits
    • +
    • Stop Test - the entire test is stopped at the end of any current samples.
    • +
    • Stop Test Now - the entire test is stopped abruptly. Any current samplers are interrupted if possible.
    • +
    +
    + Number of users to simulate. + How long JMeter should take to get all the threads started. If there are 10 threads and a ramp-up time of 100 seconds, then each thread will begin 10 seconds after the previous thread started, for a total time of 100 seconds to get the test fully up to speed. + Number of times to perform the test case. Alternatively, "forever" can be selected causing the test to run until manually stopped. + + If selected, threads are created only when the appropriate proportion of the ramp-up time has elapsed. + This is most appropriate for tests with a ramp-up time that is significantly longer than the time to execute a single thread. + I.e. where earlier threads finish before later ones start. +

    + If not selected, all threads are created when the test starts (they then pause for the appropriate proportion of the ramp-up time). + This is the original default, and is appropriate for tests where threads are active throughout most of the test. +
    + If selected, enables the scheduler + If the scheduler checkbox is selected, one can choose an absolute start time. When you start your test, JMeter will wait until the specified start time to begin testing. + Note: the Startup Delay field over-rides this - see below. + + If the scheduler checkbox is selected, one can choose an absolute end time. When you start your test, JMeter will wait until the specified start time to begin testing, and it will stop at the specified end time. + Note: the Duration field over-rides this - see below. + + + If the scheduler checkbox is selected, one can choose a relative end time. + JMeter will use this to calculate the End Time, and ignore the End Time value. + + + If the scheduler checkbox is selected, one can choose a relative startup delay. + JMeter will use this to calculate the Start Time, and ignore the Start Time value. + +
    +
    + + + +

    The WorkBench simply provides a place to temporarily store test elements while not in use, for copy/paste purposes, or any other purpose you desire. +When you save your test plan, WorkBench items are not saved with it by default unless you check "Save Workbench" option. +Your WorkBench can be saved independently, if you like (right-click on WorkBench and choose Save).

    +

    Certain test elements are only available on the WorkBench:

    +
      +
    • +
    • +
    • +
    + + + Allow to save the WorkBench's elements into the JMX file. + + +
    +
    + + +

    + The SSL Manager is a way to select a client certificate so that you can test + applications that use Public Key Infrastructure (PKI). + It is only needed if you have not set up the appropriate System properties. +

    + +Choosing a Client Certificate +

    + You may either use a Java Key Store (JKS) format key store, or a Public Key + Certificate Standard #12 (PKCS12) file for your client certificates. There + is a feature of the JSSE libraries that require you to have at least a six character + password on your key (at least for the keytool utility that comes with your + JDK). +

    +

    + To select the client certificate, choose Options->SSL Manager from the menu bar. + You will be presented with a file finder that looks for PKCS12 files by default. + Your PKCS12 file must have the extension '.p12' for SSL Manager to recognize it + as a PKCS12 file. Any other file will be treated like an average JKS key store. + If JSSE is correctly installed, you will be prompted for the password. The text + box does not hide the characters you type at this point--so make sure no one is + looking over your shoulder. The current implementation assumes that the password + for the keystore is also the password for the private key of the client you want + to authenticate as. +

    +

    Or you can set the appropriate System properties - see the system.properties file.

    +

    + The next time you run your test, the SSL Manager will examine your key store to + see if it has at least one key available to it. If there is only one key, SSL + Manager will select it for you. If there is more than one key, it currently selects the first key. + There is currently no way to select other entries in the keystore, so the desired key must be the first. +

    +Things to Look Out For +

    + You must have your Certificate Authority (CA) certificate installed properly + if it is not signed by one of the five CA certificates that ships with your + JDK. One method to install it is to import your CA certificate into a JKS + file, and name the JKS file "jssecacerts". Place the file in your JRE's + lib/security folder. This file will be read before the "cacerts" file in + the same directory. Keep in mind that as long as the "jssecacerts" file + exists, the certificates installed in "cacerts" will not be used. This may + cause problems for you. If you don't mind importing your CA certificate into + the "cacerts" file, then you can authenticate against all of the CA certificates + installed. +

    +
    + + +

    The HTTP(S) Test Script Recorder allows JMeter to intercept and record your actions while you browse your web application +with your normal browser. JMeter will create test sample objects and store them +directly into your test plan as you go (so you can view samples interactively while you make them).
    +Ensure you read this wiki page to setup correctly JMeter. +

    + +

    To use the recorder, add the HTTP(S) Test Script Recorder element to the workbench. +Select the WorkBench element in the tree, and right-click on this element to get the +Add menu (Add --> Non-Test Elements --> HTTP(S) Test Script Recorder).

    +

    +The recorder is implemented as an HTTP(S) proxy server. +You need to set up your browser use the proxy for all HTTP and HTTPS requests. +[Do not use JMeter as the proxy for any other request types - FTP, etc. - as JMeter cannot handle them.] +

    +

    +Ideally use private browsing mode when recording the session. +This should ensure that the browser starts with no stored cookies, and prevents certain changes from being saved. +For example, Firefox does not allow certificate overrides to be saved permanently. +

    +

    HTTPS recording and certificates

    +

    +HTTPS connections use certificates to authenticate the connection between the browser and the web server. +When connecting via HTTPS, the server presents the certificate to the browser. +To authenticate the certificate, the browser checks that the server certificate is signed +by a Certificate Authority (CA) that is linked to one of its in-built root CAs. +[Browsers also check that the certificate is for the correct host or domain, and that it is valid and not expired.] +If any of the browser checks fail, it will prompt the user who can then decided whether to allow the connection to proceed. +

    +

    +JMeter needs to use its own certificate to enable it to intercept the HTTPS connection from +the browser. Effectively JMeter has to pretend to be the target server. +

    +

    +For versions of JMeter from 2.10, JMeter will generate its own certificate(s). +These are generated with a validity period defined by the property proxy.cert.validity, default 7 days, and random passwords. +If JMeter detects that it is running under Java 7 or later, it will generate certificates for each target server as necessary (dynamic mode) +unless the following property is defined: proxy.cert.dynamic_keys=false. +When using dynamic mode, the certificate will be for the correct host name, and will be signed by a JMeter-generated CA certificate. +By default, this CA certificate won't be trusted by the browser, however it can be installed as a trusted certificate. +Once this is done, the generated server certificates will be accepted by the browser. +This has the advantage that even embedded HTTPS resources can be intercepted, and there is no need to override the browser checks for each new server. +(Browsers don't prompt for embedded resources. So with earlier versions, embedded resources would only be downloaded for servers that were already 'known' to the browser) +

    +

    Unless a keystore is provided (and you define the property proxy.cert.alias), +JMeter needs to use the keytool application to create the keystore entries. +Versions of JMeter after 2.10 include code to check that keytool is available by looking in various standard places. +If JMeter is unable to find the keytool application, it will report an error. +If necessary, the systen property keytool.directory can be used to tell JMeter where to find keytool. +This should be defined in the file system.properties. +

    +

    +The JMeter certificates are generated (if necessary) when the Start button is pressed. +Certificate generation can take some while, during which time the GUI will be unresponsive. +The cursor is changed to an hour-glass whilst this is happening. +When certificate generation is complete, the GUI will display a pop-up dialogue containing the details of the certificate for the root CA. +This certificate needs to be installed by the browser in order for it to accept the host certificates generated by JMeter; see below for details. +

    +

    +If necessary, you can force JMeter to regenerate the keystore (and the exported certificates - ApacheJMeterTemporaryRootCA[.usr|.crt]) by deleting the keystore file proxyserver.jks from the JMeter directory. +

    +

    +With versions of JMeter up to 2.9, it used a single certificate for all target servers. +[Likewise if JMeter is not being run under Java 7 or later] +This certificate is not one of the certificates that browsers normally trust, and will not be for the +correct host.
    +As a consequence: +

      +
    • The browser should display a dialogue asking if you want to accept the certificate or not. For example: + +1) The server's name "www.example.com" does not match the certificate's name + "JMeter Proxy (DO NOT TRUST)". Somebody may be trying to eavesdrop on you. +2) The certificate for "JMeter Proxy (DO NOT TRUST)" is signed by the unknown Certificate Authority + "JMeter Proxy (DO NOT TRUST)". It is not possible to verify that this is a valid certificate. + +You will need to accept the certificate in order to allow the JMeter Proxy to intercept the SSL traffic in order to +record it. +However, do not accept this certificate permanently; it should only be accepted temporarily. +Browsers only prompt this dialogue for the certificate of the main url, not for the resources loaded in the page, such as images, css or javascript files hosted on a secured external CDN. +If you have such resources (gmail has for example), you'll have to first browse manually to these other domains in order to accept JMeter's certificate for them. +Check in jmeter.log for secure domains that you need to register certificate for. +
    • +
    • If the browser has already registered a validated certificate for this domain, the browser will detect JMeter as a security breach and will refuse to load the page. If so, you have to remove the trusted certificate from your browser's keystore. +
    • +
    +

    +

    +Versions of JMeter from 2.10 onwards still support this method, and will continue to do so if the you define the following property: +proxy.cert.alias +The following properties can be used to change the certificate that is used: +

      +
    • proxy.cert.directory - the directory in which to find the certificate (default = JMeter bin/)
    • +
    • proxy.cert.file - name of the keystore file (default "proxyserver.jks")
    • +
    • proxy.cert.keystorepass - keystore password (default "password") [Ignored if using JMeter certificate]
    • +
    • proxy.cert.keypassword - certificate key password (default "password") [Ignored if using JMeter certificate]
    • +
    • proxy.cert.type - the certificate type (default "JKS") [Ignored if using JMeter certificate]
    • +
    • proxy.cert.factory - the factory (default "SunX509") [Ignored if using JMeter certificate]
    • +
    • proxy.cert.alias - the alias for the key to be used. If this is defined, JMeter does not attempt to generate its own certificate(s).
    • +
    • proxy.ssl.protocol - the protocol to be used (default "SSLv3")
    • +
    +

    + +If your browser currently uses a proxy (e.g. a company intranet may route all external requests via a proxy), +then you need to tell JMeter to use that proxy before starting JMeter, +using the command-line options -H and -P. +This setting will also be needed when running the generated test plan. + + +

    Installing the JMeter CA certificate for HTTPS recording

    +

    +As mentioned above, when run under Java 7, JMeter can generate certificates for each server. +For this to work smoothly, the root CA signing certificate used by JMeter needs to be trusted by the browser. +The first time that the recorder is started, it will generate the certificates if necessary. +The root CA certificate is exported into a file with the name ApacheJMeterTemporaryRootCA in the current launch directory. +When the certificates have been set up, JMeter will show a dialog with the current certificate details. +At this point, the certificate can be imported into the browser, as per the instructions below. +

    +

    +Note that once the root CA certificate has been installed as a trusted CA, the browser will trust any certificates signed by it. +Until such time as the certificate expires or the certificate is removed from the browser, it will not warn the user that the certificate is being relied upon. +So anyone that can get hold of the keystore and password can use the certificate to generate certificates which will be accepted +by any browsers that trust the JMeter root CA certificate. +For this reason, the password for the keystore and private keys are randomly generated and a short validity period used. +The passwords are stored in the local preferences area. +Please ensure that only trusted users have access to the host with the keystore. +

    +
    Installing the certificate in Firefox
    +

    +Choose the following options: +

      +
    • Tools / Options
    • +
    • Advanced / Certificates
    • +
    • View Certificates
    • +
    • Authorities
    • +
    • Import ...
    • +
    • Browse to the JMeter launch directory, and click on the file ApacheJMeterTemporaryRootCA.crt, press Open
    • +
    • Click View and check that the certificate details agree with the ones displayed by the JMeter Test Script Recorder
    • +
    • If OK, select "Trust this CA to identify web sites", and press OK
    • +
    • Close dialogs by pressing OK as necessary
    • +
    +

    +
    Installing the certificate in Chrome or Internet Explorer
    +

    +Both Chrome and Internet Explorer use the same trust store for certificates. +

      +
    • Browse to the JMeter launch directory, and click on the file ApacheJMeterTemporaryRootCA.crt, and open it
    • +
    • Click on the "Details" tab and check that the certificate details agree with the ones displayed by the JMeter Test Script Recorder
    • +
    • If OK, go back to the "General" tab, and click on "Install Certificate ..." and follow the Wizard prompts
    • +
    +

    +
    Installing the certificate in Opera
    +

    +

      +
    • Tools / Preferences / Advanced / Security
    • +
    • Manage Certificates...
    • +
    • Select "Intermediate" tab, click "Import..."
    • +
    • Browse to the JMeter launch directory, and click on the file ApacheJMeterTemporaryRootCA.usr, and open it
    • +
    • +
    +

    +
    + + + Descriptive name for this element that is shown in the tree. + The port that the HTTP(S) Test Script Recorder listens to. 8080 is the default, but you can change it. + List of domain (or host) names for HTTPS. Use this to pre-generate certificates for all servers you wish to record. +
    + For example, *.apache.org,*.incubator.apache.org +
    + Note that wildcard domains only apply to one level, + i.e. podling.incubator.apache.org matches *.incubator.apache.org but not *.apache.org +
    + The controller where the proxy will store the generated samples. By default, it will look for a Recording Controller and store them there wherever it is. + Whether to group samplers for requests from a single "click" (requests received without significant time separation), and how to represent that grouping in the recording: +
      +
    • Do not group samplers: store all recorded samplers sequentially, without any grouping.
    • +
    • Add separators between groups: add a controller named "--------------" to create a visual separation between the groups. Otherwise the samplers are all stored sequentially.
    • +
    • Put each group in a new controller: create a new for each group, and store all samplers for that group in it.
    • +
    • Store 1st sampler of each group only: only the first request in each group will be recorded. The "Follow Redirects" and "Retrieve All Embedded Resources..." flags will be turned on in those samplers.
    • +
    • Put each group in a new transaction controller: create a new for each group, and store all samplers for that group in it.
    • +
    + The property proxy.pause determines the minimum gap that JMeter needs between requests + to treat them as separate "clicks". The default is 1000 (milliseconds) i.e. 1 second. + If you are using grouping, please ensure that you leave the required gap between clicks. +
    + + Should headers be added to the plan? + If specified, a Header Manager will be added to each HTTP Sampler. + The Proxy server always removes Cookie and Authorization headers from the generated Header Managers. + By default it also removes If-Modified-Since and If-None-Match headers. + These are used to determine if the browser cache items are up to date; + when recording one normally wants to download all the content. + To change which additional headers are removed, define the JMeter property proxy.headers.remove + as a comma-separated list of headers. + + Add a blank assertion to each sampler? + Use Regex Matching when replacing variables? If checked replacement will use word boundaries, ie it will only replace word matching values of variable, not part of a word. A word boundary follows Perl5 definition and is equivalent to \b. More information below in the paragraph about "User Defined Variable replacement". + Which type of sampler to generate (the Java default or HTTPClient) + Set Redirect Automatically in the generated samplers? + Set Follow Redirects in the generated samplers?
    + Note: see "Recording and redirects" section below for important information. +
    + Set Use Keep-Alive in the generated samplers? + Set Retrieve all Embedded Resources in the generated samplers? + + Filter the requests based on the content-type - e.g. "text/html [;charset=utf-8 ]". + The fields are regular expressions which are checked to see if they are contained in the content-type. + [Does not have to match the entire field]. + The include filter is checked first, then the exclude filter. + Samples which are filtered out will not be stored. + Note: this filtering is applied to the content type of the response + + Regular expressions that are matched against the full URL that is sampled. Allows filtering of requests that are recorded. All requests pass through, but only + those that meet the requirements of the Include/Exclude fields are recorded. If both Include and Exclude are + left empty, then everything is recorded (which can result in dozens of samples recorded for each page, as images, stylesheets, + etc are recorded). If there is at least one entry in the Include field, then only requests that match one or more Include patterns are + recorded. + Regular expressions that are matched against the URL that is sampled. + Any requests that match one or more Exclude pattern are not recorded. + Notify Child Listeners of filtered samplers + Any response that match one or more Exclude pattern is not delivered to Child Listeners (View Results Tree). + Start the proxy server. JMeter writes the following message to the console once the proxy server has started up and is ready to take requests: "Proxy up and running!". + Stop the proxy server. + Stops and restarts the proxy server. This is + useful when you change/add/delete an include/exclude filter expression. +
    + +

    Recording and redirects

    +

    +During recording, the browser will follow a redirect response and generate an additional request. +The Proxy will record both the original request and the redirected request +(subject to whatever exclusions are configured). +The generated samples have "Follow Redirects" selected by default, because that is generally better. +[Redirects may depend on the original request, so repeating the originally recorded sample may not always work.] +

    +

    +Now if JMeter is set to follow the redirect during replay, it will issue the original request, +and then replay the redirect request that was recorded. +To avoid this duplicate replay, JMeter tries to detect when a sample is the result of a previous +redirect. If the current response is a redirect, JMeter will save the redirect URL. +When the next request is received, it is compared with the saved redirect URL and if there is a match, +JMeter will disable the generated sample. It also adds comments to the redirect chain. +This assumes that all the requests in a redirect chain will follow each other without any intervening requests. +To disable the redirect detection, set the property proxy.redirect.disabling=false +

    + +

    Includes and Excludes

    +

    The include and exclude patterns are treated as regular expressions (using Jakarta ORO). +They will be matched against the host name, port (actual or implied) path and query (if any) of each browser request. +If the URL you are browsing is

    +"http://jmeter.apache.org/jmeter/index.html?username=xxxx",

    +then the regular expression will be tested against the string:

    +"jmeter.apache.org:80/jmeter/index.html?username=xxxx".

    +Thus, if you want to include all .html files, your regular expression might look like:

    +".*\.html(\?.*)?" - or ".*\.html" +if you know that there is no query string or you only want html pages without query strings. +

    +

    +If there are any include patterns, then the URL must match at least one of the patterns +, otherwise it will not be recorded. +If there are any exclude patterns, then the URL must not match any of the patterns +, otherwise it will not be recorded. +Using a combination of includes and excludes, +you should be able to record what you are interested in and skip what you are not. +

    + +

    +N.B. the string that is matched by the regular expression must be the same as the whole host+path string.

    Thus "\.html" will not match j.a.o/index.html +

    + +

    Capturing binary POST data

    +

    +Versions of JMeter from 2.3.2 are able to capture binary POST data. +To configure which content-types are treated as binary, update the JMeter property proxy.binary.types. +The default settings are as follows: +

    +# These content-types will be handled by saving the request in a file:
    +proxy.binary.types=application/x-amf,application/x-java-serialized-object
    +# The files will be saved in this directory:
    +proxy.binary.directory=user.dir
    +# The files will be created with this file filesuffix:
    +proxy.binary.filesuffix=.binary
    +
    +

    + +

    Adding timers

    +

    It is also possible to have the proxy add timers to the recorded script. To +do this, create a timer directly within the HTTP(S) Test Script Recorder component. +The proxy will place a copy of this timer into each sample it records, or into +the first sample of each group if you're using grouping. This copy will then be +scanned for occurences of variable ${T} in its properties, and any such +occurences will be replaced by the time gap from the previous sampler +recorded (in milliseconds).

    + +

    When you are ready to begin, hit "start".

    +You will need to edit the proxy settings of your browser to point at the +appropriate server and port, where the server is the machine JMeter is running on, and +the port # is from the Proxy Control Panel shown above. + +

    Where Do Samples Get Recorded?

    +

    JMeter places the recorded samples in the Target Controller you choose. If you choose the default option +"Use Recording Controller", they will be stored in the first Recording Controller found in the test object tree (so be +sure to add a Recording Controller before you start recording).

    + +

    +If the Proxy does not seem to record any samples, this could be because the browser is not actually using the proxy. +To check if this is the case, try stopping the proxy. +If the browser still downloads pages, then it was not sending requests via the proxy. +Double-check the browser options. +If you are trying to record from a server running on the same host, +then check that the browser is not set to "Bypass proxy server for local addresses" +(this example is from IE7, but there will be similar options for other browsers). +If JMeter does not record browser URLs such as http://localhost/ or http://127.0.0.1/, +try using the non-loopback hostname or IP address, e.g. http://myhost/ or http://192.168.0.2/. +

    + +

    Handling of HTTP Request Defaults

    +

    If the HTTP(S) Test Script Recorder finds enabled directly within the +controller where samples are being stored, or directly within any of its parent controllers, the recorded samples +will have empty fields for the default values you specified. You may further control this behaviour by placing an +HTTP Request Defaults element directly within the HTTP(S) Test Script Recorder, whose non-blank values will override +those in the other HTTP Request Defaults. See Best +Practices with the HTTP(S) Test Script Recorder for more info.

    + +

    User Defined Variable replacement

    +

    Similarly, if the HTTP(S) Test Script Recorder finds (UDV) directly within the +controller where samples are being stored, or directly within any of its parent controllers, the recorded samples +will have any occurences of the values of those variables replaced by the corresponding variable. Again, you can +place User Defined Variables directly within the HTTP(S) Test Script Recorder to override the values to be replaced. See + Best Practices with the Test Script Recorder for more info.

    + +Please note that matching is case-sensitive. + +

    Replacement by Variables: by default, the Proxy server looks for all occurences of UDV values. +If you define the variable WEB with the value www, for example, +the string www will be replaced by ${WEB} wherever it is found. +To avoid this happening everywhere, set the "Regex Matching" check-box. +This tells the proxy server to treat values as Regexes (using the perl5 compatible regex matchers provided by ORO).

    + +

    If "Regex Matching" is selected every variable will be compiled into a perl compatible regex enclosed in +\b( and )\b. That way each match will start and end at a word boundary.

    + +Note that the boundary characters are not part of the matching group, e.g. n.* to match name out +of You can call me 'name'. + +

    If you don't want your regex to be enclosed with those boundary matchers, you have to enclose your +regex within parens, e.g ('.*?') to match 'name' out of You can call me 'name'.

    + + +The variables will be checked in random order. So ensure, that the potential matches don't overlap. +Overlapping matchers would be .* (which matches anything) and www (which +matches www only). Non-overlapping matchers would be a+ (matches a sequence +of a's) and b+ (matches a sequence of b's). + + +

    If you want to match a whole string only, enclose it in (^ and $), e.g. (^thus$). +The parens are neccessary, since the normally added boundary characters will prevent ^ and +$ to match.

    + +

    If you want to match /images at the start of a string only, use the value (^/images). +Jakarta ORO also supports zero-width look-ahead, so one can match /images/... +but retain the trailing / in the output by using (^/images(?=/))".

    + + +Note that the current version of Jakara ORO does not support look-behind - i.e. (?&lt;=...) or (?&lt;!...). + + +

    Look out for overlapping matchers. For example the value .* as a regex in a variable named +regex will partly match a previous replaced variable, which will result in something like +${{regex}, which is most probably not the desired result.

    + +

    If there are any problems interpreting any variables as patterns, these are reported in jmeter.log, +so be sure to check this if UDVs are not working as expected.

    + +

    When you are done recording your test samples, stop the proxy server (hit the "stop" button). Remember to reset +your browser's proxy settings. Now, you may want to sort and re-order the test script, add timers, listeners, a +cookie manager, etc.

    + +

    How can I record the server's responses too?

    +

    Just place a listener as a child of the HTTP(S) Test Script Recorder and the responses will be displayed. +You can also add a Post-Processor which will save the responses to files. +

    + +

    Associating requests with responses

    +

    +If you define the property proxy.number.requests=true +JMeter will add a number to each sampler and each response. +Note that there may be more responses than samplers if excludes or includes have been used. +Responses that have been excluded will have labels enclosed in [ and ], for example [23 /favicon.ico] +

    +

    Cookie Manager

    +

    +If the server you are testing against uses cookies, remember to add an to the test plan +when you have finished recording it. +During recording, the browser handles any cookies, but JMeter needs a Cookie Manager +to do the cookie handling during a test run. +The JMeter Proxy server passes on all cookies sent by the browser during recording, but does not save them to the test +plan because they are likely to change between runs. +

    +

    Authorization Manager

    +

    +The HTTP(S) Test Script Recorder grabs "Authentication" header, tries to compute the Auth Policy. If Authorization Manager was added to target +controller manually, HTTP(S) Test Script Recorder will find it and add authorization(matching ones will be removed). Otherwise +Authorization Manager will be added to target controller with authorization object. +You may have to fix automatically computed values after recording. + +

    +

    Uploading files

    +

    +Some browsers (e.g. Firefox and Opera) don't include the full name of a file when uploading files. +This can cause the JMeter proxy server to fail. +One solution is to ensure that any files to be uploaded are in the JMeter working directory, +either by copying the files there or by starting JMeter in the directory containing the files. +

    +

    Recording HTTP Based Non Textual Protocols not natively available in JMeter

    +

    +You may have to record an HTTP protocol that is not handled by default by JMeter (Custom Binary Protocol, Adobe Flex, Microsoft Silverlight... ). +Although JMeter does not provide a native proxy implementation to record these protocols, you have the ability to +record these protocols by implementing a custom SamplerCreator. This Sampler Creator will translate the binary format into a HTTPSamplerBase subclass +that can be added to the JMeter Test Case. +For more details see "Extending JMeter". +

    +
    + + + +

    +The HTTP Mirrror Server is a very simple HTTP server - it simply mirrors the data sent to it. +This is useful for checking the content of HTTP requests. +

    +

    +It uses default port 8081 since 2.6. +

    +
    + + Port on which Mirror server listens, defaults to 8081. + If set to a value > 0, number of threads serving requests will be limited to the configured number, if set to a value <=0 + a new thread will be created to serve each incoming request. Defaults to 0 + Size of queue used for holding tasks before they are executed by Thread Pool, when Thread pool is exceeded, incoming requests will + be held in this queue and discarded when this queue is full. This parameter is only used if Max Number of Threads is greater than 0. Defaults to 25 + + +Note that you can get more control over the responses by adding an HTTP Header Manager with the following name/value pairs: + + + Time to sleep in ms before sending response + Cookies to be set on response + Response status, see HTTP Status responses, example 200 OK, 500 Internal Server Error .... + Size of response, this trims the response to the requested size if that is less than the total size + Pipe separated list of headers, example:
    + headerA=valueA|headerB=valueB would set headerA to valueA and headerB to valueB. +
    +
    +

    +You can also use the following query parameters: +

    + + Generates a 302 (Temporary Redirect) with the provided location. + e.g. ?redirect=/path + + Overrides the default status return. e.g. ?status=404 Not Found + Verbose flag, writes some details to standard output. e.g. first line and redirect location if specified + +
    + + + +

    +The Property Display shows the values of System or JMeter properties. +Values can be changed by entering new text in the Value column. +It is available only on the WorkBench. +

    +
    + + Descriptive name for this element that is shown in the tree. + +
    + + + +

    +The Debug Sampler generates a sample containing the values of all JMeter variables and/or properties. +

    +

    +The values can be seen in the Listener Response Data pane. +

    +
    + + Descriptive name for this element that is shown in the tree. + Include JMeter properties? + Include JMeter variables? + Include System properties? + +
    + + + +

    +The Debug PostProcessor creates a subSample with the details of the previous Sampler properties, +JMeter variables, properties and/or System Properties. +

    +

    +The values can be seen in the Listener Response Data pane. +

    +
    + + Descriptive name for this element that is shown in the tree. + Whether to show JMeter properties (default false). + Whether to show JMeter variables (default false). + Whether to show Sampler properties (default true). + Whether to show System properties (default false). + +
    + + + +

    +The Test Fragment is used in conjunction with the and . +

    +
    + + Descriptive name for this element that is shown in the tree. + + +When using Test Fragment with , ensure you disable the Test Fragment to avoid the execution of Test Fragment itself. +This is done by default since JMeter 2.13. + +
    + + + +

    + A special type of ThreadGroup that can be utilized to perform Pre-Test Actions. The behavior of these threads + is exactly like a normal element. The difference is that these type of threads + execute before the test proceeds to the executing of regular Thread Groups. +

    +
    +
    + + + +

    + A special type of ThreadGroup that can be utilized to perform Post-Test Actions. The behavior of these threads + is exactly like a normal element. The difference is that these type of threads + execute after the test has finished executing its regular Thread Groups. +

    +
    + +Note that by default it won't run if Test is gracefully shutdown, if you want to make it run in this case, +ensure you check option "Run tearDown Thread Groups after shutdown of main threads" on Test Plan element. +If Test Plan is stopped, tearDown will not run even if option is checked. + +
    Figure 1 - Run tearDown Thread Groups after shutdown of main threads
    +
    + +^ + +
    + + +
    + diff --git a/xdocs/usermanual/functions.xml b/xdocs/usermanual/functions.xml new file mode 100644 index 00000000000..476bcac78f2 --- /dev/null +++ b/xdocs/usermanual/functions.xml @@ -0,0 +1,1321 @@ + + + + +]> + + + + + User's Manual: Functions and Variables + + + + + + +
    +

    +JMeter functions are special values that can populate fields of any Sampler or other +element in a test tree. A function call looks like this:

    + +

    ${__functionName(var1,var2,var3)}

    + +

    +Where "__functionName" matches the name of a function. +

    +Parentheses surround the parameters sent to the function, for example ${__time(YMD)} +The actual parameters vary from function to function. +Functions that require no parameters can leave off the parentheses, for example ${__threadNum}. +

    + +

    +If a function parameter contains a comma, then be sure to escape this with "\", otherwise JMeter will treat it as a parameter delimiter. +For example: +

    +${__time(EEE\, d MMM yyyy)}
    +
    +If the comma is not escaped - e.g. ${__javaScript(Math.max(2,5))} - you will get an error such as: +
    +ERROR - jmeter.functions.JavaScript: Error processing Javascript: [Math.max(2]
    +    org.mozilla.javascript.EvaluatorException: missing ) after argument list (<cmd>#1)
    + 
    + This is because the string "Math.max(2,5)" is treated as being two parameters to the __javascript function:
    + Math.max(2 and 5)
    + Other error messages are possible. +

    +

    Variables are referenced as follows: +

    +${VARIABLE}
    +
    +

    +

    + +If an undefined function or variable is referenced, JMeter does not report/log an error - the reference is returned unchanged. +For example if UNDEF is not defined as a variable, then the value of ${UNDEF} is ${UNDEF}. + +Variables, functions (and properties) are all case-sensitive. + +Versions of JMeter after 2.3.1 trim spaces from variable names before use, so for example +${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY '. + +

    + +Properties are not the same as variables. +Variables are local to a thread; properties are common to all threads, +and need to be referenced using the __P or __property function. + + +When using \ before a variable for a windows path for example C:\test\${test}, ensure you escape the \ +otherwise JMeter will not interpret the variable, example: +C:\\test\\${test}. +

    +Alternatively, just use / instead for the path separator - e.g. C:/test/${test} - Windows JVMs will convert the separators as necessary. +
    +

    List of functions, loosely grouped into types.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Type of functionNameCommentSince
    Information threadNumget thread number1.X
    Information samplerNameget the sampler name (label)2.5
    Information machineIPget the local machine IP address2.6
    Information machineNameget the local machine name1.X
    Information timereturn current time in various formats2.2
    Information loglog (or display) a message (and return the value)2.2
    Information lognlog (or display) a message (empty return value)2.2
    Input StringFromFileread a line from a file1.9
    Input FileToStringread an entire file2.4
    Input CSVReadread from CSV delimited file1.9
    Input XPathUse an XPath expression to read from a file2.0.3
    Calculation countergenerate an incrementing number1.X
    Calculation intSumadd int numbers1.8.1
    Calculation longSumadd long numbers2.3.2
    Calculation Randomgenerate a random number1.9
    Calculation RandomStringgenerate a random string2.6
    Calculation UUIDgenerate a random type 4 UUID2.9
    Scripting BeanShellrun a BeanShell script1.X
    Scripting javaScriptprocess JavaScript (Mozilla Rhino)1.9
    Scripting jexl, jexl2evaluate a Commons Jexl expressionjexl(2.2), jexl2(2.6)
    Properties property read a property2.0
    Properties Pread a property (shorthand method)2.0
    Properties setPropertyset a JMeter property2.1
    Variables splitSplit a string into variables2.0.2
    Variables Vevaluate a variable name2.3RC3
    Variables evalevaluate a variable expression2.3.1
    Variables evalVarevaluate an expression stored in a variable2.3.1
    String regexFunctionparse previous response using a regular expression1.X
    String escapeOroRegexpCharsquote meta chars used by ORO regular expression2.9
    String chargenerate Unicode char values from a list of numbers2.3.3
    String unescapeProcess strings containing Java escapes (e.g. \n & \t)2.3.3
    String unescapeHtmlDecode HTML-encoded strings2.3.3
    String escapeHtmlEncode strings using HTML encoding2.3.3
    String urldecodeDecode a application/x-www-form-urlencoded string2.10
    String urlencodeEncode a string to a application/x-www-form-urlencoded string2.10
    String TestPlanNameReturn name of current test plan2.6
    +

    + +

    There are two kinds of functions: user-defined static values (or variables), and built-in functions.

    +User-defined static values allow the user to define variables to be replaced with their static value when +a test tree is compiled and submitted to be run. This replacement happens once at the beginning of the test +run. This could be used to replace the DOMAIN field of all HTTP requests, for example - making it a simple +matter to change a test to target a different server with the same test. +

    +

    +Note that variables cannot currently be nested; i.e ${Var${N}} does not work. +The __V (variable) function (versions after 2.2) can be used to do this: ${__V(Var${N})}. +In earlier JMeter versions one can use ${__BeanShell(vars.get("Var${N}")}. +

    +

    This type of replacement is possible without functions, but was less convenient and less intuitive. +It required users to create default config elements that would fill in blank values of Samplers. +Variables allow one to replace only part of any given value, not just filling in blank values.

    +

    +With built-in functions users can compute new values at run-time based on previous response data, which +thread the function is in, the time, and many other sources. These values are generated fresh for every +request throughout the course of the test.

    +Functions are shared between threads. +Each occurrence of a function call in a test plan is handled by a separate function instance. +
    + + +

    +Functions and variables can be written into any field of any test component (apart from the TestPlan - see below). +Some fields do not allow random strings +because they are expecting numbers, and thus will not accept a function. However, most fields will allow +functions. +

    +

    +Functions which are used on the Test Plan have some restrictions. +JMeter thread variables will have not been fully set up when the functions are processed, +so variable names passed as parameters will not be set up, and variable references will not work, +so split() and regex() and the variable evaluation functions won't work. +The threadNum() function won't work (and does not make sense at test plan level). +The following functions should work OK on the test plan: +

      +
    • intSum
    • +
    • longSum
    • +
    • machineName
    • +
    • BeanShell
    • +
    • javaScript
    • +
    • jexl
    • +
    • random
    • +
    • time
    • +
    • property functions
    • +
    • log functions
    • +
    +

    +

    +Configuration elements are processed by a separate thread. +Therefore functions such as __threadNum do not work properly in elements such as User Defined Variables. +Also note that variables defined in a UDV element are not available until the element has been processed. +

    +When using variable/function references in SQL code (etc), +remember to include any necessary quotes for text strings, +i.e. use + +SELECT item from table where name='${VAR}' + +not + +SELECT item from table where name=${VAR} + +(unless VAR itself contains the quotes) + +
    + + +

    Referencing a variable in a test element is done by bracketing the variable name with '${' and '}'.

    +

    Functions are referenced in the same manner, but by convention, the names of +functions begin with "__" to avoid conflict with user value names*. Some functions take arguments to +configure them, and these go in parentheses, comma-delimited. If the function takes no arguments, the parentheses can +be omitted.

    + +

    Argument values that themselves contain commas should be escaped as necessary. +If you need to include a comma in your parameter value, escape it like so: '\,'. +This applies for example to the scripting functions - Javascript, Beanshell, Jexl - where it is necessary to escape any commas +that may be needed in script method calls - e.g. +

    +
    +${__BeanShell(vars.put("name"\,"value"))}
    +
    +

    +Alternatively, you can define your script as a variable, e.g. on the Test Plan: +

    SCRIPT          vars.put("name","value")
    +The script can then be referenced as follows: +
    ${__BeanShell(${SCRIPT})}
    +There is no need to escape commas in the SCRIPT variable because the function call is parsed before the variable is replaced with its value. +This works well in conjunction with the BSF or BeanShell Samplers, as these can be used to test Javascript, Jexl and BeanShell scripts. +

    +

    +Functions can reference variables and other functions, for example +${__XPath(${__P(xpath.file),${XPATH})} +will use the property "xpath.file" as the file name +and the contents of the variable XPATH as the expression to search for. +

    +

    +JMeter provides a tool to help you construct +function calls for various built-in functions, which you can then copy-paste. +It will not automatically escape values for you, since functions can be parameters to other functions, and you should only escape values you intend as literal. +

    + +If a string contains a backslash('\') and also contains a function or variable reference, the backslash will be removed if +it appears before '$' or ',' or '\'. +This behaviour is necessary to allow for nested functions that include commas or the string ${. +Backslashes before '$' or ',' or '\' are not removed if the string does not contain a function or variable reference. + +

    +The value of a variable or function can be reported using the __logn() function. +The __logn() function reference can be used anywhere in the test plan after the variable has been defined. +Alternatively, the Java Request sampler can be used to create a sample containing variable references; +the output will be shown in the appropriate Listener. +For versions of JMeter later than 2.3, there is a +that can be used to display the values of variables etc in the Tree View Listener. +

    +*If you define a user-defined static variable with the same name as a built-in function, your static +variable will override the built-in function. +
    + + +

    The Function Helper dialog is available from JMeter's Tools menu.

    +
    Function Helper Dialog
    +

    Using the Function Helper, you can select a function from the pull down, and assign +values for its arguments. The left column in the table provides a brief description of the +argument, and the right column is where you write in the value for that argument. Different +functions take different arguments.

    +

    Once you have done this, click the "generate" button, and the appropriate string is generated +for you to copy-paste into your test plan wherever you like.

    +
    + + + + +

    The Regex Function is used to parse the previous response (or the value of a variable) using any regular +expression (provided by user). The function returns the template string with variable values filled +in.

    +

    The __regexFunction can also store values for future use. In the sixth parameter, you can specify +a reference name. After this function executes, the same values can be retrieved at later times +using the syntax for user-defined values. For instance, if you enter "refName" as the sixth +parameter you will be able to use: +

      +
    • ${refName} to refer to the computed result of the second parameter ("Template for the +replacement string") parsed by this function
    • +
    • ${refName_g0} to refer to the entire match parsed by this function.
    • +
    • ${refName_g1} to refer to the first group parsed by this function.
    • +
    • ${refName_g#} to refer to the nth group parsed by this function.
    • +
    • ${refName_matchNr} to refer to the number of groups found by this function.
    • +
    +If using distributed testing, ensure you switch mode (see jmeter.properties) so that it's not a stripping one, see 56376 +

    +
    + + + The first argument is the regular expression + to be applied to the response data. It will grab all matches. Any parts of this expression + that you wish to use in your template string, be sure to surround in parentheses. Example: + &lt;a href="(.*)"&gt;. This will grab the value of the link and store it as the first group (there is + only 1 group). Another example: &lt;input type="hidden" name="(.*)" value="(.*)"&gt;. This will + grab the name as the first group, and the value as the second group. These values can be used + in your template string + This is the template string that will replace + the function at run-time. To refer to a group captured in the regular expression, use the syntax: + $[group_number]$. Ie: $1$, or $2$. Your template can be any string. + The third argument tells JMeter which match + to use. Your regular expression might find numerous matches. You have four choices: +
    • An integer - Tells JMeter to use that match. '1' for the first found match, '2' for the + second, and so on
    • +
    • RAND - Tells JMeter to choose a match at random.
    • +
    • ALL - Tells JMeter to use all matches, and create a template string for each one and then + append them all together. This option is little used.
    • +
    • A float number between 0 and 1 - tells JMeter to find the Xth match using the formula: + (number_of_matches_found * float_number) rounded to nearest integer.
    • +
    + If 'ALL' was selected for the above argument + value, then this argument will be inserted between each appended copy of the template value. + Default value returned if no match is found + A reference name for reusing the values parsed by this function.

    + Stored values are ${refName} (the replacement template string) and ${refName_g#} where "#" is the + group number from the regular expression ("0" can be used to refer to the entire match).
    + Input variable name. + If specified, then the value of the variable is used as the input instead of using the previous sample result. + +
    +
    + + +

    The counter generates a new number each time it is called, starting with 1 +and incrementing by +1 each time. The counter can be configured to keep each simulated user's values +separate, or to use the same counter for all users. If each user's values is incremented separately, +that is like counting the number of iterations through the test plan. A global counter is like +counting how many times that request was run. +

    +

    The counter uses an integer variable to hold the count, which therefore has a maximum of 2,147,483,647.

    +

    The counter function instances are now completely independent. +[JMeter 2.1.1 and earlier used a fixed thread variable to keep track of the per-user count, +so multiple counter functions operated on the same value.] +The global counter - "FALSE" - is separately maintained by each counter instance. +

    +

    + +Multiple __counter function calls in the same iteration won't increment the value further. + +
    +If you want to have a count that increments for each sample, use the function in a Pre-Processor such as . +

    +
    + + TRUE if you wish each simulated user's counter + to be kept independent and separate from the other users. FALSE for a global counter. + +A reference name for reusing the value created by this function.

    + Stored values are of the form ${refName}. This allows you to keep one counter and refer to its value in + multiple places. [For JMeter 2.1.1 and earlier this parameter was required.]
    +
    +
    + + +

    The thread number function simply returns the number of the thread currently +being executed. These numbers are independent of ThreadGroup, meaning thread #1 in one threadgroup +is indistinguishable from thread #1 in another threadgroup, from the point of view of this function.

    + +

    There are no arguments for this function.

    +
    + +This function does not work in any Configuration elements (e.g. User Defined Variables) as these are run from a separate thread. +Nor does it make sense to use it on the Test Plan. + +
    + + + +

    +The intSum function can be used to compute the sum of two or more integer values. +

    + +JMeter Versions 2.3.1 and earlier required the reference name to be present. +The reference name is now optional, but it must not be a valid integer. + +
    + + + The first int value. + The second int value. + The nth int value. + A reference name for reusing the value computed by this function. + If specified, the reference name must contain at least one non-numeric character otherwise it will + be treated as another int value to be added. + + +
    + + +

    The longSum function can be used to compute the sum of two or more long values. +

    + + + The first long value. + The second long value. + The nth long value. + A reference name for reusing the value computed by this function. + If specified, the reference name must contain at least one non-numeric character otherwise it will + be treated as another long value to be added. + + +
    + + + + + + +

    + The StringFromFile function can be used to read strings from a text file. + This is useful for running tests that require lots of variable data. + For example when testing a banking application, 100s or 1000s of different account numbers might be required. +

    +

    + See also the + CSV Data Set Config test element + which may be easier to use. However, that does not currently support multiple input files. +

    + +

    + Each time it is called it reads the next line from the file. + All threads share the same instance, so different threads will get different lines. + When the end of the file is reached, it will start reading again from the beginning, + unless the maximum loop count has been reached. + If there are multiple references to the function in a test script, each will open the file independently, + even if the file names are the same. + [If the value is to be used again elsewhere, use different variable names for each function call.] +

    + + Function instances are shared between threads, and the file is (re-)opened by whatever thread + happens to need the next line of input, so using the threadNumber as part of the file name + will result in unpredictable behaviour. + +

    If an error occurs opening or reading the file, then the function returns the string "**ERR**"

    + + + + Path to the file name. + (The path can be relative to the JMeter launch directory) + If using optional sequence numbers, the path name should be suitable for passing to DecimalFormat. + See below for examples. + + +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. +Defaults to "StringFromFile_". + + Initial Sequence number (if omitted, the End sequence number is treated as a loop count) + Final sequence number (if omitted, seqence numbers can increase without limit) + +

    The file name parameter is resolved when the file is opened or re-opened.

    +

    The reference name parameter (if supplied) is resolved every time the function is executed.

    +

    Using sequence numbers:

    +

    When using the optional sequence numbers, the path name is used as the format string for java.text.DecimalFormat. + The current sequence number is passed in as the only parameter. + If the optional start number is not specified, the path name is used as is. + Useful formatting sequences are: +

    + +# - insert the number, with no leading zeros or spaces +000 - insert the number packed out to 3 digits with leading zeros if necessary + +Examples:

    + + pin#'.'dat -> pin1.dat, ... pin9.dat, pin10.dat, ... pin9999.dat + pin000'.'dat -> pin001.dat ... pin099.dat ... pin999.dat ... pin9999.dat + pin'.'dat# -> pin.dat1, ... pin.dat9 ... pin.dat999 + +

    + If more digits are required than there are formatting characters, the number will be + expanded as necessary.

    + To prevent a formatting character from being interpreted, + enclose it in single quotes. Note that "." is a formatting character, + and must be enclosed in single quotes + (though #. and 000. work as expected in locales where the decimal point is also ".") +

    + In other locales (e.g. fr), the decimal point is "," - which means that "#." + becomes "nnn,".

    + See the documentation for DecimalFormat for full details.

    + If the path name does not contain any special formatting characters, + the current sequence number will be appended to the name, otherwise + the number will be inserted aaccording to the fomatting instructions.

    + If the start sequence number is omitted, and the end sequence number is specified, + the sequence number is interpreted as a loop count, and the file will be used at most "end" times. + In this case the filename is not formatted. +

    + ${_StringFromFile(PIN#'.'DAT,,1,2)} - reads PIN1.DAT, PIN2.DAT

    + ${_StringFromFile(PIN.DAT,,,2)} - reads PIN.DAT twice

    + Note that the "." in PIN.DAT above should not be quoted. + In this case the start number is omitted, so the file name is used exactly as is. +

    + + + +

    The machineName function returns the local host name

    + + + A reference name for reusing the value + computed by this function. + +
    + + +

    The machineIP function returns the local IP address

    + + + A reference name for reusing the value + computed by this function. + +
    + + + +

    +The javaScript function executes a piece of JavaScript (not Java!) code and returns its value +

    +

    +The JMeter Javascript function calls a standalone JavaScript interpreter. +Javascript is used as a scripting language, so you can do calculations etc.

    +

    +For details of the language, please see Mozilla Rhino Overview +

    +

    +The following variables are made available to the script: +

    +
      +
    • log - the logger for the function
    • +
    • ctx - JMeterContext object
    • +
    • vars - JMeterVariables object
    • +
    • threadName - String containing the current thread name (in 2.3.2 it was misspelt as "theadName")
    • +
    • sampler - current Sampler object (if any)
    • +
    • sampleResult - previous SampleResult object (if any)
    • +
    • props - JMeter Properties object
    • +
    +

    +Rhinoscript allows access to static methods via its Packages object. +See the Scripting Java documentation. +For example one can access the JMeterContextService static methods thus: +Packages.org.apache.jmeter.threads.JMeterContextService.getTotalThreads() +

    + +JMeter is not a browser, and does not interpret the JavaScript in downloaded pages. + +
    + + + The JavaScript expression to be executed. For example: +
      +
    • new Date() - return the current date and time
    • +
    • Math.floor(Math.random()*(${maxRandom}+1)) + - a random number between 0 and the variable maxRandom
    • +
    • ${minRandom}+Math.floor(Math.random()*(${maxRandom}-${minRandom}+1)) + - a random number between the variables minRandom and maxRandom
    • +
    • "${VAR}"=="abcd"
    • +
    +
    + A reference name for reusing the value + computed by this function. +
    +Remember to include any necessary quotes for text strings and JMeter variables. Also, if +the expression has commas, please make sure to escape them. For example in: + +${__javaScript('${sp}'.slice(7\,99999))} + +the comma after 7 is escaped. +
    + + +

    The random function returns a random number that lies between the given min and max values.

    + + + A number + A bigger number + A reference name for reusing the value + computed by this function. + +
    + + +

    The RandomString function returns a random String of length using characters in chars to use.

    + + + A number length of generated String + Chars used to generate String + A reference name for reusing the value + computed by this function. + +
    + + + +

    The UUID function returns a pseudo random type 4 Universally Unique IDentifier (UUID).

    +
    + + +
    + + +

    The CSVRead function returns a string from a CSV file (c.f. StringFromFile)

    +

    NOTE: versions up to 1.9.1 only supported a single file. + JMeter versions since 1.9.1 support multiple file names. +

    +

    In most cases, the newer + CSV Data Set Config element + is easier to use.

    +

    + When a filename is first encountered, the file is opened and read into an internal + array. If a blank line is detected, this is treated as end of file - this allows + trailing comments to be used (N.B. this feature was introduced in versions after 1.9.1) +

    +

    All subsequent references to the same file name use the same internal array. + N.B. the filename case is significant to the function, even if the OS doesn't care, + so CSVRead(abc.txt,0) and CSVRead(aBc.txt,0) would refer to different internal arrays. +

    +

    + The *ALIAS feature allows the same file to be opened more than once, + and also allows for shorter file names. +

    +

    + Each thread has its own internal pointer to its current row in the file array. + When a thread first refers to the file it will be allocated the next free row in + the array, so each thread will access a different row from all other threads. + [Unless there are more threads than there are rows in the array.] +

    + The function splits the line at every comma by default. + If you want to enter columns containing commas, then you will need + to change the delimiter to a character that does not appear in any + column data, by setting the property: csvread.delimiter + +
    + + + The file (or *ALIAS) to read from + + The column number in the file. + 0 = first column, 1 = second etc. + "next" - go to next line of file. + *ALIAS - open a file and assign it to the alias + + +

    For example, you could set up some variables as follows: +

      +
    • COL1a ${__CSVRead(random.txt,0)}
    • +
    • COL2a ${__CSVRead(random.txt,1)}${__CSVRead(random.txt,next)}
    • +
    • COL1b ${__CSVRead(random.txt,0)}
    • +
    • COL2b ${__CSVRead(random.txt,1)}${__CSVRead(random.txt,next)}
    • +
    +This would read two columns from one line, and two columns from the next available line. +If all the variables are defined on the same User Parameters Pre-Processor, then the lines +will be consecutive. Otherwise, a different thread may grab the next line. +

    + +The function is not suitable for use with large files, as the entire file is stored in memory. +For larger files, use CSV Data Set Config element +or StringFromFile. + +
    + + +

    The property function returns the value of a JMeter property. + If the property value cannot be found, and no default has been supplied, it returns the property name. + When supplying a default value, there is no need to provide a function name - the parameter can be set to null, and it will be ignored. +

    For example:

    +

      +
    • ${__property(user.dir)} - return value of user.dir
    • +
    • ${__property(user.dir,UDIR)} - return value of user.dir and save in UDIR
    • +
    • ${__property(abcd,ABCD,atod)} - return value of property abcd (or "atod" if not defined) and save in ABCD
    • +
    • ${__property(abcd,,atod)} - return value of property abcd (or "atod" if not defined) but don't save it
    • +
    +

    +
    + + + The property name to be retrieved. + A reference name for reusing the value + computed by this function. + The default value for the property. + +
    + + +

    This is a simplified property function which is + intended for use with properties defined on the command line. + Unlike the __property function, there is no option to save the value in a variable, + and if no default value is supplied, it is assumed to be 1. + The value of 1 was chosen because it is valid for common test variables such + as loops, thread count, ramp up etc. +

    For example:

    + +Define the property value: + +jmeter -Jgroup1.threads=7 -Jhostname1=www.realhost.edu + + +Fetch the values: +

    +${__P(group1.threads)} - return the value of group1.threads +

    +${__P(group1.loops)} - return the value of group1.loops +

    +${__P(hostname,www.dummy.org)} - return value of property hostname or www.dummy.org if not defined +

    +
    +In the examples above, the first function call would return 7, +the second would return 1 and the last would return www.dummy.org +(unless those properties were defined elsewhere!) +

    +
    + + + The property name to be retrieved. + The default value for the property. + If omitted, the default is set to "1". + +
    + + + +

    + The log function logs a message, and returns its input string +

    +
    + + + A string + OUT, ERR, DEBUG, INFO (default), WARN or ERROR + If non-empty, creates a Throwable to pass to the logger + If present, it is displayed in the string. + Useful for identifying what is being logged. + +

    The OUT and ERR log level names are used to direct the output to System.out and System.err respectively. + In this case, the output is always printed - it does not depend on the current log setting. +

    +For example: +
    +
    ${__log(Message)}
    written to the log file as "...thread Name : Message"
    +
    ${__log(Message,OUT)}
    written to console window
    +
    ${__log(${VAR},,,VAR=)}
    written to log file as "...thread Name VAR=value"
    +
    +
    + + + +

    + The logn function logs a message, and returns the empty string +

    +
    + + + A string + OUT, ERR, DEBUG, INFO (default), WARN or ERROR + If non-empty, creates a Throwable to pass to the logger + +

    The OUT and ERR log level names are used to direct the output to System.out and System.err respectively. + In this case, the output is always printed - it does not depend on the current log setting. +

    +For example: +
    +
    ${__logn(VAR1=${VAR1},OUT)}
    write the value of the variable to the console window
    +
    +
    + + + +

    + The BeanShell function evaluates the script passed to it, and returns the result. +

    +

    +For full details on using BeanShell, please see the BeanShell web-site at http://www.beanshell.org/ + +

    + +Note that a different Interpreter is used for each independent occurence of the function +in a test script, but the same Interpreter is used for subsequent invocations. +This means that variables persist across calls to the function. + +

    +A single instance of a function may be called from multiple threads. +However the function execute() method is synchronised. +

    +

    +If the property "beanshell.function.init" is defined, it is passed to the Interpreter +as the name of a sourced file. This can be used to define common methods and variables. There is a +sample init file in the bin directory: BeanShellFunction.bshrc. +

    +

    +The following variables are set before the script is executed: +

      +
    • log - the logger for the BeanShell function (*)
    • +
    • ctx - the current JMeter context variable
    • +
    • vars - the current JMeter variables
    • +
    • props - JMeter Properties object
    • +
    • threadName - the threadName (String)
    • +
    • Sampler the current Sampler, if any
    • +
    • SampleResult - the current SampleResult, if any
    • +
    +(*) means that this is set before the init file, if any, is processed. +Other variables vary from invocation to invocation. +

    +
    + + + A beanshell script (not a file name) + A reference name for reusing the value + computed by this function. + + +

    +Example: +

    +
    ${__BeanShell(123*456)}
    returns 56088
    +
    ${__BeanShell(source("function.bsh"))}
    processes the script in function.bsh
    +
    +

    + +Remember to include any necessary quotes for text strings and JMeter variables that represent text strings. + +
    + + + +

    + The split function splits the string passed to it according to the delimiter, + and returns the original string. If any delimiters are adjacent, "?" is returned as the value. + The split strings are returned in the variables ${VAR_1}, ${VAR_2} etc. + The count of variables is returned in ${VAR_n}. + From JMeter 2.1.2 onwards, a trailing delimiter is treated as a missing variable, and "?" is returned. + Also, to allow it to work better with the ForEach controller, + __split now deletes the first unused variable in case it was set by a previous split. +

    +

    + Example: +

    + Define VAR="a||c|" in the test plan. +

    + ${__split(${VAR},VAR,|)} +

    + This will return the contents of VAR, i.e. "a||c|" and set the following variables: +

    + VAR_n=4 (3 in JMeter 2.1.1 and earlier) +

    + VAR_1=a +

    + VAR_2=? +

    + VAR_3=c +

    + VAR_4=? (null in JMeter 2.1.1 and earlier) +

    + VAR_5=null (in JMeter 2.1.2 and later) + + + + A delimited string, e.g. "a|b|c" + A reference name for reusing the value + computed by this function. + The delimiter character, e.g. |. +If omitted, , is used. Note that , would need to be specified as \,. + + + + + + +

    + The XPath function reads an XML file and matches the XPath. + Each time the function is called, the next match will be returned. + At end of file, it will wrap around to the start. + If no nodes matched, then the function will return the empty string, + and a warning message will be written to the JMeter log file. + Note that the entire NodeList is held in memory. +

    +

    + Example: + ${__XPath(/path/to/build.xml, //target/@name)} + This will match all targets in build.xml and return the contents of the next name attribute + + + + a XML file to parse + a XPath expression to match nodes in the XML file + + + + + +

    The setProperty function sets the value of a JMeter property. + The default return value from the function is the empty string, + so the function call can be used anywhere functions are valid.

    +

    The original value can be returned by setting the optional 3rd parameter to "true".

    +

    Properties are global to JMeter, + so can be used to communicate between threads and thread groups

    +
    + + + The property name to be set. + The value for the property. + Should the original value be returned? + +
    + + + + +

    The time function returns the current time in various formats.

    +
    + + + + The format to be passed to SimpleDateFormat. + The function supports various shorthand aliases, see below. + If ommitted, the function returns the current time in milliseconds since the epoch. + + The name of the variable to set. + +

    If the format string is omitted, then the function returns the current time in milliseconds since the epoch. +In versions of JMeter after 2.7, if the format matches "/ddd" (where ddd are decimal digits), +then the function returns the current time in milliseconds divided by the value of ddd. +For example, "/1000" returns the current time in seconds since the epoch. +Otherwise, the current time is passed to SimpleDateFormat. +The following shorthand aliases are provided: +

    +
      +
    • YMD = yyyyMMdd
    • +
    • HMS = HHmmss
    • +
    • YMDHMS = yyyyMMdd-HHmmss
    • +
    • USER1 = whatever is in the Jmeter property time.USER1
    • +
    • USER2 = whatever is in the Jmeter property time.USER2
    • +
    +

    The defaults can be changed by setting the appropriate JMeter property, e.g. +time.YMD=yyMMdd +

    +
    + + + +

    The jexl function returns the result of evaluating a + Commons JEXL expression. + See links below for more information on JEXL expressions. +

    +

    The __jexl function uses Commons JEXL 1, and the __jexl2 function uses Commons JEXL 2

    + +
    + + + + The expression to be evaluated. For example, 6*(5+2) + + The name of the variable to set. + +

    +The following variables are made available to the script: +

    +
      +
    • log - the logger for the function
    • +
    • ctx - JMeterContext object
    • +
    • vars - JMeterVariables object
    • +
    • props - JMeter Properties object
    • +
    • threadName - String containing the current thread name (in 2.3.2 it was misspelt as "theadName")
    • +
    • sampler - current Sampler object (if any)
    • +
    • sampleResult - previous SampleResult object (if any)
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    + Jexl can also create classes and call methods on them, for example: +

    +

    + +Systemclass=log.class.forName("java.lang.System"); +now=Systemclass.currentTimeMillis(); + + Note that the Jexl documentation on the web-site wrongly suggests that "div" does integer division. + In fact "div" and "/" both perform normal division. One can get the same effect + as follows: + +i= 5 / 2; +i.intValue(); // or use i.longValue() + +

    + Versions of JMeter after 2.3.2 allow the expression to contain multiple statements. + JMeter 2.3.2 and earlier only processed the first statement (if there were multiple statements a warning was logged). + +
    + + + +

    The V (variable) function returns the result of evaluating a variable name expression. + This can be used to evaluate nested variable references (which are not currently supported). +

    +

    For example, if one has variables A1,A2 and N=1:

    +
      +
    • ${A1} - works OK
    • +
    • ${A${N}} - does not work (nested variable reference)
    • +
    • ${__V(A${N})} - works OK. A${N} becomes A1, and the __V function returns the value of A1
    • +
    +
    + + + + The variable to be evaluated. + + +
    + + + +

    The eval function returns the result of evaluating an expression stored in a variable. +

    +

    + This allows one to read a string from a file, and process any variable references in it. + For example, if the variable "query" contains "select ${column} from ${table}" + and "column" and "table" contain "name" and "customers", then ${__evalVar(query)} + will evaluate as "select name from customers". +

    +
    + + + + The variable to be evaluated. + + +
    + + +

    The eval function returns the result of evaluating a string expression. +

    +

    + This allows one to interpolate variable and function references in a string + which is stored in a variable. For example, given the following variables: +

    +
      +
    • name=Smith
    • +
    • column=age
    • +
    • table=birthdays
    • +
    • SQL=select ${column} from ${table} where name='${name}'
    • +
    + then ${__eval(${SQL})} will evaluate as "select age from birthdays where name='Smith'". + +

    + This can be used in conjunction with CSV Dataset, for example + where the both SQL statements and the values are defined in the data file. +

    +
    + + + + The variable to be evaluated. + + +
    + + + +

    + The char function returns the result of evaluating a list of numbers as Unicode characters. + See also __unescape(), below. +

    +

    + This allows one to add arbitrary character values into fields. +

    +
    + + + + The decimal number (or hex number, if prefixed by 0x, or octal, if prefixed by 0) to be converted to a Unicode character. + + +

    Examples: +
    +${__char(13,10)} = ${__char(0xD,0xA)} = ${__char(015,012)} = CRLF +
    +${__char(165)} = &#165; (yen) +

    +
    + + + +

    + The unescape function returns the result of evaluating a Java-escaped string. See also __char() above. +

    +

    + This allows one to add characters to fields which are otherwise tricky (or impossible) to define via the GUI. +

    +
    + + + + The string to be unescaped. + + +

    Examples: +
    +${__unescape(\r\n)} = CRLF +
    +${__unescape(1\t2)} = 1[tab]2 +

    +
    + + + +

    +Function to unescape a string containing HTML entity escapes +to a string containing the actual Unicode characters corresponding to the escapes. +Supports HTML 4.0 entities. +

    +

    +For example, the string "&#38;lt;Fran&#38;ccedil;ais&#38;gt;" +will become "&lt;Fran&ccedil;ais&gt;". +

    +

    +If an entity is unrecognized, it is left alone, and inserted verbatim into the result string. +e.g. "&gt;&zzzz;x" will become ">&zzzz;x". +

    +

    + Uses StringEscapeUtils#unescapeHtml(String) from Commons Lang. +

    +
    + + + + The string to be unescaped. + + +
    + + + +

    +Function which escapes the characters in a String using HTML entities. +Supports HTML 4.0 entities. +

    +

    +For example,&quot;bread&quot; &amp; &quot;butter&quot; +becomes: +&#38;quot;bread&#38;quot; &#38;amp; &#38;quot;butter&#38;quot;. +

    +

    + Uses StringEscapeUtils#escapeHtml(String) from Commons Lang. +

    +
    + + + + The string to be escaped. + + +
    + + + +

    +Function to decode a application/x-www-form-urlencoded string. +Note: use UTF-8 as the encoding scheme. +

    +

    +For example, the string Word+%22school%22+is+%22%C3%A9cole%22+in+french would get converted to + Word "school" is "&eacute;cole" in french. +

    +

    + Uses Java class URLDecoder. +

    +
    + + + + The string with URL encoded chars to decode. + + +
    + + + +

    +Function to encode a string to a application/x-www-form-urlencoded string. +

    +

    +For example, the string Word "school" is "&eacute;cole" in french would get converted to + Word+%22school%22+is+%22%C3%A9cole%22+in+french. +

    +

    + Uses Java class URLEncoder. +

    +
    + + + + String to encode in URL encoded chars. + + +
    + + + + +

    + The FileToString function can be used to read an entire file. + Each time it is called it reads the entire file. +

    +

    If an error occurs opening or reading the file, then the function returns the string "**ERR**"

    +
    + + + Path to the file name. + (The path can be relative to the JMeter launch directory) + + + The encoding to be used to read the file. If not specified, the platform default is used. + + +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. + + +

    The file name, encoding and reference name parameters are resolved every time the function is executed.

    +
    + + + + +

    + The samplerName function returns the name (i.e. label) of the current sampler. +

    +

    + The function does not work in Test elements that don't have an associated sampler. + For example the Test Plan. + Configuration elements also don't have an associated sampler. + However some Configuration elements are referenced directly by samplers, such as the HTTP Header Manager + and Http Cookie Manager, and in this case the functions are resolved in the context of the Http Sampler. + Pre-Processors, Post-Processors and Assertions always have an associated Sampler. +

    +
    + + + +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. + + +
    + + + + +

    + The TestPlanName function returns the name of the current test plan (can be used in Including Plans to know the name of the calling test plan). +

    +
    +
    + + + +

    +Function which escapes the ORO Regexp meta characters, it is the equivalent of \Q \E in Java Regexp Engine. +

    +

    +For example,[^"].+? +becomes: +\[\^\"\]\.\+\?. +

    +

    + Uses Perl5Compiler#quotemeta(String) from ORO. +

    +
    + + + + The string to be escaped. + + +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. + + +
    + +
    + + +

    +Most variables are set by calling functions or by test elements such as User Defined Variables; +in which case the user has full control over the variable name that is used. +However some variables are defined internally by JMeter. These are listed below. +

    +
      +
    • COOKIE_cookiename - contains the cookie value (see )
    • +
    • JMeterThread.last_sample_ok - whether or not the last sample was OK - true/false. +Note: this is updated after PostProcessors and Assertions have been run. +
    • +
    • START variables (see next section)
    • +
    +
    + +

    +The set of JMeter properties is initialised from the system properties defined when JMeter starts; +additional JMeter properties are defined in jmeter.properties, user.properties or on the command line. +

    +

    +Some built-in properties are defined by JMeter. These are listed below. +For convenience, the START properties are also copied to variables with the same names. +

    +
      +
    • START.MS - JMeter start time in milliseconds
    • +
    • START.YMD - JMeter start time as yyyyMMdd
    • +
    • START.HMS - JMeter start time as HHmmss
    • +
    • TESTSTART.MS - test start time in milliseconds
    • +
    +

    +Please note that the START variables / properties represent JMeter startup time, not the test start time. +They are mainly intended for use in file names etc. +

    +
    +
    + + +
    diff --git a/xdocs/usermanual/get-started.xml b/xdocs/usermanual/get-started.xml new file mode 100644 index 00000000000..c30f25a332c --- /dev/null +++ b/xdocs/usermanual/get-started.xml @@ -0,0 +1,622 @@ + + + + +]> + + + + + User's Manual: Getting Started + + + +
    +

    The easiest way to begin using JMeter is to first +download the latest production release and install it. +The release contains all of the files you need to build and run most types of tests, +e.g. Web (HTTP/HTTPS), FTP, JDBC, LDAP, Java, JUnit and more.

    +

    If you want to perform JDBC testing, +then you will, of course, need the appropriate JDBC driver from your vendor. JMeter does not come with +any JDBC drivers.

    +

    +JMeter includes the JMS API jar, but does not include a JMS client implementation. +If you want to run JMS tests, you will need to download the appropriate jars from the JMS provider. +

    + +See the JMeter Classpath section for details on installing additional jars. + +

    Next, start JMeter and go through the Building a Test Plan section +of the User Guide to familiarize yourself with JMeter basics (for example, adding and removing elements).

    +

    Finally, go through the appropriate section on how to build a specific type of Test Plan. +For example, if you are interested in testing a Web application, then see the section +Building a Web Test Plan. +The other specific Test Plan sections are: +

    +

    +

    Once you are comfortable with building and running JMeter Test Plans, you can look into the +various configuration elements (timers, listeners, assertions, and others) which give you more control +over your Test Plans.

    + +
    + +
    +

    JMeter requires that your computing environment meets some minimum requirements.

    + + +JMeter requires a fully compliant JVM 6 or higher. + +

    Because JMeter uses only standard Java APIs, please do not file bug reports if your JRE fails to run +JMeter because of JRE implementation issues.

    +
    + + +

    JMeter is a 100% Java application and should run correctly on any system +that has a compliant Java implementation.

    +

    Operating systems tested with JMeter can be viewed on +this page +on JMeter wiki.

    +

    Even if your OS is not listed on the wiki page, JMeter should run on it provided that the JVM is compliant.

    +
    +
    + +
    +

    If you plan on doing JMeter development, then you will need one or more optional packages listed below.

    + + + +

    If you want to build the JMeter source or develop JMeter plugins, then you will need a fully compliant JDK 6 or higher.

    +
    + + +

    JMeter comes with Apache's Xerces XML parser. You have the option of telling JMeter +to use a different XML parser. To do so, include the classes for the third-party parser in JMeter's classpath, +and update the jmeter.properties file with the full classname of the parser +implementation.

    +
    + + +

    JMeter has extensive Email capabilities. +It can send email based on test results, and has a POP3(S)/IMAP(S) sampler. +It also has an SMTP(S) sampler. +

    +
    + + +

    To test a web server using SSL encryption (HTTPS), JMeter requires that an +implementation of SSL be provided, as is the case with Sun Java 1.4 and above. +If your version of Java does not include SSL support, then it is possible to add an external implementation. +Include the necessary encryption packages in JMeter's classpath. +Also, update system.properties to register the SSL Provider.

    +

    +JMeter HTTP defaults to protocol level TLS. This can be changed by editting the JMeter property +https.default.protocol in jmeter.properties or user.properties. +

    +

    The JMeter HTTP samplers are configured to accept all certificates, +whether trusted or not, regardless of validity periods, etc. +This is to allow the maximum flexibility in testing servers.

    +

    If the server requires a client certificate, this can be provided.

    +

    There is also the , for greater control of certificates.

    +The JMeter proxy server (see below) supports recording HTTPS (SSL) +

    +The SMTP sampler can optionally use a local trust store or trust all certificates. +

    +
    + + +

    You will need to add your database vendor's JDBC driver to the classpath if you want to do JDBC testing. +Make sure the file is a jar file, not a zip. +

    +
    + + +

    +JMeter now includes the JMS API from Apache Geronimo, so you just need to add the appropriate JMS Client implementation +jar(s) from the JMS provider. Please refer to their documentation for details. +There may also be some information on the JMeter Wiki. +

    +
    + + +

    +You will need to add the jar activemq-all-X.X.X.jar to your classpath, e.g. by storing it in the lib/ directory. +

    +

    +The other required jars (such as commons-logging) are already included with JMeter. +

    +

    +See ActiveMQ initial configuration page +for details. +

    +
    + + +See the JMeter Classpath section for more details on installing additional jars. + +
    + +
    + +

    We recommend that most users run the latest release.

    +

    To install a release build, simply unzip the zip/tar file into the directory +where you want JMeter to be installed. Provided that you have a JRE/JDK correctly installed +and the JAVA_HOME environment variable set, there is nothing more for you to do.

    + +There can be problems (especially with client-server mode) if the directory path contains any spaces. + +

    +The installation directory structure should look something like this (where X.Y is version number): + +apache-jmeter-X.Y +apache-jmeter-X.Y/bin +apache-jmeter-X.Y/docs +apache-jmeter-X.Y/extras +apache-jmeter-X.Y/lib/ +apache-jmeter-X.Y/lib/ext +apache-jmeter-X.Y/lib/junit +apache-jmeter-X.Y/licenses +apache-jmeter-X.Y/printable_docs + +You can rename the parent directory (i.e. apache-jmeter-X.Y) if you want, but do not change any of the sub-directory names. +

    +
    + +
    +
    +

    To run JMeter, run the jmeter.bat (for Windows) or jmeter (for Unix) file. +These files are found in the bin directory. +After a short time, the JMeter GUI should appear. +

    + +

    +There are some additional scripts in the bin directory that you may find useful. +Windows script files (the .CMD files require Win2K or later): +

    +
    +
    jmeter.bat
    run JMeter (in GUI mode by default)
    +
    jmeterw.cmd
    run JMeter without the windows shell console (in GUI mode by default)
    +
    jmeter-n.cmd
    drop a JMX file on this to run a non-GUI test
    +
    jmeter-n-r.cmd
    drop a JMX file on this to run a non-GUI test remotely
    +
    jmeter-t.cmd
    drop a JMX file on this to load it in GUI mode
    +
    jmeter-server.bat
    start JMeter in server mode
    +
    mirror-server.cmd
    runs the JMeter Mirror Server in non-GUI mode
    +
    shutdown.cmd
    Run the Shutdown client to stop a non-GUI instance gracefully
    +
    stoptest.cmd
    Run the Shutdown client to stop a non-GUI instance abruptly
    +
    +The special name LAST can be used with jmeter-n.cmd, jmeter-t.cmd and jmeter-n-r.cmd +and means the last test plan that was run interactively. + +

    +The environment variable JVM_ARGS can be used to override JVM settings in the jmeter.bat script. +For example: +

    + +set JVM_ARGS="-Xms1024m -Xmx1024m -Dpropname=propvalue" +jmeter -t test.jmx ... + + +

    +Un*x script files; should work on most Linux/Unix systems: +

    +
    +
    jmeter
    run JMeter (in GUI mode by default). Defines some JVM settings which may not work for all JVMs.
    +
    jmeter-server
    start JMeter in server mode (calls jmeter script with appropriate parameters)
    +
    jmeter.sh
    very basic JMeter script (You may need to adapt JVM options like memory settings).
    +
    mirror-server.sh
    runs the JMeter Mirror Server in non-GUI mode
    +
    shutdown.sh
    Run the Shutdown client to stop a non-GUI instance gracefully
    +
    stoptest.sh
    Run the Shutdown client to stop a non-GUI instance abruptly
    +
    +

    +It may be necessary to edit the jmeter shell script if some of the JVM options are not supported +by the JVM you are using. +The JVM_ARGS environment variable can be used to override or set additional JVM options, for example: +

    + +JVM_ARGS="-Xms1024m -Xmx1024m" jmeter -t test.jmx [etc.] + +will override the HEAP settings in the script. + +

    JMeter automatically finds classes from jars in the following directories:

    +
    +
    JMETER_HOME/lib
    used for utility jars
    +
    JMETER_HOME/lib/ext
    used for JMeter components and plugins
    +
    +

    If you have developed new JMeter components, +then you should jar them and copy the jar into JMeter's lib/ext directory. +JMeter will automatically find JMeter components in any jars found here. +Do not use lib/ext for utility jars or dependency jars used by the plugins; +it is only intended for JMeter components and plugins. +

    +

    If you don't want to put JMeter plugin jars in the lib/ext directory, +then define the property search_paths in jmeter.properties. +

    +

    Utility and dependency jars (libraries etc) can be placed in the lib directory.

    +

    If you don't want to put such jars in the lib directory, +then define the property user.classpath or plugin_dependency_paths +in jmeter.properties. See below for an explanation of the differences. +

    +

    +Other jars (such as JDBC, JMS implementations and any other support libaries needed by the JMeter code) +should be placed in the lib directory - not the lib/ext directory, +or added to user.classpath.

    +JMeter will only find .jar files, not .zip. +

    You can also install utility Jar files in $JAVA_HOME/jre/lib/ext, or you can set the +property user.classpath in jmeter.properties

    +

    Note that setting the CLASSPATH environment variable will have no effect. +This is because JMeter is started with "java -jar", +and the java command silently ignores the CLASSPATH variable, and the -classpath/-cp +options when -jar is used. +[This occurs with all Java programs, not just JMeter.]

    +
    + + +

    You have the ability to create a new Test Plan from existing template.

    +

    To do so you use the menu File > Templates... or Templates icon: +

    Templates icon item
    +

    +

    A popup appears, you can then choose a template among the list: +

    Templates popup
    +

    +

    A documentation for each template explains what to do once test plan is created from template.

    +
    + + +

    If you are testing from behind a firewall/proxy server, you may need to provide JMeter with +the firewall/proxy server hostname and port number. To do so, run the jmeter[.bat] file +from a command line with the following parameters:

    +
    +-H
    [proxy server hostname or ip address]
    +-P
    [proxy server port]
    +-N
    [nonproxy hosts] (e.g. *.apache.org|localhost)
    +-u
    [username for proxy authentication - if required]
    +-a
    [password for proxy authentication - if required]
    +
    +Example: +jmeter -H my.proxy.server -P 8000 -u username -a password -N localhost +

    You can also use --proxyHost, --proxyPort, --username, and --password as parameter names

    + +Parameters provided on a command-line may be visible to other users on the system. + +

    +If the proxy host and port are provided, then JMeter sets the following System properties: +

    +
      +
    • http.proxyHost
    • +
    • http.proxyPort
    • +
    • https.proxyHost
    • +
    • https.proxyPort
    • +
    +If a nonproxy host list is provided, then JMeter sets the following System properties: +
      +
    • http.nonProxyHosts
    • +
    • https.nonProxyHosts
    • +
    +

    +So if you don't wish to set both http and https proxies, +you can define the relevant properties in system.properties instead of using the command-line parameters. +

    +

    +Proxy Settings can also be defined in a Test Plan, using either the +configuration or the sampler elements. +

    +JMeter also has its own in-built Proxy Server, the HTTP(S) Test Script Recorder. +This is only used for recording HTTP or HTTPS browser sessions. +This is not to be confused with the proxy settings described above, which are used when JMeter makes HTTP or HTTPS requests itself. +
    + + +

    For non-interactive testing, you may choose to run JMeter without the GUI. To do so, use +the following command options:

    +
    +-n
    This specifies JMeter is to run in non-gui mode
    +-t
    [name of JMX file that contains the Test Plan].
    +-l
    [name of JTL file to log sample results to].
    +-j
    [name of JMeter run log file].
    +-r
    Run the test in the servers specified by the JMeter property "remote_hosts"
    +-R
    [list of remote servers] Run the test in the specified remote servers
    +
    +

    The script also lets you specify the optional firewall/proxy server information:

    +
    +-H
    [proxy server hostname or ip address]
    +-P
    [proxy server port]
    +
    +Example +jmeter -n -t my_test.jmx -l log.jtl -H my.proxy.server -P 8000 +

    +If the property jmeterengine.stopfail.system.exit is set to true (default is false), +then JMeter will invoke System.exit(1) if it cannot stop all threads. +Normally this is not necessary. +

    +
    + + +

    For distributed testing, run JMeter in server mode on the remote node(s), and then control the server(s) from the GUI. +You can also use non-GUI mode to run remote tests. +To start the server(s), run jmeter-server[.bat] on each server host.

    +

    The script also lets you specify the optional firewall/proxy server information:

    +
    +-H
    [proxy server hostname or ip address]
    +-P
    [proxy server port]
    +
    +Example: +jmeter-server -H my.proxy.server -P 8000 +

    If you want the server to exit after a single test has been run, then define the JMeter property server.exitaftertest=true. +

    +

    To run the test from the client in non-GUI mode, use the following command:

    + +jmeter -n -t testplan.jmx -r [-Gprop=val] [-Gglobal.properties] [-X] + +where: +
    +-G
    is used to define JMeter properties to be set in the servers
    +-X
    means exit the servers at the end of the test
    +-Rserver1,server2
    can be used instead of -r to provide a list of servers to start. +Overrides remote_hosts, but does not define the property.
    +
    +

    +If the property jmeterengine.remote.system.exit is set to true (default is false), +then JMeter will invoke System.exit(0) after stopping RMI at the end of a test. +Normally this is not necessary. +

    +
    + + +

    Java system properties, JMeter properties, and logging properties can be overriden directly on the command line +(instead of modifying jmeter.properties). +To do so, use the following options:

    +
    +-D[prop_name]=[value]
    defines a java system property value.
    +-J[prop_name]=[value]
    defines a local JMeter property.
    +-G[prop_name]=[value]
    defines a JMeter property to be sent to all remote servers.
    +-G[propertyfile]
    defines a file containing JMeter properties to be sent to all remote servers.
    +-L[category]=[priority]
    overrides a logging setting, setting a particular category to the given priority level.
    +
    +

    The -L flag can also be used without the category name to set the root logging level.

    +

    Examples: +

    + +jmeter -Duser.dir=/home/mstover/jmeter_stuff \ + -Jremote_hosts=127.0.0.1 -Ljmeter.engine=DEBUG + +jmeter -LDEBUG + + The command line properties are processed early in startup, but after the logging system has been set up. + Attempts to use the -J flag to update log_level or log_file properties will have no effect. + +
    + + + JMeter does not generally use pop-up dialog boxes for errors, as these would interfere with + running tests. Nor does it report any error for a mis-spelt variable or function; instead the + reference is just used as is. See Functions and Variables for more information. + +

    If JMeter detects an error during a test, a message will be written to the log file. + The log file name is defined in the jmeter.properties file (or using the -j option, see below). + It defaults to jmeter.log, and will be found in the directory from which JMeter was launched. +

    +

    + The menu Options > Log Viewer displays the log file in a bottom pane on main JMeter window. +

    +

    + In the GUI mode, the number of error/fatal messages logged in the log file is displayed at top-right. +

    +
    Error/fatal counter
    +

    + The command-line option -j jmeterlogfile allow to process + after the initial properties file is read, + and before any further properties are processed. + It therefore allows the default of jmeter.log to be overridden. + The jmeter scripts that take a test plan name as a parameter (e.g. jmeter-n.cmd) have been updated + to define the log file using the test plan name, + e.g. for the test plan Test27.jmx the log file is set to Test27.log. +

    +

    When running on Windows, the file may appear as just jmeter unless you have set Windows to show file extensions. + [Which you should do anyway, to make it easier to detect viruses and other nasties that pretend to be text files...] +

    +

    As well as recording errors, the jmeter.log file records some information about the test run. For example:

    + +10/17/2003 12:19:20 PM INFO - jmeter.JMeter: Version 1.9.20031002 +10/17/2003 12:19:45 PM INFO - jmeter.gui.action.Load: Loading file: c:\mytestfiles\BSH.jmx +10/17/2003 12:19:52 PM INFO - jmeter.engine.StandardJMeterEngine: Running the test! +10/17/2003 12:19:52 PM INFO - jmeter.engine.StandardJMeterEngine: Starting 1 threads for group BSH. Ramp up = 1. +10/17/2003 12:19:52 PM INFO - jmeter.engine.StandardJMeterEngine: Continue on error +10/17/2003 12:19:52 PM INFO - jmeter.threads.JMeterThread: Thread BSH1-1 started +10/17/2003 12:19:52 PM INFO - jmeter.threads.JMeterThread: Thread BSH1-1 is done +10/17/2003 12:19:52 PM INFO - jmeter.engine.StandardJMeterEngine: Test has ended + +

    The log file can be helpful in determining the cause of an error, + as JMeter does not interrupt a test to display an error dialogue.

    +
    + +

    Invoking JMeter as "jmeter -?" will print a list of all the command-line options. +These are shown below.

    + +-h, --help + print usage information and exit +-v, --version + print the version information and exit +-p, --propfile {argument} + the jmeter property file to use +-q, --addprop {argument} + additional property file(s) +-t, --testfile {argument} + the jmeter test(.jmx) file to run +-j, --jmeterlogfile {argument} + the jmeter log file +-l, --logfile {argument} + the file to log samples to +-n, --nongui + run JMeter in nongui mode +-s, --server + run the JMeter server +-H, --proxyHost {argument} + Set a proxy server for JMeter to use +-P, --proxyPort {argument} + Set proxy server port for JMeter to use +-u, --username {argument} + Set username for proxy server that JMeter is to use +-a, --password {argument} + Set password for proxy server that JMeter is to use +-J, --jmeterproperty {argument}={value} + Define additional JMeter properties +-G, --globalproperty (argument)[=(value)] + Define Global properties (sent to servers) + e.g. -Gport=123 + or -Gglobal.properties +-D, --systemproperty {argument}={value} + Define additional System properties +-S, --systemPropertyFile {filename} + a property file to be added as System properties +-L, --loglevel {argument}={value} + Define loglevel: [category=]level + e.g. jorphan=INFO or jmeter.util=DEBUG +-r, --runremote (non-GUI only) + Start remote servers (as defined by the jmeter property remote_hosts) +-R, --remotestart server1,... (non-GUI only) + Start these remote servers (overrides remote_hosts) +-d, --homedir {argument} + the jmeter home directory to use +-X, --remoteexit + Exit the remote servers at end of test (non-GUI) + +

    +Note: the JMeter log file name is formatted as a SimpleDateFormat (applied to the current date) +if it contains paired single-quotes, .e.g. 'jmeter_'yyyyMMddHHmmss'.log' +

    +

    +If the special name LAST is used for the -t, -j or -l flags, +then JMeter takes that to mean the last test plan +that was run in interactive mode. +

    +
    + + +

    +Prior to version 2.5.1, JMeter invoked System.exit() when a non-GUI test completed. +This caused problems for applications that invoke JMeter directly, so JMeter no longer invokes System.exit() +for a normal test completion. [Some fatal errors may still invoke System.exit()] +JMeter will exit all the non-daemon threads it starts, but it is possible that some non-daemon threads +may still remain; these will prevent the JVM from exitting. +To detect this situation, JMeter starts a new daemon thread just before it exits. +This daemon thread waits a short while; if it returns from the wait, then clearly the +JVM has not been able to exit, and the thread prints a message to say why. +

    +

    +The property jmeter.exit.check.pause can be used to override the default pause of 2000ms (2secs). +If set to 0, then JMeter does not start the daemon thread. +

    +
    + +
    + + +
    +

    If you wish to modify the properties with which JMeter runs you need to + either modify the user.properties in the /bin directory or create + your own copy of the jmeter.properties and specify it in the command line. +

    + + Note: You can define additional JMeter properties in the file defined by the + JMeter property user.properties which has the default value user.properties. + The file will be automatically loaded if it is found in the current directory + or if it is found in the JMeter bin directory. + Similarly, system.properties is used to update system properties. + + + You can specify the class for your SSL + implementation if you don't want to use the built-in Java implementation. + + You can specify an implementation as your XML + parser. The default value is: org.apache.xerces.parsers.SAXParser + Comma-delimited list of remote JMeter hosts (or host:port if required). + If you are running JMeter in a distributed environment, list the machines where + you have JMeter remote servers running. This will allow you to control those + servers from this machine's GUI + A list of components you do not want to see in + JMeter's menus. As JMeter has more and more components added, you may wish to + customize your JMeter to show only those components you are interested in. + You may list their classname or their class label (the string that appears + in JMeter's UI) here, and they will no longer appear in the menus. + + List of paths (separated by ;) that JMeter will search for JMeter plugin classes, + for example additional samplers. A path item can either be a jar file or a directory. + Any jar file in such a directory will be automatically included in search_paths, + jar files in sub directories are ignored. + The given value is in addition to any jars found in the lib/ext directory. + + + List of paths that JMeter will search for utility and plugin dependency classes. + Use your platform path separator to separate multiple paths. + A path item can either be a jar file or a directory. + Any jar file in such a directory will be automatically included in user.classpath, + jar files in sub directories are ignored. + The given value is in addition to any jars found in the lib directory. + All entries will be added to the class path of the system class loader + and also to the path of the JMeter internal loader. + + + List of paths (separated by ;) that JMeter will search for utility + and plugin dependency classes. + A path item can either be a jar file or a directory. + Any jar file in such a directory will be automatically included in plugin_dependency_paths, + jar files in sub directories are ignored. + The given value is in addition to any jars found in the lib directory + or given by the user.classpath property. + All entries will be added to the path of the JMeter internal loader only. + For plugin dependencies using plugin_dependency_paths should be preferred over + user.classpath. + + + Name of file containing additional JMeter properties. + These are added after the initial property file, but before the -q and -J options are processed. + + + Name of file containing additional system properties. + These are added before the -S and -D options are processed. + + +

    + The command line options and properties files are processed in the following order: +

      +
    1. -p propfile
    2. +
    3. jmeter.properties (or the file from the -p option) is then loaded
    4. +
    5. -j logfile
    6. +
    7. Logging is initialised
    8. +
    9. user.properties is loaded
    10. +
    11. system.properties is loaded
    12. +
    13. all other command-line options are processed
    14. +
    +

    +

    +See also the comments in the jmeter.properties, user.properties and system.properties files for further information on other settings you can change. +

    +
    + + +
    + diff --git a/xdocs/usermanual/glossary.xml b/xdocs/usermanual/glossary.xml new file mode 100644 index 00000000000..ec72ea13a82 --- /dev/null +++ b/xdocs/usermanual/glossary.xml @@ -0,0 +1,108 @@ + + + +]> + + + + User's Manual: Glossary + + + + +
    + +

    +Elapsed time. JMeter measures the elapsed time from just before sending the request to +just after the last response has been received. +JMeter does not include the time needed to render the response, nor does JMeter process any client code, for example +Javascript. +

    + +

    +Latency. JMeter measures the latency from just before sending the request to +just after the first response has been received. Thus the time +includes all the processing needed to assemble the request as well as +assembling the first part of the response, which in general will be longer than one +byte. +Protocol analysers (such as Wireshark) measure the time when bytes are actually sent/received over the interface. +The JMeter time should be closer to that which is experienced by a +browser or other application client. +

    + +

    +Connect Time. JMeter measures the time it took to establish the connection, including SSL handshake. Note that connect time is not automatically subtracted from latency. +

    + +

    +Median is a number which divides the samples into two equal halves. +Half of the samples are smaller than the median, and half are larger. +[Some samples may equal the median.] +This is a standard statistical measure. +See, for example: Median entry at Wikipedia. +The Median is the same as the 50th Percentile +

    + +

    +90% Line (90th Percentile) is the value below which 90% of the samples fall. +The remaining samples too at least as long as the value. +This is a standard statistical measure. +See, for example: Percentile entry at Wikipedia. +

    + +

    +Standard Deviation is a measure of the variability +of a data set. This is a standard statistical measure. +See, for example: Standard Deviation entry at Wikipedia. +JMeter calculates the population standard deviation (e.g. STDEVP function in spreadheets), not the sample standard deviation (e.g. STDEV). +

    + +

    +The Thread Name as it appears in Listeners and logfiles +is derived from the Thread Group name and the thread within the group.
    +The name has the format +groupName + " " + groupIndex + "-" + threadIndex +where: +

      +
    • groupName - name of the Thread Group element
    • +
    • groupIndex - number of the Thread Group in the Test Plan, starting from 1
    • +
    • threadIndex - number of the thread within the Thread Group, starting from 1
    • +
    +A test plan with two Thread Groups each with two threads would use the names: +
    +Thread Group 1-1
    +Thread Group 1-2
    +Thread Group 2-1
    +Thread Group 2-2
    +
    +

    + +

    +Throughput is calculated as requests/unit of time. +The time is calculated from the start of the first sample to the end of the last sample. +This includes any intervals between samples, as it is supposed to represent the load on the server.
    +The formula is: Throughput = (number of requests) / (total time). +

    + + +
    + + +
    diff --git a/xdocs/usermanual/hints_and_tips.xml b/xdocs/usermanual/hints_and_tips.xml new file mode 100644 index 00000000000..b93d248a0a8 --- /dev/null +++ b/xdocs/usermanual/hints_and_tips.xml @@ -0,0 +1,115 @@ + + + +]> + + + + User's Manual: Hints and Tips + + + + +
    +

    +This section is a collection of various hints and tips that have been suggested by various questions on the JMeter User list. +If you don't find what you are looking for here, please check the JMeter Wiki. +Also, try search the JMeter User list; someone may well have already provided a solution. +

    + +

    +JMeter variables have thread scope. This is deliberate, so that threads can act indepently. +However sometimes there is a need to pass variables between different threads, in the same or different Thread Groups. +

    +

    +One way to do this is to use a property instead. +Properties are shared between all JMeter threads, so if one thread sets a property, +another thread can read the updated value. +

    +

    +If there is a lot of information that needs to be passed between threads, then consider using a file. +For example you could use the Save Responses to a file +listener or perhaps a BeanShell PostProcessor in one thread, and read the file using the HTTP Sampler "file:" protocol, +and extract the information using a PostProcessor or BeanShell element. +

    +

    +If you can derive the data before starting the test, then it may well be better to store it in a file, +read it using CSV Dataset. +

    +
    + + +

    +Most test elements include debug logging. If running a test plan from the GUI, +select the test element and use the Help Menu to enable or disable logging. +The Help Menu also has an option to display the GUI and test element class names. +You can use these to determine the correct property setting to change the logging level. +

    + +

    +It is sometimes very useful to see Log messages to debug dynamic scripting languages like BeanShell or +groovy used in JMeter. +Since version 2.6, you can view log messages directly in JMeter GUI, to do so:

    +
      +
    • use menu Options > Log Viewer, a log console will appear at the bottom of the interface
    • +
    • Or click on the Warning icon in the upper right corner of GUI
    • +
    +By default this log console is disabled, you can enable it by changing in jmeter.properties: +jmeter.loggerpanel.display=true + +To avoid using too much memory, this components limits the number of characters used by this panel: + +jmeter.loggerpanel.maxlength=80000 +
    + + +

    +It is sometimes hard to find in a Test Plan tree and elements using a variable or containing a certain URL or parameter. +A new feature is now available since 2.6, you can access it in Menu Search. +It provides search with following options: +

    +
    +
    Case sensitive
    Makes search case sensitive
    +
    Regular exp.
    Is text to search a regexp, if so Regexp will be searched in Tree of components, example "\btest\b" +will match any component that contains test in searchable elements of the component
    +
    + +
    Figure 1 - Search raw text in TreeView
    +
    Figure 2 - Result in TreeView
    +
    Figure 3 - Search Regexp in TreeView (in this example we search whole word)
    +
    Figure 4 - Result in TreeView
    + +
    + + + +

    +You can change the size of icons in the toolbar using the property jmeter.toolbar.icons.size with these +values: 22x22 (default size), 32x32 or 48x48. +

    +
    +
    Icons with the size 22x22.
    +
    Icons with the size 32x32.
    +
    Icons with the size 48x48.
    +
    +
    + + +
    diff --git a/xdocs/usermanual/include_controller_tutorial.pdf b/xdocs/usermanual/include_controller_tutorial.pdf new file mode 100644 index 00000000000..9e2f30de2df Binary files /dev/null and b/xdocs/usermanual/include_controller_tutorial.pdf differ diff --git a/xdocs/usermanual/include_controller_tutorial.sxw b/xdocs/usermanual/include_controller_tutorial.sxw new file mode 100644 index 00000000000..d495c229aa6 Binary files /dev/null and b/xdocs/usermanual/include_controller_tutorial.sxw differ diff --git a/xdocs/usermanual/index.xml b/xdocs/usermanual/index.xml new file mode 100644 index 00000000000..51041162332 --- /dev/null +++ b/xdocs/usermanual/index.xml @@ -0,0 +1,206 @@ + + + + +User's Manual + + + +
    +

    Click on the section name to go straight to the section. + Click on the "+" to go to the relevant section of the detailed section list, + where you can select individual subsections.

    + + + + + + + + +
    + +
    + diff --git a/xdocs/usermanual/intro.xml b/xdocs/usermanual/intro.xml new file mode 100644 index 00000000000..3f7d9af35d2 --- /dev/null +++ b/xdocs/usermanual/intro.xml @@ -0,0 +1,70 @@ + + + + + + User's Manual: Introduction + + + + +
    +

    +Apache JMeter is a 100% pure Java application designed +to load test client/server software +(such as a web application). It may be used +to test performance both on static and dynamic +resources such as static files, Java Servlets, ASP.NET, PHP, CGI scripts, Java objects, databases, +FTP servers, and more. JMeter can be used to simulate a heavy +load on a server, network or object to test its strength or to analyze +overall performance under different load types.

    +

    +Additionally, JMeter can help you regression test your application by letting you create +test scripts with assertions to validate that your application is returning the +results you expect. For maximum flexibility, JMeter lets you create these assertions using +regular expressions.

    + +

    But please note that JMeter is not a browser, it works at protocol level.

    + +

    Stefano Mazzocchi of the Apache Software Foundation was the original developer of JMeter. +He wrote it primarily to test the performance of Apache JServ (a project that has +since been replaced by the Apache Tomcat project). We redesigned JMeter to enhance the GUI +and to add functional-testing capabilities. +

    + +

    JMeter became a Top Level Apache project in November 2011, which means it has a Project Management Commitee and a dedicated website.

    +
    + + +

    We hope to see JMeter's capabilities rapidly expand as developers take advantage of its +pluggable architecture.
    +The primary goal of further developments will be: +

      +
    • Addition of Websocket protocol
    • +
    • Addition of FTPS and SFTP protocols
    • +
    • Enhancements to Webservices protocols (SOAP Attachments)
    • +
    • Enhancements to JMS protocol implementation
    • +
    • ...
    • +
    + +

    +
    +
    + + +
    diff --git a/xdocs/usermanual/jmeter_accesslog_sampler_step_by_step.pdf b/xdocs/usermanual/jmeter_accesslog_sampler_step_by_step.pdf new file mode 100644 index 00000000000..2788c82c0d4 Binary files /dev/null and b/xdocs/usermanual/jmeter_accesslog_sampler_step_by_step.pdf differ diff --git a/xdocs/usermanual/jmeter_accesslog_sampler_step_by_step.sxw b/xdocs/usermanual/jmeter_accesslog_sampler_step_by_step.sxw new file mode 100644 index 00000000000..513af87088e Binary files /dev/null and b/xdocs/usermanual/jmeter_accesslog_sampler_step_by_step.sxw differ diff --git a/xdocs/usermanual/jmeter_distributed_testing_step_by_step.odt b/xdocs/usermanual/jmeter_distributed_testing_step_by_step.odt new file mode 100644 index 00000000000..9e75faf1877 Binary files /dev/null and b/xdocs/usermanual/jmeter_distributed_testing_step_by_step.odt differ diff --git a/xdocs/usermanual/jmeter_distributed_testing_step_by_step.pdf b/xdocs/usermanual/jmeter_distributed_testing_step_by_step.pdf new file mode 100644 index 00000000000..8671636d89e Binary files /dev/null and b/xdocs/usermanual/jmeter_distributed_testing_step_by_step.pdf differ diff --git a/xdocs/usermanual/jmeter_proxy_step_by_step.odt b/xdocs/usermanual/jmeter_proxy_step_by_step.odt new file mode 100644 index 00000000000..f8c38f70fbb Binary files /dev/null and b/xdocs/usermanual/jmeter_proxy_step_by_step.odt differ diff --git a/xdocs/usermanual/jmeter_proxy_step_by_step.pdf b/xdocs/usermanual/jmeter_proxy_step_by_step.pdf new file mode 100644 index 00000000000..6ac2cc2f323 Binary files /dev/null and b/xdocs/usermanual/jmeter_proxy_step_by_step.pdf differ diff --git a/xdocs/usermanual/junitsampler_tutorial.pdf b/xdocs/usermanual/junitsampler_tutorial.pdf new file mode 100644 index 00000000000..e0fb970229d Binary files /dev/null and b/xdocs/usermanual/junitsampler_tutorial.pdf differ diff --git a/xdocs/usermanual/junitsampler_tutorial.sxw b/xdocs/usermanual/junitsampler_tutorial.sxw new file mode 100644 index 00000000000..78619a323c3 Binary files /dev/null and b/xdocs/usermanual/junitsampler_tutorial.sxw differ diff --git a/xdocs/usermanual/ldapanswer_xml.xml b/xdocs/usermanual/ldapanswer_xml.xml new file mode 100644 index 00000000000..ee1924d96f6 --- /dev/null +++ b/xdocs/usermanual/ldapanswer_xml.xml @@ -0,0 +1,211 @@ + + + + + User's Manual: LDAP answer XML description + Dolf Smits + + + +
    +

    + The extended LDAP sampler was built to support testing for very complex testpurposes. + It was aimed at supporting the LDAP operations as close as possible. + As the results are not passed back in a user-readable form, I invented my own xml definition to + construct an answer in xml encoding, so the results may be parsed with regextracter or alike functions. +

    + + +

    + The global structure is as follows:
    + +

    + +

    +The operation section defines the operation as it is sent to the LDAP Server. The +following tags (with a short explanation) are used + +

    +
    + + +

    +As the response code, the official LDAP error definitions are used, so this section +contains the error message as returned from the server. +A succesfull request always returns "Success" as the responsmessage. +The following tag is used: +

    +
    + +

    +As the response code, the official LDAP error definitions are used, so this section +contains the error number as returned from the server. +A succesfull request always returns 0 (zero) as the responscode. +The following tag is used: + +

    +
    + +

    +The following tag is used: +

    + +
    +
    +
    + + +
    diff --git a/xdocs/usermanual/ldapops_tutor.xml b/xdocs/usermanual/ldapops_tutor.xml new file mode 100644 index 00000000000..dccd6511e64 --- /dev/null +++ b/xdocs/usermanual/ldapops_tutor.xml @@ -0,0 +1,114 @@ + + + + + JMeter - User's Manual: LDAP Operations + + + +
    +

    + The extended LDAP sampler was built to support testing for very complex testpurposes. + It was aimed at supporting the LDAP operations as close as possible. + In this short tutorial, I will explain which LDAP operations exist and what they do. + Per operation, I will shortly explain how these operations are implemented.
    + LDAP servers are some kind of hierarchical database, they store objects (entries) in a tree. The uppermost part of a tree is called the ROOT of the tree.
    + eg. When a tree starts with dc=com, the root equals dc=com.
    + The next level can exist under the root, eg dc=test. The full name of this object (the "distinghuised name") is "dc=test,dc=com.
    + Again, a following level can be made, by adding the user "cn=admin" under dc=test,dc=com. This object has a DN (distinguished name) of "cn=admin,dc=test,dc=com".
    + The relative distinguished name (RDN) is the last part of the DN, eg. cn=admin.
    + The characteristics of an object are determined by the objectClasses, which can be seen as a collection of attributes.
    + The type of an object is determined by the "structural objectClass" eg person, organizationalUnit or country.
    + The attributes contain the data of an object, eg mailadress, name, streetadress etc. Each attribute can have 0, 1 or more values. +

    + +

    + Any contact with an LDAP server MUST start with a bind request. LDAP is a state dependent protocol. Without opening a session to + a LDAP server, no additional request can be made. + Due to some peculiarities in the JAVA libraries, 2 different bind operations are implemented. +

    + +

    + This bind is meant to open a session to a LDAP server. Any testplan should use this operation as the starting point from a session. + For each Thread (each virtual user) a seperate connection with the LDAP server is build, and so a seperate Thread bind is performed. +

    +
    + +

    + This bind is used for user authentication verification. + A proper developed LDAP client, who needs an authenticated user, perform a bind with a given distinguished name and password. + This Single bind/unbind operation is for this purpose. It builds it own seperate connection to the LDAP server, performs a + bind operation, and ends the connection again (by sending an unbind). +

    +
    +
    + +

    + To close a connection to a LDAP server, an unbind operation is needed. + As the Single bind/unbind operation already (implicitly) performs an unbind, only a Thread unbind operation is needed. + This Thread unbind just closes the connection and cleans up any resources it has used. +

    +
    + +

    + The compare operation needs the full distinguished name from a LDAP object, as well as a attribute and a value for the attribute. + It will simply check: "Has this object really this attribute with this value?". + Typical use is checking the membership of a certain user with a given group. +

    +
    + +

    + The search test simply searches for all objects which comply with a given search filter, eg. + all persons with a "employeeType=inactive" or "all persons with a userID equals user1" +

    +
    + +

    + This simply add an object to the LDAP directory. + Off course the combination of attributes and distinguishedName must be valid! +

    +
    + +

    + This operation modifies one or more attributes from a given object. + It needs the distinghised name from the object, as well as the attributes and the new values for this attribute.
    + Three versions are available, add, for adding an attribute value
    + replace, for overwriting the old attribute value with a new value
    + delete, to delete a value form an attribute, or to delete all the values of an attribute
    +

    +
    + +

    + This operation deletes an object from the LDAP server. + It needs the distinghised name from the object. +

    +
    + +

    + This operation modifies the distinguished name from an object (it "moves" the object).
    + It comes in two flavours, just renaming an entry, then you specify a new RDN (relative distinguished name, this is the lowest part of the DN)
    + eg, you can rename "cn=admin,dc=test,dc=com" to cn=administrator,dc=test,dc=com"
    + The second flavour is renaming (moving) a complete subtree by specifying a "new superior"
    + eg you can move a complete subtree "ou=retired,ou=people,dc=test,dc=com" to a new subtree "ou=retired people,dc=test,dc=com" by specifying + a new rdn "ou=retired people" and a new superior of "dc=test,dc=com" +

    +
    +
    + + +
    diff --git a/xdocs/usermanual/listeners.xml b/xdocs/usermanual/listeners.xml new file mode 100644 index 00000000000..e56af7deb0a --- /dev/null +++ b/xdocs/usermanual/listeners.xml @@ -0,0 +1,498 @@ + + + + +]> + + + + + User's Manual: Listeners + + + + +
    +

    A listener is a component that shows the results of the +samples. The results can be shown in a tree, tables, graphs or simply written to a log +file. To view the contents of a response from any given sampler, add either of the Listeners "View +Results Tree" or "View Results in table" to a test plan. To view the response time graphically, add +graph results, spline results or distribution graph. +The Listeners +section of the components page has full descriptions of all the listeners.

    + + +Different listeners display the response information in different ways. +However, they all write the same raw data to the output file - if one is specified. + +

    +The "Configure" button can be used to specify which fields to write to the file, and whether to +write it as CSV or XML. +CSV files are much smaller than XML files, so use CSV if you are generating lots of samples. +

    +

    +The file name can be specified using either a relative or an absolute path name. +Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). +Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). +If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), +then the path is assumed to be relative to the JMX file location. +

    +

    +If you only wish to record certain samples, add the Listener as a child of the sampler. +Or you can use a Simple Controller to group a set of samplers, and add the Listener to that. +The same filename can be used by multiple samplers - but make sure they all use the same configuration! +

    +
    + +
    +

    +The default items to be saved can be defined in the jmeter.properties (or user.properties) file. +The properties are used as the initial settings for the Listener Config pop-up, and are also +used for the log file specified by the -l command-line flag (commonly used for non-GUI test runs). +

    +

    To change the default format, find the following line in jmeter.properties:

    +

    jmeter.save.saveservice.output_format=

    +

    +The information to be saved is configurable. For maximum information, choose "xml" as the format and specify "Functional Test Mode" on the Test Plan element. If this box is not checked, the default saved +data includes a time stamp (the number of milliseconds since midnight, +January 1, 1970 UTC), the data type, the thread name, the label, the +response time, message, and code, and a success indicator. If checked, all information, including the full response data will be logged.

    +

    +The following example indicates how to set +properties to get a vertical bar ("|") delimited format that will +output results like:.

    +

    + +

    +timeStamp|time|label|responseCode|threadName|dataType|success|failureMessage
    +02/06/03 08:21:42|1187|Home|200|Thread Group-1|text|true|
    +02/06/03 08:21:42|47|Login|200|Thread Group-1|text|false|Test Failed: 
    +    expected to contain: password etc.
    +
    +

    +

    +The corresponding jmeter.properties that need to be set are shown below. One oddity +in this example is that the output_format is set to csv, which +typically +indicates comma-separated values. However, the default_delimiter was +set to be a vertical bar instead of a comma, so the csv tag is a +misnomer in this case. (Think of CSV as meaning character separated values)

    +

    + +

    +jmeter.save.saveservice.output_format=csv
    +jmeter.save.saveservice.assertion_results_failure_message=true
    +jmeter.save.saveservice.default_delimiter=|
    +
    + +

    +The full set of properties that affect result file output is shown below. +

    + +
    +#---------------------------------------------------------------------------
    +# Results file configuration
    +#---------------------------------------------------------------------------
    +
    +# This section helps determine how result data will be saved.
    +# The commented out values are the defaults.
    +
    +# legitimate values: xml, csv, db.  Only xml and csv are currently supported.
    +#jmeter.save.saveservice.output_format=csv
    +
    +
    +# true when field should be saved; false otherwise
    +
    +# assertion_results_failure_message only affects CSV output
    +#jmeter.save.saveservice.assertion_results_failure_message=false
    +#
    +#jmeter.save.saveservice.data_type=true
    +#jmeter.save.saveservice.label=true
    +#jmeter.save.saveservice.response_code=true
    +# response_data is not currently supported for CSV output
    +#jmeter.save.saveservice.response_data=false
    +# Save ResponseData for failed samples
    +#jmeter.save.saveservice.response_data.on_error=false
    +#jmeter.save.saveservice.response_message=true
    +#jmeter.save.saveservice.successful=true
    +#jmeter.save.saveservice.thread_name=true
    +#jmeter.save.saveservice.time=true
    +#jmeter.save.saveservice.subresults=true
    +#jmeter.save.saveservice.assertions=true
    +#jmeter.save.saveservice.latency=true
    +#jmeter.save.saveservice.connect_time=false
    +#jmeter.save.saveservice.samplerData=false
    +#jmeter.save.saveservice.responseHeaders=false
    +#jmeter.save.saveservice.requestHeaders=false
    +#jmeter.save.saveservice.encoding=false
    +#jmeter.save.saveservice.bytes=true
    +#jmeter.save.saveservice.url=false
    +#jmeter.save.saveservice.filename=false
    +#jmeter.save.saveservice.hostname=false
    +#jmeter.save.saveservice.thread_counts=true
    +#jmeter.save.saveservice.sample_count=false
    +#jmeter.save.saveservice.idle_time=false
    +
    +# Timestamp format
    +# legitimate values: none, ms, or a format suitable for SimpleDateFormat
    +#jmeter.save.saveservice.timestamp_format=ms
    +#jmeter.save.saveservice.timestamp_format=yyyy/MM/dd HH:mm:ss.SSS
    +
    +# Put the start time stamp in logs instead of the end
    +sampleresult.timestamp.start=true
    +
    +# Whether to use System.nanoTime() - otherwise only use System.currentTimeMillis()
    +#sampleresult.useNanoTime=true
    +
    +# Use a background thread to calculate the nanoTime offset
    +# Set this to <= 0 to disable the background thread
    +#sampleresult.nanoThreadSleep=5000
    +
    +# legitimate values: none, first, all
    +#jmeter.save.saveservice.assertion_results=none
    +
    +# For use with Comma-separated value (CSV) files or other formats
    +# where the fields' values are separated by specified delimiters.
    +# Default:
    +#jmeter.save.saveservice.default_delimiter=,
    +# For TAB, since JMeter 2.3 one can use:
    +#jmeter.save.saveservice.default_delimiter=\t
    +
    +#jmeter.save.saveservice.print_field_names=false
    +
    +# Optional list of JMeter variable names whose values are to be saved in the result data files.
    +# Use commas to separate the names. For example:
    +#sample_variables=SESSION_ID,REFERENCE
    +# N.B. The current implementation saves the values in XML as attributes,
    +# so the names must be valid XML names.
    +# Versions of JMeter after 2.3.2 send the variable to all servers
    +# to ensure that the correct data is available at the client.
    +
    +# Optional xml processing instruction for line 2 of the file:
    +#jmeter.save.saveservice.xml_pi=&lt;?xml-stylesheet type="text/xsl" href="sample.xsl"?>
    +
    +# Prefix used to identify filenames that are relative to the current base
    +#jmeter.save.saveservice.base_prefix=~/
    +
    +

    +

    +The date format to be used for the timestamp_format is described in +SimpleDateFormat. +The timestamp format is used for both writing and reading files. +If the format is set to "ms", and the column does not parse as a long integer, +JMeter (2.9+) will try the following formats: +

      +
    • yyyy/MM/dd HH:mm:ss.SSS
    • +
    • yyyy/MM/dd HH:mm:ss
    • +
    • yyyy-MM-dd HH:mm:ss.SSS
    • +
    • yyyy-MM-dd HH:mm:ss
    • +
    • MM/dd/yy HH:mm:ss (this is for compatibility with previous versions; it is not recommended as a format)
    • +
    +Matching is now also strict (non-lenient). +JMeter 2.8 and earlier used lenient mode which could result in timestamps with incorrect dates +(times were usually correct).

    + +

    +JMeter supports the sample_variables +property to define a list of additional JMeter variables which are to be saved with +each sample in the JTL files. The values are written to CSV files as additional columns, +and as additional attributes in XML files. See above for an example. +

    +
    + + +

    +Listeners can be configured to save different items to the result log files (JTL) by using the Config popup as shown below. +The defaults are defined as described in the Listener Default Configuration section above. +Items with (CSV) after the name only apply to the CSV format; items with (XML) only apply to XML format. +CSV format cannot currently be used to save any items that include line-breaks. +

    +

    Configuration dialogue
    +
    +

    +Note that cookies, method and the query string are saved as part of the "Sampler Data" option. +

    +
    + +
    +

    +When running in non-GUI mode, the -l flag can be used to create a top-level listener for the test run. +This is in addition to any Listeners defined in the test plan. +The configuration of this listener is controlled by entries in the file jmeter.properties +as described in the previous section. +

    +

    +This feature can be used to specify different data and log files for each test run, for example: +

    +jmeter -n -t testplan.jmx -l testplan_01.jtl -j testplan_01.log
    +jmeter -n -t testplan.jmx -l testplan_02.jtl -j testplan_02.log
    +
    +

    +

    +Note that JMeter logging messages are written to the file jmeter.log by default. +This file is recreated each time, so if you want to keep the log files for each run, +you will need to rename it using the -j option as above. The -j option was added in version 2.3. +

    +

    Versions of JMeter after 2.3.1 support variables in the log file name. +If the filename contains paired single-quotes, then the name is processed +as a SimpleDateFormat format applied to the current date, for example: +log_file='jmeter_'yyyyMMddHHmmss'.tmp'. +This can be used to generate a unique name for each test run. +

    +
    + +
    +

    Listeners can use a lot of memory if there are a lot of samples. +Most of the listeners currently keep a copy of every sample they display, apart from: +

    +
      +
    • Simple Data Writer
    • +
    • BeanShell/BSF Listener
    • +
    • Mailer Visualizer
    • +
    • Monitor Results
    • +
    • Summary Report
    • +
    +

    +The following Listeners no longer need to keep copies of every single sample. +Instead, samples with the same elapsed time are aggregated. +Less memory is now needed, especially if most samples only take a second or two at most. +

    +
      +
    • Aggregate Report
    • +
    • Aggregate Graph
    • +
    • Distribution Graph
    • +
    +

    To minimise the amount of memory needed, use the Simple Data Writer, and use the CSV format.

    +
    + +
    +

    +The CSV log format depends on which data items are selected in the configuration. +Only the specified data items are recorded in the file. +The order of appearance of columns is fixed, and is as follows: +

    +
      +
    • timeStamp - in milliseconds since 1/1/1970
    • +
    • elapsed - in milliseconds
    • +
    • label - sampler label
    • +
    • responseCode - e.g. 200, 404
    • +
    • responseMessage - e.g. OK
    • +
    • threadName
    • +
    • dataType - e.g. text
    • +
    • success - true or false
    • +
    • failureMessage - if any
    • +
    • bytes - number of bytes in the sample
    • +
    • grpThreads - number of active threads in this thread group
    • +
    • allThreads - total number of active threads in all groups
    • +
    • URL
    • +
    • Filename - if Save Response to File was used
    • +
    • latency - time to first response
    • +
    • connect - time to establish connection
    • +
    • encoding
    • +
    • SampleCount - number of samples (1, unless multiple samples are aggregated)
    • +
    • ErrorCount - number of errors (0 or 1, unless multiple samples are aggregated)
    • +
    • Hostname where the sample was generated
    • +
    • IdleTime - number of milliseconds of 'Idle' time (normally 0)
    • +
    • Variables, if specified
    • +
    + +
    + +
    +

    +The format of the updated XML (2.1) is as follows (line breaks will be different): +

    +
    +&lt;?xml version="1.0" encoding="UTF-8"?>
    +&lt;testResults version="1.2">
    +
    +-- HTTP Sample, with nested samples 
    +
    +&lt;httpSample t="1392" lt="351" ts="1144371014619" s="true" 
    +     lb="HTTP Request" rc="200" rm="OK" 
    +     tn="Listen 1-1" dt="text" de="iso-8859-1" by="12407">
    +  &lt;httpSample t="170" lt="170" ts="1144371015471" s="true" 
    +        lb="http://www.apache.org/style/style.css" rc="200" rm="OK" 
    +        tn="Listen 1-1" dt="text" de="ISO-8859-1" by="1002">
    +    &lt;responseHeader class="java.lang.String">HTTP/1.1 200 OK
    +Date: Fri, 07 Apr 2006 00:50:14 GMT
    +...
    +Content-Type: text/css
    +&lt;/responseHeader>
    +    &lt;requestHeader class="java.lang.String">MyHeader: MyValue&lt;/requestHeader>
    +    &lt;responseData class="java.lang.String">body, td, th {
    +    font-size: 95%;
    +    font-family: Arial, Geneva, Helvetica, sans-serif;
    +    color: black;
    +    background-color: white;
    +}
    +...
    +&lt;/responseData>
    +    &lt;cookies class="java.lang.String">&lt;/cookies>
    +    &lt;method class="java.lang.String">GET&lt;/method>
    +    &lt;queryString class="java.lang.String">&lt;/queryString>
    +    &lt;url>http://www.apache.org/style/style.css&lt;/url>
    +  &lt;/httpSample>
    +  &lt;httpSample t="200" lt="180" ts="1144371015641" s="true" 
    +     lb="http://www.apache.org/images/asf_logo_wide.gif" 
    +     rc="200" rm="OK" tn="Listen 1-1" dt="bin" de="ISO-8859-1" by="5866">
    +    &lt;responseHeader class="java.lang.String">HTTP/1.1 200 OK
    +Date: Fri, 07 Apr 2006 00:50:14 GMT
    +...
    +Content-Type: image/gif
    +&lt;/responseHeader>
    +    &lt;requestHeader class="java.lang.String">MyHeader: MyValue&lt;/requestHeader>
    +    &lt;responseData class="java.lang.String">http://www.apache.org/asf.gif&lt;/responseData>
    +      &lt;responseFile class="java.lang.String">Mixed1.html&lt;/responseFile>
    +    &lt;cookies class="java.lang.String">&lt;/cookies>
    +    &lt;method class="java.lang.String">GET&lt;/method>
    +    &lt;queryString class="java.lang.String">&lt;/queryString>
    +    &lt;url>http://www.apache.org/asf.gif&lt;/url>
    +  &lt;/httpSample>
    +  &lt;responseHeader class="java.lang.String">HTTP/1.1 200 OK
    +Date: Fri, 07 Apr 2006 00:50:13 GMT
    +...
    +Content-Type: text/html; charset=ISO-8859-1
    +&lt;/responseHeader>
    +  &lt;requestHeader class="java.lang.String">MyHeader: MyValue&lt;/requestHeader>
    +  &lt;responseData class="java.lang.String"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    +               "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    +...
    +&amp;lt;html&amp;gt;
    + &amp;lt;head&amp;gt;
    +...
    + &amp;lt;/head&amp;gt;
    + &amp;lt;body&amp;gt;        
    +...
    + &amp;lt;/body&amp;gt;
    +&amp;lt;/html&amp;gt;
    +&lt;/responseData>
    +  &lt;cookies class="java.lang.String">&lt;/cookies>
    +  &lt;method class="java.lang.String">GET&lt;/method>
    +  &lt;queryString class="java.lang.String">&lt;/queryString>
    +  &lt;url>http://www.apache.org/&lt;/url>
    +&lt;/httpSample>
    +
    +-- nonHTTPP Sample
    +
    +&lt;sample t="0" lt="0" ts="1144372616082" s="true" lb="Example Sampler"
    +    rc="200" rm="OK" tn="Listen 1-1" dt="text" de="ISO-8859-1" by="10">
    +  &lt;responseHeader class="java.lang.String">&lt;/responseHeader>
    +  &lt;requestHeader class="java.lang.String">&lt;/requestHeader>
    +  &lt;responseData class="java.lang.String">Listen 1-1&lt;/responseData>
    +  &lt;responseFile class="java.lang.String">Mixed2.unknown&lt;/responseFile>
    +  &lt;samplerData class="java.lang.String">ssssss&lt;/samplerData>
    +&lt;/sample>
    +
    +&lt;/testResults>
    +
    +

    +Note that the sample node name may be either "sample" or "httpSample". +

    +
    + +
    +

    +The format of the JTL files is identical for 2.2 and 2.1. Format 2.2 only affects JMX files. +

    +
    + +
    +

    +The sample attributes have the following meaning: +

    + + + + + + + + + + + + + + + + + + + + + +
    AttributeContent
    byBytes
    deData encoding
    dtData type
    ecError count (0 or 1, unless multiple samples are aggregated)
    hnHostname where the sample was generated
    itIdle Time = time not spent sampling (milliseconds) (generally 0)
    lbLabel
    ltLatency = time to initial response (milliseconds) - not all samplers support this
    ctConnect Time = time to establish the connection (milliseconds) - not all samplers support this
    naNumber of active threads for all thread groups
    ngNumber of active threads in this group
    rcResponse Code (e.g. 200)
    rmResponse Message (e.g. OK)
    sSuccess flag (true/false)
    scSample count (1, unless multiple samples are aggregated)
    tElapsed time (milliseconds)
    tnThread Name
    tstimeStamp (milliseconds since midnight Jan 1, 1970 UTC)
    varnameValue of the named variable (versions of JMeter after 2.3.1)
    +

    +Versions 2.1 and 2.1.1 of JMeter saved the Response Code as "rs", but read it back expecting to find "rc". +This has been corrected so that it is always saved as "rc"; either "rc" or "rs" can be read. +

    + +Versions of JMeter after 2.3.1 allow additional variables to be saved with the test plan. +Currently, the variables are saved as additional attributes. +The testplan variable name is used as the attribute name. +See Sample variables (above) for more information. + +
    + +
    +

    +As shown above, the response data can be saved in the XML log file if required. +However, this can make the file rather large, and the text has to be encoded so +that it is still valid XML. Also, images cannot be included. +
    +Another solution is to use the Post-Processor Save Responses to a file. +This generates a new file for each sample, and saves the file name with the sample. +The file name can then be included in the sample log output. +The data will be retrieved from the file if necessary when the sample log file is reloaded. +

    +
    +
    +

    To view an existing results file, you can use the File "Browse..." button to select a file. +If necessary, just create a dummy testplan with the appropriate Listener in it. +

    +

    Results can be read from XML or CSV format files. +When reading from CSV results files, the header (if present) is used to determine which fields were saved. +In order to interpret a header-less CSV file correctly, the appropriate JMeter properties must be set. +

    + +Versions of JMeter up to 2.3.2 used to clear any current data before loading the new file. +This is no longer done, thus allowing files to be merged. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. + +
    +
    +

    JMeter is capable of saving any listener as a PNG file. To do so, select the +listener in the left panel. Click Edit > Save As Image. A file dialog will +appear. Enter the desired name and save the listener. +

    +

    +The Listeners which generate output as tables can also be saved using Copy/Paste. +Select the desired cells in the table, and use the OS Copy short-cut (normally Control+C). +The data will be saved to the clipboard, from where it can be pasted into another application, +e.g. a spreadsheet or text editor. +

    +
    Figure 1 - Edit > Save As Image
    + +
    + +
    diff --git a/xdocs/usermanual/realtime-results.xml b/xdocs/usermanual/realtime-results.xml new file mode 100644 index 00000000000..f1a66eadc1a --- /dev/null +++ b/xdocs/usermanual/realtime-results.xml @@ -0,0 +1,180 @@ + + + + +]> + + + + + Philippe Mouawad + User's Manual: Live Statistics + + + + +
    +

    Since JMeter 2.13 you can get realtime results sent to a backend through the +Backend Listener using potentially any backend (JDBC, JMS, Webservice...) implementing AbstractBackendListenerClient.
    +JMeter ships with a GraphiteBackendListenerClient which allows you to send metrics to a Graphite Backend.
    +This feature provides: +

      +
    • Live results
    • +
    • Nice graphs for metrics
    • +
    • Ability to compare 2 or more load tests
    • +
    • Storing monitoring data as long as JMeter results in the same backend
    • +
    • ...
    • +
    +In this document we will present the configuration setup to graph and historize the data in 2 different backends: +
      +
    • InfluxDB
    • +
    • Graphite
    • +
    +

    + + +

    + Threads metrics are the following: +

    +
    + <rootMetricsPrefix>.test.minAT
    Min active threads
    + <rootMetricsPrefix>.test.maxAT
    Max active threads
    + <rootMetricsPrefix>.test.meanAT
    Mean active threads
    + <rootMetricsPrefix>.test.startedT
    Started threads
    + <rootMetricsPrefix>.test.endedT
    Finished threads
    +
    +
    + +

    Response times metrics are the following:

    +
    + <rootMetricsPrefix>.<samplerName>.ok.count +
    Number of successful responses for sampler name
    + <rootMetricsPrefix>.<samplerName>.ok.min +
    Min response time for successful responses of sampler name
    + <rootMetricsPrefix>.<samplerName>.ok.max +
    Max response time for successful responses of sampler name
    + <rootMetricsPrefix>.<samplerName>.ok.pct<percentileValue> +
    Percentile computed for successful responses of sampler name. You can input as many percentiles as you want (3 or 4 being a reasonable value).
    + When percentile contains a comma for example "99.9", dot is sanitized by "_" leading to 99_9. + By default listener computes percentiles 90%, 95% and 99%
    + <rootMetricsPrefix>.<samplerName>.ko.count +
    Number of failed responses for sampler name
    + <rootMetricsPrefix>.<samplerName>.ko.min +
    Min response time for failed responses of sampler name
    + <rootMetricsPrefix>.<samplerName>.ko.max +
    Max response time for failed responses of sampler name
    + <rootMetricsPrefix>.<samplerName>.ko.pct<percentileValue> +
    Percentile computed for failed responses of sampler name. You can input as many percentiles as you want (3 or 4 being a reasonable value).
    + When percentile contains a comma for example "99.9", dot is sanitized by "_" leading to 99_9. + By default listener computes percentiles 90%, 95% and 99%
    + <rootMetricsPrefix>.<samplerName>.a.count +
    Number of responses for sampler name
    + <rootMetricsPrefix>.<samplerName>.a.min +
    Min response time for responses of sampler name
    + <rootMetricsPrefix>.<samplerName>.a.max +
    Max response time for responses of sampler name
    + <rootMetricsPrefix>.<samplerName>.a.pct<percentileValue> +
    Percentile computed for responses of sampler name. You can input as many percentiles as you want (3 or 4 being a reasonable value).
    + When percentile contains a comma for example "99.9", dot is sanitized by "_" leading to 99_9. + By default listener computes percentiles 90%, 95% and 99%
    +
    +

    + By default JMeter sends only metrics for all samplers using "all" as samplerName. +

    +
    +
    + +

    + To make JMeter send metrics to backend add a BackendListener using the GraphiteBackendListenerClient. +

    +
    Graphite configuration
    +
    + + +

    InfluxDB is an open-source, distributed,time-series database that allows to +easily store metrics. +Installation and configuration is very easy, read this for more details InfluxDB documentation.
    +InfluxDB data can be easily viewed in a browser through either Influga or Grafana. +We will use Grafana in this case. +

    + +

    To enable Graphite listener in InfluxDB, edit files /opt/influxdb/shared/config.toml or /usr/local/etc/influxdb.conf, + find "input_plugins.graphite" and set this: +

    + +# Configure the graphite api +[input_plugins.graphite] +enabled = true +address = "0.0.0.0" # If not set, is actually set to bind-address. +port = 2003 +database = "jmeter" # store graphite data in this database +# udp_enabled = true # enable udp interface on the same port as the tcp interface + +
    + +

    Connect to InfluxDB admin console and create two databases: +

      +
    • grafana : Used by Grafana to store the dashboards we will create
    • +
    • jmeter : Used by InfluxDB to store the data sent to Graphite Listener as per database="jmeter" config + element in influxdb.conf or config.toml
    • +
    +

    +
    + +

    + Installing grafana is just a matter of putting the unzipped bundle behind an Apache HTTP server.
    + Read documentation for more details. + Open config.js file and find datasources element, and edit it like this:
    +

    + +datasources: { + influxdb: { + type: 'influxdb', + url: "http://localhost:8086/db/jmeter", + username: 'root', + password: 'root', + }, + grafana: { + type: 'influxdb', + url: "http://localhost:8086/db/grafana", + username: 'root', + password: 'root', + grafanaDB: true + }, +}, + + + Note that grafana has "grafanaDB:true". Also note that here we use root user for simplicity + It is better to dedicate a special user with less rights. + + Here is the kind of dashboard that you could obtain: +
    Grafana dashboard
    + +
    +
    + + +

    TODO.

    +
    + + +
    + + +
    diff --git a/xdocs/usermanual/regular_expressions.xml b/xdocs/usermanual/regular_expressions.xml new file mode 100644 index 00000000000..0ed609d7cd5 --- /dev/null +++ b/xdocs/usermanual/regular_expressions.xml @@ -0,0 +1,255 @@ + + + +]> + + + + User's Manual: Regular Expressions + + + + +
    + +

    +JMeter includes the pattern matching software Apache Jakarta ORO +
    +There is some documentation for this on the Jakarta web-site, for example + +a summary of the pattern matching characters +

    +

    +There is also documentation on an older incarnation of the product at +OROMatcher User's guide, which might prove useful. +

    +

    +The pattern matching is very similar to the pattern matching in Perl. +A full installation of Perl will include plenty of documentation on regular expressions - look for perlrequick, +perlretut, perlre and perlreref. +

    +

    +It is worth stressing the difference between "contains" and "matches", as used on the Response Assertion test element: +

    +
    +
    "contains"
    means that the regular expression matched at least some part of the target, +so 'alphabet' "contains" 'ph.b.' because the regular expression matches the substring 'phabe'. +
    +
    +"matches"
    means that the regular expression matched the whole target. +So 'alphabet' is "matched" by 'al.*t'. +
    +
    +

    In this case, it is equivalent to wrapping the regular expression in ^ and $, viz '^al.*t$'. +

    +

    However, this is not always the case. +For example, the regular expression 'alp|.lp.*' is "contained" in 'alphabet', +but does not "match" 'alphabet'. +

    +

    Why? Because when the pattern matcher finds the sequence 'alp' in 'alphabet', it stops trying any other +combinations - and 'alp' is not the same as 'alphabet', as it does not include 'habet'. +

    + +Unlike Perl, there is no need to (i.e. do not) enclose the regular expression in //. + +

    +So how does one use the modifiers ismx etc if there is no trailing /? +The solution is to use extended regular expressions, i.e. /abc/i becomes (?i)abc. +See also Placement of modifiers below. +

    +
    + +

    Extract single string

    +

    +Suppose you want to match the following portion of a web-page: +
    +name="file" value="readme.txt"> +
    +and you want to extract readme.txt. +
    +A suitable regular expression would be: +
    +name="file" value="(.+?)"> +

    +The special characters above are: +

    +
    +
    ( and )
    these enclose the portion of the match string to be returned
    +
    .
    match any character
    +
    +
    one or more times
    +
    ?
    don't be greedy, i.e. stop when first match succeeds
    +
    +

    +Note: without the ?, the .+ would continue past the first "> +until it found the last possible "> - which is probably not what was intended. +

    +

    +Note: although the above expression works, it's more efficient to use the following expression: +
    +name="file" value="([^"]+)"> +where

    +[^"] - means match anything except "

    +In this case, the matching engine can stop looking as soon as it sees the first ", +whereas in the previous case the engine has to check that it has found "> rather than say " >. +

    +

    Extract multiple strings

    +

    +Suppose you want to match the following portion of a web-page:
    +name="file.name" value="readme.txt" +and you want to extract both file.name and readme.txt. +
    +A suitable reqular expression would be: +
    +name="([^"]+)" value="([^"]+)" +
    +This would create 2 groups, which could be used in the JMeter Regular Expression Extractor template as $1$ and $2$. +

    +

    +The JMeter Regex Extractor saves the values of the groups in additional variables. +

    +

    +For example, assume: +

    +
      +
    • Reference Name: MYREF
    • +
    • Regex: name="(.+?)" value="(.+?)"
    • +
    • Template: $1$$2$
    • +
    +Do not enclose the regular expression in / / +

    +The following variables would be set: +

    +
    +
    MYREF
    file.namereadme.txt
    +
    MYREF_g0
    name="file.name" value="readme.txt"
    +
    MYREF_g1
    file.name
    +
    MYREF_g2
    readme.txt
    +
    +These variables can be referred to later on in the JMeter test plan, as ${MYREF}, ${MYREF_g1} etc. +

    +
    + +

    The pattern matching behaves in various slightly different ways, +depending on the setting of the multi-line and single-line modifiers. +Note that the single-line and multi-line operators have nothing to do with each other; +they can be specified independently. +

    +

    Single-line mode

    +

    +Single-line mode only affects how the '.' meta-character is interpreted. +

    +

    +Default behaviour is that '.' matches any character except newline. +In single-line mode, '.' also matches newline. +

    + +

    Multi-line mode

    +

    +Multi-line mode only affects how the meta-characters '^' and '$' are interpreted. +

    +

    +Default behaviour is that '^' and '$' only match at the very beginning and end of the string. +When Multi-line mode is used, the '^' metacharacter matches at the beginning of every line, +and the '$' metacharacter matches at the end of every line.

    + +
    + + +

    +Regular expressions use certain characters as meta characters - these characters have a special meaning to the RE engine. +Such characters must be escaped by preceeding them with \ (backslash) in order to treat them as ordinary characters. +Here is a list of the meta characters and their meaning (please check the ORO documentation if in doubt). +

    +
    +
    ( and )
    grouping
    +
    [ and ]
    character classes
    +
    { and }
    repetition
    +
    *, + and ?
    repetition
    +
    .
    wild-card character
    +
    \
    escape character
    +
    |
    alternatives
    +
    ^ and $
    start and end of string or line
    +
    + +Please note that ORO does not support the \Q and \E meta-characters. +[In other RE engines, these can be used to quote a portion of an RE so that the meta-characters stand for themselves.] +You can use function to do the equivalent, see ${__escapeOroRegexpChars(valueToEscape)}. + +

    +The following Perl5 extended regular expressions are supported by ORO. + +

    +
    (?#text)
    +
    An embedded comment causing text to be ignored.
    +
    (?:regexp)
    +
    Groups things like "()" but doesn't cause the group match to be saved.
    +
    (?=regexp)
    +
    A zero-width positive lookahead assertion. For example, \w+(?=\s) matches a word followed by whitespace, without including whitespace in the MatchResult.
    +
    (?!regexp)
    +
    A zero-width negative lookahead assertion. For example foo(?!bar) matches any occurrence of "foo" that +isn't followed by "bar". Remember that this is a zero-width assertion, which means that a(?!b)d will +match ad because a is followed by a character that is not b (the d) and a d +follows the zero-width assertion.
    +
    (?imsx)
    +
    One or more embedded pattern-match modifiers. i enables case insensitivity, m enables multiline treatment +of the input, s enables single line treatment of the input, and x enables extended whitespace comments.
    +
    +Note that (?<=regexp) - lookbehind - is not supported. +

    + +
    + + +

    +Modifiers can be placed anywhere in the regex, and apply from that point onwards. +[A bug in ORO means that they cannot be used at the very end of the regex. +However they would have no effect there anyway.] +

    +

    +The single-line (?s) and multi-line (?m) modifiers are normally placed at the start of the regex. +

    +

    +The ignore-case modifier (?i) may be usefully applied to just part of a regex, +for example:

    + +Match ExAct case or (?i)ArBiTrARY(?-i) case + +would match Match ExAct case or arbitrary case as well as Match ExAct case or ARBitrary case, but not Match exact case or ArBiTrARY case. +
    +
    + +
    +

    +Since JMeter 2.4, the listener View Results Tree +include a RegExp Tester to test regular expressions directly on sampler response data. +

    +

    +There is a Website to test Java Regular expressions. +

    +

    +Another approach is to use a simple test plan to test the regular expressions. +The Java Request sampler can be used to generate a sample, or the HTTP Sampler can be used to load a file. +Add a Debug Sampler and a Tree View Listener and changes to the regular expression can be tested quickly, +without needing to access any external servers. +

    +
    + + +
    diff --git a/xdocs/usermanual/remote-test.xml b/xdocs/usermanual/remote-test.xml new file mode 100644 index 00000000000..f603e0ff65d --- /dev/null +++ b/xdocs/usermanual/remote-test.xml @@ -0,0 +1,329 @@ + + + + +]> + + + + + User's Manual: Remote (Distributed) Testing + + + + +
    + +

    In the event that your JMeter client machine is unable, performance-wise, to simulate +enough users to stress your server or is limited at network level, an option exists to control multiple, remote JMeter +engines from a single JMeter client. By running JMeter remotely, you can replicate +a test across many low-end computers and thus simulate a larger load on the server. One +instance of the JMeter client can control any number of remote JMeter instances, and collect +all the data from them. This offers the following features: + +

      +
    • Saving of test samples to the local machine
    • +
    • Managment of multiple JMeterEngines from a single machine
    • +
    • No need to copy the test plan to each server - the client sends it to all the servers
    • +
    +

    + +Note: The same test plan is run by all the servers. +JMeter does not distribute the load between servers, each runs the full test plan. +So if you set 1000 Threads and have 6 JMeter server, you end up injecting 6000 Threads. + +

    +However, remote mode does use more resources than running the same number of non-GUI tests independently. +If many server instances are used, the client JMeter can become overloaded, as can the client network connection. +This has been improved by switching to Stripped modes (see below) but you should always check that your client is not overloaded. +

    +

    Note that while you can execute the JMeterEngine on your application +server, you need to be mindful of the fact that this will be adding processing +overhead on the application server and thus your testing results will be +somewhat tainted. The recommended approach is to have one or more machines on +the same Ethernet segment as your application server that you configure to run +the JMeter Engine. This will minimize the impact of the network on the test +results without impacting the performance of the application server +itself. +

    + +

    Step 0: Configure the nodes

    +

    +Make sure that all the nodes (client and servers) : +

      +
    • are running exactly the same version of JMeter.
    • +
    • are using the same version of Java on all systems. Using different versions of Java may work but is discouraged.
    • +
    +

    +

    +If the test uses any data files, note that these are not sent across by the client so +make sure that these are available in the appropriate directory on each server. +If necessary you can define different values for properties by editing the user.properties or system.properties +files on each server. These properties will be picked up when the server is started and may be +used in the test plan to affect its behaviour (e.g. connecting to a different remote server). +Alternatively use different content in any datafiles used by the test +(e.g. if each server must use unique ids, divide these between the data files) +

    + +

    Step 1: Start the servers

    +

    To run JMeter in remote node, start the JMeter server component on all machines you wish to run on by running +the JMETER_HOME/bin/jmeter-server (unix) or JMETER_HOME/bin/jmeter-server.bat (windows) script.

    +

    Note that there can only be one JMeter server on each node unless different RMI ports are used.

    +

    Since JMeter 2.3.1, the JMeter server application starts the RMI registry itself; +there is no need to start RMI registry separately. +To revert to the previous behaviour, define the JMeter property server.rmi.create=false on the server host systems. +

    +

    +By default, RMI uses a dynamic port for the JMeter server engine. This can cause problems for firewalls, +so with versions of JMeter after 2.3.2 you can define the JMeter property server.rmi.localport +to control this port number. +If this is non-zero, it will be used as the local port number for the server engine. +

    +

    Step 2: Add the server IP to your client's Properties File

    +

    Edit the properties file on the controlling JMeter machine. In JMETER_HOME/bin/jmeter.properties, +find the property named, "remote_hosts", and +add the value of your running JMeter server's IP address. Multiple such servers can be added, comma-delimited.

    +

    Note that you can use the -R command line option +instead to specify the remote host(s) to use. This has the same effect as using -r and -Jremote_hosts={serverlist}. + E.g.

    jmeter -Rhost1,127.0.0.1,host2 +

    If you define the JMeter property server.exitaftertest=true, then the server will exit after it runs a single test. +See also the -X flag (described below) +

    +

    Step 3a: Start the JMeter Client from a GUI client to check configuration

    +

    Now you are ready to start the controlling JMeter client. For MS-Windows, start the client with the script "bin/jmeter.bat". For UNIX, +use the script "bin/jmeter". You will notice that the Run menu contains two new sub-menus: "Remote Start" and "Remote Stop" +(see figure 1). These menus contain the client that you set in the properties file. Use the remote start and stop instead of the +normal JMeter start and stop menu items.

    +
    Figure 1 - Run Menu
    + +

    Step 3b: Start the JMeter from a non-GUI Client

    +

    +GUI mode should only be used for debugging, as a better alternative, you should start the test on remote server(s) from a non-GUI (command-line) client. +The command to do this is:

    + +jmeter -n -t script.jmx -r + +or + +jmeter -n -t script.jmx -R server1,server2... + +Other flags that may be useful: +
    +-Gproperty=value
    define a property in all the servers (may appear more than once)
    +-X
    Exit remote servers at the end of the test.
    +
    +The first example will start the test on whatever servers are defined in the JMeter property remote_hosts;
    +The second example will define remote_hosts from the list of servers and then start the test on the remote servers. +
    +The command-line client will exit when all the remote servers have stopped. + + +

    In some cases, the jmeter-server script may not work for you (if you are using an OS platform not anticipated by the JMeter developers). +Here is how to start the JMeter servers (step 1 above) with a more manual process:

    +

    Step 1a: Start the RMI Registry

    +

    +Since JMeter 2.3.1, the RMI registry is started by the JMeter server, so this section does not apply in the normal case. +To revert to the previous behaviour, define the JMeter property server.rmi.create=false on the server host systems +and follow the instructions below. +

    +

    JMeter uses Remote Method Invocation (RMI) as the remote communication mechanism. Therefore, you need +to run the RMI Registry application (which is named, "rmiregistry") that comes with the JDK and is located in the "bin" +directory. Before running rmiregistry, make sure that the following jars are in your system claspath: +

      +
    • JMETER_HOME/lib/ext/ApacheJMeter_core.jar
    • +
    • JMETER_HOME/lib/jorphan.jar
    • +
    • JMETER_HOME/lib/logkit-2.0.jar
    • +
    +The +rmiregistry application needs access to certain JMeter classes. Run rmiregistry with no parameters. By default the +application listens to port 1099.

    + +

    Step 1b: Start the JMeter Server

    +

    Once the RMI Registry application is running, start the JMeter Server. +Use the "-s" option with the jmeter startup script ("jmeter -s").

    + +

    Steps 2 and 3 remain the same.

    +
    + +

    +JMeter/RMI requires a connection from the client to the server. This will use the port you chose, default 1099.
    +JMeter/RMI also requires a reverse connection in order to return sample results from the server to the client.
    +This will use a high-numbered port.
    +This port can be controlled by jmeter property called client.rmi.localport in jmeter.properties.
    +If there are any firewalls or other network filters between JMeter client and server, +you will need to make sure that they are set up to allow the connections through. +If necessary, use monitoring software to show what traffic is being generated. +

    +

    If you're running Suse Linux, these tips may help. The default installation may enable the firewall. In that case, +remote testing will not work properly. The following tips were contributed by Sergey Ten.

    +

    If you see connections refused, turn on debugging by passing the following options.

    + +rmiregistry -J-Dsun.rmi.log.debug=true \ + -J-Dsun.rmi.server.exceptionTrace=true \ + -J-Dsun.rmi.loader.logLevel=verbose \ + -J-Dsun.rmi.dgc.logLevel=verbose \ + -J-Dsun.rmi.transport.logLevel=verbose \ + -J-Dsun.rmi.transport.tcp.logLevel=verbose \ + +

    Since JMeter 2.3.1, the RMI registry is started by the server; however the options can still be passed in from the JMeter command line. +For example: "jmeter -s -Dsun.rmi.loader.logLevel=verbose" (i.e. omit the -J prefixes). +Alternatively the properties can be defined in the system.properties file. +

    +

    The solution to the problem is to remove the loopbacks 127.0.0.1 and 127.0.0.2 from etc/hosts. +What happens is jmeter-server can't connect to rmiregistry if 127.0.0.2 loopback is not available. +Use the following settings to fix the problem.

    +

    Replace

    +`dirname $0`/jmeter -s "$@" +

    With

    + +HOST="-Djava.rmi.server.hostname=[computer_name][computer_domain] \ + -Djava.security.policy=`dirname $0`/[policy_file]" \ +`dirname $0`/jmeter $HOST -s "$@" + +

    Also create a policy file and add [computer_name][computer_domain] line to /etc/hosts.

    + +

    In order to better support SSH-tunneling of the RMI communication channels used +in remote testing, since JMeter 2.6:

    +
      +
    • a new property "client.rmi.localport" can be set to control the RMI port used by the RemoteSampleListenerImpl
    • +
    • To support tunneling RMI traffic over an SSH tunnel as the remote endpoint using a port on the local machine, + loopback interface is now allowed to be used if it has been specified directly using the Java System Property + "java.rmi.server.hostname" parameter.
    • +
    +
    + +

    By default, JMeter uses the standard RMI port 1099. It is possible to change this. For this to work successfully, +all the following need to agree:

    +
      +
    • On the server, start rmiregistry using the new port number
    • +
    • On the server, start JMeter with the property server_port defined
    • +
    • On the client, update the remote_hosts property to include the new remote host:port settings
    • +
    + +

    Since Jmeter 2.1.1, the jmeter-server scripts provide support for changing the port. +For example, assume you want to use port 1664 (perhaps 1099 is already used).

    +On Windows (in a DOS box) + +C:\JMETER> SET SERVER_PORT=1664 +C:\JMETER> JMETER-SERVER [other options] + +On Unix: + +$ SERVER_PORT=1664 jmeter-server [other options] + +[N.B. use upper case for the environment variable] +

    +In both cases, the script starts rmiregistry on the specified port, +and then starts JMeter in server mode, having defined the "server_port" property. +

    +

    +The chosen port will be logged in the server jmeter.log file (rmiregistry does not create a log file). +

    +
    + + +

    +Listeners in the test plan send their results back to the client JMeter which writes the results to the specified files +By default, samples are sent back synchronously as they are generated. +This can affect the maximum throughput of the server test; the sample result has to be sent back before the thread can +continue. +There are some JMeter properties that can be set to alter this behaviour. +

    +
    +mode
    sample sending mode - default is StrippedBatch since 2.9. This should be set on the client node. +
    + Standard
    send samples synchronously as soon as they are generated
    + Hold
    hold samples in an array until the end of a run. This may use a lot of memory on the server and is discouraged.
    + DiskStore
    store samples in a disk file (under java.io.temp) until the end of a run. + The serialised data file is deleted on JVM exit.
    + StrippedDiskStore
    remove responseData from succesful samples, and use DiskStore sender to send them.
    + Batch
    send saved samples when either the count (num_sample_threshold) or time (time_threshold) exceeds a threshold, + at which point the samples are sent synchronously. + The thresholds can be configured on the server using the following properties: +
    + num_sample_threshold
    number of samples to accumulate, default 100
    + time_threshold
    time threshold, default 60000 ms = 60 seconds
    +
    + + See also the Asynch mode, described below.
    + Statistical
    send a summary sample when either the count or time exceeds a threshold. + The samples are summarised by thread group name and sample label. + The following fields are accumulated: +
      +
    • elapsed time
    • +
    • latency
    • +
    • bytes
    • +
    • sample count
    • +
    • error count
    • +
    + Other fields that vary between samples are lost. +
    + Stripped
    remove responseData from succesful samples
    + StrippedBatch
    remove responseData from succesful samples, and use Batch sender to send them.
    + Asynch
    samples are temporarily stored in a local queue. A separate worker thread sends the samples. + This allows the test thread to continue without waiting for the result to be sent back to the client. + However, if samples are being created faster than they can be sent, the queue will eventually fill up, + and the sampler thread will block until some samples can be drained from the queue. + This mode is useful for smoothing out peaks in sample generation. + The queue size can be adjusted by setting the JMeter property + asynch.batch.queue.size (default 100) on the server node. +
    + StrippedAsynch
    remove responseData from succesful samples, and use Async sender to send them.
    + Custom implementation
    set the mode parameter to your custom sample sender class name. + This must implement the interface SampleSender and have a constructor which takes a single + parameter of type RemoteSampleListener. +
    +
    +
    +
    +Stripped mode family strips responseData so this means that some Elements that rely +on the previous responseData being available will not work.
    +This is not really a problem as there is always a more efficient way to implement this feature. +
    +

    The following properties apply to the Batch and Statistical modes:

    +
    + num_sample_threshold
    number of samples in a batch (default 100)
    + time_threshold
    number of milliseconds to wait (default 60 seconds)
    +
    +
    + + + +

    + For large-scale tests there is a chance that some part of remote servers will be unavailable or down. + For example, when you use automation script to allocate many cloud machines and use them as generators, + some of requested machines might fail booting because of cloud's issues. + Since JMeter 2.13 there are new properties to control this behaviour. +

    +

    + First what you might want is to retry initialization attempts in hope that failed nodes just slightly delayed their boot. + To enable retries, you should set client.tries property to total number of connection attempts. + By default it does only one attempt. To control retry delay, set the client.retries_delay property + to number of milliseconds to sleep between attempts. +

    +

    + Finally, you might still want to run the test with those generators that succeeded initialization and skipping failed nodes. + To enable that, set the client.continue_on_fail=true property. +

    +
    + +
    + + +
    diff --git a/xdocs/usermanual/test_plan.xml b/xdocs/usermanual/test_plan.xml new file mode 100644 index 00000000000..0aa0e30773a --- /dev/null +++ b/xdocs/usermanual/test_plan.xml @@ -0,0 +1,505 @@ + + + + +]> + + + + +User's Manual: Elements of a Test Plan + + + + +
    + +

    The Test Plan object has a checkbox called "Functional Testing". If selected, it +will cause JMeter to record the data returned from the server for each sample. If you have +selected a file in your test listeners, this data will be written to file. This can be useful if +you are doing a small run to ensure that JMeter is configured correctly, and that your server +is returning the expected results. The consequence is that the file will grow huge quickly, and +JMeter's performance will suffer. This option should be off if you are doing stress-testing (it +is off by default).

    +

    If you are not recording the data to file, this option makes no difference.

    +

    You can also use the Configuration button on a listener to decide what fields to save.

    + + +

    Thread group elements are the beginning points of any test plan. +All controllers and samplers must be under a thread group. +Other elements, e.g. Listeners, may be placed directly under the test plan, +in which case they will apply to all the thread groups. +As the name implies, the thread group +element controls the number of threads JMeter will use to execute your test. The +controls for a thread group allow you to: +

    • Set the number of threads
    • +
    • Set the ramp-up period
    • +
    • Set the number of times to execute the test
    • +

    + +

    Each thread will execute the test plan in its entirety and completely independently +of other test threads. Multiple threads are used to simulate concurrent connections +to your server application.

    + +

    The ramp-up period tells JMeter how long to take to "ramp-up" to the full number of +threads chosen. If 10 threads are used, and the ramp-up period is 100 seconds, then +JMeter will take 100 seconds to get all 10 threads up and running. Each thread will +start 10 (100/10) seconds after the previous thread was begun. If there are 30 threads +and a ramp-up period of 120 seconds, then each successive thread will be delayed by 4 seconds.

    + +

    Ramp-up needs to be long enough to avoid too large a work-load at the start +of a test, and short enough that the last threads start running before +the first ones finish (unless one wants that to happen). +

    +

    +Start with Ramp-up = number of threads and adjust up or down as needed. +

    + +

    By default, the thread group is configured to loop once through its elements.

    + +

    Version 1.9 introduces a test run scheduler. + Click the checkbox at the bottom of the Thread Group panel to reveal extra fields + in which you can enter the start and end times of the run. + When the test is started, JMeter will wait if necessary until the start-time has been reached. + At the end of each cycle, JMeter checks if the end-time has been reached, and if so, the run is stopped, + otherwise the test is allowed to continue until the iteration limit is reached.

    +

    Alternatively, one can use the relative delay and duration fields. + Note that delay overrides start-time, and duration over-rides end-time.

    +
    + + + +

    +JMeter has two types of Controllers: Samplers and Logical Controllers. +These drive the processing of a test. +

    + +

    Samplers tell JMeter to send requests to a server. For +example, add an HTTP Request Sampler if you want JMeter +to send an HTTP request. You can also customize a request by adding one +or more Configuration Elements to a Sampler. For more +information, see +Samplers.

    + +

    Logical Controllers let you customize the logic that JMeter uses to +decide when to send requests. For example, you can add an Interleave +Logic Controller to alternate between two HTTP Request Samplers. +For more information, see Logical Controllers.

    + +
    + + + +

    +Samplers tell JMeter to send requests to a server and wait for a response. +They are processed in the order they appear in the tree. +Controllers can be used to modify the number of repetitions of a sampler. +

    +

    +JMeter samplers include: +

      +
    • FTP Request
    • +
    • HTTP Request
    • +
    • JDBC Request
    • +
    • Java object request
    • +
    • LDAP Request
    • +
    • SOAP/XML-RPC Request
    • +
    • WebService (SOAP) Request
    • +
    +Each sampler has several properties you can set. +You can further customize a sampler by adding one or more Configuration Elements to the Test Plan. +

    + +

    If you are going to send multiple requests of the same type (for example, +HTTP Request) to the same server, consider using a Defaults Configuration +Element. Each controller has one or more Defaults elements (see below).

    + +

    Remember to add a Listener to your test plan to view and/or store the +results of your requests to disk.

    + +

    If you are interested in having JMeter perform basic validation on +the response of your request, add an Assertion to +the sampler. For example, in stress testing a web application, the server +may return a successful "HTTP Response" code, but the page may have errors on it or +may be missing sections. You could add assertions to check for certain HTML tags, +common error strings, and so on. JMeter lets you create these assertions using regular +expressions.

    + +

    JMeter's built-in samplers

    +
    + + +

    Logic Controllers let you customize the logic that JMeter uses to +decide when to send requests. +Logic Controllers can change the order of requests coming from their +child elements. They can modify the requests themselves, cause JMeter to repeat +requests, etc. +

    + +

    To understand the effect of Logic Controllers on a test plan, consider the +following test tree:

    + +

    +

      +
    • Test Plan
    • +
        +
      • Thread Group
      • +
          +
        • Once Only Controller
        • +
            +
          • Login Request (an )
          • +
          +
        • Load Search Page (HTTP Sampler)
        • +
        • Interleave Controller
        • +
            +
          • Search "A" (HTTP Sampler)
          • +
          • Search "B" (HTTP Sampler)
          • +
          • HTTP default request (Configuration Element)
          • +
          +
        • HTTP default request (Configuration Element)
        • +
        • Cookie Manager (Configuration Element)
        • +
        +
      +
    +

    + +

    The first thing about this test is that the login request will be executed only +the first time through. Subsequent iterations will skip it. This is due to the +effects of the .

    + +

    After the login, the next Sampler loads the search page (imagine a +web application where the user logs in, and then goes to a search page to do a search). This +is just a simple request, not filtered through any Logic Controller.

    + +

    After loading the search page, we want to do a search. Actually, we want to do +two different searches. However, we want to re-load the search page itself between +each search. We could do this by having 4 simple HTTP request elements (load search, +search "A", load search, search "B"). Instead, we use the which passes on one child request each time through the test. It keeps the +ordering (ie - it doesn't pass one on at random, but "remembers" its place) of its +child elements. Interleaving 2 child requests may be overkill, but there could easily have +been 8, or 20 child requests.

    + +

    Note the that +belongs to the Interleave Controller. Imagine that "Search A" and "Search B" share +the same PATH info (an HTTP request specification includes domain, port, method, protocol, +path, and arguments, plus other optional items). This makes sense - both are search requests, + hitting the same back-end search engine (a servlet or cgi-script, let's say). Rather than + configure both HTTP Samplers with the same information in their PATH field, we + can abstract that information out to a single Configuration Element. When the Interleave + Controller "passes on" requests from "Search A" or "Search B", it will fill in the blanks with + values from the HTTP default request Configuration Element. So, we leave the PATH field + blank for those requests, and put that information into the Configuration Element. In this +case, this is a minor benefit at best, but it demonstrates the feature.

    + +

    The next element in the tree is another HTTP default request, this time added to the +Thread Group itself. The Thread Group has a built-in Logic Controller, and thus, it uses +this Configuration Element exactly as described above. It fills in the blanks of any +Request that passes through. It is extremely useful in web testing to leave the DOMAIN +field blank in all your HTTP Sampler elements, and instead, put that information +into an HTTP default request element, added to the Thread Group. By doing so, you can +test your application on a different server simply by changing one field in your Test Plan. +Otherwise, you'd have to edit each and every Sampler.

    + +

    The last element is a . A Cookie Manager should be added to all web tests - otherwise JMeter will +ignore cookies. By adding it at the Thread Group level, we ensure that all HTTP requests +will share the same cookies.

    + +

    Logic Controllers can be combined to achieve various results. See the list of built-in +Logic Controllers.

    +
    + + +

    The Test Fragment element is a special type of controller that +exists on the Test Plan tree at the same level as the Thread Group element. It is distinguished +from a Thread Group in that it is not executed unless it is +referenced by either a or an . +

    +

    This element is purely for code re-use within Test Plans and was introduced in Version 2.5

    +
    + + +

    Listeners provide access to the information JMeter gathers about the test cases while +JMeter runs. The listener plots the response times on a graph. +The "View Results Tree" Listener shows details of sampler requests and responses, and can display basic HTML and XML representations of the response. +Other listeners provide summary or aggregation information. +

    + +

    +Additionally, listeners can direct the data to a file for later use. +Every listener in JMeter provides a field to indicate the file to store data to. +There is also a Configuration button which can be used to choose which fields to save, and whether to use CSV or XML format. +Note that all Listeners save the same data; the only difference is in the way the data is presented on the screen. +

    + +

    +Listeners can be added anywhere in the test, including directly under the test plan. +They will collect data only from elements at or below their level. +

    + +

    There are several listeners +that come with JMeter.

    +
    + + + +

    By default, a JMeter thread sends requests without pausing between each request. +We recommend that you specify a delay by adding one of the available timers to +your Thread Group. If you do not add a delay, JMeter could overwhelm your server by +making too many requests in a very short amount of time.

    + +

    The timer will cause JMeter to delay a certain amount of time before each +sampler which is in its scope.

    + +

    +If you choose to add more than one timer to a Thread Group, JMeter takes the sum of +the timers and pauses for that amount of time before executing the samplers to which the timers apply. +Timers can be added as children of samplers or controllers in order to restrict the samplers to which they are applied. +

    +

    +To provide a pause at a single place in a test plan, one can use the Sampler. +

    +
    + + + +

    Assertions allow you to assert facts about responses received from the +server being tested. Using an assertion, you can essentially "test" that your +application is returning the results you expect it to.

    + +

    For instance, you can assert that the response to a query will contain some +particular text. The text you specify can be a Perl-style regular expression, and +you can indicate that the response is to contain the text, or that it should match +the whole response.

    + +

    You can add an assertion to any Sampler. For example, you can +add an assertion to a HTTP Request that checks for the text, "&lt;/HTML&gt;". JMeter +will then check that the text is present in the HTTP response. If JMeter cannot find the +text, then it will mark this as a failed request.

    + +

    +Note that assertions apply to all samplers which are in its scope. +To restrict the assertion to a single sampler, add the assertion as a child of the sampler. +

    + +

    To view the assertion results, add an Assertion Listener to the Thread Group. +Failed Assertions will also show up in the Tree View and Table Listeners, +and will count towards the error %age for example in the Aggregate and Summary reports. +

    +
    + + +

    A configuration element works closely with a Sampler. Although it does not send requests +(except for ), it can add to or modify requests.

    + +

    A configuration element is accessible from only inside the tree branch where you place the element. +For example, if you place an HTTP Cookie Manager inside a Simple Logic Controller, the Cookie Manager will +only be accessible to HTTP Request Controllers you place inside the Simple Logic Controller (see figure 1). +The Cookie Manager is accessible to the HTTP requests "Web Page 1" and "Web Page 2", but not "Web Page 3".

    +

    Also, a configuration element inside a tree branch has higher precedence than the same element in a "parent" +branch. For example, we defined two HTTP Request Defaults elements, "Web Defaults 1" and "Web Defaults 2". +Since we placed "Web Defaults 1" inside a Loop Controller, only "Web Page 2" can access it. The other HTTP +requests will use "Web Defaults 2", since we placed it in the Thread Group (the "parent" of all other branches).

    + +
    Figure 1 - + Test Plan Showing Accessability of Configuration Elements
    + + +The Configuration element is different. +It is processed at the start of a test, no matter where it is placed. +For simplicity, it is suggested that the element is placed only at the start of a Thread Group. + +
    + + +

    A Pre-Processor executes some action prior to a Sampler Request being made. +If a Pre-Processor is attached to a Sampler element, then it will execute just prior to that sampler element running. +A Pre-Processor is most often used to modify the settings of a Sample Request just before it runs, or to update variables that aren't extracted from response text. +See the scoping rules for more details on when Pre-Processors are executed.

    +
    + + +

    A Post-Processor executes some action after a Sampler Request has been made. +If a Post-Processor is attached to a Sampler element, then it will execute just after that sampler element runs. +A Post-Processor is most often used to process the response data, often to extract values from it. +See the scoping rules for more details on when Post-Processors are executed.

    +
    + + +
      +
    1. Configuration elements
    2. +
    3. Pre-Processors
    4. +
    5. Timers
    6. +
    7. Sampler
    8. +
    9. Post-Processors (unless SampleResult is null)
    10. +
    11. Assertions (unless SampleResult is null)
    12. +
    13. Listeners (unless SampleResult is null)
    14. +
    + + +Please note that Timers, Assertions, Pre- and Post-Processors are only processed if there is a sampler to which they apply. +Logic Controllers and Samplers are processed in the order in which they appear in the tree. +Other test elements are processed according to the scope in which they are found, and the type of test element. +[Within a type, elements are processed in the order in which they appear in the tree]. + +

    +For example, in the following test plan: +

      +
    • Controller
    • +
        +
      • Post-Processor 1
      • +
      • Sampler 1
      • +
      • Sampler 2
      • +
      • Timer 1
      • +
      • Assertion 1
      • +
      • Pre-Processor 1
      • +
      • Timer 2
      • +
      • Post-Processor 2
      • +
      +
    +The order of execution would be: +
    +Pre-Processor 1
    +Timer 1
    +Timer 2
    +Sampler 1
    +Post-Processor 1
    +Post-Processor 2
    +Assertion 1
    +
    +Pre-Processor 1
    +Timer 1
    +Timer 2
    +Sampler 2
    +Post-Processor 1
    +Post-Processor 2
    +Assertion 1
    +
    +

    +
    + + +

    +The JMeter test tree contains elements that are both hierarchical and ordered. Some elements in the test trees are strictly hierarchical (Listeners, Config Elements, Post-Procesors, Pre-Processors, Assertions, Timers), and some are primarily ordered (controllers, samplers). When you create your test plan, you will create an ordered list of sample request (via Samplers) that represent a set of steps to be executed. These requests are often organized within controllers that are also ordered. Given the following test tree:

    +
    Example test tree
    +

    The order of requests will be, One, Two, Three, Four.

    +

    Some controllers affect the order of their subelements, and you can read about these specific controllers in the component reference.

    +

    Other elements are hierarchical. An Assertion, for instance, is hierarchical in the test tree. +If its parent is a request, then it is applied to that request. If its +parent is a Controller, then it affects all requests that are descendants of +that Controller. In the following test tree:

    +
    Hierarchy example
    +

    Assertion #1 is applied only to Request One, while Assertion #2 is applied to Requests Two and Three.

    +

    Another example, this time using Timers:

    +
    complex example
    +

    In this example, the requests are named to reflect the order in which they will be executed. Timer #1 will apply to Requests Two, Three, and Four (notice how order is irrelevant for hierarchical elements). Assertion #1 will apply only to Request Three. Timer #2 will affect all the requests.

    +

    Hopefully these examples make it clear how configuration (hierarchical) elements are applied. If you imagine each Request being passed up the tree branches, to its parent, then to its parent's parent, etc, and each time collecting all the configuration elements of that parent, then you will see how it works.

    + +The Configuration elements Header Manager, Cookie Manager and Authorization manager are +treated differently from the Configuration Default elements. +The settings from the Configuration Default elements are merged into a set of values that the Sampler has access to. +However, the settings from the Managers are not merged. +If more than one Manager is in the scope of a Sampler, +only one Manager is used, but there is currently no way to specify which is used. + +
    + + + + +

    +JMeter properties are defined in jmeter.properties (see Gettting Started - Configuring JMeter for more details). +

    +Properties are global to jmeter, and are mostly used to define some of the defaults JMeter uses. +For example the property remote_hosts defines the servers that JMeter will try to run remotely. +Properties can be referenced in test plans +- see Functions - read a property - +but cannot be used for thread-specific values. +

    +

    +JMeter variables are local to each thread. The values may be the same for each thread, or they may be different. +

    +If a variable is updated by a thread, only the thread copy of the variable is changed. +For example the Post-Processor +will set its variables according to the sample that its thread has read, and these can be used later +by the same thread. +For details of how to reference variables and functions, see Functions and Variables +

    +

    +Note that the values defined by the and the configuration element +are made available to the whole test plan at startup. +If the same variable is defined by multiple UDV elements, then the last one takes effect. +Once a thread has started, the initial set of variables is copied to each thread. +Other elements such as the + Pre-Processor or Post-Processor +may be used to redefine the same variables (or create new ones). These redefinitions only apply to the current thread. +

    +

    +The setProperty function can be used to define a JMeter property. +These are global to the test plan, so can be used to pass information between threads - should that be needed. +

    +Both variables and properties are case-sensitive. +
    + + +

    +Variables don't have to vary - they can be defined once, and if left alone, will not change value. +So you can use them as short-hand for expressions that appear frequently in a test plan. +Or for items which are constant during a run, but which may vary between runs. +For example, the name of a host, or the number of threads in a thread group. +

    +

    +When deciding how to structure a Test Plan, +make a note of which items are constant for the run, but which may change between runs. +Decide on some variable names for these - +perhaps use a naming convention such as prefixing them with C_ or K_ or using uppercase only +to distinguish them from variables that need to change during the test. +Also consider which items need to be local to a thread - +for example counters or values extracted with the Regular Expression Post-Processor. +You may wish to use a different naming convention for these. +

    +

    +For example, you might define the following on the Test Plan: +

    +HOST             www.example.com
    +THREADS          10
    +LOOPS            20
    +
    +You can refer to these in the test plan as ${HOST} ${THREADS} etc. +If you later want to change the host, just change the value of the HOST variable. +This works fine for small numbers of tests, but becomes tedious when testing lots of different combinations. +One solution is to use a property to define the value of the variables, for example: +
    +HOST             ${__P(host,www.example.com)}
    +THREADS          ${__P(threads,10)}
    +LOOPS            ${__P(loops,20)}
    +
    +You can then change some or all of the values on the command-line as follows: +
    +jmeter ... -Jhost=www3.example.org -Jloops=13
    +
    +

    +
    + +
    + + +
    diff --git a/xdocs/velocity.properties b/xdocs/velocity.properties new file mode 100644 index 00000000000..b85b0aaa28d --- /dev/null +++ b/xdocs/velocity.properties @@ -0,0 +1,18 @@ +# 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. + +resource.loader.1.resource.path = xdocs/stylesheets +# set true to debug vsl files +runtime.references.strict=true \ No newline at end of file